From c41f3bdf10e324eb671d70b88616b14ed303fc1d Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Thu, 15 Feb 2024 12:50:09 +0100 Subject: [PATCH] convert a few eip1153 json tests --- .../cancun/eip1153_tstore/test_basic_tload.py | 141 ++++++++++++++++++ .../cancun/eip1153_tstore/test_tload_calls.py | 108 ++++++++++++++ .../eip1153_tstore/test_tload_reentrancy.py | 112 ++++++++++++++ .../eip1153_tstore/test_tstore_reentrancy.py | 141 ++++++++++++++++++ 4 files changed, 502 insertions(+) create mode 100644 tests/cancun/eip1153_tstore/test_basic_tload.py create mode 100644 tests/cancun/eip1153_tstore/test_tload_calls.py create mode 100644 tests/cancun/eip1153_tstore/test_tload_reentrancy.py create mode 100644 tests/cancun/eip1153_tstore/test_tstore_reentrancy.py diff --git a/tests/cancun/eip1153_tstore/test_basic_tload.py b/tests/cancun/eip1153_tstore/test_basic_tload.py new file mode 100644 index 0000000000..7455bd917a --- /dev/null +++ b/tests/cancun/eip1153_tstore/test_basic_tload.py @@ -0,0 +1,141 @@ +""" +Ethereum Transient Storage EIP Tests +https://eips.ethereum.org/EIPS/eip-1153 +""" + +from typing import Dict, Union + +import pytest + +from ethereum_test_tools import ( + Account, + Address, + Environment, + StateTestFiller, + TestAddress, + Transaction, +) +from ethereum_test_tools.vm.opcode import Opcodes as Op + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-1153.md" +REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" + + +@pytest.mark.valid_from("Cancun") +def test_basic_tload( + state_test: StateTestFiller, +): + """ + Covered .json vectors: + + (01_tloadBeginningTxnFiller.yml) + load arbitrary value is 0 at beginning of transaction + + (02_tloadAfterTstoreFiller.yml) + tload from same slot after tstore returns correct value + + (03_tloadAfterStoreIs0Filler.yml) + Loading any other slot after storing to a slot returns 0. + + (16_tloadGasFiller.yml) + tload costs 100 gas same as a warm sload + + (18_tloadAfterStoreFiller.yml) + tload from same slot after store returns 0 + """ + + address_to = Address("A00000000000000000000000000000000000000A") + tload_at_transaction_begin_result = 1 + + tstore_value = 88 + tload_after_tstore_result = 2 + tload_after_tstore_result_second_time = 3 + tload_wrong_after_tstore_result = 4 + + # N OPNAME GAS_COST TOTAL_GAS REMAINING_GAS STACK + # 28-1 MSTORE 2 20748 4958252 2:[4ba82f,0,] + # MSTORE [0] = 4958255 + # 29-1 PUSH1 3 20754 4958246 + # 30-1 TLOAD 100 20757 4958243 1:[10,] + # 31-1 GAS 2 20857 4958143 1:[2,] + # 32-1 PUSH1 3 20859 4958141 2:[2,4ba7bd,] + # 33-1 MSTORE 6 20862 4958138 3:[2,4ba7bd,20,] + # MSTORE [32] = 4958141 + extra_opcode_gas = 11 # mstore(3), push1(3),gas(2),push1(3) + + tload_nonzero_gas_price_result = 16 + tload_zero_gas_price_result = 1601 + + tload_from_sstore_result = 18 + + pre = { + address_to: Account( + balance=1000000000000000000, + nonce=0, + code=Op.JUMPDEST() + # 01_tloadBeginningTxnFiller.yml + + Op.SSTORE(tload_at_transaction_begin_result, Op.TLOAD(0)) + # 02_tloadAfterTstoreFiller.yml + + Op.TSTORE(2, tstore_value) + + Op.SSTORE(tload_after_tstore_result, Op.TLOAD(2)) + + Op.SSTORE(tload_after_tstore_result_second_time, Op.TLOAD(2)) + # 03_tloadAfterStoreIs0Filler.yml + + Op.TSTORE(3, tstore_value) + Op.SSTORE(tload_wrong_after_tstore_result, Op.TLOAD(0)) + # 16_tloadGasFiller.yml calculate tload gas + + Op.TSTORE(16, 2) + + Op.MSTORE(0, Op.GAS()) # hot load the memory + + Op.MSTORE(0, Op.GAS()) + + Op.TLOAD(16) + + Op.MSTORE(32, Op.GAS()) + + Op.SSTORE(tload_nonzero_gas_price_result, Op.SUB(Op.MLOAD(0), Op.MLOAD(32))) + + Op.SSTORE(tload_nonzero_gas_price_result, Op.SUB(Op.SLOAD(16), extra_opcode_gas)) + # from zero slot + + Op.MSTORE(0, Op.GAS()) + + Op.TLOAD(5) + + Op.MSTORE(32, Op.GAS()) + + Op.SSTORE(tload_zero_gas_price_result, Op.SUB(Op.MLOAD(0), Op.MLOAD(32))) + + Op.SSTORE(tload_zero_gas_price_result, Op.SUB(Op.SLOAD(1601), extra_opcode_gas)) + # 18_tloadAfterStoreFiller.yml + + Op.SSTORE(18, 22) + Op.SSTORE(tload_from_sstore_result, Op.TLOAD(18)), + storage={ + tload_at_transaction_begin_result: 0xFF, + tload_after_tstore_result: 0xFF, + tload_after_tstore_result_second_time: 0xFF, + tload_wrong_after_tstore_result: 0xFF, + tload_nonzero_gas_price_result: 0xFF, + tload_zero_gas_price_result: 0xFF, + tload_from_sstore_result: 0xFF, + }, + ), + TestAddress: Account( + balance=7000000000000000000, + nonce=0, + code="0x", + storage={}, + ), + } + + post: Dict[Address, Union[Account, object]] = {} + + post[address_to] = Account( + storage={ + tload_at_transaction_begin_result: 0x00, + tload_after_tstore_result: tstore_value, + tload_after_tstore_result_second_time: tstore_value, + tload_wrong_after_tstore_result: 0x00, + tload_nonzero_gas_price_result: 100, + tload_zero_gas_price_result: 100, + tload_from_sstore_result: 0x00, + } + ) + + tx = Transaction( + nonce=0, + to=address_to, + gas_price=10, + data=b"", + gas_limit=5000000, + value=0, + ) + + state_test(env=Environment(), pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip1153_tstore/test_tload_calls.py b/tests/cancun/eip1153_tstore/test_tload_calls.py new file mode 100644 index 0000000000..37454552c4 --- /dev/null +++ b/tests/cancun/eip1153_tstore/test_tload_calls.py @@ -0,0 +1,108 @@ +""" +Ethereum Transient Storage EIP Tests +https://eips.ethereum.org/EIPS/eip-1153 +""" + +from typing import Dict, Union + +import pytest + +from ethereum_test_tools import ( + Account, + Address, + Environment, + StateTestFiller, + TestAddress, + Transaction, +) +from ethereum_test_tools.vm.opcode import Opcodes as Op + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-1153.md" +REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" + + +@pytest.mark.valid_from("Cancun") +@pytest.mark.parametrize("call_type", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL]) +def test_tload_calls(state_test: StateTestFiller, call_type: Op): + """ + Covered .json vectors: + + (04_tloadAfterCallFiller.yml) + Loading a slot after a call to another contract is 0. + + (12_tloadDelegateCallFiller.yml) + delegatecall reads transient storage in the context of the current address + """ + + address_to = Address("A00000000000000000000000000000000000000A") + address_call = Address("B00000000000000000000000000000000000000B") + + # Storages + str_a_tload_after_subcall_result = 0 + str_a_subcall_result = 1 + str_b_subcall_tload_result = 2 + str_b_subcall_updated_tload_result = 3 + + pre = { + address_to: Account( + balance=1000000000000000000, + nonce=0, + code=Op.JUMPDEST() + + Op.TSTORE(0, 10) + + Op.SSTORE(str_a_subcall_result, call_type(Op.GAS(), address_call, 0, 0, 32, 0, 0)) + + Op.SSTORE(str_a_tload_after_subcall_result, Op.TLOAD(0)), + storage={ + str_a_subcall_result: 0xFF, + str_a_tload_after_subcall_result: 0xFF, + }, + ), + address_call: Account( + balance=7000000000000000000, + nonce=0, + code=Op.JUMPDEST() + + Op.SSTORE(str_b_subcall_tload_result, Op.TLOAD(0)) + + Op.TSTORE(0, 20) + + Op.SSTORE(str_b_subcall_updated_tload_result, Op.TLOAD(0)), + storage={ + str_b_subcall_tload_result: 0xFF, + str_b_subcall_updated_tload_result: 0xFF, + }, + ), + TestAddress: Account( + balance=7000000000000000000, + nonce=0, + code="0x", + storage={}, + ), + } + + post: Dict[Address, Union[Account, object]] = {} + + post[address_to] = Account( + storage={ + # other calls don't change context, there for tload updated in this account + str_a_tload_after_subcall_result: 10 if call_type == Op.CALL else 20, + str_a_subcall_result: 1, + # since context unchanged the subcall works as if continued execution + str_b_subcall_tload_result: 0 if call_type == Op.CALL else 10, + str_b_subcall_updated_tload_result: 0 if call_type == Op.CALL else 20, + } + ) + + post[address_call] = Account( + storage={ + str_b_subcall_tload_result: 0 if call_type == Op.CALL else 0xFF, + str_b_subcall_updated_tload_result: 20 if call_type == Op.CALL else 0xFF, + } + ) + + tx = Transaction( + nonce=0, + to=address_to, + gas_price=10, + data=b"", + gas_limit=5000000, + value=0, + ) + + state_test(env=Environment(), pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip1153_tstore/test_tload_reentrancy.py b/tests/cancun/eip1153_tstore/test_tload_reentrancy.py new file mode 100644 index 0000000000..36857e60c0 --- /dev/null +++ b/tests/cancun/eip1153_tstore/test_tload_reentrancy.py @@ -0,0 +1,112 @@ +""" +Ethereum Transient Storage EIP Tests +https://eips.ethereum.org/EIPS/eip-1153 +""" + +from typing import Dict, Union + +import pytest + +from ethereum_test_tools import ( + Account, + Address, + Case, + Environment, + Hash, + StateTestFiller, + Switch, + TestAddress, + Transaction, +) +from ethereum_test_tools.vm.opcode import Opcodes as Op + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-1153.md" +REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" + + +@pytest.mark.valid_from("Cancun") +@pytest.mark.parametrize("call_type", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL]) +@pytest.mark.parametrize("call_return", [Op.RETURN, Op.REVERT, Op.OOG]) +def test_tload_reentrancy(state_test: StateTestFiller, call_type: Op, call_return: Op): + """ + Covered .json vectors: + + (05_tloadReentrancyFiller.yml) + Reentrant calls access the same transient storage + """ + + address_to = Address("A00000000000000000000000000000000000000A") + + # Storages + str_tload_in_reentrancy_result = 1 + str_tload_after_reentrancy_result = 2 + str_reentrancy_call_worked = 3 + + # Function names + do_load = 1 + do_reenter = 2 + + def make_call(call_type: Op) -> bytes: + if call_type == Op.DELEGATECALL or call_type == Op.STATICCALL: + return call_type(Op.GAS(), Op.ADDRESS(), 0, 32, 32, 32) + else: + return call_type(Op.GAS(), Op.ADDRESS(), 0, 0, 32, 32, 32) + + pre = { + address_to: Account( + balance=1000000000000000000, + nonce=0, + code=Switch( + cases=[ + Case( + condition=Op.EQ(Op.CALLDATALOAD(0), do_load), + action=Op.MSTORE(0, Op.TLOAD(0)) + call_return(0, 32), + ), + Case( + condition=Op.EQ(Op.CALLDATALOAD(0), do_reenter), + action=Op.TSTORE(0, 44) + + Op.MSTORE(0, do_load) + Op.MSTORE(32, 0xFF) + + Op.SSTORE(str_reentrancy_call_worked, make_call(call_type)) + + Op.SSTORE(str_tload_in_reentrancy_result, Op.MLOAD(32)) + + Op.SSTORE(str_tload_after_reentrancy_result, Op.TLOAD(0)), + ), + ], + default_action=b"", + ), + storage={ + str_tload_in_reentrancy_result: 0xFF, + str_tload_after_reentrancy_result: 0xFF, + str_reentrancy_call_worked: 0xFF, + }, + ), + TestAddress: Account( + balance=7000000000000000000, + nonce=0, + code="0x", + storage={}, + ), + } + + post: Dict[Address, Union[Account, object]] = {} + + post[address_to] = Account( + storage={ + # if call OOG, we fail to obtain the result + str_tload_in_reentrancy_result: 0xFF if call_return == Op.OOG else 44, + str_tload_after_reentrancy_result: 44, + str_reentrancy_call_worked: ( + 0 if call_return == Op.REVERT or call_return == Op.OOG else 1 + ), + } + ) + + tx = Transaction( + nonce=0, + to=address_to, + gas_price=10, + data=Hash(do_reenter), + gas_limit=5000000, + value=0, + ) + + state_test(env=Environment(), pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip1153_tstore/test_tstore_reentrancy.py b/tests/cancun/eip1153_tstore/test_tstore_reentrancy.py new file mode 100644 index 0000000000..df4883f9a9 --- /dev/null +++ b/tests/cancun/eip1153_tstore/test_tstore_reentrancy.py @@ -0,0 +1,141 @@ +""" +Ethereum Transient Storage EIP Tests +https://eips.ethereum.org/EIPS/eip-1153 +""" + +from typing import Dict, Union + +import pytest + +from ethereum_test_tools import ( + Account, + Address, + Case, + Environment, + Hash, + StateTestFiller, + Switch, + TestAddress, + Transaction, +) +from ethereum_test_tools.vm.opcode import Opcodes as Op + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-1153.md" +REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0" + + +@pytest.mark.valid_from("Cancun") +@pytest.mark.parametrize("call_type", [Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL]) +@pytest.mark.parametrize("call_return", [Op.RETURN, Op.REVERT, Op.OOG]) +def test_tstore_reentrancy(state_test: StateTestFiller, call_type: Op, call_return: Op): + """ + Covered .json vectors: + + (06_tstoreInReentrancyCallFiller.yml) + Reentrant calls access the same transient storage + + (07_tloadAfterReentrancyStoreFiller.yml) + Successfully returned calls do not revert transient storage writes + + (08_revertUndoesTransientStoreFiller.yml) + Revert undoes the transient storage writes from a call. + + (09_revertUndoesAllFiller.yml) + Revert undoes all the transient storage writes to the same key from the failed call. + + (20_oogUndoesTransientStoreInCallFiller.yml) + Out of gas undoes the transient storage writes from a call. + """ + address_to = Address("A00000000000000000000000000000000000000A") + + # Storages + str_tload_before_call = 0 + str_tload_in_reentrancy_call = 1 + str_tload_after_call = 2 + str_reentrancy_call_worked = 3 + str_tload_1_after_call = 4 + + # Function names + do_tstore = 1 + do_reenter = 2 + + def make_call(call_type: Op) -> bytes: + if call_type == Op.DELEGATECALL or call_type == Op.STATICCALL: + return call_type(Op.GAS(), Op.ADDRESS(), 0, 32, 32, 32) + else: + return call_type(Op.GAS(), Op.ADDRESS(), 0, 0, 32, 32, 32) + + pre = { + address_to: Account( + balance=1000000000000000000, + nonce=0, + code=Switch( + cases=[ + Case( + condition=Op.EQ(Op.CALLDATALOAD(0), do_tstore), + action=Op.TSTORE(0, 89) + + Op.TSTORE(0, 90) + + Op.TSTORE(1, 11) + + Op.TSTORE(1, 12) + + Op.MSTORE(0, Op.TLOAD(0)) + + call_return(0, 32), + ), + Case( + condition=Op.EQ(Op.CALLDATALOAD(0), do_reenter), + action=Op.TSTORE(0, 80) + + Op.SSTORE(str_tload_before_call, Op.TLOAD(0)) + + Op.MSTORE(0, do_tstore) + Op.MSTORE(32, 0xFF) + + Op.SSTORE(str_reentrancy_call_worked, make_call(call_type)) + + Op.SSTORE(str_tload_in_reentrancy_call, Op.MLOAD(32)) + + Op.SSTORE(str_tload_after_call, Op.TLOAD(0)) + + Op.SSTORE(str_tload_1_after_call, Op.TLOAD(1)), + ), + ], + default_action=b"", + ), + storage={ + str_tload_before_call: 0xFF, + str_tload_in_reentrancy_call: 0xFF, + str_tload_after_call: 0xFF, + str_reentrancy_call_worked: 0xFF, + str_tload_1_after_call: 0xFF, + }, + ), + TestAddress: Account( + balance=7000000000000000000, + nonce=0, + code="0x", + storage={}, + ), + } + + post: Dict[Address, Union[Account, object]] = {} + + def failing_calls() -> bool: + return call_type == Op.STATICCALL or call_return == Op.REVERT or call_return == Op.OOG + + post[address_to] = Account( + storage={ + str_tload_before_call: 80, + str_tload_in_reentrancy_call: ( + # we fail to obtain in call result if it fails + 0xFF if call_type == Op.STATICCALL or call_return == Op.OOG else 90 + ), + # reentrant tstore overrides value in upper level + str_tload_after_call: 80 if failing_calls() else 90, + str_tload_1_after_call: 0 if failing_calls() else 12, + # tstore in static call not allowed + str_reentrancy_call_worked: 0 if failing_calls() else 1, + } + ) + + tx = Transaction( + nonce=0, + to=address_to, + gas_price=10, + data=Hash(do_reenter), + gas_limit=5000000, + value=0, + ) + + state_test(env=Environment(), pre=pre, post=post, tx=tx)