From 0a12daa17fc5c4c672cb941d3d00c65d07c30ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Walter?= Date: Fri, 4 Oct 2024 12:31:32 +0100 Subject: [PATCH] Fix withdrawals is Option and improve block serde --- cairo/src/model.cairo | 2 +- cairo/tests/test_serde.py | 33 ++++++++++++--------- cairo/tests/utils/models.py | 57 ++++++++++++++++++++++++++++++------- cairo/tests/utils/serde.py | 40 +++++++++++++++++++++++--- 4 files changed, 103 insertions(+), 29 deletions(-) diff --git a/cairo/src/model.cairo b/cairo/src/model.cairo index 4ef4348e..bbfbbdfa 100644 --- a/cairo/src/model.cairo +++ b/cairo/src/model.cairo @@ -219,7 +219,7 @@ namespace model { state_root: Uint256, transactions_root: Uint256, receipt_root: Uint256, - withdrawals_root: Uint256, + withdrawals_root: Option, bloom: felt*, difficulty: Uint256, number: felt, diff --git a/cairo/tests/test_serde.py b/cairo/tests/test_serde.py index 180d90eb..2401e74d 100644 --- a/cairo/tests/test_serde.py +++ b/cairo/tests/test_serde.py @@ -1,22 +1,27 @@ from src.utils.uint256 import int_to_uint256 -from tests.utils.models import Account, to_int +from tests.utils.models import Account, Block, to_int class TestSerde: def test_block(self, cairo_run, block): - cairo_run("test_block", block=block) - - def test_account(self, cairo_run, account): - result = cairo_run("test_account", account=account) - # Storage needs to handle differently because of the hashing of the keys - assert { - k: int_to_uint256(to_int(v)) for k, v in result["storage"].items() - } == account.storage - result["storage"] = {} - account.storage = {} - - assert Account.model_validate(result) == account + result = cairo_run("test_block", block=block) + assert Block.model_validate(result) == block def test_state(self, cairo_run, state): - cairo_run("test_state", state=state) + result = cairo_run("test_state", state=state) + assert [int(key, 16) for key in result["accounts"].keys()] == list( + state.accounts.keys() + ) + for result, account in zip( + result["accounts"].values(), state.accounts.values() + ): + # Storage needs to be handled differently because of the hashing of the keys + assert { + k: int_to_uint256(to_int(v)) + for k, v in result["storage"].items() + if v is not None + } == account.storage + result["storage"] = {} + account.storage = {} + assert Account.model_validate(result) == account diff --git a/cairo/tests/utils/models.py b/cairo/tests/utils/models.py index dcf174d0..c513b131 100644 --- a/cairo/tests/utils/models.py +++ b/cairo/tests/utils/models.py @@ -59,9 +59,12 @@ def split_uint256(cls, values): for key in [ "parent_hash", "uncle_hash", + "ommers_hash", "state_root", "transactions_trie", + "transactions_root", "receipt_trie", + "receipt_root", "withdrawals_root", "difficulty", "mix_hash", @@ -80,6 +83,7 @@ def split_uint256(cls, values): def split_option(cls, values): values = values.copy() for key in [ + "withdrawals_root", "base_fee_per_gas", "blob_gas_used", "excess_blob_gas", @@ -88,8 +92,9 @@ def split_option(cls, values): ]: if key not in values: key = to_camel(key) - is_some = key in values - value = to_int(values.get(key, 0)) + is_some = key in values and values[key] is not None + # it's possible that values[key] exists and is None, that why we can't use get default value + value = to_int(values.get(key) or 0) values[to_camel(key) + "IsSome"] = is_some value_type = cls.model_fields[to_snake(key) + "_value"].annotation values[to_camel(key) + "Value"] = ( @@ -124,17 +129,49 @@ def parse_bloom(cls, v): parent_hash_low: int parent_hash_high: int - uncle_hash_low: int - uncle_hash_high: int + uncle_hash_low: int = Field( + validation_alias=AliasChoices( + "ommersHash", "uncleHash", "ommers_hash", "uncle_hash", "ommers_hash_low" + ) + ) + uncle_hash_high: int = Field( + validation_alias=AliasChoices( + "ommersHashHigh", "uncleHashHigh", "ommers_hash_high" + ) + ) coinbase: int state_root_low: int state_root_high: int - transactions_trie_low: int - transactions_trie_high: int - receipt_trie_low: int - receipt_trie_high: int - withdrawals_root_low: int - withdrawals_root_high: int + transactions_trie_low: int = Field( + validation_alias=AliasChoices( + "transactionsTrie", + "transactionsRoot", + "transactions_trie", + "transactions_root", + "transactions_root_low", + ) + ) + transactions_trie_high: int = Field( + validation_alias=AliasChoices( + "transactionsTrieHigh", "transactionsRootHigh", "transactions_root_high" + ) + ) + receipt_trie_low: int = Field( + validation_alias=AliasChoices( + "transactionsTrie", + "transactionsRoot", + "transactions_trie", + "transactions_root", + "transactions_root_low", + ) + ) + receipt_trie_high: int = Field( + validation_alias=AliasChoices( + "receiptTrieHigh", "receiptRootHigh", "receipt_root_high" + ) + ) + withdrawals_root_is_some: bool + withdrawals_root_value: Tuple[int, int] bloom: Tuple[int, ...] difficulty_low: int difficulty_high: int diff --git a/cairo/tests/utils/serde.py b/cairo/tests/utils/serde.py index 857d647d..24fba30c 100644 --- a/cairo/tests/utils/serde.py +++ b/cairo/tests/utils/serde.py @@ -1,3 +1,5 @@ +from typing import Optional + from eth_utils.address import to_checksum_address from starkware.cairo.lang.compiler.ast.cairo_types import ( TypeFelt, @@ -94,7 +96,7 @@ def serialize_pointers(self, name, ptr): output[name] = member_ptr return output - def serialize_struct(self, name, ptr): + def serialize_struct(self, name, ptr) -> Optional[dict]: """ Serialize a struct, e.g. Uint256. """ @@ -233,10 +235,32 @@ def serialize_rlp_item(self, ptr): def serialize_block(self, ptr): raw = self.serialize_pointers("model.Block", ptr) - return { - "block_header": self.serialize_struct( - "model.BlockHeader", raw["block_header"] + header = self.serialize_struct("model.BlockHeader", raw["block_header"]) + if header is None: + raise ValueError("Block header is None") + header = { + **header, + "withdrawals_root": ( + self.serialize_uint256(header["withdrawals_root"]) + if header["withdrawals_root"] is not None + else None ), + "parent_beacon_block_root": ( + self.serialize_uint256(header["parent_beacon_block_root"]) + if header["parent_beacon_block_root"] is not None + else None + ), + "requests_root": ( + self.serialize_uint256(header["requests_root"]) + if header["requests_root"] is not None + else None + ), + "extra_data": bytes(header["extra_data"][: header["extra_data_len"]]), + "bloom": bytes.fromhex("".join(f"{b:032x}" for b in header["bloom"])), + } + del header["extra_data_len"] + return { + "block_header": header, "transactions": self.serialize_list( raw["transactions"], "model.TransactionEncoded", @@ -244,6 +268,12 @@ def serialize_block(self, ptr): ), } + def serialize_option(self, ptr): + raw = self.serialize_pointers("model.Option", ptr) + if raw["is_some"] == 0: + return None + return raw["value"] + def serialize_scope(self, scope, scope_ptr): if scope.path[-1] == "State": return self.serialize_state(scope_ptr) @@ -265,6 +295,8 @@ def serialize_scope(self, scope, scope_ptr): return self.serialize_rlp_item(scope_ptr) if scope.path[-1] == ("Block"): return self.serialize_block(scope_ptr) + if scope.path[-1] == ("Option"): + return self.serialize_option(scope_ptr) try: return self.serialize_struct(str(scope), scope_ptr) except MissingIdentifierError: