From 4ce676916fd08f8fbe19d3c80ac066de581375ca Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Cornilleau Date: Mon, 9 Sep 2024 17:56:45 +0200 Subject: [PATCH 1/5] EVM/Node: eth call state diff encoding --- .../lib_dev/encodings/ethereum_types.ml | 27 ++++++++++++++++--- .../lib_dev/encodings/ethereum_types.mli | 7 +++++ etherlink/bin_node/lib_dev/state_override.ml | 16 ++++++++++- 3 files changed, 45 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 e38b1ff9a5ac..9714495d7e01 100644 --- a/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml +++ b/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml @@ -1059,10 +1059,23 @@ module From_rlp = struct Result_syntax.return @@ Z.to_int @@ Z.of_bits @@ Bytes.to_string b) end +module StorageKey = struct + type t = hex + + let compare (Hex a) (Hex b) = String.compare a b + + let of_string = hex_of_string + + let to_string = hex_to_string +end + +module StorageMap = MapMake (StorageKey) + type state_account_override = { balance : quantity option; nonce : quantity option; code : hex option; + state_diff : hex StorageMap.t; } type state_override = state_account_override AddressMap.t @@ -1070,12 +1083,18 @@ type state_override = state_account_override AddressMap.t let state_account_override_encoding = let open Data_encoding in conv - (fun {balance; nonce; code} -> (balance, nonce, code)) - (fun (balance, nonce, code) -> {balance; nonce; code}) - (obj3 + (fun {balance; nonce; code; state_diff} -> + (balance, nonce, code, state_diff)) + (fun (balance, nonce, code, state_diff) -> + {balance; nonce; code; state_diff}) + (obj4 (opt "balance" quantity_encoding) (opt "nonce" quantity_encoding) - (opt "code" hex_encoding)) + (opt "code" hex_encoding) + (dft + "state_diff" + (StorageMap.associative_array_encoding hex_encoding) + StorageMap.empty)) let state_override_empty = AddressMap.empty diff --git a/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli b/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli index e2eb6532cd97..db0d09f40f3a 100644 --- a/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli +++ b/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli @@ -232,10 +232,17 @@ type txpool = { val txpool_encoding : txpool Data_encoding.t +module StorageMap : sig + include Map.S with type key = hex + + val associative_array_encoding : 'a Data_encoding.t -> 'a t Data_encoding.t +end + type state_account_override = { balance : quantity option; nonce : quantity option; code : hex option; + state_diff : hex StorageMap.t; } 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 9d4a9bd7c73f..75e6070a217a 100644 --- a/etherlink/bin_node/lib_dev/state_override.ml +++ b/etherlink/bin_node/lib_dev/state_override.ml @@ -11,7 +11,20 @@ let durable_nonce v = v |> Ethereum_types.encode_u64_le |> Bytes.to_string let durable_code = Ethereum_types.hex_to_bytes -let update_account address {Ethereum_types.balance; nonce; code} state = +let update_storage address state_diff state = + let open Ethereum_types in + let update key value state = + let (Hex key) = key in + let (Hex value) = value in + Evm_state.modify + ~key:(Durable_storage_path.Accounts.storage address key) + ~value + state + in + StorageMap.fold_s update state_diff state + +let update_account address {Ethereum_types.balance; nonce; code; state_diff} + state = let open Durable_storage_path in let open Lwt_syntax in let update v_opt key encode state = @@ -24,6 +37,7 @@ let update_account address {Ethereum_types.balance; nonce; code} 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 let update_accounts state_override state = -- GitLab From c93d6cea1077ae940fa6d6c8700fb746d34654b1 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Cornilleau Date: Mon, 9 Sep 2024 17:57:17 +0200 Subject: [PATCH 2/5] EVM/tezt: state diff override test --- .../state_override_tester.sol | 5 +- .../state_override_tester_readable.sol | 12 ++- etherlink/tezt/tests/eth_call.ml | 86 ++++++++++++++++++- 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/etherlink/kernel_evm/solidity_examples/state_override_tester.sol b/etherlink/kernel_evm/solidity_examples/state_override_tester.sol index c31bc544624a..a91b3b8e612d 100644 --- a/etherlink/kernel_evm/solidity_examples/state_override_tester.sol +++ b/etherlink/kernel_evm/solidity_examples/state_override_tester.sol @@ -9,7 +9,10 @@ contract Child { contract StateOverrideTester { - int private count = 42; + uint32 private count = 42; // private, so requires an explicit accessor + uint32 public const2 = 0xffffffff; + uint256 public sep = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + uint32 public const3 = 0xffffffff; function getBalance() public view returns (uint256) { return msg.sender.balance; diff --git a/etherlink/kernel_evm/solidity_examples/state_override_tester_readable.sol b/etherlink/kernel_evm/solidity_examples/state_override_tester_readable.sol index 1eebcb578686..527367d81dfc 100644 --- a/etherlink/kernel_evm/solidity_examples/state_override_tester_readable.sol +++ b/etherlink/kernel_evm/solidity_examples/state_override_tester_readable.sol @@ -2,13 +2,21 @@ pragma solidity ^0.8.4; contract StateOverrideTester { - int private count = 42; + // first two 32bit value should be in same memory slot + uint32 private count = 42; // private, so require an explicit accessor + uint32 public const2 = 0xffffffff; + + // define a 32byte value to open a new memory slot + uint256 public sep = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + + // should be a third slot + uint32 public const3 = 0xffffffff; function getBalance() public view returns (uint256) { return msg.sender.balance; } - function getCount() public view returns (int) { + function getCount() public view returns (uint32) { return count; } } diff --git a/etherlink/tezt/tests/eth_call.ml b/etherlink/tezt/tests/eth_call.ml index 0075b7a1b3cf..4b7d0e90d800 100644 --- a/etherlink/tezt/tests/eth_call.ml +++ b/etherlink/tezt/tests/eth_call.ml @@ -49,7 +49,7 @@ let test_call_state_override_balance = @@ fun {sequencer; _} _protocol -> (* This test checks that the simulation allows balance override. - To do so we deploy a contract which returns the balance of the message + To do so we deploy a contract which returns the balance of the message sender, and call it with a non-sensical address. *) let* constant = Solidity_contracts.state_override_tester () in @@ -238,9 +238,93 @@ let test_call_state_override_nonce = unit +let test_call_state_override_state_diff = + register + ~kernels:[Latest] (* Not a kernel specific test. *) + ~tags:["evm"; "state_override"; "state_diff"; "eth_call"] + ~title:"Can override part of account storage in eth_call" + @@ fun {sequencer; _} _protocol -> + (* + This test checks that the simulation allows state diff 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 "const3()" in + check_value + call_result + "0x00000000000000000000000000000000000000000000000000000000ffffffff" ; + + (* try again with an override *) + let state_diff = + `O + [ + ( "0x0000000000000000000000000000000000000000000000000000000000000000", + `String + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + ] + in + let override = [`O [(contract, `O [("state_diff", state_diff)])]] in + let* call_result = make_call ~override "getCount()" in + check_value + call_result + "0x0000000000000000000000000000000000000000000000000000000000000000" ; + (* const2 is stored in same memory slot so should change *) + let* call_result = make_call ~override "const2()" in + check_value + call_result + "0x0000000000000000000000000000000000000000000000000000000000000000" ; + (* const3 is stored in a distinct memory slot so should be unchanged *) + let* call_result = make_call ~override "const3()" in + check_value + call_result + "0x00000000000000000000000000000000000000000000000000000000ffffffff" ; + 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_balance protocols -- GitLab From fedb83e96ffc7e3f91de5b7f3aa15ee7088d01dd Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Cornilleau Date: Thu, 12 Sep 2024 14:27:51 +0200 Subject: [PATCH 3/5] EVM/Node: wrap State_override in tzresult --- etherlink/bin_node/lib_dev/evm_ro_context.ml | 2 +- etherlink/bin_node/lib_dev/state_override.ml | 26 ++++++++++++------- etherlink/bin_node/lib_dev/state_override.mli | 4 ++- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/etherlink/bin_node/lib_dev/evm_ro_context.ml b/etherlink/bin_node/lib_dev/evm_ro_context.ml index 166e5eaaf741..d438723263f5 100644 --- a/etherlink/bin_node/lib_dev/evm_ro_context.ml +++ b/etherlink/bin_node/lib_dev/evm_ro_context.ml @@ -205,7 +205,7 @@ struct ~destination:Ctxt.ctxt.smart_rollup_address () in - let*! simulate_state = + let* simulate_state = State_override.update_accounts state_override simulate_state in let* raw_insights = diff --git a/etherlink/bin_node/lib_dev/state_override.ml b/etherlink/bin_node/lib_dev/state_override.ml index 75e6070a217a..41e278e20c19 100644 --- a/etherlink/bin_node/lib_dev/state_override.ml +++ b/etherlink/bin_node/lib_dev/state_override.ml @@ -13,24 +13,30 @@ let durable_code = Ethereum_types.hex_to_bytes let update_storage address state_diff state = let open Ethereum_types in + let open Lwt_result_syntax in let update key value state = let (Hex key) = key in let (Hex value) = value in - Evm_state.modify - ~key:(Durable_storage_path.Accounts.storage address key) - ~value - state + let*! state = + Evm_state.modify + ~key:(Durable_storage_path.Accounts.storage address key) + ~value + state + in + return state in - StorageMap.fold_s update state_diff state + StorageMap.fold_es update state_diff state let update_account address {Ethereum_types.balance; nonce; code; state_diff} state = let open Durable_storage_path in - let open Lwt_syntax in + let open Lwt_result_syntax in let update v_opt key encode state = match v_opt with - | None -> Lwt_syntax.return state - | Some v -> Evm_state.modify ~key ~value:(encode v) state + | None -> return state + | Some v -> + let*! state = Evm_state.modify ~key ~value:(encode v) state in + return state in let* state = update balance (Accounts.balance address) durable_balance state @@ -42,5 +48,5 @@ let update_account address {Ethereum_types.balance; nonce; code; state_diff} let update_accounts state_override state = match state_override with - | None -> Lwt_syntax.return state - | Some so -> Ethereum_types.AddressMap.fold_s update_account so state + | None -> Lwt_result_syntax.return state + | Some so -> Ethereum_types.AddressMap.fold_es update_account so state diff --git a/etherlink/bin_node/lib_dev/state_override.mli b/etherlink/bin_node/lib_dev/state_override.mli index 470131e98571..2c54a5acbcce 100644 --- a/etherlink/bin_node/lib_dev/state_override.mli +++ b/etherlink/bin_node/lib_dev/state_override.mli @@ -6,4 +6,6 @@ (*****************************************************************************) val update_accounts : - Ethereum_types.state_override option -> Evm_state.t -> Evm_state.t Lwt.t + Ethereum_types.state_override option -> + Evm_state.t -> + Evm_state.t tzresult Lwt.t -- GitLab From b78e9d0b78abb4afc269c97a73d4d660deaf7abb Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Cornilleau Date: Thu, 12 Sep 2024 15:28:27 +0200 Subject: [PATCH 4/5] EVM/Node: input validation for state override --- .../bin_node/lib_dev/durable_storage_path.ml | 65 ++++++++++++++++--- .../bin_node/lib_dev/durable_storage_path.mli | 15 +++++ etherlink/bin_node/lib_dev/state_override.ml | 26 ++++++-- 3 files changed, 89 insertions(+), 17 deletions(-) diff --git a/etherlink/bin_node/lib_dev/durable_storage_path.ml b/etherlink/bin_node/lib_dev/durable_storage_path.ml index 17ff4106fab9..d9466b81cc99 100644 --- a/etherlink/bin_node/lib_dev/durable_storage_path.ml +++ b/etherlink/bin_node/lib_dev/durable_storage_path.ml @@ -51,29 +51,74 @@ let sequencer_key = EVM.make "/sequencer" let maximum_gas_per_transaction = EVM.make "/maximum_gas_per_transaction" module Accounts = struct - let accounts = World_state.make "/eth_accounts" + let accounts_path = World_state.make "/eth_accounts" - let balance = "/balance" + let balance_path = "/balance" - let nonce = "/nonce" + let nonce_path = "/nonce" - let code = "/code" + let code_path = "/code" let code_hash = "/code.hash" - let storage = "/storage" + let storage_path = "/storage" - let account (Address (Hex s)) = accounts ^ "/" ^ s + let account (Address (Hex s)) = accounts_path ^ "/" ^ s - let balance address = account address ^ balance + let balance address = account address ^ balance_path - let nonce address = account address ^ nonce + let nonce address = account address ^ nonce_path - let code address = account address ^ code + let code address = account address ^ code_path let code_hash address = account address ^ code_hash - let storage address index = account address ^ storage ^ "/" ^ index + let storage address index = account address ^ storage_path ^ "/" ^ index + + type error += Invalid_address of string | Invalid_key of string + + let () = + register_error_kind + `Permanent + ~id:"durable_storage_invalid_address" + ~title:"Invalid address" + ~description:"Tried to access the account storage of an invalid address." + ~pp:(fun ppf path -> + Format.fprintf ppf "No account storage for invalid address %s" path) + Data_encoding.(obj1 (req "durable_storage_invalid_address" string)) + (function Invalid_address path -> Some path | _ -> None) + (fun path -> Invalid_address path) ; + register_error_kind + `Permanent + ~id:"durable_storage_invalid_key" + ~title:"Invalid storage key" + ~description:"Tried to access an invalid key in an account storage." + ~pp:(fun ppf path -> + Format.fprintf ppf "%s is not a valid storage key" path) + Data_encoding.(obj1 (req "durable_storage_invalid_key" string)) + (function Invalid_key path -> Some path | _ -> None) + (fun path -> Invalid_key path) + + let account_e (Address (Hex s)) = + let open Result_syntax in + if String.length s = 40 then return (accounts_path ^ "/" ^ s) + else tzfail (Invalid_address s) + + let concat_e address path = + let open Result_syntax in + let* address_path = account_e address in + return (address_path ^ path) + + let balance_e address = concat_e address balance_path + + let nonce_e address = concat_e address nonce_path + + let code_e address = concat_e address code_path + + let storage_e address index = + if String.length index = 64 then + concat_e address (storage_path ^ "/" ^ index) + else Result_syntax.tzfail (Invalid_key index) 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 a7d51da3da97..6cce82a852ed 100644 --- a/etherlink/bin_node/lib_dev/durable_storage_path.mli +++ b/etherlink/bin_node/lib_dev/durable_storage_path.mli @@ -54,6 +54,21 @@ module Accounts : sig (** Path to the account's storage at a given index. *) val storage : address -> path -> path + + type error += Invalid_address of string | Invalid_key of string + + (** Path to the account's balance. Error if address is invalid. *) + val balance_e : address -> path tzresult + + (** Path to the account's nonce. Error if address is invalid. *) + val nonce_e : address -> path tzresult + + (** Path to the account's code. Error if address is invalid. *) + val code_e : address -> path tzresult + + (** 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 end module Code : sig diff --git a/etherlink/bin_node/lib_dev/state_override.ml b/etherlink/bin_node/lib_dev/state_override.ml index 41e278e20c19..e0b4baccb011 100644 --- a/etherlink/bin_node/lib_dev/state_override.ml +++ b/etherlink/bin_node/lib_dev/state_override.ml @@ -11,19 +11,31 @@ 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 + +let () = + register_error_kind + `Permanent + ~id:"durable_storage_invalid_value" + ~title:"Invalid storage value" + ~description:"Tried to set an invalid value in an account storage." + ~pp:(fun ppf path -> + 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) + let update_storage address state_diff state = let open Ethereum_types in let open Lwt_result_syntax in let update key value state = let (Hex key) = key in let (Hex value) = value in - let*! state = - Evm_state.modify - ~key:(Durable_storage_path.Accounts.storage address key) - ~value - state - in - return state + if String.length value = 64 then + let*? key = Durable_storage_path.Accounts.storage_e address key in + let*! state = Evm_state.modify ~key ~value state in + return state + else tzfail (Invalid_storage_value value) in StorageMap.fold_es update state_diff state -- GitLab From ac817c5f6f3761ec015e019fca7e7b308508419c Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Cornilleau Date: Thu, 12 Sep 2024 16:46:45 +0200 Subject: [PATCH 5/5] EVM/tezt: small test for state_diff error --- etherlink/tezt/tests/eth_call.ml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/etherlink/tezt/tests/eth_call.ml b/etherlink/tezt/tests/eth_call.ml index 4b7d0e90d800..719c42bac497 100644 --- a/etherlink/tezt/tests/eth_call.ml +++ b/etherlink/tezt/tests/eth_call.ml @@ -319,6 +319,25 @@ let test_call_state_override_state_diff = check_value call_result "0x00000000000000000000000000000000000000000000000000000000ffffffff" ; + + (* 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 -- GitLab