From 292e1860b48301cbf094b38bb5965e4967d59021 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Cornilleau Date: Thu, 12 Sep 2024 20:01:11 +0200 Subject: [PATCH 1/5] EVM/Node: state override encoding --- .../bin_node/lib_dev/encodings/ethereum_types.ml | 15 ++++++++++----- .../bin_node/lib_dev/encodings/ethereum_types.mli | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml b/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml index 9714495d7e01..2b15e70f439d 100644 --- a/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml +++ b/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml @@ -1076,6 +1076,7 @@ type state_account_override = { nonce : quantity option; code : hex option; state_diff : hex StorageMap.t; + state : hex StorageMap.t; } type state_override = state_account_override AddressMap.t @@ -1083,14 +1084,18 @@ type state_override = state_account_override AddressMap.t let state_account_override_encoding = let open Data_encoding in conv - (fun {balance; nonce; code; state_diff} -> - (balance, nonce, code, state_diff)) - (fun (balance, nonce, code, state_diff) -> - {balance; nonce; code; state_diff}) - (obj4 + (fun {balance; nonce; code; state; state_diff} -> + (balance, nonce, code, state, state_diff)) + (fun (balance, nonce, code, state, state_diff) -> + {balance; nonce; code; state; state_diff}) + (obj5 (opt "balance" quantity_encoding) (opt "nonce" quantity_encoding) (opt "code" hex_encoding) + (dft + "state" + (StorageMap.associative_array_encoding hex_encoding) + StorageMap.empty) (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 db0d09f40f3a..49c00072d499 100644 --- a/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli +++ b/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli @@ -243,6 +243,7 @@ type state_account_override = { nonce : quantity option; code : hex option; state_diff : hex StorageMap.t; + state : hex StorageMap.t; } type state_override = state_account_override AddressMap.t -- GitLab From cfdc581bc47cabc13a844bee1b890a6ade3b22cb Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Thu, 5 Sep 2024 11:35:27 +0200 Subject: [PATCH 2/5] EVM/Node: extract delete function --- etherlink/bin_node/lib_dev/evm_state.ml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/etherlink/bin_node/lib_dev/evm_state.ml b/etherlink/bin_node/lib_dev/evm_state.ml index 2d6d0b3c49c6..18ea040d412f 100644 --- a/etherlink/bin_node/lib_dev/evm_state.ml +++ b/etherlink/bin_node/lib_dev/evm_state.ml @@ -275,28 +275,23 @@ let apply_blueprint ?wasm_pvm_fallback ?log_file ?profile ~data_dir ~config return (Apply_success {evm_state; block}) | _ -> return Apply_failure -let clear_delayed_inbox evm_state = +let delete ~kind evm_state path = let open Lwt_syntax in - let delayed_inbox_path = - Tezos_scoru_wasm.Durable.key_of_string_exn - Durable_storage_path.delayed_inbox - in + let key = Tezos_scoru_wasm.Durable.key_of_string_exn path in let* pvm_state = Wasm_utils.Ctx.Tree_encoding_runner.decode Tezos_scoru_wasm.Wasm_pvm.pvm_state_encoding evm_state in - let* durable = - Tezos_scoru_wasm.Durable.delete - ~kind:Directory - pvm_state.durable - delayed_inbox_path - in + let* durable = Tezos_scoru_wasm.Durable.delete ~kind pvm_state.durable key in Wasm_utils.Ctx.Tree_encoding_runner.encode Tezos_scoru_wasm.Wasm_pvm.pvm_state_encoding {pvm_state with durable} evm_state +let clear_delayed_inbox evm_state = + delete ~kind:Directory evm_state Durable_storage_path.delayed_inbox + let wasm_pvm_version state = Wasm_utils.Wasm.get_wasm_version state let irmin_store_path ~data_dir = Filename.Infix.(data_dir // "store") -- GitLab From 440013051d24828c69e0a5da5f060d7100c22c29 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Cornilleau Date: Thu, 12 Sep 2024 20:03:04 +0200 Subject: [PATCH 3/5] EVM/node: replace state override --- .../bin_node/lib_dev/durable_storage_path.ml | 2 + .../bin_node/lib_dev/durable_storage_path.mli | 2 + etherlink/bin_node/lib_dev/evm_state.mli | 5 +- etherlink/bin_node/lib_dev/state_override.ml | 63 +++++++++++++++---- 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/etherlink/bin_node/lib_dev/durable_storage_path.ml b/etherlink/bin_node/lib_dev/durable_storage_path.ml index d9466b81cc99..287650c91b42 100644 --- a/etherlink/bin_node/lib_dev/durable_storage_path.ml +++ b/etherlink/bin_node/lib_dev/durable_storage_path.ml @@ -119,6 +119,8 @@ module Accounts = struct if String.length index = 64 then concat_e address (storage_path ^ "/" ^ index) else Result_syntax.tzfail (Invalid_key index) + + let storage_dir_e address = concat_e address storage_path end module Code = struct diff --git a/etherlink/bin_node/lib_dev/durable_storage_path.mli b/etherlink/bin_node/lib_dev/durable_storage_path.mli index 6cce82a852ed..cd367ba41271 100644 --- a/etherlink/bin_node/lib_dev/durable_storage_path.mli +++ b/etherlink/bin_node/lib_dev/durable_storage_path.mli @@ -69,6 +69,8 @@ module Accounts : sig (** Path to the account's storage at a given index. Error if address or storage key is invalid. *) val storage_e : address -> path -> path tzresult + + val storage_dir_e : address -> path tzresult end module Code : sig diff --git a/etherlink/bin_node/lib_dev/evm_state.mli b/etherlink/bin_node/lib_dev/evm_state.mli index dfb92296b52a..b9f93ec480b3 100644 --- a/etherlink/bin_node/lib_dev/evm_state.mli +++ b/etherlink/bin_node/lib_dev/evm_state.mli @@ -38,7 +38,10 @@ val init : kernel:string -> t tzresult Lwt.t state. *) val modify : key:string -> value:string -> t -> t Lwt.t -(** [exists evm_state key] returns [true] if a value or a tree/subtree +(** [delete ~kind evm_state key] delete the value/directory at [key] *) +val delete : kind:Tezos_scoru_wasm.Durable.kind -> t -> string -> t Lwt.t + +(** [exists evm_state key] returns [true] if a value or a tree/subtree exists under [key] in [evm_state], [false] otherwise. *) val exists : t -> string -> bool Lwt.t diff --git a/etherlink/bin_node/lib_dev/state_override.ml b/etherlink/bin_node/lib_dev/state_override.ml index 83b79bd5345e..0688a5856017 100644 --- a/etherlink/bin_node/lib_dev/state_override.ml +++ b/etherlink/bin_node/lib_dev/state_override.ml @@ -11,7 +11,7 @@ let durable_nonce v = v |> Ethereum_types.encode_u64_le |> Bytes.to_string let durable_code = Ethereum_types.hex_to_bytes -type error += Invalid_storage_value of string +type error += Invalid_storage_value of string | State_and_state_diff let () = register_error_kind @@ -23,7 +23,16 @@ let () = Format.fprintf ppf "%s is not a valid storage value" path) Data_encoding.(obj1 (req "durable_storage_invalid_value" string)) (function Invalid_storage_value path -> Some path | _ -> None) - (fun path -> Invalid_storage_value path) + (fun path -> Invalid_storage_value path) ; + register_error_kind + `Permanent + ~id:"durable_storage_state_and_state_diff" + ~title:"Invalid state override" + ~description: + "Invalid state override, state and state diff are mutually exclusive." + Data_encoding.empty + (function State_and_state_diff -> Some () | _ -> None) + (fun () -> State_and_state_diff) let update_storage address state_diff state = let open Ethereum_types in @@ -41,8 +50,23 @@ let update_storage address state_diff state = in StorageMap.fold_es update state_diff state -let update_account address {Ethereum_types.balance; nonce; code; 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 + +let is_invalid state_override = + let open Ethereum_types in + not + (StorageMap.is_empty state_override.state + || StorageMap.is_empty state_override.state_diff) + +let update_account address state_override evm_state = let open Durable_storage_path in let open Lwt_result_syntax in let update v_opt key encode state = @@ -52,13 +76,30 @@ let update_account address {Ethereum_types.balance; nonce; code; state_diff} let*! state = Evm_state.modify ~key ~value:(encode v) state in return state in - let* state = - update balance (Accounts.balance address) durable_balance state - in - let* state = update nonce (Accounts.nonce address) durable_nonce state in - let* state = update code (Accounts.code address) durable_code state in - let* state = update_storage address state_diff state in - return state + if is_invalid state_override then tzfail State_and_state_diff + else + let* evm_state = + update + state_override.balance + (Accounts.balance address) + durable_balance + evm_state + in + let* evm_state = + update + state_override.nonce + (Accounts.nonce address) + durable_nonce + evm_state + in + let* evm_state = + update state_override.code (Accounts.code address) durable_code evm_state + in + let* evm_state = + update_storage address state_override.state_diff evm_state + in + let* evm_state = replace_storage address state_override.state evm_state in + return evm_state let update_accounts state_override state = match state_override with -- GitLab From 22ad1693d5bcbd34ef4f1af871372d79373dc0b2 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Cornilleau Date: Thu, 12 Sep 2024 20:08:26 +0200 Subject: [PATCH 4/5] EVM/tezt: test state replace --- etherlink/tezt/tests/eth_call.ml | 112 +++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/etherlink/tezt/tests/eth_call.ml b/etherlink/tezt/tests/eth_call.ml index 2bce07929ab2..b4594e3ed12a 100644 --- a/etherlink/tezt/tests/eth_call.ml +++ b/etherlink/tezt/tests/eth_call.ml @@ -340,10 +340,122 @@ let test_call_state_override_state_diff = unit +let test_call_state_override_state = + register + ~kernels:[Latest] (* Not a kernel specific test. *) + ~tags:["evm"; "state_override"; "state_replace"; "eth_call"] + ~title:"Can override completely account storage in eth_call" + @@ 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 again with an override *) + let state = + `O + [ + ( "0x0000000000000000000000000000000000000000000000000000000000000000", + `String + "0x0000000000000000000000000000000000000000000000001111111122222222" + ); + ] + in + let override = [`O [(contract, `O [("state", state)])]] in + let* call_result = make_call ~override "getCount()" in + check_value + call_result + "0x0000000000000000000000000000000000000000000000000000000022222222" ; + (* const2 is stored in same memory slot so should change *) + let* call_result = make_call ~override "const2()" in + check_value + call_result + "0x0000000000000000000000000000000000000000000000000000000011111111" ; + (* sep is stored in a distinct memory slot but we replaced everything *) + let* call_result = make_call ~override "sep()" in + check_value + call_result + "0x0000000000000000000000000000000000000000000000000000000000000000" ; + (* const3 is stored in a distinct memory slot but we replaced everything *) + let* call_result = make_call ~override "const3()" in + check_value + call_result + "0x0000000000000000000000000000000000000000000000000000000000000000" ; + + (* try with an invalid override *) + let invalid = + `O + [ + ( "0x00", + `String + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + ] + in + let override = [`O [(contract, `O [("state_diff", 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 protocols = Protocol.all let () = test_call_state_override_code protocols ; test_call_state_override_nonce protocols ; test_call_state_override_state_diff protocols ; + test_call_state_override_state protocols ; test_call_state_override_balance protocols -- GitLab From 2b263bf21a9060ebfdaa61b8f01316fee874aae1 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Cornilleau Date: Mon, 16 Sep 2024 15:35:17 +0200 Subject: [PATCH 5/5] EVM/Node: state & stateDiff changelog --- etherlink/CHANGES_NODE.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index e1dea662901a..e59cc873aff3 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -12,6 +12,11 @@ - Add a flag `--no-sync` to the observer command. If the flag is set, the observer does not synchronise with the EVM node endpoint. (!14889) +#### RPCs + +- Completes state override option of `eth_call` RPC, by adding `state` and + `stateDiff`, similar to go-ethereum. (!14869, !14921) + #### Metrics - Add the `smart_rollup_address` label to the `octez_evm_node_info` metrics. (!14906) -- GitLab