diff --git a/cairo/ethereum/cancun/blocks.cairo b/cairo/ethereum/cancun/blocks.cairo index 2339fb7f0..6b22384c1 100644 --- a/cairo/ethereum/cancun/blocks.cairo +++ b/cairo/ethereum/cancun/blocks.cairo @@ -2,7 +2,7 @@ from ethereum_types.numeric import U64, U256, Uint, bool from ethereum_types.bytes import Bytes, Bytes8, Bytes32, TupleBytes, TupleBytes32 from ethereum.cancun.fork_types import Address, Bloom, Root from ethereum.crypto.hash import Hash32 - +from ethereum.cancun.transactions_types import LegacyTransaction struct WithdrawalStruct { index: U64, validator_index: U64, @@ -88,3 +88,41 @@ struct ReceiptStruct { struct Receipt { value: ReceiptStruct*, } + +struct UnionBytesLegacyTransactionEnum { + bytes: Bytes, + legacy: LegacyTransaction, +} + +struct UnionBytesLegacyTransaction { + value: UnionBytesLegacyTransactionEnum*, +} + +struct TupleUnionBytesLegacyTransactionStruct { + data: UnionBytesLegacyTransaction*, + len: felt, +} + +struct TupleUnionBytesLegacyTransaction { + value: TupleUnionBytesLegacyTransactionStruct*, +} + +struct BlockStruct { + header: Header, + transactions: TupleUnionBytesLegacyTransaction, + ommers: TupleHeader, + withdrawals: TupleWithdrawal, +} + +struct Block { + value: BlockStruct*, +} + +struct ListBlockStruct { + data: Block*, + len: felt, +} + +struct ListBlock { + value: ListBlockStruct*, +} diff --git a/cairo/ethereum/cancun/fork.cairo b/cairo/ethereum/cancun/fork.cairo index 12e7e1cd8..d57bbc85e 100644 --- a/cairo/ethereum/cancun/fork.cairo +++ b/cairo/ethereum/cancun/fork.cairo @@ -8,16 +8,28 @@ from starkware.cairo.common.cairo_builtins import ( ) from starkware.cairo.common.dict_access import DictAccess from starkware.cairo.common.math import assert_not_zero, split_felt, assert_le_felt -from starkware.cairo.common.math_cmp import is_le +from starkware.cairo.common.math_cmp import is_le, is_le_felt from starkware.cairo.common.registers import get_fp_and_pc -from ethereum_rlp.rlp import Extended, ExtendedImpl, encode_receipt_to_buffer +from ethereum_rlp.rlp import Extended, ExtendedImpl, encode_receipt_to_buffer, encode_header from ethereum_types.bytes import Bytes, Bytes0, BytesStruct, TupleBytes32 from ethereum_types.numeric import Uint, bool, U256, U256Struct, U64 -from ethereum.cancun.blocks import Header, Receipt, ReceiptStruct, TupleLog, Log, TupleLogStruct +from ethereum.cancun.blocks import ( + Header, + Receipt, + ReceiptStruct, + TupleLog, + Log, + TupleLogStruct, + Block, + ListBlock, + TupleHeader, +) from ethereum.cancun.bloom import logs_bloom from ethereum.cancun.fork_types import ( Address, + ListHash32, + ListHash32Struct, OptionalAddress, SetAddress, SetAddressStruct, @@ -65,11 +77,12 @@ from ethereum.cancun.vm.gas import ( calculate_blob_gas_price, ) from ethereum.cancun.vm.interpreter import process_message_call, MessageCallOutput -from ethereum.crypto.hash import keccak256 +from ethereum.crypto.hash import keccak256, Hash32 from ethereum.exceptions import OptionalEthereumException from ethereum.utils.numeric import ( divmod, min, + is_zero, U256_add, U256__eq__, U256_from_felt, @@ -89,6 +102,16 @@ const EMPTY_OMMER_HASH_LOW = 0xd312451b948a7413f0a142fd40d49347; const EMPTY_OMMER_HASH_HIGH = 0x1dcc4de8dec75d7aab85b567b6ccd41a; const VERSIONED_HASH_VERSION_KZG = 0x01; +struct BlockChainStruct { + blocks: ListBlock, + state: State, + chain_id: U64, +} + +struct BlockChain { + value: BlockChainStruct*, +} + func calculate_base_fee_per_gas{range_check_ptr}( block_gas_limit: Uint, parent_gas_limit: Uint, @@ -732,3 +755,74 @@ func _check_versioned_hashes_version{range_check_ptr}( return (); } + +func get_last_256_block_hashes{ + range_check_ptr, bitwise_ptr: BitwiseBuiltin*, keccak_ptr: KeccakBuiltin* +}(chain: BlockChain) -> ListHash32 { + alloc_locals; + + // If no blocks, return empty array + if (chain.value.blocks.value.len == 0) { + let (empty_hashes_alloc: Hash32*) = alloc(); + tempvar empty_hashes = ListHash32(new ListHash32Struct(data=empty_hashes_alloc, len=0)); + return empty_hashes; + } + + // Get last 255 blocks or all blocks if less than 255 + let is_le_255 = is_le(chain.value.blocks.value.len, 255); + if (is_le_255 != FALSE) { + tempvar start_idx = 0; + } else { + tempvar start_idx = chain.value.blocks.value.len - 255; + } + tempvar recent_blocks_len = chain.value.blocks.value.len - start_idx; + + // Allocate list for hashes + let (hashes: Hash32*) = alloc(); + + // Get parent hashes from recent blocks + _get_parent_hashes{hashes=hashes}( + chain.value.blocks.value.data + start_idx, recent_blocks_len, 0 + ); + + // Add hash of most recent block + let most_recent_block: Block* = chain.value.blocks.value.data + chain.value.blocks.value.len - + 1; + let most_recent_hash = keccak256_header(most_recent_block.value.header); + assert hashes[recent_blocks_len] = most_recent_hash; + tempvar list_hash_32 = ListHash32(new ListHash32Struct(data=hashes, len=recent_blocks_len + 1)); + return list_hash_32; +} + +// Helper function to get parent hashes using a loop +func _get_parent_hashes{hashes: Hash32*}(blocks: Block*, len: felt, idx: felt) { + tempvar idx = idx; + + loop: + let idx = [ap - 1]; + + let end_loop = is_zero(idx - len); + jmp end if end_loop != 0; + + // Get block at current index and store parent hash + let block: Block* = blocks + idx; + assert hashes[idx] = block.value.header.value.parent_hash; + + tempvar idx = idx + 1; + + jmp loop; + + end: + return (); +} + +// Helper to compute header hash +func keccak256_header{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, keccak_ptr: KeccakBuiltin*}( + header: Header +) -> Hash32 { + // First RLP encode the header + let encoded_header = encode_header(header); + + // Then compute keccak256 of the encoded bytes + return keccak256(encoded_header); +} diff --git a/cairo/tests/ethereum/cancun/test_fork.py b/cairo/tests/ethereum/cancun/test_fork.py index d312bf208..182a03fa5 100644 --- a/cairo/tests/ethereum/cancun/test_fork.py +++ b/cairo/tests/ethereum/cancun/test_fork.py @@ -8,18 +8,20 @@ from hypothesis import strategies as st from hypothesis.strategies import composite, integers -from ethereum.cancun.blocks import Header, Log +from ethereum.cancun.blocks import Block, Header, Log from ethereum.cancun.fork import ( GAS_LIMIT_ADJUSTMENT_FACTOR, + BlockChain, calculate_base_fee_per_gas, check_gas_limit, check_transaction, + get_last_256_block_hashes, make_receipt, process_transaction, validate_header, ) from ethereum.cancun.fork_types import Address -from ethereum.cancun.state import set_account +from ethereum.cancun.state import State, set_account from ethereum.cancun.transactions import ( AccessListTransaction, BlobTransaction, @@ -259,3 +261,12 @@ def test_check_transaction( state, tx, gas_available, chain_id, base_fee_per_gas, excess_blob_gas ) assert cairo_state == state + + @given(blocks=st.lists(st.builds(Block), max_size=300)) + def test_get_last_256_block_hashes(self, cairo_run, blocks): + chain = BlockChain(blocks=blocks, state=State(), chain_id=U64(1)) + + py_result = get_last_256_block_hashes(chain) + cairo_result = cairo_run("get_last_256_block_hashes", chain) + + assert py_result == cairo_result diff --git a/cairo/tests/test_serde.cairo b/cairo/tests/test_serde.cairo index 7313c2b15..7bf289956 100644 --- a/cairo/tests/test_serde.cairo +++ b/cairo/tests/test_serde.cairo @@ -30,6 +30,7 @@ from ethereum.cancun.blocks import ( Log, TupleLog, Receipt, + ListBlock, ) from ethereum.cancun.transactions_types import ( @@ -48,3 +49,4 @@ from ethereum.exceptions import EthereumException from ethereum.cancun.state import TransientStorage from ethereum.cancun.vm import Environment from ethereum.cancun.vm.interpreter import MessageCallOutput +from ethereum.cancun.fork import BlockChain diff --git a/cairo/tests/test_serde.py b/cairo/tests/test_serde.py index 27472c2c9..e43a57207 100644 --- a/cairo/tests/test_serde.py +++ b/cairo/tests/test_serde.py @@ -9,7 +9,8 @@ from starkware.cairo.lang.vm.memory_dict import MemoryDict from starkware.cairo.lang.vm.memory_segments import MemorySegmentManager -from ethereum.cancun.blocks import Header, Log, Receipt, Withdrawal +from ethereum.cancun.blocks import Block, Header, Log, Receipt, Withdrawal +from ethereum.cancun.fork import BlockChain from ethereum.cancun.fork_types import Account, Address, Bloom, Root, VersionedHash from ethereum.cancun.state import State, TransientStorage from ethereum.cancun.transactions import ( @@ -261,6 +262,11 @@ def test_type( List[Tuple[U256, U256]], ExtendMemory, MessageCallOutput, + Union[Bytes, LegacyTransaction], + Tuple[Union[Bytes, LegacyTransaction], ...], + Block, + List[Block], + BlockChain, ], ): assume(no_empty_sequence(b)) diff --git a/cairo/tests/utils/args_gen.py b/cairo/tests/utils/args_gen.py index e5d656eb6..b75d2af20 100644 --- a/cairo/tests/utils/args_gen.py +++ b/cairo/tests/utils/args_gen.py @@ -104,7 +104,8 @@ from cairo_addons.vm import DictTracker as RustDictTracker from cairo_addons.vm import MemorySegmentManager as RustMemorySegmentManager from cairo_addons.vm import Relocatable as RustRelocatable -from ethereum.cancun.blocks import Header, Log, Receipt, Withdrawal +from ethereum.cancun.blocks import Block, Header, Log, Receipt, Withdrawal +from ethereum.cancun.fork import BlockChain from ethereum.cancun.fork_types import Account, Address, Bloom, Root, VersionedHash from ethereum.cancun.state import State, TransientStorage from ethereum.cancun.transactions import ( @@ -448,6 +449,15 @@ def __eq__(self, other): ("ethereum", "cancun", "blocks", "Log"): Log, ("ethereum", "cancun", "blocks", "TupleLog"): Tuple[Log, ...], ("ethereum", "cancun", "blocks", "Receipt"): Receipt, + ("ethereum", "cancun", "blocks", "UnionBytesLegacyTransaction"): Union[ + Bytes, LegacyTransaction + ], + ("ethereum", "cancun", "blocks", "TupleUnionBytesLegacyTransaction"): Tuple[ + Union[Bytes, LegacyTransaction], ... + ], + ("ethereum", "cancun", "blocks", "Block"): Block, + ("ethereum", "cancun", "blocks", "ListBlock"): List[Block], + ("ethereum", "cancun", "fork", "BlockChain"): BlockChain, ("ethereum", "cancun", "fork_types", "Address"): Address, ("ethereum", "cancun", "fork_types", "SetAddress"): Set[Address], ("ethereum", "cancun", "fork_types", "Root"): Root,