From 7d137929aaa60569e8ed2af9615d7e1ca43d0adc Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 9 Dec 2022 12:09:26 -0600 Subject: [PATCH 01/12] Code refactor + generators (initcode) --- src/ethereum_test_tools/code/__init__.py | 3 + src/ethereum_test_tools/code/code.py | 34 ++-- src/ethereum_test_tools/code/generators.py | 186 +++++++++++++++++++++ src/ethereum_test_tools/common/__init__.py | 1 - src/ethereum_test_tools/common/helpers.py | 72 -------- src/ethereum_test_tools/tests/test_code.py | 92 ++++++++-- whitelist.txt | 3 + 7 files changed, 291 insertions(+), 100 deletions(-) create mode 100644 src/ethereum_test_tools/code/generators.py diff --git a/src/ethereum_test_tools/code/__init__.py b/src/ethereum_test_tools/code/__init__.py index 8e2fbccb90f..57818006918 100644 --- a/src/ethereum_test_tools/code/__init__.py +++ b/src/ethereum_test_tools/code/__init__.py @@ -2,10 +2,13 @@ Code related utilities and classes. """ from .code import Code, code_to_bytes, code_to_hex +from .generators import CodeGasMeasure, Initcode from .yul import Yul __all__ = ( "Code", + "CodeGasMeasure", + "Initcode", "Yul", "code_to_bytes", "code_to_hex", diff --git a/src/ethereum_test_tools/code/code.py b/src/ethereum_test_tools/code/code.py index c17b9f886a9..62f4a310d67 100644 --- a/src/ethereum_test_tools/code/code.py +++ b/src/ethereum_test_tools/code/code.py @@ -2,31 +2,31 @@ Code object that is an interface to different assembler/compiler backends. """ +from dataclasses import dataclass from re import sub -from typing import Union +from typing import Optional, Union -class Code(object): +@dataclass(kw_only=True) +class Code: """ Generic code object. """ - bytecode: bytes | None = None - - def __init__(self, code: bytes | str | None): - if code is not None: - if type(code) is bytes: - self.bytecode = code - elif type(code) is str: - if code.startswith("0x"): - code = code[2:] - self.bytecode = bytes.fromhex(code) - else: - raise TypeError("code has invalid type") + bytecode: Optional[bytes] = None + """ + bytes array that represents the bytecode of this object. + """ + name: Optional[str] = None + """ + Name used to describe this code. + Usually used to add extra information to a test case. + """ def assemble(self) -> bytes: """ - Assembles using `eas`. + Transform the Code object into bytes. + Normally will be overriden by the classes that inherit this class. """ if self.bytecode is None: return bytes() @@ -37,13 +37,13 @@ def __add__(self, other: Union[str, bytes, "Code"]) -> "Code": """ Adds two code objects together, by converting both to bytes first. """ - return Code(code_to_bytes(self) + code_to_bytes(other)) + return Code(bytecode=(code_to_bytes(self) + code_to_bytes(other))) def __radd__(self, other: Union[str, bytes, "Code"]) -> "Code": """ Adds two code objects together, by converting both to bytes first. """ - return Code(code_to_bytes(other) + code_to_bytes(self)) + return Code(bytecode=(code_to_bytes(other) + code_to_bytes(self))) def code_to_bytes(code: str | bytes | Code) -> bytes: diff --git a/src/ethereum_test_tools/code/generators.py b/src/ethereum_test_tools/code/generators.py new file mode 100644 index 00000000000..fcb0df680d5 --- /dev/null +++ b/src/ethereum_test_tools/code/generators.py @@ -0,0 +1,186 @@ +""" +Code generating classes and functions. +""" + +from dataclasses import dataclass +from typing import Optional + +from ..common.helpers import ceiling_division +from .code import Code, code_to_bytes + +GAS_PER_DEPLOYED_CODE_BYTE = 0xC8 + + +class Initcode(Code): + """ + Helper class used to generate initcode for the specified deployment code. + + The execution gas cost of the initcode is calculated, and also the + deployment gas costs for the deployed code. + + The initcode can be padded to a certain length if necessary, which + does not affect the deployed code. + + Other costs such as the CREATE2 hashing costs or the initcode_word_cost + of EIP-3860 are *not* taken into account by any of these calculated + costs. + """ + + deploy_code: bytes | str | Code + """ + Bytecode to be deployed by the initcode. + """ + execution_gas: int + """ + Gas cost of executing the initcode, without considering deployment gas + costs. + """ + deployment_gas: int + """ + Gas cost of deploying the cost, subtracted after initcode execution, + """ + + def __init__( + self, + *, + deploy_code: str | bytes | Code, + initcode_length: Optional[int] = None, + padding_byte: int = 0x00, + name: Optional[str] = None, + ): + """ + Generate legacy initcode that inits a contract with the specified code. + The initcode can be padded to a specified length for testing purposes. + """ + self.execution_gas = 0 + self.deploy_code = deploy_code + deploy_code_bytes = code_to_bytes(self.deploy_code) + code_length = len(deploy_code_bytes) + + initcode = bytearray() + + # PUSH2: length= + initcode.append(0x61) + initcode += code_length.to_bytes(length=2, byteorder="big") + self.execution_gas += 3 + + # PUSH1: offset=0 + initcode.append(0x60) + initcode.append(0x00) + self.execution_gas += 3 + + # DUP2 + initcode.append(0x81) + self.execution_gas += 3 + + # PUSH1: initcode_length=11 (constant) + initcode.append(0x60) + initcode.append(0x0B) + self.execution_gas += 3 + + # DUP3 + initcode.append(0x82) + self.execution_gas += 3 + + # CODECOPY: destinationOffset=0, offset=0, length + initcode.append(0x39) + self.execution_gas += ( + 3 + + (3 * ceiling_division(code_length, 32)) + + (3 * code_length) + + ((code_length * code_length) // 512) + ) + + # RETURN: offset=0, length + initcode.append(0xF3) + self.execution_gas += 0 + + pre_padding_bytes = bytes(initcode) + deploy_code_bytes + + if initcode_length is not None: + if len(pre_padding_bytes) > initcode_length: + raise Exception("Invalid specified length for initcode") + + padding_bytes = bytes( + [padding_byte] * (initcode_length - len(pre_padding_bytes)) + ) + else: + padding_bytes = bytes() + + self.deployment_gas = GAS_PER_DEPLOYED_CODE_BYTE * len( + deploy_code_bytes + ) + + super().__init__(bytecode=pre_padding_bytes + padding_bytes, name=name) + + +@dataclass(kw_only=True) +class CodeGasMeasure(Code): + """ + Helper class used to generate bytecode that measures gas usage of a + bytecode, taking into account and subtracting any extra overhead gas costs + required to execute. + By default, the result gas calculation is saved to storage key 0. + """ + + code: bytes | str | Code + """ + Bytecode to be executed to measure the gas usage. + """ + overhead_cost: int = 0 + """ + Extra gas cost to be subtracted from extra operations. + """ + extra_stack_items: int = 0 + """ + Extra stack items that remain at the end of the execution. + To be considered when subtracting the value of the previous GAS operation, + and to be popped at the end of the execution. + """ + sstore_key: int = 0 + """ + Storage key to save the gas used. + """ + + def assemble(self) -> bytes: + """ + Assemble the bytecode that measures gas usage. + """ + res = bytes() + res += bytes( + [ + 0x5A, # GAS + ] + ) + res += code_to_bytes(self.code) # Execute code to measure its gas cost + res += bytes( + [ + 0x5A, # GAS + ] + ) + # We need to swap and pop for each extra stack item that remained from + # the execution of the code + res += ( + bytes( + [ + 0x90, # SWAP1 + 0x50, # POP + ] + ) + * self.extra_stack_items + ) + res += bytes( + [ + 0x90, # SWAP1 + 0x03, # SUB + 0x60, # PUSH1 + self.overhead_cost + 2, # Overhead cost + GAS opcode price + 0x90, # SWAP1 + 0x03, # SUB + 0x60, # PUSH1 + self.sstore_key, # -> SSTORE key + 0x55, # SSTORE + 0x00, # STOP + ] + ) + return res diff --git a/src/ethereum_test_tools/common/__init__.py b/src/ethereum_test_tools/common/__init__.py index 1ad4fc235ea..4f48a98c799 100644 --- a/src/ethereum_test_tools/common/__init__.py +++ b/src/ethereum_test_tools/common/__init__.py @@ -29,7 +29,6 @@ "AddrAA", "AddrBB", "Block", - "CodeGasMeasure", "EmptyTrieRoot", "Environment", "Fixture", diff --git a/src/ethereum_test_tools/common/helpers.py b/src/ethereum_test_tools/common/helpers.py index cf833033440..071dde2a138 100644 --- a/src/ethereum_test_tools/common/helpers.py +++ b/src/ethereum_test_tools/common/helpers.py @@ -29,75 +29,3 @@ def to_hash(input: int | str) -> str: if type(input) is int: return "0x" + input.to_bytes(32, "big").hex() raise Exception("invalid type to convert to hash") - - -@dataclass(kw_only=True) -class CodeGasMeasure(Code): - """ - Helper class used to generate bytecode that measures gas usage of a - bytecode, taking into account and subtracting any extra overhead gas costs - required to execute. - By default, the result gas calculation is saved to storage key 0. - """ - - code: bytes | str | Code - """ - Bytecode to be executed to measure the gas usage. - """ - overhead_cost: int = 0 - """ - Extra gas cost to be subtracted from extra operations. - """ - extra_stack_items: int = 0 - """ - Extra stack items that remain at the end of the execution. - To be considered when subtracting the value of the previous GAS operation, - and to be popped at the end of the execution. - """ - sstore_key: int = 0 - """ - Storage key to save the gas used. - """ - - def assemble(self) -> bytes: - """ - Assemble the bytecode that measures gas usage. - """ - res = bytes() - res += bytes( - [ - 0x5A, # GAS - ] - ) - res += code_to_bytes(self.code) # Execute code to measure its gas cost - res += bytes( - [ - 0x5A, # GAS - ] - ) - # We need to swap and pop for each extra stack item that remained from - # the execution of the code - res += ( - bytes( - [ - 0x90, # SWAP1 - 0x50, # POP - ] - ) - * self.extra_stack_items - ) - res += bytes( - [ - 0x90, # SWAP1 - 0x03, # SUB - 0x60, # PUSH1 - self.overhead_cost + 2, # Overhead cost + GAS opcode price - 0x90, # SWAP1 - 0x03, # SUB - 0x60, # PUSH1 - self.sstore_key, # -> SSTORE key - 0x55, # SSTORE - 0x00, # STOP - ] - ) - return res diff --git a/src/ethereum_test_tools/tests/test_code.py b/src/ethereum_test_tools/tests/test_code.py index a3ab411edc4..7b7cdc650d3 100644 --- a/src/ethereum_test_tools/tests/test_code.py +++ b/src/ethereum_test_tools/tests/test_code.py @@ -2,23 +2,29 @@ Test suite for `ethereum_test.code` module. """ -from ..code import Code, Yul +import pytest + +from ..code import Code, Initcode, Yul, code_to_bytes def test_code(): """ Test `ethereum_test.types.code`. """ - assert Code("").assemble() == bytes() - assert Code("0x").assemble() == bytes() - assert Code("0x01").assemble() == bytes.fromhex("01") - assert Code("01").assemble() == bytes.fromhex("01") + assert code_to_bytes("") == bytes() + assert code_to_bytes("0x") == bytes() + assert code_to_bytes("0x01") == bytes.fromhex("01") + assert code_to_bytes("01") == bytes.fromhex("01") - assert (Code("0x01") + "0x02").assemble() == bytes.fromhex("0102") - assert ("0x01" + Code("0x02")).assemble() == bytes.fromhex("0102") - assert ("0x01" + Code("0x02") + "0x03").assemble() == bytes.fromhex( - "010203" - ) + assert ( + Code(bytecode=code_to_bytes("0x01")) + "0x02" + ).assemble() == bytes.fromhex("0102") + assert ( + "0x01" + Code(bytecode=code_to_bytes("0x02")) + ).assemble() == bytes.fromhex("0102") + assert ( + "0x01" + Code(bytecode=code_to_bytes("0x02")) + "0x03" + ).assemble() == bytes.fromhex("010203") def test_yul(): @@ -99,3 +105,69 @@ def test_yul(): expected_bytecode += bytes.fromhex("55") assert Yul(long_code).assemble() == expected_bytecode + + +@pytest.mark.parametrize( + "initcode,bytecode", + [ + ( + Initcode(deploy_code=bytes()), + bytes( + [ + 0x61, + 0x00, + 0x00, + 0x60, + 0x00, + 0x81, + 0x60, + 0x0B, + 0x82, + 0x39, + 0xF3, + ] + ), + ), + ( + Initcode(deploy_code=bytes(), initcode_length=20), + bytes( + [ + 0x61, + 0x00, + 0x00, + 0x60, + 0x00, + 0x81, + 0x60, + 0x0B, + 0x82, + 0x39, + 0xF3, + ] + + [0x00] * 9 # padding + ), + ), + ( + Initcode(deploy_code=bytes([0x00]), initcode_length=20), + bytes( + [ + 0x61, + 0x00, + 0x01, + 0x60, + 0x00, + 0x81, + 0x60, + 0x0B, + 0x82, + 0x39, + 0xF3, + ] + + [0x00] + + [0x00] * 8 # padding + ), + ), + ], +) +def test_initcode(initcode: Initcode, bytecode: bytes): + assert initcode.assemble() == bytecode diff --git a/whitelist.txt b/whitelist.txt index b69af8a1fb5..362950a7bc7 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -37,6 +37,7 @@ utils validator vm +byteorder delitem dirname fromhex @@ -88,6 +89,7 @@ balance origin caller callvalue +calldata calldataload calldatasize calldatacopy @@ -96,6 +98,7 @@ codecopy gasprice extcodesize extcodecopy +initcode returndatasize returndatacopy extcodehash From acad8573315cca079da88ae080749bd0324d54c6 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 9 Dec 2022 12:11:45 -0600 Subject: [PATCH 02/12] Helpers: ceiling division, compute create addresses, tx data cost, is_fork --- src/ethereum_test_tools/__init__.py | 14 ++++- src/ethereum_test_tools/common/__init__.py | 13 +++- src/ethereum_test_tools/common/helpers.py | 72 +++++++++++++++++++++- src/ethereum_test_tools/vm/fork.py | 12 ++++ 4 files changed, 106 insertions(+), 5 deletions(-) diff --git a/src/ethereum_test_tools/__init__.py b/src/ethereum_test_tools/__init__.py index 7c05bb3d1f6..af887708806 100644 --- a/src/ethereum_test_tools/__init__.py +++ b/src/ethereum_test_tools/__init__.py @@ -3,21 +3,25 @@ tests. """ -from .code import Code, Yul +from .code import Code, CodeGasMeasure, Initcode, Yul from .common import ( Account, Block, - CodeGasMeasure, Environment, JSONEncoder, TestAddress, Transaction, + ceiling_division, + compute_create2_address, + compute_create_address, + eip_2028_transaction_data_cost, to_address, to_hash, ) from .filling.decorators import test_from, test_only from .filling.fill import fill_test from .spec import BlockchainTest, StateTest +from .vm.fork import is_fork __all__ = ( "Account", @@ -26,16 +30,22 @@ "Code", "CodeGasMeasure", "Environment", + "Initcode", "JSONEncoder", "StateTest", "Storage", "TestAddress", "Transaction", "Yul", + "ceiling_division", + "compute_create2_address", + "compute_create_address", "fill_test", + "is_fork", "test_from", "test_only", "to_address", "to_hash", + "eip_2028_transaction_data_cost", "verify_post_alloc", ) diff --git a/src/ethereum_test_tools/common/__init__.py b/src/ethereum_test_tools/common/__init__.py index 4f48a98c799..899cdfcfa4d 100644 --- a/src/ethereum_test_tools/common/__init__.py +++ b/src/ethereum_test_tools/common/__init__.py @@ -8,7 +8,14 @@ TestAddress, TestPrivateKey, ) -from .helpers import CodeGasMeasure, to_address, to_hash +from .helpers import ( + ceiling_division, + compute_create2_address, + compute_create_address, + eip_2028_transaction_data_cost, + to_address, + to_hash, +) from .types import ( Account, Block, @@ -39,6 +46,10 @@ "TestAddress", "TestPrivateKey", "Transaction", + "ceiling_division", + "compute_create2_address", + "compute_create_address", + "eip_2028_transaction_data_cost", "str_or_none", "to_address", "to_hash", diff --git a/src/ethereum_test_tools/common/helpers.py b/src/ethereum_test_tools/common/helpers.py index 071dde2a138..0cbb5c8aa90 100644 --- a/src/ethereum_test_tools/common/helpers.py +++ b/src/ethereum_test_tools/common/helpers.py @@ -2,9 +2,77 @@ Helper functions/classes used to generate Ethereum tests. """ -from dataclasses import dataclass +from ethereum.crypto.hash import keccak256 +from ethereum.rlp import encode -from ..code import Code, code_to_bytes +""" +Helper functions +""" + + +def ceiling_division(a: int, b: int) -> int: + """ + Calculates the ceil without using floating point. + Used by many of the EVM's formulas + """ + return -(a // -b) + + +def compute_create_address(address: str | int, nonce: int) -> str: + """ + Compute address of the resulting contract created using a transaction + or the `CREATE` opcode. + """ + if type(address) is str: + if address.startswith("0x"): + address = address[2:] + address_bytes = bytes.fromhex(address) + elif type(address) is int: + address_bytes = address.to_bytes(length=20, byteorder="big") + if nonce == 0: + nonce_bytes = bytes() + else: + nonce_bytes = nonce.to_bytes(length=1, byteorder="big") + hash = keccak256(encode([address_bytes, nonce_bytes])) + return "0x" + hash[-20:].hex() + + +def compute_create2_address( + address: str | int, salt: int, initcode: bytes +) -> str: + """ + Compute address of the resulting contract created using the `CREATE2` + opcode. + """ + ff = bytes([0xFF]) + if type(address) is str: + if address.startswith("0x"): + address = address[2:] + address_bytes = bytes.fromhex(address) + elif type(address) is int: + address_bytes = address.to_bytes(length=20, byteorder="big") + salt_bytes = salt.to_bytes(length=32, byteorder="big") + initcode_hash = keccak256(initcode) + hash = keccak256(ff + address_bytes + salt_bytes + initcode_hash) + return "0x" + hash[-20:].hex() + + +def eip_2028_transaction_data_cost(data: bytes | str) -> int: + """ + Calculates the cost of a given data as part of a transaction, based on the + costs specified in EIP-2028: https://eips.ethereum.org/EIPS/eip-2028 + """ + if type(data) is str: + if data.startswith("0x"): + data = data[2:] + data = bytes.fromhex(data) + cost = 0 + for b in data: + if b == 0: + cost += 4 + else: + cost += 16 + return cost def to_address(input: int | str) -> str: diff --git a/src/ethereum_test_tools/vm/fork.py b/src/ethereum_test_tools/vm/fork.py index e08cac11087..95088de8a84 100644 --- a/src/ethereum_test_tools/vm/fork.py +++ b/src/ethereum_test_tools/vm/fork.py @@ -70,6 +70,18 @@ def is_merged(fork: str) -> bool: return i >= forks.index("merged") +def is_fork(fork: str, which: str) -> bool: + """ + Returns `True` if `fork` is `which` or beyond, `False otherwise. + """ + fork_lower = fork.lower() + if fork_lower not in forks: + return False + + i = forks.index(fork_lower) + return i >= forks.index(which.lower()) + + def get_reward(fork: str) -> int: """ Returns the expected reward amount in wei of a given fork From 2246ea267522db5e3878fd7e582482a9d10ef6ae Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 9 Dec 2022 12:12:09 -0600 Subject: [PATCH 03/12] Extra debug info --- src/ethereum_test_tools/common/types.py | 7 ++++++- src/ethereum_test_tools/filling/fill.py | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index 64f6ba9019c..fba70561f0a 100644 --- a/src/ethereum_test_tools/common/types.py +++ b/src/ethereum_test_tools/common/types.py @@ -122,10 +122,15 @@ def __init__(self, key: int, want: int, got: int, *args): def __str__(self): """Print exception string""" - return "incorrect value for key {0}: want {1}, got {2}".format( + return ( + "incorrect value for key {0}: want {1} (dec:{2})," + + " got {3} (dec:{4})" + ).format( Storage.key_value_to_string(self.key), Storage.key_value_to_string(self.want), + self.want, Storage.key_value_to_string(self.got), + self.got, ) @staticmethod diff --git a/src/ethereum_test_tools/filling/fill.py b/src/ethereum_test_tools/filling/fill.py index ae28bfa7625..2872dbbc91c 100644 --- a/src/ethereum_test_tools/filling/fill.py +++ b/src/ethereum_test_tools/filling/fill.py @@ -40,9 +40,18 @@ def fill_test( genesis = test.make_genesis(b11r, t8n, fork) - (blocks, head, alloc) = test.make_blocks( - b11r, t8n, genesis, fork, reward=get_reward(fork), eips=eips - ) + try: + (blocks, head, alloc) = test.make_blocks( + b11r, + t8n, + genesis, + fork, + reward=get_reward(fork), + eips=eips, + ) + except Exception as e: + print(f"Exception during test '{test.name}'") + raise e fixture = Fixture( blocks=blocks, From 5747894093e0d2e20778cbe7255f2b3aaa91fbc2 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 9 Dec 2022 12:12:22 -0600 Subject: [PATCH 04/12] Add Shanghai --- src/ethereum_test_tools/vm/fork.py | 1 + src/evm_transition_tool/__init__.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ethereum_test_tools/vm/fork.py b/src/ethereum_test_tools/vm/fork.py index 95088de8a84..0147543c39c 100644 --- a/src/ethereum_test_tools/vm/fork.py +++ b/src/ethereum_test_tools/vm/fork.py @@ -22,6 +22,7 @@ "london", "arrow glacier", "merged", + "shanghai", ] diff --git a/src/evm_transition_tool/__init__.py b/src/evm_transition_tool/__init__.py index 966c3146633..81dd5b0dd53 100644 --- a/src/evm_transition_tool/__init__.py +++ b/src/evm_transition_tool/__init__.py @@ -230,6 +230,7 @@ def version(self) -> str: "london": "London", "arrow glacier": "ArrowGlacier", "merged": "Merged", + "shanghai": "Shanghai", } fork_list = list(fork_map.keys()) From 0b27aa699720aeb4d5e99da6cc9fee7768aef27b Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 9 Dec 2022 12:13:47 -0600 Subject: [PATCH 05/12] EIPs 3651, 3855, 3860 --- fillers/eips/__init__.py | 3 + fillers/eips/eip3651.py | 343 +++++++++++++++++++ fillers/eips/eip3855.py | 253 ++++++++++++++ fillers/eips/eip3860.py | 700 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 1299 insertions(+) create mode 100644 fillers/eips/__init__.py create mode 100644 fillers/eips/eip3651.py create mode 100644 fillers/eips/eip3855.py create mode 100644 fillers/eips/eip3860.py diff --git a/fillers/eips/__init__.py b/fillers/eips/__init__.py new file mode 100644 index 00000000000..a0d7959b419 --- /dev/null +++ b/fillers/eips/__init__.py @@ -0,0 +1,3 @@ +""" +Cross-client Ethereum Improvement Proposal Tests +""" diff --git a/fillers/eips/eip3651.py b/fillers/eips/eip3651.py new file mode 100644 index 00000000000..6bcaf85160e --- /dev/null +++ b/fillers/eips/eip3651.py @@ -0,0 +1,343 @@ +""" +Test EIP-3651: Warm COINBASE +EIP: https://eips.ethereum.org/EIPS/eip-3651 +Source tests: https://github.com/ethereum/tests/pull/1082 +""" +from typing import Dict + +from ethereum_test_tools import ( + Account, + CodeGasMeasure, + Environment, + StateTest, + Transaction, + Yul, + is_fork, + test_from, + to_address, + to_hash, +) + + +@test_from(fork="merged") +def test_warm_coinbase_call_out_of_gas(fork): + """ + Test warm coinbase. + """ + env = Environment( + coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + + caller_code = Yul( + """ + { + // Depending on the called contract here, the subcall will perform + // another call/delegatecall/staticcall/callcode that will only + // succeed if coinbase is considered warm by default + // (post-Shanghai). + let calladdr := calldataload(0) + + // Amount of gas required to make a call to a warm account. + // Calling a cold account with this amount of gas results in + // exception. + let callgas := 100 + + switch calladdr + case 0x100 { + // Extra: COINBASE + 6xPUSH1 + DUP6 + 2xPOP + callgas := add(callgas, 27) + } + case 0x200 { + // Extra: COINBASE + 6xPUSH1 + DUP6 + 2xPOP + callgas := add(callgas, 27) + } + case 0x300 { + // Extra: COINBASE + 5xPUSH1 + DUP6 + 2xPOP + callgas := add(callgas, 24) + } + case 0x400 { + // Extra: COINBASE + 5xPUSH1 + DUP6 + 2xPOP + callgas := add(callgas, 24) + } + // Call and save result + sstore(0, call(callgas, calladdr, 0, 0, 0, 0, 0)) + } + """ + ) + + call_code = Yul( + """ + { + let cb := coinbase() + pop(call(0, cb, 0, 0, 0, 0, 0)) + } + """ + ) + + callcode_code = Yul( + """ + { + let cb := coinbase() + pop(callcode(0, cb, 0, 0, 0, 0, 0)) + } + """ + ) + + delegatecall_code = Yul( + """ + { + let cb := coinbase() + pop(delegatecall(0, cb, 0, 0, 0, 0)) + } + """ + ) + + staticcall_code = Yul( + """ + { + let cb := coinbase() + pop(staticcall(0, cb, 0, 0, 0, 0)) + } + """ + ) + + pre = { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": Account( + balance=1000000000000000000000 + ), + "0xcccccccccccccccccccccccccccccccccccccccc": Account( + code=caller_code + ), + to_address(0x100): Account(code=call_code), + to_address(0x200): Account(code=callcode_code), + to_address(0x300): Account(code=delegatecall_code), + to_address(0x400): Account(code=staticcall_code), + } + + for i, data in enumerate( + [to_hash(x) for x in range(0x100, 0x400 + 1, 0x100)] + ): + + tx = Transaction( + ty=0x0, + data=data, + chain_id=0x0, + nonce=0, + to="0xcccccccccccccccccccccccccccccccccccccccc", + gas_limit=100000000, + gas_price=10, + protected=False, + ) + + post = {} + + if is_fork(fork=fork, which="shanghai"): + post["0xcccccccccccccccccccccccccccccccccccccccc"] = Account( + storage={ + # On shanghai and beyond, calls with only 100 gas to + # coinbase will succeed. + 0: 1, + } + ) + else: + post["0xcccccccccccccccccccccccccccccccccccccccc"] = Account( + storage={ + # Before shanghai, calls with only 100 gas to + # coinbase will fail. + 0: 0, + } + ) + + yield StateTest(env=env, pre=pre, post=post, txs=[tx]) + + +@test_from(fork="merged") +def test_warm_coinbase_gas_usage(fork): + """ + Test gas usage of different opcodes assuming warm coinbase. + """ + env = Environment( + coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + difficulty=0x20000, + gas_limit=10000000000, + number=1, + timestamp=1000, + ) + + # List of opcodes that are affected by + gas_measured_opcodes: Dict[str, CodeGasMeasure] = { + "EXTCODESIZE": CodeGasMeasure( + code=bytes( + [ + 0x41, # addr: COINBASE + 0x3B, # EXTCODESIZE + ] + ), + overhead_cost=2, + extra_stack_items=1, + ), + "EXTCODECOPY": CodeGasMeasure( + code=bytes( + [ + 0x60, # length + 0x00, + 0x60, # offset + 0x00, + 0x60, # offset + 0x00, + 0x41, # addr: COINBASE + 0x3C, # EXTCODECOPY + ] + ), + overhead_cost=2 + 3 + 3 + 3, + ), + "EXTCODEHASH": CodeGasMeasure( + code=bytes( + [ + 0x41, # addr: COINBASE + 0x3F, # EXTCODEHASH + ] + ), + overhead_cost=2, + extra_stack_items=1, + ), + "BALANCE": CodeGasMeasure( + code=bytes( + [ + 0x41, # addr: COINBASE + 0x31, # BALANCE + ] + ), + overhead_cost=2, + extra_stack_items=1, + ), + "CALL": CodeGasMeasure( + code=bytes( + [ + 0x60, # returnLength + 0x00, + 0x60, # returnOffset + 0x00, + 0x60, # argsLength + 0x00, + 0x60, # argsOffset + 0x00, + 0x60, # value + 0x00, + 0x41, # addr: COINBASE + 0x60, # gas + 0xFF, + 0xF1, # CALL + ] + ), + overhead_cost=3 + 2 + 3 + 3 + 3 + 3 + 3, + extra_stack_items=1, + ), + "CALLCODE": CodeGasMeasure( + code=bytes( + [ + 0x60, # returnLength + 0x00, + 0x60, # returnOffset + 0x00, + 0x60, # argsLength + 0x00, + 0x60, # argsOffset + 0x00, + 0x60, # value + 0x00, + 0x41, # addr: COINBASE + 0x60, # gas + 0xFF, + 0xF2, # CALLCODE + ] + ), + overhead_cost=3 + 2 + 3 + 3 + 3 + 3 + 3, + extra_stack_items=1, + ), + "DELEGATECALL": CodeGasMeasure( + code=bytes( + [ + 0x60, # returnLength + 0x00, + 0x60, # returnOffset + 0x00, + 0x60, # argsLength + 0x00, + 0x60, # argsOffset + 0x00, + 0x41, # addr: COINBASE + 0x60, # gas + 0xFF, + 0xF4, # DELEGATECALL + ] + ), + overhead_cost=3 + 2 + 3 + 3 + 3 + 3, + extra_stack_items=1, + ), + "STATICCALL": CodeGasMeasure( + code=bytes( + [ + 0x60, # returnLength + 0x00, + 0x60, # returnOffset + 0x00, + 0x60, # argsLength + 0x00, + 0x60, # argsOffset + 0x00, + 0x41, # addr: COINBASE + 0x60, # gas + 0xFF, + 0xFA, # STATICCALL + ] + ), + overhead_cost=3 + 2 + 3 + 3 + 3 + 3, + extra_stack_items=1, + ), + } + + for opcode in gas_measured_opcodes: + measure_address = to_address(0x100) + pre = { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": Account( + balance=1000000000000000000000 + ), + measure_address: Account( + code=gas_measured_opcodes[opcode], + ), + } + + if is_fork(fork, "shanghai"): + expected_gas = 100 # Warm account access cost after EIP-3651 + else: + expected_gas = 2600 # Cold account access cost before EIP-3651 + + post = { + measure_address: Account( + storage={ + 0x00: expected_gas, + } + ) + } + tx = Transaction( + ty=0x0, + chain_id=0x0, + nonce=0, + to=measure_address, + gas_limit=100000000, + gas_price=10, + protected=False, + ) + + yield StateTest( + env=env, + pre=pre, + post=post, + txs=[tx], + name="warm_coinbase_opcode_" + opcode.lower(), + ) diff --git a/fillers/eips/eip3855.py b/fillers/eips/eip3855.py new file mode 100644 index 00000000000..db84f384a27 --- /dev/null +++ b/fillers/eips/eip3855.py @@ -0,0 +1,253 @@ +""" +Test EIP-3855: PUSH0 Instruction +EIP: https://eips.ethereum.org/EIPS/eip-3855 +Source tests: https://github.com/ethereum/tests/pull/1033 +""" + +from ethereum_test_tools import ( + Account, + CodeGasMeasure, + Environment, + StateTest, + Transaction, + Yul, + test_from, + to_address, +) + + +@test_from(fork="shanghai", eips=[3855]) +def test_push0(fork): + """ + Test push0 opcode. + """ + env = Environment() + + pre = { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": Account( + balance=1000000000000000000000 + ), + } + + post = {} + + # Entry point for all test cases this address + tx = Transaction( + to=to_address(0x100), + gas_limit=100000, + ) + + """ + Test case 1: Simple PUSH0 as key to SSTORE + """ + pre[to_address(0x100)] = Account( + code=bytes( + [ + 0x60, # PUSH1 + 0x01, + 0x5F, # PUSH0 + 0x55, # SSTORE + ] + ), + ) + + post[to_address(0x100)] = Account( + storage={ + 0x00: 0x01, + } + ) + + yield StateTest( + env=env, pre=pre, post=post, txs=[tx], name="push0_key_sstore" + ) + + """ + Test case 2: Fill stack with PUSH0, then OR all values and save using + SSTORE + """ + pre[to_address(0x100)] = Account( + code=bytes( + [ + 0x5F, # PUSH0 + ] + * 1024 + ) + + bytes( + [ + 0x17, # OR + ] + * 1023 + ) + + bytes( + [ + 0x60, # PUSH1 + 0x01, + 0x90, # SWAP1 + 0x55, # SSTORE + ] + ), + ) + + post[to_address(0x100)] = Account( + storage={ + 0x00: 0x01, + } + ) + + yield StateTest( + env=env, pre=pre, post=post, txs=[tx], name="push0_fill_stack" + ) + + """ + Test case 3: Stack overflow by using PUSH0 1025 times + """ + pre[to_address(0x100)] = Account( + code=Yul("{ sstore(0, 1) }") + + bytes( + [ + 0x5F, # PUSH0 + ] + * 1025 # Stack overflow + ), + ) + + post[to_address(0x100)] = Account( + storage={ + 0x00: 0x00, + } + ) + + yield StateTest( + env=env, pre=pre, post=post, txs=[tx], name="push0_stack_overflow" + ) + + """ + Test case 4: Update already existing storage value + """ + pre[to_address(0x100)] = Account( + code=bytes( + [ + 0x60, # PUSH1 + 0x02, + 0x5F, # PUSH0 + 0x55, # SSTORE + 0x5F, # PUSH0 + 0x60, # PUSH1 + 0x01, + 0x55, # SSTORE + ] + ), + storage={ + 0x00: 0x0A, + 0x01: 0x0A, + }, + ) + + post[to_address(0x100)] = Account( + storage={ + 0x00: 0x02, + 0x01: 0x00, + } + ) + + yield StateTest( + env=env, pre=pre, post=post, txs=[tx], name="push0_storage_overwrite" + ) + + """ + Test case 5: PUSH0 during staticcall + """ + + pre[to_address(0x100)] = Account( + code=Yul( + """ + { + sstore(0, staticcall(100000, 0x200, 0, 0, 0, 0)) + sstore(0, 1) + returndatacopy(0x1f, 0, 1) + sstore(1, mload(0)) + } + """ + ) + ) + pre[to_address(0x200)] = Account( + code=bytes( + [ + 0x60, # PUSH1 + 0xFF, + 0x5F, # PUSH0 + 0x53, # MSTORE8 + 0x60, # PUSH1 + 0x01, + 0x60, # PUSH1 + 0x00, + 0xF3, # RETURN + ] + ), + ) + post[to_address(0x100)] = Account( + storage={ + 0x00: 0x01, + 0x01: 0xFF, + } + ) + + yield StateTest( + env=env, pre=pre, post=post, txs=[tx], name="push0_during_staticcall" + ) + + del pre[to_address(0x200)] + + """ + Test case 6: Jump to a JUMPDEST next to a PUSH0, must succeed. + """ + pre[to_address(0x100)] = Account( + code=bytes( + [ + 0x60, # PUSH1 + 0x04, + 0x56, # JUMP + 0x5F, # PUSH0 + 0x5B, # JUMPDEST + 0x60, # PUSH1 + 0x01, + 0x5F, # PUSH0 + 0x55, # SSTORE + 0x00, # STOP + ] + ), + ) + + post[to_address(0x100)] = Account( + storage={ + 0x00: 0x01, + } + ) + + yield StateTest( + env=env, pre=pre, post=post, txs=[tx], name="push0_before_jumpdest" + ) + + """ + Test case 7: PUSH0 gas cost + """ + pre[to_address(0x100)] = Account( + code=CodeGasMeasure( + code=bytes( + [ + 0x5F, # PUSH0 + ] + ), + extra_stack_items=1, + ), + ) + + post[to_address(0x100)] = Account( + storage={ + 0x00: 0x02, + } + ) + + yield StateTest( + env=env, pre=pre, post=post, txs=[tx], name="push0_gas_cost" + ) diff --git a/fillers/eips/eip3860.py b/fillers/eips/eip3860.py new file mode 100644 index 00000000000..e76fc430623 --- /dev/null +++ b/fillers/eips/eip3860.py @@ -0,0 +1,700 @@ +""" +Test EIP-3860: Limit and meter initcode +EIP: https://eips.ethereum.org/EIPS/eip-3860 +Source tests: https://github.com/ethereum/tests/pull/990 + https://github.com/ethereum/tests/pull/1012 +""" + + +from typing import Any, Dict + +from ethereum_test_tools import ( + Account, + Block, + BlockchainTest, + Environment, + Initcode, + StateTest, + TestAddress, + Transaction, + Yul, + ceiling_division, + compute_create2_address, + compute_create_address, + eip_2028_transaction_data_cost, + test_from, + to_address, +) + +""" +General constants used for testing purposes +""" + +MAX_INITCODE_SIZE = 0xC000 +INITCODE_WORD_COST = 0x02 +INITCODE_RESULTING_DEPLOYED_CODE = bytes([0x00]) + +BASE_TRANSACTION_GAS = 0x5208 +CREATE_CONTRACT_BASE_GAS = 0x7D00 +GAS_OPCODE_GAS = 0x02 +PUSH_DUP_OPCODE_GAS = 0x03 + +""" +Helper functions +""" + + +def calculate_initcode_word_cost(length: int) -> int: + """ + Calculates the added word cost on contract creation added by the + length of the initcode based on the formula: + INITCODE_WORD_COST * ceil(len(initcode) / 32) + """ + return INITCODE_WORD_COST * ceiling_division(length, 32) + + +def calculate_create2_word_cost(length: int) -> int: + """ + Calculates the added word cost on contract creation added by the + length of the initcode based on the formula: + INITCODE_WORD_COST * ceil(len(initcode) / 32) + """ + return 6 * ceiling_division(length, 32) + + +def calculate_create_tx_intrinsic_cost( + initcode: Initcode, eip_active: bool +) -> int: + """ + Calcultes the intrinsic gas cost of a transaction that contains initcode + and creates a contract + """ + cost = ( + BASE_TRANSACTION_GAS # G_transaction + + CREATE_CONTRACT_BASE_GAS # G_transaction_create + + eip_2028_transaction_data_cost( + initcode.assemble() + ) # Transaction calldata cost + ) + if eip_active: + cost += calculate_initcode_word_cost(len(initcode.assemble())) + return cost + + +def calculate_create_tx_execution_cost( + initcode: Initcode, + eip_active: bool, +) -> int: + """ + Calculates the minimum total execution gas cost of a transaction that + contains initcode and creates a contract for the contract to be + successfully deployed + """ + cost = calculate_create_tx_intrinsic_cost( + initcode=initcode, eip_active=eip_active + ) + cost += initcode.deployment_gas + cost += initcode.execution_gas + return cost + + +""" +Initcode templates used throughout the tests +""" +INITCODE_ONES_MAX_LIMIT = Initcode( + deploy_code=INITCODE_RESULTING_DEPLOYED_CODE, + initcode_length=MAX_INITCODE_SIZE, + padding_byte=0x01, + name="max_size_ones_initcode", +) + +INITCODE_ZEROS_MAX_LIMIT = Initcode( + deploy_code=INITCODE_RESULTING_DEPLOYED_CODE, + initcode_length=MAX_INITCODE_SIZE, + padding_byte=0x00, + name="max_size_zeros_initcode", +) + +INITCODE_ONES_OVER_LIMIT = Initcode( + deploy_code=INITCODE_RESULTING_DEPLOYED_CODE, + initcode_length=MAX_INITCODE_SIZE + 1, + padding_byte=0x01, + name="over_limit_ones_initcode", +) + +INITCODE_ZEROS_OVER_LIMIT = Initcode( + deploy_code=INITCODE_RESULTING_DEPLOYED_CODE, + initcode_length=MAX_INITCODE_SIZE + 1, + padding_byte=0x00, + name="over_limit_zeros_initcode", +) + +INITCODE_ZEROS_32_BYTES = Initcode( + deploy_code=INITCODE_RESULTING_DEPLOYED_CODE, + initcode_length=32, + padding_byte=0x00, + name="32_bytes_initcode", +) + +INITCODE_ZEROS_33_BYTES = Initcode( + deploy_code=INITCODE_RESULTING_DEPLOYED_CODE, + initcode_length=33, + padding_byte=0x00, + name="33_bytes_initcode", +) + +INITCODE_ZEROS_49120_BYTES = Initcode( + deploy_code=INITCODE_RESULTING_DEPLOYED_CODE, + initcode_length=49120, + padding_byte=0x00, + name="49120_bytes_initcode", +) + +INITCODE_ZEROS_49121_BYTES = Initcode( + deploy_code=INITCODE_RESULTING_DEPLOYED_CODE, + initcode_length=49121, + padding_byte=0x00, + name="49121_bytes_initcode", +) + +EMPTY_INITCODE = Initcode( + deploy_code=bytes(), + name="empty_initcode", +) +EMPTY_INITCODE.bytecode = bytes() +EMPTY_INITCODE.deployment_gas = 0 +EMPTY_INITCODE.execution_gas = 0 + +SINGLE_BYTE_INITCODE = Initcode( + deploy_code=bytes(), + name="single_byte_initcode", +) +SINGLE_BYTE_INITCODE.bytecode = bytes([0x00]) +SINGLE_BYTE_INITCODE.deployment_gas = 0 +SINGLE_BYTE_INITCODE.execution_gas = 0 + +""" +Test cases using a contract creating transaction +""" + + +def generate_tx_initcode_limit_test_cases( + initcode: Initcode, + eip_3860_active: bool, +): + """ + Generates a BlockchainTest based on the provided `initcode` and + its length. + """ + env = Environment() + + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + + post: Dict[Any, Any] = {} + + created_contract_address = compute_create_address( + address=TestAddress, + nonce=0, + ) + + tx = Transaction( + nonce=0, + to=None, + data=initcode, + gas_limit=10000000, + gas_price=10, + ) + + block = Block(txs=[tx]) + + if len(initcode.assemble()) > MAX_INITCODE_SIZE and eip_3860_active: + post[created_contract_address] = Account.NONEXISTENT + tx.error = "max initcode size exceeded" + block.exception = "max initcode size exceeded" + else: + post[created_contract_address] = Account(code="0x00") + + yield BlockchainTest( + pre=pre, + post=post, + blocks=[block], + genesis_environment=env, + name=f"initcode_tx_{initcode.name}", + ) + + +@test_from(fork="shanghai", eips=[3860]) +def test_initcode_limit_contract_creating_tx(fork): + """ + Test creating a contract using a transaction using an initcode that is + on/over the max allowed limit. + """ + yield from generate_tx_initcode_limit_test_cases( + initcode=INITCODE_ZEROS_MAX_LIMIT, + eip_3860_active=True, + ) + yield from generate_tx_initcode_limit_test_cases( + initcode=INITCODE_ONES_MAX_LIMIT, + eip_3860_active=True, + ) + yield from generate_tx_initcode_limit_test_cases( + initcode=INITCODE_ZEROS_OVER_LIMIT, + eip_3860_active=True, + ) + yield from generate_tx_initcode_limit_test_cases( + initcode=INITCODE_ONES_OVER_LIMIT, + eip_3860_active=True, + ) + + +def generate_gas_cost_test_cases( + initcode: Initcode, + eip_3860_active: bool, +): + """ + Generates 4 test cases that verify the intrinsic gas cost of a + contract creating transaction: + 1) Test with exact intrinsic gas, contract create fails, + but tx is valid. + 2) Test with exact intrinsic gas minus one, contract create fails + and tx is invalid. + 3) Test with exact execution gas minus one, contract create fails, + but tx is valid. + 4) Test with exact execution gas, contract create succeeds. + + Initcode must be withing valid EIP-3860 length. + """ + # Common setup to all test cases + env = Environment() + pre = { + TestAddress: Account(balance=1000000000000000000000), + } + post: Dict[Any, Any] = {} + created_contract_address = compute_create_address( + address=TestAddress, + nonce=0, + ) + + # Calculate both the intrinsic tx gas cost and the total execution + # gas cost, used throughout all tests + exact_tx_intrinsic_gas = calculate_create_tx_intrinsic_cost( + initcode, eip_3860_active + ) + exact_tx_execution_gas = calculate_create_tx_execution_cost( + initcode, + eip_3860_active, + ) + + """ + Test case 1: Test with exact intrinsic gas, contract create fails, + but tx is valid. + """ + tx = Transaction( + nonce=0, + to=None, + data=initcode, + gas_limit=exact_tx_intrinsic_gas, + gas_price=10, + ) + block = Block(txs=[tx]) + if exact_tx_execution_gas == exact_tx_intrinsic_gas: + # Special scenario where the execution of the initcode and + # gas cost to deploy are zero + post[created_contract_address] = Account(code=initcode.deploy_code) + else: + post[created_contract_address] = Account.NONEXISTENT + + yield BlockchainTest( + pre=pre, + post=post, + blocks=[block], + genesis_environment=env, + name=f"{initcode.name}_tx_exact_intrinsic_gas", + ) + + """ + Test case 2: Test with exact intrinsic gas minus one, contract create fails + and tx is invalid. + """ + tx = Transaction( + nonce=0, + to=None, + data=initcode, + gas_limit=exact_tx_intrinsic_gas - 1, + gas_price=10, + error="intrinsic gas too low", + ) + block = Block( + txs=[tx], + exception="intrinsic gas too low", + ) + post[created_contract_address] = Account.NONEXISTENT + + yield BlockchainTest( + pre=pre, + post=post, + blocks=[block], + genesis_environment=env, + name=f"{initcode.name}_tx_under_intrinsic_gas", + ) + + """ + Test case 3: Test with exact execution gas minus one, contract create + fails, but tx is valid. + """ + if exact_tx_execution_gas == exact_tx_intrinsic_gas: + # Test case is virtually equal to previous + pass + else: + tx = Transaction( + nonce=0, + to=None, + data=initcode, + gas_limit=exact_tx_execution_gas - 1, + gas_price=10, + ) + block = Block(txs=[tx]) + post[created_contract_address] = Account.NONEXISTENT + + yield BlockchainTest( + pre=pre, + post=post, + blocks=[block], + genesis_environment=env, + name=f"{initcode.name}_tx_under_execution_gas", + ) + + """ + Test case 4: Test with exact execution gas, contract create succeeds. + """ + tx = Transaction( + nonce=0, + to=None, + data=initcode, + gas_limit=exact_tx_execution_gas, + gas_price=10, + ) + block = Block(txs=[tx]) + post[created_contract_address] = Account(code=initcode.deploy_code) + + yield BlockchainTest( + pre=pre, + post=post, + blocks=[block], + genesis_environment=env, + name=f"{initcode.name}_tx_exact_execution_gas", + ) + + +@test_from(fork="shanghai", eips=[3860]) +def test_initcode_limit_contract_creating_tx_gas_usage(fork): + """ + Test EIP-3860 Limit Initcode Gas Usage for a contract + creating transaction, using different initcode lengths. + """ + yield from generate_gas_cost_test_cases( + initcode=INITCODE_ZEROS_MAX_LIMIT, + eip_3860_active=True, + ) + + yield from generate_gas_cost_test_cases( + initcode=INITCODE_ONES_MAX_LIMIT, + eip_3860_active=True, + ) + + # Test cases to verify the initcode word cost limits + + yield from generate_gas_cost_test_cases( + initcode=EMPTY_INITCODE, + eip_3860_active=True, + ) + + yield from generate_gas_cost_test_cases( + initcode=SINGLE_BYTE_INITCODE, + eip_3860_active=True, + ) + + yield from generate_gas_cost_test_cases( + initcode=INITCODE_ZEROS_32_BYTES, + eip_3860_active=True, + ) + + yield from generate_gas_cost_test_cases( + initcode=INITCODE_ZEROS_33_BYTES, + eip_3860_active=True, + ) + + yield from generate_gas_cost_test_cases( + initcode=INITCODE_ZEROS_49120_BYTES, + eip_3860_active=True, + ) + + yield from generate_gas_cost_test_cases( + initcode=INITCODE_ZEROS_49121_BYTES, + eip_3860_active=True, + ) + + +""" +Test cases using the CREATE opcode +""" + + +def generate_create_opcode_initcode_test_cases( + opcode: str, + initcode: Initcode, + eip_3860_active: bool, +): + """ + Generates a StateTest using the `CREATE`/`CREATE2` opcode based on the + provided `initcode`, its executing cost, and the deployed code. + """ + env = Environment() + + if opcode == "create": + code = Yul( + """ + { + let contract_length := calldatasize() + calldatacopy(0, 0, contract_length) + let gas1 := gas() + let res := create(0, 0, contract_length) + let gas2 := gas() + sstore(0, res) + sstore(1, sub(gas1, gas2)) + } + """ + ) + created_contract_address = compute_create_address( + address=0x100, + nonce=1, + ) + + elif opcode == "create2": + code = Yul( + """ + { + let contract_length := calldatasize() + calldatacopy(0, 0, contract_length) + let gas1 := gas() + let res := create2(0, 0, contract_length, 0) + let gas2 := gas() + sstore(0, res) + sstore(1, sub(gas1, gas2)) + } + """ + ) + created_contract_address = compute_create2_address( + address=0x100, + salt=0, + initcode=initcode.assemble(), + ) + else: + raise Exception("invalid opcode for generator") + + pre = { + TestAddress: Account(balance=1000000000000000000000), + to_address(0x100): Account( + code=code, + nonce=1, + ), + } + + post: Dict[Any, Any] = {} + + tx = Transaction( + nonce=0, + to=to_address(0x100), + data=initcode, + gas_limit=10000000, + gas_price=10, + ) + + # Calculate the expected gas of the contract creation operation + expected_gas_usage = ( + CREATE_CONTRACT_BASE_GAS + GAS_OPCODE_GAS + (3 * PUSH_DUP_OPCODE_GAS) + ) + if opcode == "create2": + # Extra PUSH operation + expected_gas_usage += PUSH_DUP_OPCODE_GAS + + if len(initcode.assemble()) > MAX_INITCODE_SIZE and eip_3860_active: + post[created_contract_address] = Account.NONEXISTENT + post[to_address(0x100)] = Account( + nonce=1, + storage={ + 0: 0, + 1: expected_gas_usage, + }, + ) + else: + # The initcode is only executed if the length check succeeds + expected_gas_usage += initcode.execution_gas + # The code is only deployed if the length check succeeds + expected_gas_usage += initcode.deployment_gas + + if opcode == "create2": + # CREATE2 hashing cost should only be deducted if the initcode + # does not exceed the max length + expected_gas_usage += calculate_create2_word_cost( + len(initcode.assemble()) + ) + + if eip_3860_active: + # Initcode word cost is only deducted if the length check succeeds + expected_gas_usage += calculate_initcode_word_cost( + len(initcode.assemble()) + ) + + post[created_contract_address] = Account(code=initcode.deploy_code) + post[to_address(0x100)] = Account( + nonce=2, + storage={ + 0: created_contract_address, + 1: expected_gas_usage, + }, + ) + + yield StateTest( + env=env, + pre=pre, + post=post, + txs=[tx], + name=f"{opcode}_opcode_{initcode.name}", + ) + + +@test_from(fork="shanghai", eips=[3860]) +def test_initcode_limit_create_opcode(fork): + """ + Test creating a contract using the CREATE opcode with an initcode that is + on/over the max allowed limit. + """ + yield from generate_create_opcode_initcode_test_cases( + opcode="create", + initcode=INITCODE_ZEROS_MAX_LIMIT, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create", + initcode=INITCODE_ONES_MAX_LIMIT, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create", + initcode=INITCODE_ZEROS_OVER_LIMIT, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create", + initcode=INITCODE_ONES_OVER_LIMIT, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create", + initcode=EMPTY_INITCODE, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create", + initcode=SINGLE_BYTE_INITCODE, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create", + initcode=INITCODE_ZEROS_32_BYTES, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create", + initcode=INITCODE_ZEROS_33_BYTES, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create", + initcode=INITCODE_ZEROS_49120_BYTES, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create", + initcode=INITCODE_ZEROS_49121_BYTES, + eip_3860_active=True, + ) + + +@test_from(fork="shanghai", eips=[3860]) +def test_initcode_limit_create2_opcode(fork): + """ + Test creating a contract using the CREATE2 opcode with an initcode that is + on/over the max allowed limit. + """ + yield from generate_create_opcode_initcode_test_cases( + opcode="create2", + initcode=INITCODE_ZEROS_MAX_LIMIT, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create2", + initcode=INITCODE_ONES_MAX_LIMIT, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create2", + initcode=INITCODE_ZEROS_OVER_LIMIT, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create2", + initcode=INITCODE_ONES_OVER_LIMIT, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create2", + initcode=EMPTY_INITCODE, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create2", + initcode=SINGLE_BYTE_INITCODE, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create2", + initcode=INITCODE_ZEROS_32_BYTES, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create2", + initcode=INITCODE_ZEROS_33_BYTES, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create2", + initcode=INITCODE_ZEROS_49120_BYTES, + eip_3860_active=True, + ) + + yield from generate_create_opcode_initcode_test_cases( + opcode="create2", + initcode=INITCODE_ZEROS_49121_BYTES, + eip_3860_active=True, + ) From 914397c7098fd509833078a477d751dd8a433288 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 9 Dec 2022 12:31:21 -0600 Subject: [PATCH 06/12] Minor comment fix --- fillers/eips/eip3860.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fillers/eips/eip3860.py b/fillers/eips/eip3860.py index e76fc430623..b46c93db8c1 100644 --- a/fillers/eips/eip3860.py +++ b/fillers/eips/eip3860.py @@ -32,6 +32,7 @@ MAX_INITCODE_SIZE = 0xC000 INITCODE_WORD_COST = 0x02 +KECCAK_WORD_COST = 0x06 INITCODE_RESULTING_DEPLOYED_CODE = bytes([0x00]) BASE_TRANSACTION_GAS = 0x5208 @@ -56,10 +57,9 @@ def calculate_initcode_word_cost(length: int) -> int: def calculate_create2_word_cost(length: int) -> int: """ Calculates the added word cost on contract creation added by the - length of the initcode based on the formula: - INITCODE_WORD_COST * ceil(len(initcode) / 32) + hashing of the initcode during create2 contract creation. """ - return 6 * ceiling_division(length, 32) + return KECCAK_WORD_COST * ceiling_division(length, 32) def calculate_create_tx_intrinsic_cost( From 416cb66b4b636d25902aa7e427322ffc8c361ae9 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 9 Dec 2022 12:33:39 -0600 Subject: [PATCH 07/12] Parameter name change --- fillers/eips/eip3860.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fillers/eips/eip3860.py b/fillers/eips/eip3860.py index b46c93db8c1..44ce1e1f16c 100644 --- a/fillers/eips/eip3860.py +++ b/fillers/eips/eip3860.py @@ -63,7 +63,7 @@ def calculate_create2_word_cost(length: int) -> int: def calculate_create_tx_intrinsic_cost( - initcode: Initcode, eip_active: bool + initcode: Initcode, eip_3860_active: bool ) -> int: """ Calcultes the intrinsic gas cost of a transaction that contains initcode @@ -76,14 +76,14 @@ def calculate_create_tx_intrinsic_cost( initcode.assemble() ) # Transaction calldata cost ) - if eip_active: + if eip_3860_active: cost += calculate_initcode_word_cost(len(initcode.assemble())) return cost def calculate_create_tx_execution_cost( initcode: Initcode, - eip_active: bool, + eip_3860_active: bool, ) -> int: """ Calculates the minimum total execution gas cost of a transaction that @@ -91,7 +91,7 @@ def calculate_create_tx_execution_cost( successfully deployed """ cost = calculate_create_tx_intrinsic_cost( - initcode=initcode, eip_active=eip_active + initcode=initcode, eip_3860_active=eip_3860_active ) cost += initcode.deployment_gas cost += initcode.execution_gas From 23736b931b91220feeaf9b92afe764fc228294b0 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 9 Dec 2022 12:38:35 -0600 Subject: [PATCH 08/12] Comments, wording --- fillers/eips/eip3860.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fillers/eips/eip3860.py b/fillers/eips/eip3860.py index 44ce1e1f16c..bd2e920c57a 100644 --- a/fillers/eips/eip3860.py +++ b/fillers/eips/eip3860.py @@ -210,10 +210,14 @@ def generate_tx_initcode_limit_test_cases( block = Block(txs=[tx]) if len(initcode.assemble()) > MAX_INITCODE_SIZE and eip_3860_active: + # Initcode is above the max size, tx inclusion in the block makes + # it invalid. post[created_contract_address] = Account.NONEXISTENT tx.error = "max initcode size exceeded" block.exception = "max initcode size exceeded" else: + # Initcode is at or below the max size, tx inclusion in the block + # is ok and the contract is successfully created. post[created_contract_address] = Account(code="0x00") yield BlockchainTest( @@ -264,7 +268,7 @@ def generate_gas_cost_test_cases( but tx is valid. 4) Test with exact execution gas, contract create succeeds. - Initcode must be withing valid EIP-3860 length. + Initcode must be within valid EIP-3860 length. """ # Common setup to all test cases env = Environment() From 53085f701c255965ef62b500aa954c2d39ab2aa4 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 9 Dec 2022 12:39:46 -0600 Subject: [PATCH 09/12] Tox fix --- whitelist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/whitelist.txt b/whitelist.txt index 362950a7bc7..97c810e0bc5 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -51,6 +51,7 @@ lllc solc yul +keccak sha3 stop From 6257568fd551db35b8bfa471c63bcfaddf5b3c00 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 9 Dec 2022 17:02:46 -0600 Subject: [PATCH 10/12] Fix copy pre-state on fill --- src/ethereum_test_tools/filling/fill.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ethereum_test_tools/filling/fill.py b/src/ethereum_test_tools/filling/fill.py index 2872dbbc91c..a1126504490 100644 --- a/src/ethereum_test_tools/filling/fill.py +++ b/src/ethereum_test_tools/filling/fill.py @@ -1,6 +1,7 @@ """ Filler object definitions. """ +from copy import copy from typing import List, Mapping, Optional from evm_block_builder import BlockBuilder @@ -60,7 +61,7 @@ def fill_test( fork="+".join([fork] + [str(eip) for eip in eips]) if eips is not None else fork, - pre_state=test.pre, + pre_state=copy(test.pre), post_state=alloc, seal_engine=engine, name=test.name, From 1bad10b96e1b2b4cd162d7238b822d169fd50456 Mon Sep 17 00:00:00 2001 From: marioevz Date: Sat, 10 Dec 2022 08:59:41 -0600 Subject: [PATCH 11/12] Review fixes --- fillers/eips/eip3651.py | 9 +++------ fillers/eips/eip3855.py | 37 +++++++++++++++++++------------------ fillers/eips/eip3860.py | 19 +++++++++---------- 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/fillers/eips/eip3651.py b/fillers/eips/eip3651.py index 6bcaf85160e..9373325aa13 100644 --- a/fillers/eips/eip3651.py +++ b/fillers/eips/eip3651.py @@ -10,6 +10,7 @@ CodeGasMeasure, Environment, StateTest, + TestAddress, Transaction, Yul, is_fork, @@ -106,9 +107,7 @@ def test_warm_coinbase_call_out_of_gas(fork): ) pre = { - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": Account( - balance=1000000000000000000000 - ), + TestAddress: Account(balance=1000000000000000000000), "0xcccccccccccccccccccccccccccccccccccccccc": Account( code=caller_code ), @@ -304,9 +303,7 @@ def test_warm_coinbase_gas_usage(fork): for opcode in gas_measured_opcodes: measure_address = to_address(0x100) pre = { - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": Account( - balance=1000000000000000000000 - ), + TestAddress: Account(balance=1000000000000000000000), measure_address: Account( code=gas_measured_opcodes[opcode], ), diff --git a/fillers/eips/eip3855.py b/fillers/eips/eip3855.py index db84f384a27..7a91b5e0773 100644 --- a/fillers/eips/eip3855.py +++ b/fillers/eips/eip3855.py @@ -9,6 +9,7 @@ CodeGasMeasure, Environment, StateTest, + TestAddress, Transaction, Yul, test_from, @@ -24,23 +25,23 @@ def test_push0(fork): env = Environment() pre = { - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": Account( - balance=1000000000000000000000 - ), + TestAddress: Account(balance=1000000000000000000000), } post = {} + code_address = to_address(0x100) + # Entry point for all test cases this address tx = Transaction( - to=to_address(0x100), + to=code_address, gas_limit=100000, ) """ Test case 1: Simple PUSH0 as key to SSTORE """ - pre[to_address(0x100)] = Account( + pre[code_address] = Account( code=bytes( [ 0x60, # PUSH1 @@ -51,7 +52,7 @@ def test_push0(fork): ), ) - post[to_address(0x100)] = Account( + post[code_address] = Account( storage={ 0x00: 0x01, } @@ -65,7 +66,7 @@ def test_push0(fork): Test case 2: Fill stack with PUSH0, then OR all values and save using SSTORE """ - pre[to_address(0x100)] = Account( + pre[code_address] = Account( code=bytes( [ 0x5F, # PUSH0 @@ -88,7 +89,7 @@ def test_push0(fork): ), ) - post[to_address(0x100)] = Account( + post[code_address] = Account( storage={ 0x00: 0x01, } @@ -101,7 +102,7 @@ def test_push0(fork): """ Test case 3: Stack overflow by using PUSH0 1025 times """ - pre[to_address(0x100)] = Account( + pre[code_address] = Account( code=Yul("{ sstore(0, 1) }") + bytes( [ @@ -111,7 +112,7 @@ def test_push0(fork): ), ) - post[to_address(0x100)] = Account( + post[code_address] = Account( storage={ 0x00: 0x00, } @@ -124,7 +125,7 @@ def test_push0(fork): """ Test case 4: Update already existing storage value """ - pre[to_address(0x100)] = Account( + pre[code_address] = Account( code=bytes( [ 0x60, # PUSH1 @@ -143,7 +144,7 @@ def test_push0(fork): }, ) - post[to_address(0x100)] = Account( + post[code_address] = Account( storage={ 0x00: 0x02, 0x01: 0x00, @@ -158,7 +159,7 @@ def test_push0(fork): Test case 5: PUSH0 during staticcall """ - pre[to_address(0x100)] = Account( + pre[code_address] = Account( code=Yul( """ { @@ -185,7 +186,7 @@ def test_push0(fork): ] ), ) - post[to_address(0x100)] = Account( + post[code_address] = Account( storage={ 0x00: 0x01, 0x01: 0xFF, @@ -201,7 +202,7 @@ def test_push0(fork): """ Test case 6: Jump to a JUMPDEST next to a PUSH0, must succeed. """ - pre[to_address(0x100)] = Account( + pre[code_address] = Account( code=bytes( [ 0x60, # PUSH1 @@ -218,7 +219,7 @@ def test_push0(fork): ), ) - post[to_address(0x100)] = Account( + post[code_address] = Account( storage={ 0x00: 0x01, } @@ -231,7 +232,7 @@ def test_push0(fork): """ Test case 7: PUSH0 gas cost """ - pre[to_address(0x100)] = Account( + pre[code_address] = Account( code=CodeGasMeasure( code=bytes( [ @@ -242,7 +243,7 @@ def test_push0(fork): ), ) - post[to_address(0x100)] = Account( + post[code_address] = Account( storage={ 0x00: 0x02, } diff --git a/fillers/eips/eip3860.py b/fillers/eips/eip3860.py index bd2e920c57a..f21c6ed6466 100644 --- a/fillers/eips/eip3860.py +++ b/fillers/eips/eip3860.py @@ -30,15 +30,15 @@ General constants used for testing purposes """ -MAX_INITCODE_SIZE = 0xC000 -INITCODE_WORD_COST = 0x02 -KECCAK_WORD_COST = 0x06 +MAX_INITCODE_SIZE = 49152 +INITCODE_WORD_COST = 2 +KECCAK_WORD_COST = 6 INITCODE_RESULTING_DEPLOYED_CODE = bytes([0x00]) -BASE_TRANSACTION_GAS = 0x5208 -CREATE_CONTRACT_BASE_GAS = 0x7D00 -GAS_OPCODE_GAS = 0x02 -PUSH_DUP_OPCODE_GAS = 0x03 +BASE_TRANSACTION_GAS = 21000 +CREATE_CONTRACT_BASE_GAS = 32000 +GAS_OPCODE_GAS = 2 +PUSH_DUP_OPCODE_GAS = 3 """ Helper functions @@ -86,9 +86,8 @@ def calculate_create_tx_execution_cost( eip_3860_active: bool, ) -> int: """ - Calculates the minimum total execution gas cost of a transaction that - contains initcode and creates a contract for the contract to be - successfully deployed + Calculates the total execution gas cost of a transaction that + contains initcode and creates a contract """ cost = calculate_create_tx_intrinsic_cost( initcode=initcode, eip_3860_active=eip_3860_active From 567b42e3200a4431c8ba12b3ea76a159d486dec9 Mon Sep 17 00:00:00 2001 From: marioevz Date: Sat, 10 Dec 2022 09:28:08 -0600 Subject: [PATCH 12/12] Format fixes --- fillers/eips/eip3855.py | 252 ++++++++++++++++------------------------ 1 file changed, 98 insertions(+), 154 deletions(-) diff --git a/fillers/eips/eip3855.py b/fillers/eips/eip3855.py index 7a91b5e0773..d3a909bbac1 100644 --- a/fillers/eips/eip3855.py +++ b/fillers/eips/eip3855.py @@ -24,39 +24,32 @@ def test_push0(fork): """ env = Environment() - pre = { - TestAddress: Account(balance=1000000000000000000000), - } - + pre = {TestAddress: Account(balance=1000000000000000000000)} post = {} - code_address = to_address(0x100) + addr_1 = to_address(0x100) + addr_2 = to_address(0x200) - # Entry point for all test cases this address + # Entry point for all test cases is the same address tx = Transaction( - to=code_address, + to=addr_1, gas_limit=100000, ) """ Test case 1: Simple PUSH0 as key to SSTORE """ - pre[code_address] = Account( - code=bytes( - [ - 0x60, # PUSH1 - 0x01, - 0x5F, # PUSH0 - 0x55, # SSTORE - ] - ), + code = bytes( + [ + 0x60, # PUSH1 + 0x01, + 0x5F, # PUSH0 + 0x55, # SSTORE + ] ) - post[code_address] = Account( - storage={ - 0x00: 0x01, - } - ) + pre[addr_1] = Account(code=code) + post[addr_1] = Account(storage={0x00: 0x01}) yield StateTest( env=env, pre=pre, post=post, txs=[tx], name="push0_key_sstore" @@ -66,35 +59,20 @@ def test_push0(fork): Test case 2: Fill stack with PUSH0, then OR all values and save using SSTORE """ - pre[code_address] = Account( - code=bytes( - [ - 0x5F, # PUSH0 - ] - * 1024 - ) - + bytes( - [ - 0x17, # OR - ] - * 1023 - ) - + bytes( - [ - 0x60, # PUSH1 - 0x01, - 0x90, # SWAP1 - 0x55, # SSTORE - ] - ), - ) - - post[code_address] = Account( - storage={ - 0x00: 0x01, - } + code = bytes([0x5F] * 1024) # PUSH0 + code += bytes([0x17] * 1023) # OR + code += bytes( + [ + 0x60, # PUSH1 + 0x01, + 0x90, # SWAP1 + 0x55, # SSTORE + ] ) + pre[addr_1] = Account(code=code) + post[addr_1] = Account(storage={0x00: 0x01}) + yield StateTest( env=env, pre=pre, post=post, txs=[tx], name="push0_fill_stack" ) @@ -102,21 +80,18 @@ def test_push0(fork): """ Test case 3: Stack overflow by using PUSH0 1025 times """ - pre[code_address] = Account( - code=Yul("{ sstore(0, 1) }") - + bytes( - [ - 0x5F, # PUSH0 - ] - * 1025 # Stack overflow - ), + code = bytes( + [ + 0x60, # PUSH1 + 0x01, + 0x5F, # PUSH0 + 0x55, # SSTORE + ] ) + code += bytes([0x5F] * 1025) # PUSH0, stack overflow - post[code_address] = Account( - storage={ - 0x00: 0x00, - } - ) + pre[addr_1] = Account(code=code) + post[addr_1] = Account(storage={0x00: 0x00}) yield StateTest( env=env, pre=pre, post=post, txs=[tx], name="push0_stack_overflow" @@ -125,32 +100,22 @@ def test_push0(fork): """ Test case 4: Update already existing storage value """ - pre[code_address] = Account( - code=bytes( - [ - 0x60, # PUSH1 - 0x02, - 0x5F, # PUSH0 - 0x55, # SSTORE - 0x5F, # PUSH0 - 0x60, # PUSH1 - 0x01, - 0x55, # SSTORE - ] - ), - storage={ - 0x00: 0x0A, - 0x01: 0x0A, - }, - ) - - post[code_address] = Account( - storage={ - 0x00: 0x02, - 0x01: 0x00, - } + code = bytes( + [ + 0x60, # PUSH1 + 0x02, + 0x5F, # PUSH0 + 0x55, # SSTORE + 0x5F, # PUSH0 + 0x60, # PUSH1 + 0x01, + 0x55, # SSTORE + ] ) + pre[addr_1] = Account(code=code, storage={0x00: 0x0A, 0x01: 0x0A}) + post[addr_1] = Account(storage={0x00: 0x02, 0x01: 0x00}) + yield StateTest( env=env, pre=pre, post=post, txs=[tx], name="push0_storage_overwrite" ) @@ -158,73 +123,61 @@ def test_push0(fork): """ Test case 5: PUSH0 during staticcall """ - - pre[code_address] = Account( - code=Yul( - """ - { - sstore(0, staticcall(100000, 0x200, 0, 0, 0, 0)) - sstore(0, 1) - returndatacopy(0x1f, 0, 1) - sstore(1, mload(0)) - } - """ - ) - ) - pre[to_address(0x200)] = Account( - code=bytes( - [ - 0x60, # PUSH1 - 0xFF, - 0x5F, # PUSH0 - 0x53, # MSTORE8 - 0x60, # PUSH1 - 0x01, - 0x60, # PUSH1 - 0x00, - 0xF3, # RETURN - ] - ), - ) - post[code_address] = Account( - storage={ - 0x00: 0x01, - 0x01: 0xFF, + code_1 = Yul( + """ + { + sstore(0, staticcall(100000, 0x200, 0, 0, 0, 0)) + sstore(0, 1) + returndatacopy(0x1f, 0, 1) + sstore(1, mload(0)) } - ) + """ + ) + code_2 = bytes( + [ + 0x60, # PUSH1 + 0xFF, + 0x5F, # PUSH0 + 0x53, # MSTORE8 + 0x60, # PUSH1 + 0x01, + 0x60, # PUSH1 + 0x00, + 0xF3, # RETURN + ] + ) + + pre[addr_1] = Account(code=code_1) + pre[addr_2] = Account(code=code_2) + post[addr_1] = Account(storage={0x00: 0x01, 0x01: 0xFF}) yield StateTest( env=env, pre=pre, post=post, txs=[tx], name="push0_during_staticcall" ) - del pre[to_address(0x200)] + del pre[addr_2] """ Test case 6: Jump to a JUMPDEST next to a PUSH0, must succeed. """ - pre[code_address] = Account( - code=bytes( - [ - 0x60, # PUSH1 - 0x04, - 0x56, # JUMP - 0x5F, # PUSH0 - 0x5B, # JUMPDEST - 0x60, # PUSH1 - 0x01, - 0x5F, # PUSH0 - 0x55, # SSTORE - 0x00, # STOP - ] - ), - ) - - post[code_address] = Account( - storage={ - 0x00: 0x01, - } + code = bytes( + [ + 0x60, # PUSH1 + 0x04, + 0x56, # JUMP + 0x5F, # PUSH0 + 0x5B, # JUMPDEST + 0x60, # PUSH1 + 0x01, + 0x5F, # PUSH0 + 0x55, # SSTORE + 0x00, # STOP + ] ) + pre[addr_1] = Account(code=code) + post[addr_1] = Account(storage={0x00: 0x01}) + yield StateTest( env=env, pre=pre, post=post, txs=[tx], name="push0_before_jumpdest" ) @@ -232,22 +185,13 @@ def test_push0(fork): """ Test case 7: PUSH0 gas cost """ - pre[code_address] = Account( - code=CodeGasMeasure( - code=bytes( - [ - 0x5F, # PUSH0 - ] - ), - extra_stack_items=1, - ), + code = CodeGasMeasure( + code=bytes([0x5F]), # PUSH0 + extra_stack_items=1, ) - post[code_address] = Account( - storage={ - 0x00: 0x02, - } - ) + pre[addr_1] = Account(code=code) + post[addr_1] = Account(storage={0x00: 0x02}) yield StateTest( env=env, pre=pre, post=post, txs=[tx], name="push0_gas_cost"