From c8d4cb8da48c3763d4cc8471bdc30cdb4c7dda6a Mon Sep 17 00:00:00 2001 From: Mathieu <60658558+enitrat@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:29:10 +0700 Subject: [PATCH 1/9] fix: process_message_call gas on error (#781) --- cairo/ethereum/cancun/vm/interpreter.cairo | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cairo/ethereum/cancun/vm/interpreter.cairo b/cairo/ethereum/cancun/vm/interpreter.cairo index 1ca0d908..b58cefd1 100644 --- a/cairo/ethereum/cancun/vm/interpreter.cairo +++ b/cairo/ethereum/cancun/vm/interpreter.cairo @@ -422,7 +422,7 @@ func process_message_call{ if (has_collision.value + has_storage.value != FALSE) { // Return early with collision error tempvar collision_error = new EthereumException(AddressCollision); - let msg = create_empty_message_call_output(collision_error); + let msg = create_empty_message_call_output(Uint(0), collision_error); return msg; } @@ -488,8 +488,8 @@ func process_message_call{ // Prepare return values based on error state if (cast(evm.value.error, felt) != 0) { - let msg = create_empty_message_call_output(evm.value.error); squash_evm{evm=evm}(); + let msg = create_empty_message_call_output(evm.value.gas_left, evm.value.error); return msg; } @@ -512,7 +512,9 @@ func process_message_call{ return msg; } -func create_empty_message_call_output(error: EthereumException*) -> MessageCallOutput { +func create_empty_message_call_output( + gas_left: Uint, error: EthereumException* +) -> MessageCallOutput { alloc_locals; let (empty_logs: Log*) = alloc(); tempvar empty_tuple_log = TupleLog(new TupleLogStruct(data=empty_logs, len=0)); @@ -539,7 +541,7 @@ func create_empty_message_call_output(error: EthereumException*) -> MessageCallO tempvar msg = MessageCallOutput( new MessageCallOutputStruct( - gas_left=Uint(0), + gas_left=gas_left, refund_counter=U256(new U256Struct(0, 0)), logs=empty_tuple_log, accounts_to_delete=empty_set1, From 996eea2d9f91749286ad4c7cca164d38ef0e9e3f Mon Sep 17 00:00:00 2001 From: Mathieu <60658558+enitrat@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:54:09 +0700 Subject: [PATCH 2/9] fix: original state tries instantiation (#782) The issue was that we relied on the presence of an `original_storage_tries`, which would be set in the first tx in a block, and not overriden for future txs. The correct logic is to be based on whether the main_trie has a parent or not. --- cairo/ethereum/cancun/state.cairo | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cairo/ethereum/cancun/state.cairo b/cairo/ethereum/cancun/state.cairo index 8b849e41..687eb7fd 100644 --- a/cairo/ethereum/cancun/state.cairo +++ b/cairo/ethereum/cancun/state.cairo @@ -634,8 +634,9 @@ func begin_transaction{ let fp_and_pc = get_fp_and_pc(); local __fp__: felt* = fp_and_pc.fp_val; - // Set original storage tries if not already set - if (cast(state.value.original_storage_tries.value, felt) == 0) { + // Set original storage tries if in root transaction - indicated by a main trie with no parent + // dict + if (cast(state.value._main_trie.value._data.value.parent_dict, felt) == 0) { let storage_tries = state.value._storage_tries; let (new_dict_ptr_start, new_dict_ptr_end) = dict_copy( cast(storage_tries.value._data.value.dict_ptr_start, DictAccess*), From e4d884c187487989d58cf7a7827740ec1934b5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Walter?= Date: Wed, 12 Feb 2025 15:54:40 +0100 Subject: [PATCH 3/9] Remove unused programs (#778) --- cairo/programs/fibonacci.cairo | 18 ------------------ cairo/scripts/compile_cairo.py | 8 -------- 2 files changed, 26 deletions(-) delete mode 100644 cairo/programs/fibonacci.cairo diff --git a/cairo/programs/fibonacci.cairo b/cairo/programs/fibonacci.cairo deleted file mode 100644 index 822e830b..00000000 --- a/cairo/programs/fibonacci.cairo +++ /dev/null @@ -1,18 +0,0 @@ -func main() { - // Call fib(1, 1, 10). - let result: felt = fib(1, 1, 10); - - // Make sure the 10th Fibonacci number is 144. - assert result = 144; - ret; -} - -func fib(first_element, second_element, n) -> (res: felt) { - jmp fib_body if n != 0; - tempvar result = second_element; - return (second_element,); - - fib_body: - tempvar y = first_element + second_element; - return fib(second_element, y, n - 1); -} diff --git a/cairo/scripts/compile_cairo.py b/cairo/scripts/compile_cairo.py index 9d2b095f..0471224f 100644 --- a/cairo/scripts/compile_cairo.py +++ b/cairo/scripts/compile_cairo.py @@ -23,14 +23,6 @@ def compile_cairo(file_name, should_implement_hints=True): json.dump(program.Schema().dump(program), f, indent=4, sort_keys=True) -def compile_os(): - compile_cairo(Path(__file__).parents[1] / "programs" / "os.cairo") - - -def compile_fibonacci(): - compile_cairo(Path(__file__).parents[1] / "programs" / "fibonacci.cairo") - - if __name__ == "__main__": parser = argparse.ArgumentParser(description="Compile Cairo program") parser.add_argument("file_name", help="The Cairo file to compile") From f0d50ada22e651d2106f11248c4e08dcf77205ab Mon Sep 17 00:00:00 2001 From: Oba Date: Wed, 12 Feb 2025 16:36:27 +0100 Subject: [PATCH 4/9] fix: push_or_replace Stack[List[T]] (#779) Close #699 Co-authored-by: Elias Tazartes <66871571+Eikix@users.noreply.github.com> --- .../cancun/vm/instructions/test_arithmetic.py | 7 +++---- .../cancun/vm/instructions/test_control_flow.py | 7 +++---- .../cancun/vm/instructions/test_environment.py | 11 +++-------- .../ethereum/cancun/vm/instructions/test_keccak.py | 4 +--- .../ethereum/cancun/vm/instructions/test_log.py | 6 ++---- .../vm/instructions/test_memory_instructions.py | 13 ++++--------- .../ethereum/cancun/vm/instructions/test_storage.py | 6 ++---- .../ethereum/cancun/vm/instructions/test_system.py | 6 ++---- cairo/tests/utils/args_gen.py | 12 +++++++++++- 9 files changed, 31 insertions(+), 41 deletions(-) diff --git a/cairo/tests/ethereum/cancun/vm/instructions/test_arithmetic.py b/cairo/tests/ethereum/cancun/vm/instructions/test_arithmetic.py index 525dace7..a34ce7fc 100644 --- a/cairo/tests/ethereum/cancun/vm/instructions/test_arithmetic.py +++ b/cairo/tests/ethereum/cancun/vm/instructions/test_arithmetic.py @@ -12,7 +12,6 @@ smod, sub, ) -from ethereum.cancun.vm.stack import push from ethereum_types.numeric import U256 from hypothesis import given @@ -122,9 +121,9 @@ def test_addmod(self, cairo_run, evm: Evm): @given(evm=arithmetic_tests_strategy) def test_addmod_overflow_u256_cairo(self, cairo_run_py, evm: Evm): - push(evm.stack, U256(2**256 - 1)) - push(evm.stack, U256(2**256 - 2)) - push(evm.stack, U256(2**256 - 2)) + evm.stack.push_or_replace_many( + [U256(2**256 - 1), U256(2**256 - 2), U256(2**256 - 2)] + ) try: cairo_result = cairo_run_py("addmod", evm) except Exception as cairo_error: diff --git a/cairo/tests/ethereum/cancun/vm/instructions/test_control_flow.py b/cairo/tests/ethereum/cancun/vm/instructions/test_control_flow.py index 1bedfef0..13afcbf5 100644 --- a/cairo/tests/ethereum/cancun/vm/instructions/test_control_flow.py +++ b/cairo/tests/ethereum/cancun/vm/instructions/test_control_flow.py @@ -7,7 +7,6 @@ pc, stop, ) -from ethereum.cancun.vm.stack import push from ethereum.exceptions import EthereumException from ethereum_types.numeric import U256 from hypothesis import given @@ -44,7 +43,7 @@ def test_jump(self, cairo_run, evm: Evm, push_valid_jump_destination: bool): if push_valid_jump_destination and evm.valid_jump_destinations else 0 ) - push(evm.stack, U256(jump_dest)) + evm.stack.push_or_replace(U256(jump_dest)) try: cairo_result = cairo_run("jump", evm) @@ -73,13 +72,13 @@ def test_jumpi( jumpi_condition: bool, ): # Modify the stack to match the valid_jump_destinations generated by hypothesis - push(evm.stack, U256(jumpi_condition)) + evm.stack.push_or_replace(U256(jumpi_condition)) jump_dest = ( next(iter(evm.valid_jump_destinations)) if push_valid_jump_destination and evm.valid_jump_destinations else 0 ) - push(evm.stack, U256(jump_dest)) + evm.stack.push_or_replace(U256(jump_dest)) try: cairo_result = cairo_run("jumpi", evm) diff --git a/cairo/tests/ethereum/cancun/vm/instructions/test_environment.py b/cairo/tests/ethereum/cancun/vm/instructions/test_environment.py index b280eee4..636a3dd5 100644 --- a/cairo/tests/ethereum/cancun/vm/instructions/test_environment.py +++ b/cairo/tests/ethereum/cancun/vm/instructions/test_environment.py @@ -23,7 +23,6 @@ returndatasize, self_balance, ) -from ethereum.cancun.vm.stack import push from ethereum.exceptions import EthereumException from ethereum_types.numeric import U256 from hypothesis import given @@ -95,9 +94,7 @@ def codecopy_strategy(draw): # 80% chance to push valid values onto stack should_push = draw(integers(0, 99)) < 80 if should_push: - push(evm.stack, size) - push(evm.stack, code_start_index) - push(evm.stack, memory_start_index) + evm.stack.push_or_replace_many([size, code_start_index, memory_start_index]) return evm @@ -126,9 +123,7 @@ def calldatacopy_strategy(draw): # 80% chance to push valid values onto stack should_push = draw(integers(0, 99)) < 80 if should_push: - push(evm.stack, size) - push(evm.stack, data_start_index) - push(evm.stack, memory_start_index) + evm.stack.push_or_replace_many([size, data_start_index, memory_start_index]) return evm @@ -155,7 +150,7 @@ def evm_accessed_addresses_strategy(draw): # Draw an address from one of the available options address = draw(st.one_of(*address_options)) - push(evm.stack, U256.from_be_bytes(address)) + evm.stack.push_or_replace(U256.from_be_bytes(address)) return evm diff --git a/cairo/tests/ethereum/cancun/vm/instructions/test_keccak.py b/cairo/tests/ethereum/cancun/vm/instructions/test_keccak.py index c46a2dbf..f54a6642 100644 --- a/cairo/tests/ethereum/cancun/vm/instructions/test_keccak.py +++ b/cairo/tests/ethereum/cancun/vm/instructions/test_keccak.py @@ -1,6 +1,5 @@ from ethereum.cancun.vm import Evm from ethereum.cancun.vm.instructions.keccak import keccak -from ethereum.cancun.vm.stack import push from ethereum.exceptions import EthereumException from ethereum_types.numeric import U256 from hypothesis import given @@ -20,8 +19,7 @@ def test_keccak(self, cairo_run, evm: Evm, start_index: U256, size: U256): """ Test the keccak instruction by comparing Cairo and Python implementations """ - push(evm.stack, start_index) - push(evm.stack, size) + evm.stack.push_or_replace_many([start_index, size]) try: cairo_result = cairo_run("keccak", evm) except EthereumException as cairo_error: diff --git a/cairo/tests/ethereum/cancun/vm/instructions/test_log.py b/cairo/tests/ethereum/cancun/vm/instructions/test_log.py index aae7e452..b1b26cdc 100644 --- a/cairo/tests/ethereum/cancun/vm/instructions/test_log.py +++ b/cairo/tests/ethereum/cancun/vm/instructions/test_log.py @@ -1,6 +1,5 @@ from ethereum.cancun.vm import Evm from ethereum.cancun.vm.instructions.log import log0, log1, log2, log3, log4 -from ethereum.cancun.vm.stack import push from ethereum.exceptions import EthereumException from ethereum_types.numeric import U256 from hypothesis import given @@ -44,10 +43,9 @@ def log_strategy(draw, num_topics: int): # 80% chance to push valid values onto stack should_push = draw(integers(0, 99)) < 80 if should_push: - push(evm.stack, start_index) - push(evm.stack, size) + evm.stack.push_or_replace_many([start_index, size]) for topic in reversed(topics): - push(evm.stack, topic) + evm.stack.push_or_replace(topic) return evm diff --git a/cairo/tests/ethereum/cancun/vm/instructions/test_memory_instructions.py b/cairo/tests/ethereum/cancun/vm/instructions/test_memory_instructions.py index 0b4578fc..2cc28454 100644 --- a/cairo/tests/ethereum/cancun/vm/instructions/test_memory_instructions.py +++ b/cairo/tests/ethereum/cancun/vm/instructions/test_memory_instructions.py @@ -1,6 +1,5 @@ from ethereum.cancun.vm import Evm from ethereum.cancun.vm.instructions.memory import mcopy, mload, msize, mstore, mstore8 -from ethereum.cancun.vm.stack import push from ethereum.exceptions import EthereumException from ethereum_types.numeric import U256 from hypothesis import given @@ -27,8 +26,7 @@ def test_mstore( self, cairo_run, evm: Evm, start_position: U256, size: U256, push_on_stack: bool ): if push_on_stack: # to ensure valid cases are generated - push(evm.stack, start_position) - push(evm.stack, size) + evm.stack.push_or_replace_many([start_position, size]) try: cairo_result = cairo_run("mstore", evm) except EthereumException as cairo_error: @@ -49,8 +47,7 @@ def test_mstore8( self, cairo_run, evm: Evm, start_position: U256, size: U256, push_on_stack: bool ): if push_on_stack: # to ensure valid cases are generated - push(evm.stack, start_position) - push(evm.stack, size) + evm.stack.push_or_replace_many([start_position, size]) try: cairo_result = cairo_run("mstore8", evm) @@ -71,7 +68,7 @@ def test_mload( self, cairo_run, evm: Evm, start_position: U256, push_on_stack: bool ): if push_on_stack: # to ensure valid cases are generated - push(evm.stack, start_position) + evm.stack.push_or_replace(start_position) try: cairo_result = cairo_run("mload", evm) @@ -112,9 +109,7 @@ def test_mcopy( push_on_stack: bool, ): if push_on_stack: # to ensure valid cases are generated - push(evm.stack, size) - push(evm.stack, start_position) - push(evm.stack, destination) + evm.stack.push_or_replace_many([size, start_position, destination]) try: cairo_result = cairo_run("mcopy", evm) except EthereumException as cairo_error: diff --git a/cairo/tests/ethereum/cancun/vm/instructions/test_storage.py b/cairo/tests/ethereum/cancun/vm/instructions/test_storage.py index e6403383..54c8749a 100644 --- a/cairo/tests/ethereum/cancun/vm/instructions/test_storage.py +++ b/cairo/tests/ethereum/cancun/vm/instructions/test_storage.py @@ -12,7 +12,6 @@ GAS_WARM_ACCESS, ) from ethereum.cancun.vm.instructions.storage import sload, sstore, tload, tstore -from ethereum.cancun.vm.stack import push from ethereum_types.bytes import Bytes32 from ethereum_types.numeric import U256, Uint from hypothesis import given @@ -82,7 +81,7 @@ def test_sload_on_filled_storage( state = evm.env.state set_account(state, address, Account(balance=U256(1), nonce=U256(2), code=b"")) set_storage(state, address, key, value) - push(evm.stack, U256.from_be_bytes(key)) + evm.stack.push_or_replace(U256.from_be_bytes(key)) try: cairo_evm = cairo_run("sload", evm) except Exception as cairo_error: @@ -158,8 +157,7 @@ def test_sstore_refund_counter( current_value, ) # Push the new value and the key to the stack - push(evm.stack, new_value) - push(evm.stack, U256.from_be_bytes(key)) + evm.stack.push_or_replace_many([new_value, U256.from_be_bytes(key)]) try: cairo_evm = cairo_run("sstore", evm) diff --git a/cairo/tests/ethereum/cancun/vm/instructions/test_system.py b/cairo/tests/ethereum/cancun/vm/instructions/test_system.py index 6325fff8..919cce83 100644 --- a/cairo/tests/ethereum/cancun/vm/instructions/test_system.py +++ b/cairo/tests/ethereum/cancun/vm/instructions/test_system.py @@ -13,7 +13,6 @@ selfdestruct, staticcall, ) -from ethereum.cancun.vm.stack import push from ethereum_types.numeric import U256, Uint from hypothesis import given from hypothesis import strategies as st @@ -67,8 +66,7 @@ def revert_return_strategy(draw): # 80% chance to push valid values onto stack should_push = draw(st.integers(0, 99)) < 80 if should_push: - push(evm.stack, size) - push(evm.stack, memory_start) + evm.stack.push_or_replace_many([size, memory_start]) return evm @@ -106,7 +104,7 @@ def beneficiary_from_state(draw): else: beneficiary = draw(st.from_type(Address)) - push(evm.stack, U256.from_be_bytes(beneficiary)) + evm.stack.push_or_replace(U256.from_be_bytes(beneficiary)) # 20% chance to set beneficiary to originator beneficiary_is_originator = draw(st.integers(0, 99)) < 20 diff --git a/cairo/tests/utils/args_gen.py b/cairo/tests/utils/args_gen.py index 07b4fda6..04194737 100644 --- a/cairo/tests/utils/args_gen.py +++ b/cairo/tests/utils/args_gen.py @@ -163,7 +163,17 @@ class MutableBloom(bytearray): class Stack(List[T]): - pass + MAX_SIZE = 1024 + + def push_or_replace(self, value: T): + if len(self) >= self.MAX_SIZE: + self.pop() + self.append(value) + + def push_or_replace_many(self, values: List[T]): + if len(self) + len(values) > self.MAX_SIZE: + del self[self.MAX_SIZE - len(values) :] + self.extend(values) @dataclass From d0ec3d482cb99987a415b50364be8e78bc7bf970 Mon Sep 17 00:00:00 2001 From: Mathieu <60658558+enitrat@users.noreply.github.com> Date: Thu, 13 Feb 2025 02:38:58 +0700 Subject: [PATCH 5/9] fix: skip None values in _prepare_trie_inner_storage (#787) --- cairo/ethereum/cancun/trie.cairo | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cairo/ethereum/cancun/trie.cairo b/cairo/ethereum/cancun/trie.cairo index 760044a1..234073f6 100644 --- a/cairo/ethereum/cancun/trie.cairo +++ b/cairo/ethereum/cancun/trie.cairo @@ -1147,6 +1147,13 @@ func _prepare_trie_inner_storage{ return mapping_ptr_end; } + // Skip all None values, which are deleted trie entries + if (dict_ptr.new_value.value == 0) { + return _prepare_trie_inner_storage( + trie, dict_ptr + Bytes32U256DictAccess.SIZE, mapping_ptr_end + ); + } + let preimage_b32 = _get_bytes32_preimage_for_key( dict_ptr.key.value, cast(trie.value._data.value.dict_ptr, DictAccess*) ); From 9f6760b511649f23bbdb8e7843b67b8b4769ab02 Mon Sep 17 00:00:00 2001 From: Mathieu <60658558+enitrat@users.noreply.github.com> Date: Thu, 13 Feb 2025 02:39:20 +0700 Subject: [PATCH 6/9] fix: squash main trie before computing root (#789) Otherwise we iterate multiple times on the same entries. --- cairo/ethereum/cancun/state.cairo | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/cairo/ethereum/cancun/state.cairo b/cairo/ethereum/cancun/state.cairo index 687eb7fd..7d78c847 100644 --- a/cairo/ethereum/cancun/state.cairo +++ b/cairo/ethereum/cancun/state.cairo @@ -1165,11 +1165,34 @@ func state_root{ raise('AssertionError'); } + // Squash the main trie for unique keys + let main_trie = state.value._main_trie; + let main_trie_start = cast(main_trie.value._data.value.dict_ptr_start, DictAccess*); + let main_trie_end = cast(main_trie.value._data.value.dict_ptr, DictAccess*); + + let (squashed_main_trie_start, squashed_main_trie_end) = dict_squash( + main_trie_start, main_trie_end + ); + + tempvar squashed_main_trie = TrieAddressOptionalAccount( + new TrieAddressOptionalAccountStruct( + secured=bool(1), + default=OptionalAccount(cast(0, AccountStruct*)), + _data=MappingAddressAccount( + new MappingAddressAccountStruct( + dict_ptr_start=cast(squashed_main_trie_start, AddressAccountDictAccess*), + dict_ptr=cast(squashed_main_trie_end, AddressAccountDictAccess*), + parent_dict=cast(0, MappingAddressAccountStruct*), + ), + ), + ), + ); + let storage_roots_ = storage_roots(state); tempvar trie_union = EthereumTries( new EthereumTriesEnum( - account=state.value._main_trie, + account=squashed_main_trie, storage=TrieBytes32U256(cast(0, TrieBytes32U256Struct*)), transaction=TrieBytesOptionalUnionBytesLegacyTransaction( cast(0, TrieBytesOptionalUnionBytesLegacyTransactionStruct*) From 314de24704d99249fd281ea0eb0b8ef0094aa407 Mon Sep 17 00:00:00 2001 From: Mathieu <60658558+enitrat@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:03:43 +0700 Subject: [PATCH 7/9] fix: iterate on squashed storage tries in storage roots computation (#792) The iteration was done on the the unsquashed tries, meaning that multiple entries would appear for the same address. --- cairo/ethereum/cancun/state.cairo | 10 ++++++++-- cairo/ethereum/cancun/trie.cairo | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cairo/ethereum/cancun/state.cairo b/cairo/ethereum/cancun/state.cairo index 7d78c847..3439ab3f 100644 --- a/cairo/ethereum/cancun/state.cairo +++ b/cairo/ethereum/cancun/state.cairo @@ -970,8 +970,8 @@ func storage_roots{ ); build_map_addr_storage_trie{ - map_addr_storage=map_addr_storage, storage_tries_ptr_end=storage_tries_end - }(storage_tries_start); + map_addr_storage=map_addr_storage, storage_tries_ptr_end=squashed_storage_tries_end + }(squashed_storage_tries_start); // Squash the Mapping[address, trie[bytes32, u256]] to iterate over each address let (squashed_map_addr_storage_start, squashed_map_addr_storage_end) = dict_squash( @@ -1092,6 +1092,12 @@ func build_map_addr_storage_trie{ return (); } + // Skip all None values, which are deleted trie entries. We don't need them to compute + // the storage roots. + if (cast(storage_tries_ptr.new_value, felt) == 0) { + return build_map_addr_storage_trie(storage_tries_ptr + DictAccess.SIZE); + } + let tup_address_b32 = get_tuple_address_bytes32_preimage_for_key( storage_tries_ptr.key, storage_tries_ptr_end ); diff --git a/cairo/ethereum/cancun/trie.cairo b/cairo/ethereum/cancun/trie.cairo index 234073f6..26a8f587 100644 --- a/cairo/ethereum/cancun/trie.cairo +++ b/cairo/ethereum/cancun/trie.cairo @@ -1148,6 +1148,8 @@ func _prepare_trie_inner_storage{ } // Skip all None values, which are deleted trie entries + // Note: Considering that the given trie was built from the state._storage_tries of type + // Trie[Tuple[Address, Bytes32], U256], there should not be any None values remaining. if (dict_ptr.new_value.value == 0) { return _prepare_trie_inner_storage( trie, dict_ptr + Bytes32U256DictAccess.SIZE, mapping_ptr_end From bb032ff52220dff3f5e53f2e8fb5b9832700d75d Mon Sep 17 00:00:00 2001 From: Oba Date: Thu, 13 Feb 2025 08:13:38 +0100 Subject: [PATCH 8/9] cov: fork.cairo (#785) Close #784 --------- Co-authored-by: Elias Tazartes <66871571+Eikix@users.noreply.github.com> --- cairo/tests/ethereum/cancun/test_fork.py | 48 +++++++++++++++++++----- cairo/tests/utils/strategies.py | 4 ++ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/cairo/tests/ethereum/cancun/test_fork.py b/cairo/tests/ethereum/cancun/test_fork.py index a5a149cd..44e8e6f3 100644 --- a/cairo/tests/ethereum/cancun/test_fork.py +++ b/cairo/tests/ethereum/cancun/test_fork.py @@ -15,19 +15,21 @@ validate_header, ) from ethereum.cancun.fork_types import Address, VersionedHash -from ethereum.cancun.state import State, set_account +from ethereum.cancun.state import Account, State, set_account from ethereum.cancun.transactions import ( AccessListTransaction, BlobTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, signing_hash_155, signing_hash_1559, signing_hash_2930, signing_hash_4844, signing_hash_pre155, ) +from ethereum.cancun.utils.address import to_address from ethereum.cancun.vm import Environment from ethereum.cancun.vm.gas import TARGET_BLOB_GAS_PER_BLOCK from ethereum.crypto.hash import Hash32, keccak256 @@ -42,7 +44,14 @@ from cairo_addons.testing.errors import strict_raises from tests.ethereum.cancun.vm.test_interpreter import unimplemented_precompiles from tests.utils.constants import OMMER_HASH -from tests.utils.strategies import account_strategy, address, bytes32, small_bytes, uint +from tests.utils.strategies import ( + account_strategy, + address, + bounded_u256_strategy, + bytes32, + small_bytes, + uint, +) MIN_BASE_FEE = 1_000 @@ -71,6 +80,7 @@ def tx_with_small_data(draw, gas_strategy=uint, gas_price_strategy=uint): to=to, gas=gas_strategy, gas_price=gas_price_strategy, + value=bounded_u256_strategy(max_value=2**128 - 1), ) access_list_tx = st.builds( @@ -80,6 +90,7 @@ def tx_with_small_data(draw, gas_strategy=uint, gas_price_strategy=uint): to=to, gas=gas_strategy, gas_price=gas_price_strategy, + value=bounded_u256_strategy(max_value=2**128 - 1), ) base_fee_per_gas = draw(gas_price_strategy) @@ -94,6 +105,7 @@ def tx_with_small_data(draw, gas_strategy=uint, gas_price_strategy=uint): gas=gas_strategy, max_priority_fee_per_gas=st.just(max_priority_fee_per_gas), max_fee_per_gas=st.just(max_fee_per_gas), + value=bounded_u256_strategy(max_value=2**128 - 1), ) blob_tx = st.builds( @@ -107,6 +119,7 @@ def tx_with_small_data(draw, gas_strategy=uint, gas_price_strategy=uint): blob_versioned_hashes=st.lists(st.from_type(VersionedHash), max_size=3).map( tuple ), + value=bounded_u256_strategy(max_value=2**128 - 1), ) # Choose one transaction type @@ -194,11 +207,8 @@ def tx_with_sender_in_state( env = draw(st.from_type(Environment)) if env.gas_price < env.base_fee_per_gas: env.gas_price = draw( - st.one_of( - st.integers( - min_value=int(env.base_fee_per_gas), max_value=2**64 - 1 - ).map(Uint), - st.just(env.base_fee_per_gas), + st.integers(min_value=int(env.base_fee_per_gas), max_value=2**64 - 1).map( + Uint ) ) # Values too high would cause taylor_exponential to run indefinitely. @@ -206,10 +216,30 @@ def tx_with_sender_in_state( st.integers(0, 10 * int(TARGET_BLOB_GAS_PER_BLOCK)).map(U64) ) state = env.state + tx = draw(tx_strategy) account = draw(account_strategy) + private_key = draw(st.from_type(PrivateKey)) + expected_address = int(private_key.public_key.to_address(), 16) + if draw(integers(0, 99)) < 80: + # to ensure the account has enough balance and tx.gas > intrinsic_cost + # also that the code is empty + env.origin = to_address(Uint(expected_address)) + if calculate_intrinsic_cost(tx) > tx.gas: + tx = replace(tx, gas=(calculate_intrinsic_cost(tx) + Uint(10000))) + + set_account( + state, + to_address(Uint(expected_address)), + Account( + balance=U256(tx.value) * U256(env.gas_price) + + U256(env.excess_blob_gas) + + U256(10000), + nonce=account.nonce, + code=bytes(), + ), + ) # 2 * chain_id + 35 + v must be less than 2^64 for the signature of a legacy transaction to be valid chain_id = draw(st.integers(min_value=1, max_value=(2**64 - 37) // 2).map(U64)) - tx = draw(tx_strategy) nonce = U256(account.nonce) # To avoid useless failures, set nonce of the transaction to the nonce of the sender account tx = ( @@ -217,8 +247,6 @@ def tx_with_sender_in_state( if not isinstance(tx, LegacyTransaction) else replace(tx, nonce=nonce) ) - private_key = draw(st.from_type(PrivateKey)) - expected_address = int(private_key.public_key.to_address(), 16) pre_155 = draw(integers(0, 99)) < 50 if isinstance(tx, LegacyTransaction): diff --git a/cairo/tests/utils/strategies.py b/cairo/tests/utils/strategies.py index 211dddb2..7014985b 100644 --- a/cairo/tests/utils/strategies.py +++ b/cairo/tests/utils/strategies.py @@ -578,3 +578,7 @@ def register_type_strategies(): st.register_type_strategy(MutableBloom, bloom.map(MutableBloom)) st.register_type_strategy(Environment, environment_lite) st.register_type_strategy(Header, header) + st.register_type_strategy( + VersionedHash, + st.binary(min_size=31, max_size=31).map(lambda x: VersionedHash(b"\x01" + x)), + ) From 76a71d99c4d596ce56fb7fafddb829f437ea1bf0 Mon Sep 17 00:00:00 2001 From: Mathieu <60658558+enitrat@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:26:01 +0700 Subject: [PATCH 9/9] fix: hashdict_read hint should not skip zero values (#791) The problem is that `if value` would return `False` if `value := 0`. But if we delete the entry in a trie (by writing 0), then the next read on the same (address, key) should be returning `0` as `prev_value`, not `default_factory()`. This caused an issue when squashing the storage_tries when computing the storage roots. --------- Co-authored-by: Elias Tazartes Co-authored-by: Elias Tazartes <66871571+Eikix@users.noreply.github.com> --- cairo/ethereum/cancun/trie.cairo | 43 +++++++++++++++---- .../src/cairo_addons/hints/hashdict.py | 4 +- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/cairo/ethereum/cancun/trie.cairo b/cairo/ethereum/cancun/trie.cairo index 26a8f587..9e61ca02 100644 --- a/cairo/ethereum/cancun/trie.cairo +++ b/cairo/ethereum/cancun/trie.cairo @@ -547,8 +547,11 @@ func trie_get_TrieAddressOptionalAccount{ let fp_and_pc = get_fp_and_pc(); local __fp__: felt* = fp_and_pc.fp_val; - with dict_ptr { - let (pointer) = hashdict_read(1, &key.value); + let (pointer) = hashdict_read{dict_ptr=dict_ptr}(1, &key.value); + if (pointer == 0) { + tempvar pointer = cast(trie.value.default.value, felt); + } else { + tempvar pointer = pointer; } let new_dict_ptr = cast(dict_ptr, AddressAccountDictAccess*); let parent_dict = trie.value._data.value.parent_dict; @@ -567,6 +570,7 @@ func trie_get_TrieAddressOptionalAccount{ func trie_get_TrieTupleAddressBytes32U256{ poseidon_ptr: PoseidonBuiltin*, trie: TrieTupleAddressBytes32U256 }(address: Address, key: Bytes32) -> U256 { + alloc_locals; let dict_ptr = cast(trie.value._data.value.dict_ptr, DictAccess*); let (keys) = alloc(); @@ -574,8 +578,11 @@ func trie_get_TrieTupleAddressBytes32U256{ assert keys[1] = key.value.low; assert keys[2] = key.value.high; - with dict_ptr { - let (pointer) = hashdict_read(3, keys); + let (pointer) = hashdict_read{dict_ptr=dict_ptr}(3, keys); + if (pointer == 0) { + tempvar pointer = cast(trie.value.default.value, felt); + } else { + tempvar pointer = pointer; } let new_dict_ptr = cast(dict_ptr, TupleAddressBytes32U256DictAccess*); let parent_dict = trie.value._data.value.parent_dict; @@ -594,14 +601,18 @@ func trie_get_TrieTupleAddressBytes32U256{ func trie_get_TrieBytes32U256{poseidon_ptr: PoseidonBuiltin*, trie: TrieBytes32U256}( key: Bytes32 ) -> U256 { + alloc_locals; let dict_ptr = cast(trie.value._data.value.dict_ptr, DictAccess*); let (keys) = alloc(); assert keys[0] = key.value.low; assert keys[1] = key.value.high; - with dict_ptr { - let (pointer) = hashdict_read(2, keys); + let (pointer) = hashdict_read{dict_ptr=dict_ptr}(2, keys); + if (pointer == 0) { + tempvar pointer = cast(trie.value.default.value, felt); + } else { + tempvar pointer = pointer; } let new_dict_ptr = cast(dict_ptr, Bytes32U256DictAccess*); let parent_dict = trie.value._data.value.parent_dict; @@ -620,10 +631,14 @@ func trie_get_TrieBytes32U256{poseidon_ptr: PoseidonBuiltin*, trie: TrieBytes32U func trie_get_TrieBytesOptionalUnionBytesLegacyTransaction{ poseidon_ptr: PoseidonBuiltin*, trie: TrieBytesOptionalUnionBytesLegacyTransaction }(key: Bytes) -> OptionalUnionBytesLegacyTransaction { + alloc_locals; let dict_ptr = cast(trie.value._data.value.dict_ptr, DictAccess*); - with dict_ptr { - let (pointer) = hashdict_read(key.value.len, key.value.data); + let (pointer) = hashdict_read{dict_ptr=dict_ptr}(key.value.len, key.value.data); + if (pointer == 0) { + tempvar pointer = cast(trie.value.default.value, felt); + } else { + tempvar pointer = pointer; } let new_dict_ptr = cast(dict_ptr, BytesOptionalUnionBytesLegacyTransactionDictAccess*); let parent_dict = trie.value._data.value.parent_dict; @@ -646,9 +661,15 @@ func trie_get_TrieBytesOptionalUnionBytesLegacyTransaction{ func trie_get_TrieBytesOptionalUnionBytesReceipt{ poseidon_ptr: PoseidonBuiltin*, trie: TrieBytesOptionalUnionBytesReceipt }(key: Bytes) -> OptionalUnionBytesReceipt { + alloc_locals; let dict_ptr = cast(trie.value._data.value.dict_ptr, DictAccess*); let (pointer) = hashdict_read{dict_ptr=dict_ptr}(key.value.len, key.value.data); + if (pointer == 0) { + tempvar pointer = cast(trie.value.default.value, felt); + } else { + tempvar pointer = pointer; + } let new_dict_ptr = cast(dict_ptr, BytesOptionalUnionBytesReceiptDictAccess*); let parent_dict = trie.value._data.value.parent_dict; tempvar mapping = MappingBytesOptionalUnionBytesReceipt( @@ -668,9 +689,15 @@ func trie_get_TrieBytesOptionalUnionBytesReceipt{ func trie_get_TrieBytesOptionalUnionBytesWithdrawal{ poseidon_ptr: PoseidonBuiltin*, trie: TrieBytesOptionalUnionBytesWithdrawal }(key: Bytes) -> OptionalUnionBytesWithdrawal { + alloc_locals; let dict_ptr = cast(trie.value._data.value.dict_ptr, DictAccess*); let (pointer) = hashdict_read{dict_ptr=dict_ptr}(key.value.len, key.value.data); + if (pointer == 0) { + tempvar pointer = cast(trie.value.default.value, felt); + } else { + tempvar pointer = pointer; + } let new_dict_ptr = cast(dict_ptr, BytesOptionalUnionBytesWithdrawalDictAccess*); let parent_dict = trie.value._data.value.parent_dict; tempvar mapping = MappingBytesOptionalUnionBytesWithdrawal( diff --git a/python/cairo-addons/src/cairo_addons/hints/hashdict.py b/python/cairo-addons/src/cairo_addons/hints/hashdict.py index c6287b15..107df592 100644 --- a/python/cairo-addons/src/cairo_addons/hints/hashdict.py +++ b/python/cairo-addons/src/cairo_addons/hints/hashdict.py @@ -13,7 +13,7 @@ def hashdict_read(dict_manager: DictManager, ids: VmConsts, memory: MemoryDict): preimage = tuple([memory[ids.key + i] for i in range(ids.key_len)]) # Not using [] here because it will register the value for that key in the tracker. value = dict_tracker.data.get(preimage) - if value: + if value is not None: ids.value = value else: ids.value = dict_tracker.data.default_factory() @@ -41,7 +41,7 @@ def hashdict_write(dict_manager: DictManager, ids: VmConsts, memory: MemoryDict) dict_tracker.current_ptr += ids.DictAccess.SIZE preimage = tuple([memory[ids.key + i] for i in range(ids.key_len)]) prev_value = dict_tracker.data.get(preimage) - if prev_value: + if prev_value is not None: ids.dict_ptr.prev_value = prev_value else: ids.dict_ptr.prev_value = dict_tracker.data.default_factory()