diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index e59cc873aff32f5780b659d7be5de51df5ffe851..aa36a84b95ac1bc66174cd75221b06051032ba36 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -15,7 +15,7 @@ #### RPCs - Completes state override option of `eth_call` RPC, by adding `state` and - `stateDiff`, similar to go-ethereum. (!14869, !14921) + `stateDiff`, similar to go-ethereum. (!14869, !14921, !14970) #### Metrics diff --git a/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml b/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml index 2b15e70f439d853cc93730c5e03b3536afeb5505..0b4d3b8453b8c7000634484c147da08b29201b90 100644 --- a/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml +++ b/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml @@ -1076,11 +1076,30 @@ type state_account_override = { nonce : quantity option; code : hex option; state_diff : hex StorageMap.t; - state : hex StorageMap.t; + state : hex StorageMap.t option; + (* For [state] we make the distinction between + * the option was set with an empty state (`Some StorageMap.empty`) + * the option was not set (`None`). + The former means the state needs to be erased, the other means the + option was not set and can be ignored. + *) } type state_override = state_account_override AddressMap.t +(* Encode a into a `Some `. + + This specialized encoding is necessary because `Data_encoding.option` cannot + be combined with `StorageMap.associative_array_encoding` as both are + nullable. +*) +let state_encoding = + let open Data_encoding in + conv + (function Some s -> s | None -> StorageMap.empty) + (fun s -> Some s) + (StorageMap.associative_array_encoding hex_encoding) + let state_account_override_encoding = let open Data_encoding in conv @@ -1092,10 +1111,7 @@ let state_account_override_encoding = (opt "balance" quantity_encoding) (opt "nonce" quantity_encoding) (opt "code" hex_encoding) - (dft - "state" - (StorageMap.associative_array_encoding hex_encoding) - StorageMap.empty) + (dft "state" state_encoding None) (dft "state_diff" (StorageMap.associative_array_encoding hex_encoding) diff --git a/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli b/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli index 49c00072d499edd47c3fbb4188e6834158de7468..f40a9bf7585719fa2bdef9fc87bae83614bee2ed 100644 --- a/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli +++ b/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli @@ -243,7 +243,7 @@ type state_account_override = { nonce : quantity option; code : hex option; state_diff : hex StorageMap.t; - state : hex StorageMap.t; + state : hex StorageMap.t option; } type state_override = state_account_override AddressMap.t diff --git a/etherlink/bin_node/lib_dev/state_override.ml b/etherlink/bin_node/lib_dev/state_override.ml index 0688a585601723e42c0acf737f9b5274cff33fd9..2b1573ece1516d75dfe119f83b721eee1f0f21ed 100644 --- a/etherlink/bin_node/lib_dev/state_override.ml +++ b/etherlink/bin_node/lib_dev/state_override.ml @@ -52,18 +52,19 @@ let update_storage address state_diff state = let replace_storage address state_override state = let open Lwt_result_syntax in - if Ethereum_types.StorageMap.is_empty state_override then return state - else - let*? key = Durable_storage_path.Accounts.storage_dir_e address in - let*! state = - Evm_state.delete ~kind:Tezos_scoru_wasm.Durable.Directory state key - in - update_storage address state_override state + match state_override with + | None -> return state + | Some state_override -> + let*? key = Durable_storage_path.Accounts.storage_dir_e address in + let*! state = + Evm_state.delete ~kind:Tezos_scoru_wasm.Durable.Directory state key + in + update_storage address state_override state let is_invalid state_override = let open Ethereum_types in not - (StorageMap.is_empty state_override.state + (Option.is_none state_override.state || StorageMap.is_empty state_override.state_diff) let update_account address state_override evm_state = diff --git a/etherlink/tezt/tests/eth_call.ml b/etherlink/tezt/tests/eth_call.ml index b4594e3ed12a842e95feb5548ac25fc60db25308..0ee6cfb41cb27be0f06043d3ed58047ff810e518 100644 --- a/etherlink/tezt/tests/eth_call.ml +++ b/etherlink/tezt/tests/eth_call.ml @@ -441,13 +441,94 @@ let test_call_state_override_state = ); ] in - let override = [`O [(contract, `O [("state_diff", invalid)])]] in + let override = [`O [(contract, `O [("state", invalid)])]] in let* call_result = make_call ~override "getCount()" in Check.( (Evm_node.extract_error_message call_result |> JSON.as_string = "Error:\n 00 is not a valid storage key\n") string) ~error_msg:"Expected error %R but got %L " ; + unit + +let test_call_state_override_state_empty = + register + ~kernels:[Latest] (* Not a kernel specific test. *) + ~tags:["evm"; "state_override"; "state_empty"; "eth_call"] + ~title:"Can override completely account storage in eth_call by empty state" + @@ fun {sequencer; _} _protocol -> + (* + This test checks that the simulation allows state override. + To do so we deploy a contract with a value in storage, and call it with an + alternative storage that changes that value. + *) + let* constant = Solidity_contracts.state_override_tester_readable () in + let* () = Eth_cli.add_abi ~label:constant.label ~abi:constant.abi () in + (* Deploy the contract. *) + let* contract, _tx_hash = + send_transaction_to_sequencer + (fun () -> + Eth_cli.deploy + ~source_private_key:Eth_account.bootstrap_accounts.(0).private_key + ~endpoint:(Evm_node.endpoint sequencer) + ~abi:constant.abi + ~bin:constant.bin) + sequencer + in + + (* helpers *) + let call_method m = + let* calldata = Cast.calldata m in + return (`O [("to", `String contract); ("data", `String calldata)]) + in + let make_call ?(override = []) m = + let* call = call_method m in + Evm_node.call_evm_rpc + sequencer + {method_ = "eth_call"; parameters = `A (call :: override)} + in + let check_value call_result expected = + Check.( + (Evm_node.extract_result call_result |> JSON.as_string = expected) string) + ~error_msg:"Expected result %R but got %L " + in + + (* Check the starting contract storage *) + let* call_result = make_call "getCount()" in + check_value + call_result + "0x000000000000000000000000000000000000000000000000000000000000002a" ; + let* call_result = make_call "const2()" in + check_value + call_result + "0x00000000000000000000000000000000000000000000000000000000ffffffff" ; + let* call_result = make_call "sep()" in + check_value + call_result + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ; + let* call_result = make_call "const3()" in + check_value + call_result + "0x00000000000000000000000000000000000000000000000000000000ffffffff" ; + + (* try with an empty storage override *) + let empty = `O [] in + let override = [`O [(contract, `O [("state", empty)])]] in + let* call_result = make_call ~override "getCount()" in + check_value + call_result + "0x0000000000000000000000000000000000000000000000000000000000000000" ; + let* call_result = make_call ~override "const2()" in + check_value + call_result + "0x0000000000000000000000000000000000000000000000000000000000000000" ; + let* call_result = make_call ~override "sep()" in + check_value + call_result + "0x0000000000000000000000000000000000000000000000000000000000000000" ; + let* call_result = make_call ~override "const3()" in + check_value + call_result + "0x0000000000000000000000000000000000000000000000000000000000000000" ; unit @@ -458,4 +539,5 @@ let () = test_call_state_override_nonce protocols ; test_call_state_override_state_diff protocols ; test_call_state_override_state protocols ; + test_call_state_override_state_empty protocols ; test_call_state_override_balance protocols