Skip to content

EVM/Exec: Attempting a CREATE should update nonce's caller even if the contract creation fails

Arnaud Bihan requested to merge arnaud@functori@fix-contract-nonce into master

Context

As said in #6825 (closed), when using CREATE opcode, caller's nonce is bumped, but currently it's only done when the contract creation success, otherwise the transaction is rollbacked including the nonce.

According to the ethereum yellow paper (p. 37), if the CREATE instruction doesn't trigger a CallTooDeep error (> 1024) and not enough fund to fullfil the transaction, then the nonce of the caller (the contract that uses the CREATE instruction) is bumped.

Screenshot_from_2024-02-08_11-03-09

Here Ia is the address of the caller and x -> _o[x]n_ is the function to get the nonce of x

Sub context

With this code refactorisation, it's really easy to fix the problem spotted in this issue !6898, the fix in question is done in the 4th commit of this MR

Manually testing the MR

If you checkout on arnaud@functori@fix-create-return:

make -f kernels.mk evm-evaluation-assessor
evm-evaluation-assessor -d <ETH-TEST> --result -o <RESULT.LOG>

Now compile on this branch:

make -f kernels.mk evm-evaluation-assessor
evm-evaluation-assessor -d <ETH-TEST> --diff <RESULT.LOG> -o <NEW.LOG>

Here is the content of NEW.LOG:

stCreate2_create2collisionCode2_data_index_1_gas_index_0_value_index_0: Failure -> Success
stSystemOperationsTest_createWithInvalidOpcode_data_index_0_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_0_gas_index_0_value_index_0: Failure -> Success
stCreate2_create2collisionNonce_data_index_0_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_3_gas_index_0_value_index_1: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_1_gas_index_0_value_index_1: Failure -> Success
stCreateTest_CREATE_FirstByte_loop_data_index_0_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_5_gas_index_0_value_index_0: Failure -> Success
stCreate2_create2collisionNonce_data_index_1_gas_index_0_value_index_0: Failure -> Success
stStaticCall_static_callcodecallcodecall_110_OOGE2_data_index_0_gas_index_0_value_index_0: Failure -> Success
stCreate2_create2collisionCode_data_index_1_gas_index_0_value_index_0: Failure -> Success
stCreate2_create2collisionCode_data_index_0_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_4_gas_index_0_value_index_0: Failure -> Success
stCreate2_create2collisionNonce_data_index_2_gas_index_0_value_index_0: Failure -> Success
stCreate2_create2collisionCode_data_index_2_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateCollisionToEmpty_data_index_2_gas_index_0_value_index_0: Success -> Failure
stCreateTest_CreateCollisionToEmpty_data_index_2_gas_index_0_value_index_1: Success -> Failure
stCreateTest_CreateAddressWarmAfterFail_data_index_12_gas_index_0_value_index_0: Failure -> Success
stCreate2_create2InitCodes_data_index_1_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_12_gas_index_0_value_index_1: Failure -> Success
stCreateTest_CreateCollisionToEmpty_data_index_1_gas_index_0_value_index_0: Success -> Failure
stCreateTest_CreateAddressWarmAfterFail_data_index_3_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_5_gas_index_0_value_index_1: Failure -> Success
stCreate2_create2collisionCode2_data_index_0_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_1_gas_index_0_value_index_0: Failure -> Success
stRevertTest_RevertOpcodeCreate_data_index_0_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_4_gas_index_0_value_index_1: Failure -> Success
stCreate2_RevertOpcodeCreate_data_index_0_gas_index_0_value_index_0: Failure -> Success
stCreate2_create2InitCodes_data_index_3_gas_index_0_value_index_0: Failure -> Success
stCreate2_create2InitCodes_data_index_2_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateCollisionToEmpty_data_index_1_gas_index_0_value_index_1: Success -> Failure
stCreateTest_CreateAddressWarmAfterFail_data_index_0_gas_index_0_value_index_1: Failure -> Success
stCreate2_CREATE2_FirstByte_loop_data_index_0_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_11_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_2_gas_index_0_value_index_0: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_11_gas_index_0_value_index_1: Failure -> Success
stCreateTest_CreateAddressWarmAfterFail_data_index_2_gas_index_0_value_index_1: Failure -> Success

Why is there a regression ?

When implementing the fix, it solves 16 tests but 6 are crashing here are the one that are crashing:

stCreateTest_CreateCollisionToEmpty_data_index_2_gas_index_0_value_index_0: Success -> Failure
stCreateTest_CreateCollisionToEmpty_data_index_2_gas_index_0_value_index_1: Success -> Failure
stCreateTest_CreateCollisionToEmpty_data_index_1_gas_index_0_value_index_0: Success -> Failure
stCreateTest_CreateCollisionToEmpty_data_index_1_gas_index_0_value_index_1: Success -> Failure

Those tests fail because the nonce of the contract that calls create expect a nonce to 0 but we get 1.

Basically, these test tries to create a contract that trigger a collision and store the result in the storage "{ (MSTORE 0 0x6001600155) [[1]] (CREATE 0 27 5) }"

I search and didn't found any documentation on the expected nonce after a create collision. So i did some test on the sepolia testnet for a CREATE opcode collision.

Here is the transaction that will trigger the collision https://sepolia.etherscan.io/tx/0x2c913521f1cc61a12ebdabd297ca95df9b1b8028ac52101be2a4b2056c528149 at block 5150413

The nonce of 0x7a98d2df230b56e4b2572ef7400614e6eb6d0687 at block 5150412 is 0x01 and will try to create contract 0xb121ec9bae3236713360426ea0366e5aa9dc3d71 which is not empty.

After the transaction that generate a collision, 0x7a98d2df230b56e4b2572ef7400614e6eb6d0687 is incremented to 2 even with the collision.

So the implementation of the nonce matches the one on sepolia and the yellow paper but i don't quite get why those 4 tests fails.

My interpretation would be that the nonce is not bumped because the execution of this test triggers an out of gas.

On a create collision CREATE opcode consumes all gas (like here https://sepolia.etherscan.io/tx/0x2c913521f1cc61a12ebdabd297ca95df9b1b8028ac52101be2a4b2056c528149 or here https://ethereum.stackexchange.com/questions/127217/gas-cost-of-create2).

So after attempting the creation and consumes a bunch of gas, we try to store the result of CREATE in the first slot of the storage therefore trigger an out of gas. I test this on sepolia https://sepolia.etherscan.io/tx/0x4847fc7d8419426065d274ce42298559b083d4a019338d9fec118a45a95ae05d and it did trigger an out of gas exception.

On this transaction https://sepolia.etherscan.io/tx/0x2c913521f1cc61a12ebdabd297ca95df9b1b8028ac52101be2a4b2056c528149, the create contract is the same but instead of storing the result of the collision in the storage, I returned it which does not trigger an out of gas (because return is cheaper than store in a cold slot).

But if you check the gas cost of this transaction, you will see that the difference between limit and used is 1498 (150,000 | 148,502). Knowing that a SSTORE cost more than 2000 gas when the slot of the storage is cold (https://eips.ethereum.org/EIPS/eip-2929). So this transaction would also had trigger an OOG exception.

I tested on REVM as well and at the end of the test the remaining gas to do the SSTORE is 746 because REVM implement this notion of hot/cold access the OOG exception is trigger and make the test work (and because our gas cost is not the exact same as ethereum if i'm not misunderstanding)

Checklist

  • Document the interface of any function added or modified (see the coding guidelines)
  • Document any change to the user interface, including configuration parameters (see node configuration)
  • Provide automatic testing (see the testing guide).
  • For new features and bug fixes, add an item in the appropriate changelog (docs/protocols/alpha.rst for the protocol and the environment, CHANGES.rst at the root of the repository for everything else).
  • Select suitable reviewers using the Reviewers field below.
  • Select as Assignee the next person who should take action on that MR

Closes #6825 (closed) Closes #6898 (closed)

Edited by Arnaud Bihan

Merge request reports