Skip to content

Creating a contract via eth_call should return the return value of the init code #13022

@raulk

Description

@raulk

This is a mischievious edge case that has gone unnoticed (AFAIK) for a few years, yet it was uncovered while investigating a report from Recall folks.

Original report

Running this script to deploy Safe on an IPC subnet fails due to an eth_call returning an empty return value. This is the error thrown by the script.

Luckily, our reporter was able to isolate and reproduce the issue with a single request:

$ curl -s https://... \                               
  --header "Content-Type: application/json" \
  --data '{
    "jsonrpc": "2.0",
    "method": "eth_call",
    "params": [
      {
        "data": "0x604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"
      },
      "latest"
    ],
    "id": 1
  }'
{"jsonrpc":"2.0","result":"0x","id":1}

Whereas running this request against Sepolia returns the following:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"
}

The script in question is verifying the chain's behaviour by simulating the deployment of a minimal CREATE2 factory contract via eth_call, and expecting the node to return the effectively deployed bytecode after evaluating the supplied initcode. It then goes on to perform a hash assertion on that value, which fails.

What's going on?

When a contract is deployed, the bytecode sent as data in the deployment transaction is interpreted as the "initcode". Whatever value is RETURNed from the initcode becomes the effectively deployed bytecode on-chain.

In Ethereum:

  • When executed in the context of a transaction, the node elides the return value from the transaction receipt (probably for efficiency reasons).
  • When executed in the context of a call, the node does not override the default behaviour and simply propagates the return value from the EVM in the response.

In Filecoin:

  • The EAM (Ethereum Address Manager) doesn't return the deployed bytecode to the client.
  • In our design, the EAM creates the EVM actor via the InitActor, which in turn creates the actor in the state tree and invokes its constructor supplying the initcode. The EVM constructor evaluates the initcode and stores the bytecode in its own state, i.e. all our deployment logic sits in actors and is well encapsulated, whereas in Ethereum the client is responsible for updating the state tree. Hence the client must have access to the return value.

Easy solution

The affected behaviour is not consensus-critical. The mismatch could be patched in the Eth JSON-RPC server by querying the saved bytecode after a contract deployment call for first-order contract creations. For indirect creations, there's nothing to worry about (CREATE* opcodes don't return the bytecode anyway).

I guess the hard part is querying the "intermediate" state tree from the Eth JSON-RPC server implementation in Lotus. But there's several ways to overcome that implementation detail.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    🐱 Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions