Revision 5c0277382a82ab3642ddd1de790a362e8c047837 authored by Pierre-Louis on 11 September 2023, 13:01:33 UTC, committed by Pierre-Louis on 25 September 2023, 12:16:22 UTC
1 parent 97eebdf
Raw File
evm_rollup.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2023 Nomadic Labs <contact@nomadic-labs.com>                *)
(* Copyright (c) 2023 TriliTech <contact@trili.tech>                         *)
(* Copyright (c) 2023 Marigold <contact@marigold.dev>                        *)
(* Copyright (c) 2023 Functori <contact@functori.com>                        *)
(*                                                                           *)
(* Permission is hereby granted, free of charge, to any person obtaining a   *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense,  *)
(* and/or sell copies of the Software, and to permit persons to whom the     *)
(* Software is furnished to do so, subject to the following conditions:      *)
(*                                                                           *)
(* The above copyright notice and this permission notice shall be included   *)
(* in all copies or substantial portions of the Software.                    *)
(*                                                                           *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL   *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING   *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER       *)
(* DEALINGS IN THE SOFTWARE.                                                 *)
(*                                                                           *)
(*****************************************************************************)

(* Testing
   -------
   Component:    Smart Optimistic Rollups: EVM Kernel
   Requirement:  make -f kernels.mk build
                 npm install eth-cli
   Invocation:   dune exec tezt/tests/main.exe -- --file evm_rollup.ml
*)
open Sc_rollup_helpers

let pvm_kind = "wasm_2_0_0"

let kernel_inputs_path = "tezt/tests/evm_kernel_inputs"

let exchanger_path () =
  Base.(project_root // "src/kernel_evm/l1_bridge/exchanger.tz")

let bridge_path () =
  Base.(project_root // "src/kernel_evm/l1_bridge/evm_bridge.tz")

let admin_path () = Base.(project_root // "src/kernel_evm/l1_bridge/admin.tz")

type l1_contracts = {exchanger : string; bridge : string; admin : string}

type full_evm_setup = {
  node : Node.t;
  client : Client.t;
  sc_rollup_node : Sc_rollup_node.t;
  sc_rollup_client : Sc_rollup_client.t;
  sc_rollup_address : string;
  originator_key : string;
  rollup_operator_key : string;
  evm_proxy_server : Evm_proxy_server.t;
  endpoint : string;
  l1_contracts : l1_contracts option;
}

let hex_256_of n = Printf.sprintf "%064x" n

let evm_proxy_server_version proxy_server =
  let endpoint = Evm_proxy_server.endpoint proxy_server in
  let get_version_url = endpoint ^ "/version" in
  Curl.get get_version_url

let get_transaction_status ~endpoint ~tx =
  let* receipt = Eth_cli.get_receipt ~endpoint ~tx in
  match receipt with
  | None -> failwith "no transaction receipt, it probably isn't mined yet."
  | Some r -> return r.status

let check_tx_succeeded ~endpoint ~tx =
  let* status = get_transaction_status ~endpoint ~tx in
  Check.(is_true status) ~error_msg:"Expected transaction to succeed." ;
  unit

let check_tx_failed ~endpoint ~tx =
  let* status = get_transaction_status ~endpoint ~tx in
  Check.(is_false status) ~error_msg:"Expected transaction to fail." ;
  unit

(** [get_value_in_storage client addr nth] fetch the [nth] value in the storage
    of account [addr]  *)
let get_value_in_storage sc_rollup_client address nth =
  Sc_rollup_client.inspect_durable_state_value
    ~hooks
    sc_rollup_client
    ~pvm_kind
    ~operation:Sc_rollup_client.Value
    ~key:(Durable_storage_path.storage address ~key:(hex_256_of nth) ())

let check_str_in_storage ~evm_setup ~address ~nth ~expected =
  let*! value = get_value_in_storage evm_setup.sc_rollup_client address nth in
  Check.((value = Some expected) (option string))
    ~error_msg:"Unexpected value in storage, should be %R, but got %L" ;
  unit

let check_nb_in_storage ~evm_setup ~address ~nth ~expected =
  check_str_in_storage ~evm_setup ~address ~nth ~expected:(hex_256_of expected)

let get_storage_size sc_rollup_client ~address =
  let*! storage =
    Sc_rollup_client.inspect_durable_state_value
      ~hooks
      sc_rollup_client
      ~pvm_kind
      ~operation:Sc_rollup_client.Subkeys
      ~key:(Durable_storage_path.storage address ())
  in
  return (List.length storage)

let check_storage_size sc_rollup_client ~address size =
  (* check storage size *)
  let* storage_size = get_storage_size sc_rollup_client ~address in
  Check.((storage_size = size) int)
    ~error_msg:"Unexpected storage size, should be %R, but is %L" ;
  unit

(** [next_evm_level ~sc_rollup_node ~node ~client] moves [sc_rollup_node] to
    the [node]'s next level. *)
let next_evm_level ~sc_rollup_node ~node ~client =
  let* () = Client.bake_for_and_wait client in
  let* level = Node.get_level node in
  Sc_rollup_node.wait_for_level ~timeout:30. sc_rollup_node level

(** [wait_for_transaction_receipt ~evm_proxy_server ~transaction_hash] takes an
    transaction_hash and returns only when the receipt is non null, or [count]
    blocks have passed and the receipt is still not available. *)
let wait_for_transaction_receipt ?(count = 3) ~evm_proxy_server
    ~transaction_hash () =
  let rec loop count =
    let* () = Lwt_unix.sleep 5. in
    let* receipt =
      Evm_proxy_server.(
        call_evm_rpc
          evm_proxy_server
          {
            method_ = "eth_getTransactionReceipt";
            parameters = `A [`String transaction_hash];
          })
    in
    if receipt |> Evm_proxy_server.extract_result |> JSON.is_null then
      if count > 0 then loop (count - 1)
      else Test.fail "Transaction still hasn't been included"
    else
      receipt |> Evm_proxy_server.extract_result
      |> Transaction.transaction_receipt_of_json |> return
  in
  loop count

let wait_for_application ~sc_rollup_node ~node ~client apply () =
  let* start_level = Client.level client in
  let max_iteration = 10 in
  let application_result = apply () in
  let rec loop () =
    let* () = Lwt_unix.sleep 5. in
    let* new_level = next_evm_level ~sc_rollup_node ~node ~client in
    if start_level + max_iteration < new_level then
      Test.fail
        "Baked more than %d blocks and the operation's application is still \
         pending"
        max_iteration ;
    if Lwt.state application_result = Lwt.Sleep then loop () else unit
  in
  (* Using [Lwt.both] ensures that any exception thrown in [tx_hash] will be
     thrown by [Lwt.both] as well. *)
  let* result, () = Lwt.both application_result (loop ()) in
  return result

let send_and_wait_until_tx_mined ~sc_rollup_node ~node ~client
    ~source_private_key ~to_public_key ~value ~evm_proxy_server_endpoint ?data
    () =
  let send =
    Eth_cli.transaction_send
      ~source_private_key
      ~to_public_key
      ~value
      ~endpoint:evm_proxy_server_endpoint
      ?data
  in
  wait_for_application ~sc_rollup_node ~node ~client send ()

(* sending more than ~300 tx could fail, because or curl *)
let send_n_transactions ~sc_rollup_node ~node ~client ~evm_proxy_server txs =
  let requests =
    List.map
      (fun tx ->
        Evm_proxy_server.
          {method_ = "eth_sendRawTransaction"; parameters = `A [`String tx]})
      txs
  in
  let* hashes = Evm_proxy_server.batch_evm_rpc evm_proxy_server requests in
  let hashes =
    hashes |> JSON.as_list
    |> List.map (fun json ->
           Evm_proxy_server.extract_result json |> JSON.as_string)
  in
  let first_hash = List.hd hashes in
  (* Let's wait until one of the transactions is injected into a block, and
      test this block contains the `n` transactions as expected. *)
  let* receipt =
    wait_for_application
      ~sc_rollup_node
      ~node
      ~client
      (wait_for_transaction_receipt
         ~evm_proxy_server
         ~transaction_hash:first_hash)
      ()
  in
  return (requests, receipt, hashes)

let setup_l1_contracts ~admin client =
  (* Originates the exchanger. *)
  let* exchanger =
    Client.originate_contract
      ~alias:"exchanger"
      ~amount:Tez.zero
      ~src:Constant.bootstrap1.public_key_hash
      ~init:"Unit"
      ~prg:(exchanger_path ())
      ~burn_cap:Tez.one
      client
  in
  let* () = Client.bake_for_and_wait client in

  (* Originates the bridge. *)
  let* bridge =
    Client.originate_contract
      ~alias:"evm-bridge"
      ~amount:Tez.zero
      ~src:Constant.bootstrap1.public_key_hash
      ~init:(sf "Pair %S None" exchanger)
      ~prg:(bridge_path ())
      ~burn_cap:Tez.one
      client
  in
  let* () = Client.bake_for_and_wait client in

  (* Originates the administrator contract. *)
  let* admin =
    Client.originate_contract
      ~alias:"evm-admin"
      ~amount:Tez.zero
      ~src:Constant.bootstrap1.public_key_hash
      ~init:(sf "%S" admin.Account.public_key_hash)
      ~prg:(admin_path ())
      ~burn_cap:Tez.one
      client
  in
  let* () = Client.bake_for_and_wait client in

  return {exchanger; bridge; admin}

let default_bootstrap_account_balance = Wei.of_eth_int 9999

let make_config ?bootstrap_accounts ?ticketer ?administrator ?legacy_dictator ()
    =
  let open Sc_rollup_helpers.Installer_kernel_config in
  let ticketer =
    Option.fold
      ~some:(fun ticketer ->
        let value = Hex.(of_string ticketer |> show) in
        let to_ = Durable_storage_path.ticketer in
        [Set {value; to_}])
      ~none:[]
      ticketer
  in
  let bootstrap_accounts =
    Option.fold
      ~some:
        (Array.fold_left
           (fun acc Eth_account.{address; _} ->
             let value =
               Wei.(to_le_bytes default_bootstrap_account_balance)
               |> Hex.of_bytes |> Hex.show
             in
             let to_ = Durable_storage_path.balance address in
             Set {value; to_} :: acc)
           [])
      ~none:[]
      bootstrap_accounts
  in
  let administrator =
    (* If we use a legacy dictator, we do not set the same key. *)
    let r =
      match legacy_dictator with
      | Some dictator ->
          Some
            ( Durable_storage_path.Legacy.dictator,
              dictator.Eth_account.public_key )
      | None ->
          Option.map
            (fun administrator ->
              let to_ = Durable_storage_path.admin in
              let administrator = Hex.(of_string administrator |> show) in
              (to_, administrator))
            administrator
    in
    Option.fold ~some:(fun (to_, value) -> [Set {value; to_}]) ~none:[] r
  in
  match ticketer @ bootstrap_accounts @ administrator with
  | [] -> None
  | res -> Some (`Config res)

type kernel_installee = {base_installee : string; installee : string}

let set_storage_version_in_config (`Config instrs) =
  `Config
    (Installer_kernel_config.Set
       {value = "0000000000000000"; to_ = "/evm/storage_version"}
    :: instrs)

let setup_evm_kernel ?config ?kernel_installee
    ?(originator_key = Constant.bootstrap1.public_key_hash)
    ?(rollup_operator_key = Constant.bootstrap1.public_key_hash)
    ?(bootstrap_accounts = Eth_account.bootstrap_accounts)
    ?(with_administrator = true) ?legacy_dictator
    ?(ghostnet_storage_version = false) ~admin protocol =
  let* node, client = setup_l1 protocol in
  let* l1_contracts =
    match admin with
    | Some admin ->
        let* res = setup_l1_contracts ~admin client in
        return (Some res)
    | None -> return None
  in
  (* If a L1 bridge was set up, we make the kernel aware of the address. *)
  let config =
    match config with
    | Some config -> Some config
    | None ->
        let ticketer =
          Option.map (fun {exchanger; _} -> exchanger) l1_contracts
        in
        let administrator =
          if with_administrator then
            Option.map (fun {admin; _} -> admin) l1_contracts
          else None
        in
        make_config
          ~bootstrap_accounts
          ?ticketer
          ?administrator
          ?legacy_dictator
          ()
  in
  let config =
    (* TODO: https://gitlab.com/tezos/tezos/-/issues/6296
       This corner case where we need to set a specific storage version
       is required because ghostnet doesn't have a storage version set in
       the storage.
       Upgrading on ghostnet should lead to the removal of this code. *)
    if ghostnet_storage_version then
      Option.map set_storage_version_in_config config
    else config
  in
  let sc_rollup_node =
    Sc_rollup_node.create
      Operator
      node
      ~base_dir:(Client.base_dir client)
      ~default_operator:rollup_operator_key
  in
  (* Start a rollup node *)
  let* boot_sector =
    let base_installee, installee =
      match kernel_installee with
      | Some {base_installee; installee} -> (base_installee, installee)
      | None -> ("./", "evm_kernel")
    in
    prepare_installer_kernel
      ~base_installee
      ~preimages_dir:
        (Filename.concat (Sc_rollup_node.data_dir sc_rollup_node) "wasm_2_0_0")
      ?config
      installee
  in
  let* sc_rollup_address =
    originate_sc_rollup
      ~kind:pvm_kind
      ~boot_sector
      ~parameters_ty:"pair (pair bytes (ticket unit)) (pair nat bytes)"
      ~src:originator_key
      client
  in
  let* () =
    Sc_rollup_node.run sc_rollup_node sc_rollup_address ["--log-kernel-debug"]
  in
  let sc_rollup_client = Sc_rollup_client.create ~protocol sc_rollup_node in
  (* EVM Kernel installation level. *)
  let* () = Client.bake_for_and_wait client in
  let* level = Node.get_level node in
  let* _ = Sc_rollup_node.wait_for_level ~timeout:30. sc_rollup_node level in
  let* evm_proxy_server =
    Evm_proxy_server.init ~mode:`Development sc_rollup_node
  in
  let endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  return
    {
      node;
      client;
      sc_rollup_node;
      sc_rollup_client;
      sc_rollup_address;
      originator_key;
      rollup_operator_key;
      evm_proxy_server;
      endpoint;
      l1_contracts;
    }

let setup_past_genesis ?with_administrator ?kernel_installee ?originator_key
    ?rollup_operator_key ~admin ?legacy_dictator ?ghostnet_storage_version
    protocol =
  let* ({node; client; sc_rollup_node; _} as full_setup) =
    setup_evm_kernel
      ?kernel_installee
      ?originator_key
      ?rollup_operator_key
      ?with_administrator
      ?legacy_dictator
      ?ghostnet_storage_version
      ~admin
      protocol
  in
  (* Force a level to got past the genesis block *)
  let* _level = next_evm_level ~sc_rollup_node ~node ~client in
  return full_setup

let setup_mockup () =
  let evm_proxy_server = Evm_proxy_server.mockup () in
  let* () = Evm_proxy_server.run evm_proxy_server in
  return evm_proxy_server

type contract = {label : string; abi : string; bin : string}

let deploy ~contract ~sender full_evm_setup =
  let {node; client; sc_rollup_node; evm_proxy_server; _} = full_evm_setup in
  let evm_proxy_server_endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let* () = Eth_cli.add_abi ~label:contract.label ~abi:contract.abi () in
  let send_deploy () =
    Eth_cli.deploy
      ~source_private_key:sender.Eth_account.private_key
      ~endpoint:evm_proxy_server_endpoint
      ~abi:contract.label
      ~bin:contract.bin
  in
  wait_for_application ~sc_rollup_node ~node ~client send_deploy ()

let send ~sender ~receiver ~value ?data full_evm_setup =
  let {node; client; sc_rollup_node; evm_proxy_server; _} = full_evm_setup in
  let evm_proxy_server_endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let send =
    Eth_cli.transaction_send
      ~source_private_key:sender.Eth_account.private_key
      ~to_public_key:receiver.Eth_account.address
      ~value
      ~endpoint:evm_proxy_server_endpoint
      ?data
  in
  wait_for_application ~sc_rollup_node ~node ~client send ()

let send_external_message_and_wait ~sc_rollup_node ~node ~client ~sender
    ~hex_msg =
  let* () =
    Client.Sc_rollup.send_message
      ~src:sender
      ~msg:("hex:[ \"" ^ hex_msg ^ "\" ]")
      client
  in
  let* _ = next_evm_level ~sc_rollup_node ~node ~client in
  unit

let check_block_progression ~sc_rollup_node ~node ~client ~endpoint
    ~expected_block_level =
  let* _level = next_evm_level ~sc_rollup_node ~node ~client in
  let* block_number = Eth_cli.block_number ~endpoint in
  return
  @@ Check.((block_number = expected_block_level) int)
       ~error_msg:"Unexpected block number, should be %%R, but got %%L"

let test_evm_proxy_server_connection =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"]
    ~title:"EVM proxy server connection"
  @@ fun protocol ->
  let* tezos_node, tezos_client = setup_l1 protocol in
  let* sc_rollup =
    originate_sc_rollup
      ~kind:"wasm_2_0_0"
      ~parameters_ty:"string"
      ~src:Constant.bootstrap1.alias
      tezos_client
  in
  let sc_rollup_node =
    Sc_rollup_node.create
      Observer
      tezos_node
      ~base_dir:(Client.base_dir tezos_client)
      ~default_operator:Constant.bootstrap1.alias
  in
  let evm_proxy = Evm_proxy_server.create sc_rollup_node in
  (* Tries to start the EVM proxy server without a listening rollup node. *)
  let process = Evm_proxy_server.spawn_run evm_proxy in
  let* () = Process.check ~expect_failure:true process in
  (* Starts the rollup node. *)
  let* _ = Sc_rollup_node.run sc_rollup_node sc_rollup [] in
  (* Starts the EVM proxy server and asks its version. *)
  let* () = Evm_proxy_server.run evm_proxy in
  let*? process = evm_proxy_server_version evm_proxy in
  let* () = Process.check process in
  unit

let test_originate_evm_kernel =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"]
    ~title:"Originate EVM kernel with installer"
  @@ fun protocol ->
  let* {node; client; sc_rollup_node; sc_rollup_client; _} =
    setup_evm_kernel ~admin:None protocol
  in
  (* First run of the installed EVM kernel, it will initialize the directory
     "eth_accounts". *)
  let* () = Client.bake_for_and_wait client in
  let* first_evm_run_level = Node.get_level node in
  let* level =
    Sc_rollup_node.wait_for_level
      ~timeout:30.
      sc_rollup_node
      first_evm_run_level
  in
  Check.(level = first_evm_run_level)
    Check.int
    ~error_msg:"Current level has moved past first EVM run (%L = %R)" ;
  let evm_key = "evm" in
  let*! storage_root_keys =
    Sc_rollup_client.inspect_durable_state_value
      ~hooks
      sc_rollup_client
      ~pvm_kind
      ~operation:Sc_rollup_client.Subkeys
      ~key:""
  in
  Check.(
    list_mem
      string
      evm_key
      storage_root_keys
      ~error_msg:"Expected %L to be initialized by the EVM kernel.") ;
  unit

let test_rpc_getBalance =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "get_balance"]
    ~title:"RPC method eth_getBalance"
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  let evm_proxy_server_endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let* balance =
    Eth_cli.balance
      ~account:Eth_account.bootstrap_accounts.(0).address
      ~endpoint:evm_proxy_server_endpoint
  in
  Check.((balance = default_bootstrap_account_balance) Wei.typ)
    ~error_msg:
      (sf
         "Expected balance of %s should be %%R, but got %%L"
         Eth_account.bootstrap_accounts.(0).address) ;
  unit

let test_rpc_getBlockByNumber =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "get_block_by_number"]
    ~title:"RPC method eth_getBlockByNumber"
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  let evm_proxy_server_endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let* block =
    Eth_cli.get_block ~block_id:"0" ~endpoint:evm_proxy_server_endpoint
  in
  Check.((block.number = 0l) int32)
    ~error_msg:"Unexpected block number, should be %%R, but got %%L" ;
  unit

let test_rpc_getBlockByHash =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "get_block_by_hash"]
    ~title:"RPC method eth_getBlockByHash"
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  let evm_proxy_server_endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let* block =
    Eth_cli.get_block ~block_id:"0" ~endpoint:evm_proxy_server_endpoint
  in
  Check.((block.number = 0l) int32)
    ~error_msg:"Unexpected block number, should be %%R, but got %%L" ;
  let* block' =
    Eth_cli.get_block
      ~block_id:(Option.get block.hash)
      ~endpoint:evm_proxy_server_endpoint
  in
  assert (block = block') ;
  unit

let test_l2_block_size_non_zero =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "block"; "size"]
    ~title:"Block size is greater than zero"
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  let evm_proxy_server_endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let* block =
    Eth_cli.get_block ~block_id:"0" ~endpoint:evm_proxy_server_endpoint
  in
  Check.((block.size > 0l) int32)
    ~error_msg:"Unexpected block size, should be > 0, but got %%L" ;
  unit

let transaction_count_request address =
  Evm_proxy_server.
    {
      method_ = "eth_getTransactionCount";
      parameters = `A [`String address; `String "latest"];
    }

let get_transaction_count proxy_server address =
  let* transaction_count =
    Evm_proxy_server.call_evm_rpc
      proxy_server
      (transaction_count_request address)
  in
  return JSON.(transaction_count |-> "result" |> as_int64)

let test_rpc_getTransactionCount =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "get_transaction_count"]
    ~title:"RPC method eth_getTransactionCount"
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  let* transaction_count =
    get_transaction_count
      evm_proxy_server
      Eth_account.bootstrap_accounts.(0).address
  in
  Check.((transaction_count = 0L) int64)
    ~error_msg:"Expected a nonce of %R, but got %L" ;
  unit

let test_rpc_getTransactionCountBatch =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "get_transaction_count_as_batch"]
    ~title:"RPC method eth_getTransactionCount in batch"
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  let* transaction_count =
    get_transaction_count
      evm_proxy_server
      Eth_account.bootstrap_accounts.(0).address
  in
  let* transaction_count_batch =
    let* transaction_count =
      Evm_proxy_server.batch_evm_rpc
        evm_proxy_server
        [transaction_count_request Eth_account.bootstrap_accounts.(0).address]
    in
    match JSON.as_list transaction_count with
    | [transaction_count] ->
        return JSON.(transaction_count |-> "result" |> as_int64)
    | _ -> Test.fail "Unexpected result from batching one request"
  in
  Check.((transaction_count = transaction_count_batch) int64)
    ~error_msg:"Nonce from a single request is %L, but got %R from batching it" ;
  unit

let test_rpc_batch =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "rpc"; "batch"]
    ~title:"RPC batch requests"
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  let* transaction_count, chain_id =
    let transaction_count =
      transaction_count_request Eth_account.bootstrap_accounts.(0).address
    in
    let chain_id =
      Evm_proxy_server.{method_ = "eth_chainId"; parameters = `Null}
    in
    let* results =
      Evm_proxy_server.batch_evm_rpc
        evm_proxy_server
        [transaction_count; chain_id]
    in
    match JSON.as_list results with
    | [transaction_count; chain_id] ->
        return
          ( JSON.(transaction_count |-> "result" |> as_int64),
            JSON.(chain_id |-> "result" |> as_int64) )
    | _ -> Test.fail "Unexpected result from batching two requests"
  in
  Check.((transaction_count = 0L) int64)
    ~error_msg:"Expected a nonce of %R, but got %L" ;
  (* Default chain id for Ethereum custom networks, not chosen randomly. *)
  let default_chain_id = 1337L in
  Check.((chain_id = default_chain_id) int64)
    ~error_msg:"Expected a chain_id of %R, but got %L" ;
  unit

let test_l2_blocks_progression =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "l2_blocks_progression"]
    ~title:"Check L2 blocks progression"
  @@ fun protocol ->
  let* {node; client; sc_rollup_node; endpoint; _} =
    setup_evm_kernel ~admin:None protocol
  in
  let* () =
    check_block_progression
      ~sc_rollup_node
      ~node
      ~client
      ~endpoint
      ~expected_block_level:1
  in
  let* () =
    check_block_progression
      ~sc_rollup_node
      ~node
      ~client
      ~endpoint
      ~expected_block_level:2
  in
  unit

let test_consistent_block_hashes =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "l2_blocks"]
    ~title:"Check L2 blocks consistency of hashes"
  @@ fun protocol ->
  let* {node; client; sc_rollup_node; endpoint; _} =
    setup_evm_kernel ~admin:None protocol
  in
  let new_block () =
    let* _level = next_evm_level ~sc_rollup_node ~node ~client in
    let* number = Eth_cli.block_number ~endpoint in
    Eth_cli.get_block ~block_id:(string_of_int number) ~endpoint
  in

  let* block0 = new_block () in
  let* block1 = new_block () in
  let* block2 = new_block () in
  let* block3 = new_block () in

  let check_parent_hash parent block =
    let parent_hash = Option.value ~default:"" parent.Block.hash in
    Check.((block.Block.parent = parent_hash) string)
      ~error_msg:"Unexpected parent hash, should be %%R, but got %%L"
  in

  (* Check consistency accross blocks. *)
  check_parent_hash block0 block1 ;
  check_parent_hash block1 block2 ;
  check_parent_hash block2 block3 ;

  let block_hashes, parent_hashes =
    List.map
      (fun Block.{hash; parent; _} -> (hash, parent))
      [block0; block1; block2; block3]
    |> List.split
  in
  let block_hashes_uniq = List.sort_uniq compare block_hashes in
  let parent_hashes_uniq = List.sort_uniq compare parent_hashes in

  (* Check unicity of hashes and parent hashes. *)
  Check.(List.(length block_hashes = length block_hashes_uniq) int)
    ~error_msg:"The list of block hashes must be unique" ;
  Check.(List.(length parent_hashes = length parent_hashes_uniq) int)
    ~error_msg:"The list of block parent hashes must be unique" ;

  unit

(** The info for the "storage.sol" contract.
    See [src/kernel_evm/solidity_examples] *)
let simple_storage =
  {
    label = "simpleStorage";
    abi = kernel_inputs_path ^ "/storage.abi";
    bin = kernel_inputs_path ^ "/storage.bin";
  }

(** The info for the "erc20tok.sol" contract.
    See [src/kernel_evm/solidity_examples] *)
let erc20 =
  {
    label = "erc20tok";
    abi = kernel_inputs_path ^ "/erc20tok.abi";
    bin = kernel_inputs_path ^ "/erc20tok.bin";
  }

(** The info for the "loop.sol" contract.
    See [src/kernel_evm/benchmarks/scripts/benchmarks/contracts/loop.sol] *)
let loop =
  {
    label = "loop";
    abi = kernel_inputs_path ^ "/loop.abi";
    bin = kernel_inputs_path ^ "/loop.bin";
  }

(** Test that the contract creation works.  *)
let test_l2_deploy_simple_storage =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "l2_deploy"]
    ~title:"Check L2 contract deployment"
  @@ fun protocol ->
  let* ({sc_rollup_client; evm_proxy_server; _} as full_evm_setup) =
    setup_past_genesis ~admin:None protocol
  in
  let endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let sender = Eth_account.bootstrap_accounts.(0) in
  let* contract_address, tx =
    deploy ~contract:simple_storage ~sender full_evm_setup
  in
  let address = String.lowercase_ascii contract_address in
  Check.(
    (address = "0xd77420f73b4612a7a99dba8c2afd30a1886b0344")
      string
      ~error_msg:"Expected address to be %R but was %L.") ;

  let* code_in_kernel =
    Evm_proxy_server.fetch_contract_code evm_proxy_server contract_address
  in
  (* The same deployment has been reproduced on the Sepolia testnet, resulting
     on this specific code. *)
  let expected_code =
    "0x608060405234801561001057600080fd5b50600436106100415760003560e01c80634e70b1dc1461004657806360fe47b1146100645780636d4ce63c14610080575b600080fd5b61004e61009e565b60405161005b91906100d0565b60405180910390f35b61007e6004803603810190610079919061011c565b6100a4565b005b6100886100ae565b60405161009591906100d0565b60405180910390f35b60005481565b8060008190555050565b60008054905090565b6000819050919050565b6100ca816100b7565b82525050565b60006020820190506100e560008301846100c1565b92915050565b600080fd5b6100f9816100b7565b811461010457600080fd5b50565b600081359050610116816100f0565b92915050565b600060208284031215610132576101316100eb565b5b600061014084828501610107565b9150509291505056fea2646970667358221220ec57e49a647342208a1f5c9b1f2049bf1a27f02e19940819f38929bf67670a5964736f6c63430008120033"
  in
  Check.((code_in_kernel = expected_code) string)
    ~error_msg:"Unexpected code %L, it should be %R" ;
  (* The transaction was a contract creation, the transaction object
     must not contain the [to] field. *)
  let* tx_object = Eth_cli.transaction_get ~endpoint ~tx_hash:tx in
  (match tx_object with
  | Some tx_object ->
      Check.((tx_object.to_ = None) (option string))
        ~error_msg:
          "The transaction object of a contract creation should not have the \
           [to] field present"
  | None -> Test.fail "The transaction object of %s should be available" tx) ;

  let*! accounts =
    Sc_rollup_client.inspect_durable_state_value
      ~hooks
      sc_rollup_client
      ~pvm_kind
      ~operation:Sc_rollup_client.Subkeys
      ~key:Durable_storage_path.eth_accounts
  in
  (* check tx status*)
  let* () = check_tx_succeeded ~endpoint ~tx in

  (* check contract account was created *)
  Check.(
    list_mem
      string
      (Helpers.normalize contract_address)
      (List.map String.lowercase_ascii accounts)
      ~error_msg:"Expected %L account to be initialized by contract creation.") ;
  unit

let send_call_set_storage_simple contract_address sender n
    {sc_rollup_node; node; client; endpoint; _} =
  let call_set (sender : Eth_account.t) n =
    Eth_cli.contract_send
      ~source_private_key:sender.private_key
      ~endpoint
      ~abi_label:simple_storage.label
      ~address:contract_address
      ~method_call:(Printf.sprintf "set(%d)" n)
  in
  wait_for_application ~sc_rollup_node ~node ~client (call_set sender n) ()

(** Test that a contract can be called,
    and that the call can modify the storage.  *)
let test_l2_call_simple_storage =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "l2_deploy"; "l2_call"]
    ~title:"Check L2 contract call"
  @@ fun protocol ->
  (* setup *)
  let* ({evm_proxy_server; sc_rollup_client; _} as evm_setup) =
    setup_past_genesis ~admin:None protocol
  in
  let endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let sender = Eth_account.bootstrap_accounts.(0) in

  (* deploy contract *)
  let* address, _tx = deploy ~contract:simple_storage ~sender evm_setup in

  (* set 42 *)
  let* tx = send_call_set_storage_simple address sender 42 evm_setup in

  let* () = check_tx_succeeded ~endpoint ~tx in
  let* () = check_storage_size sc_rollup_client ~address 1 in
  let* () = check_nb_in_storage ~evm_setup ~address ~nth:0 ~expected:42 in

  (* set 24 by another user *)
  let* tx =
    send_call_set_storage_simple
      address
      Eth_account.bootstrap_accounts.(1)
      24
      evm_setup
  in

  let* () = check_tx_succeeded ~endpoint ~tx in
  let* () = check_storage_size sc_rollup_client ~address 1 in
  (* value stored has changed *)
  let* () = check_nb_in_storage ~evm_setup ~address ~nth:0 ~expected:24 in

  (* set -1 *)
  (* some environments prevent sending a negative value, as the value is
     unsigned (eg remix) but it is actually the expected result *)
  let* tx = send_call_set_storage_simple address sender (-1) evm_setup in

  let* () = check_tx_succeeded ~endpoint ~tx in
  let* () = check_storage_size sc_rollup_client ~address 1 in
  (* value stored has changed *)
  let* () =
    check_str_in_storage
      ~evm_setup
      ~address
      ~nth:0
      ~expected:
        "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
  in
  unit

let test_l2_deploy_erc20 =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "l2_deploy"; "erc20"; "l2_call"]
    ~title:"Check L2 erc20 contract deployment"
  @@ fun protocol ->
  (* setup *)
  let* ({sc_rollup_client; evm_proxy_server; node; client; sc_rollup_node; _} as
       evm_setup) =
    setup_past_genesis ~admin:None protocol
  in
  let endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let sender = Eth_account.bootstrap_accounts.(0) in
  let player = Eth_account.bootstrap_accounts.(1) in

  (* deploy the contract *)
  let* address, tx = deploy ~contract:erc20 ~sender evm_setup in
  Check.(
    (String.lowercase_ascii address
    = "0xd77420f73b4612a7a99dba8c2afd30a1886b0344")
      string
      ~error_msg:"Expected address to be %R but was %L.") ;

  (* check tx status *)
  let* () = check_tx_succeeded ~endpoint ~tx in

  (* check account was created *)
  let*! accounts =
    Sc_rollup_client.inspect_durable_state_value
      ~hooks
      sc_rollup_client
      ~pvm_kind
      ~operation:Sc_rollup_client.Subkeys
      ~key:Durable_storage_path.eth_accounts
  in
  Check.(
    list_mem
      string
      (Helpers.normalize address)
      (List.map String.lowercase_ascii accounts)
      ~error_msg:"Expected %L account to be initialized by contract creation.") ;

  (* minting / burning *)
  let call_mint (sender : Eth_account.t) n =
    Eth_cli.contract_send
      ~source_private_key:sender.private_key
      ~endpoint
      ~abi_label:erc20.label
      ~address
      ~method_call:(Printf.sprintf "mint(%d)" n)
  in
  let call_burn ?(expect_failure = false) (sender : Eth_account.t) n =
    Eth_cli.contract_send
      ~expect_failure
      ~source_private_key:sender.private_key
      ~endpoint
      ~abi_label:erc20.label
      ~address
      ~method_call:(Printf.sprintf "burn(%d)" n)
  in

  (* sender mints 42 *)
  let* tx =
    wait_for_application ~sc_rollup_node ~node ~client (call_mint sender 42) ()
  in
  let* () = check_tx_succeeded ~endpoint ~tx in

  (* totalSupply is the first value in storage *)
  let* () = check_nb_in_storage ~evm_setup ~address ~nth:0 ~expected:42 in

  (* player mints 100 *)
  let* tx =
    wait_for_application ~sc_rollup_node ~node ~client (call_mint player 100) ()
  in
  let* () = check_tx_succeeded ~endpoint ~tx in
  (* totalSupply is the first value in storage *)
  let* () = check_nb_in_storage ~evm_setup ~address ~nth:0 ~expected:142 in

  (* sender tries to burn 100, should fail *)
  let* _tx =
    wait_for_application
      ~sc_rollup_node
      ~node
      ~client
      (call_burn ~expect_failure:true sender 100)
      ()
  in
  let* () = check_nb_in_storage ~evm_setup ~address ~nth:0 ~expected:142 in

  (* sender tries to burn 42, should succeed *)
  let* tx =
    wait_for_application ~sc_rollup_node ~node ~client (call_burn sender 42) ()
  in
  let* () = check_tx_succeeded ~endpoint ~tx in
  let* () = check_nb_in_storage ~evm_setup ~address ~nth:0 ~expected:100 in
  unit

(* TODO: add internal parameters here (e.g the kernel version) *)
type config_result = {chain_id : int64}

let config_setup evm_setup =
  let web3_clientVersion =
    Evm_proxy_server.{method_ = "web3_clientVersion"; parameters = `A []}
  in
  let chain_id =
    Evm_proxy_server.{method_ = "eth_chainId"; parameters = `Null}
  in
  let* results =
    Evm_proxy_server.batch_evm_rpc
      evm_setup.evm_proxy_server
      [web3_clientVersion; chain_id]
  in
  match JSON.as_list results with
  | [web3_clientVersion; chain_id] ->
      (* We don't need to return the web3_clientVersion because,
         it might change after the upgrade.
         The only thing that we need to look out for is,
         are we able to retrieve it and deserialize it. *)
      let _sanity_check = JSON.(web3_clientVersion |-> "result" |> as_string) in
      return {chain_id = JSON.(chain_id |-> "result" |> as_int64)}
  | _ -> Test.fail "Unexpected result from batching two requests"

let ensure_config_setup_integrity ~config_result evm_setup =
  let* upcoming_config_setup = config_setup evm_setup in
  assert (config_result = upcoming_config_setup) ;
  unit

let ensure_block_integrity ~block_result evm_setup =
  let* block =
    Eth_cli.get_block
      ~block_id:(Int32.to_string block_result.Block.number)
      ~endpoint:evm_setup.endpoint
  in
  (* only the relevant fields *)
  assert (block.number = block_result.number) ;
  assert (block.hash = block_result.hash) ;
  assert (block.timestamp = block_result.timestamp) ;
  assert (block.transactions = block_result.transactions) ;
  unit

let latest_block evm_setup =
  let* latest_block =
    Evm_proxy_server.(
      call_evm_rpc
        evm_setup.evm_proxy_server
        {
          method_ = "eth_getBlockByNumber";
          parameters = `A [`String "latest"; `Bool false];
        })
  in
  return @@ (latest_block |> Evm_proxy_server.extract_result |> Block.of_json)

type transfer_result = {
  sender_balance_before : Wei.t;
  sender_balance_after : Wei.t;
  sender_nonce_before : int64;
  sender_nonce_after : int64;
  value : Wei.t;
  tx_hash : string;
  tx_object : Transaction.transaction_object;
  tx_receipt : Transaction.transaction_receipt;
  receiver_balance_before : Wei.t;
  receiver_balance_after : Wei.t;
}

let get_tx_object ~endpoint ~tx_hash =
  let* tx_object = Eth_cli.transaction_get ~endpoint ~tx_hash in
  match tx_object with
  | Some tx_object -> return tx_object
  | None -> Test.fail "The transaction object of %s should be available" tx_hash

let get_transaction_receipt ~full_evm_setup ~tx_hash =
  let* json =
    Evm_proxy_server.call_evm_rpc
      full_evm_setup.evm_proxy_server
      {method_ = "eth_getTransactionReceipt"; parameters = `A [`String tx_hash]}
  in
  return JSON.(json |-> "result" |> Transaction.transaction_receipt_of_json)

let ensure_transfer_result_integrity ~transfer_result ~sender ~receiver
    full_evm_setup =
  let endpoint = Evm_proxy_server.endpoint full_evm_setup.evm_proxy_server in
  let balance account = Eth_cli.balance ~account ~endpoint in
  let* sender_balance = balance sender.Eth_account.address in
  assert (sender_balance = transfer_result.sender_balance_after) ;
  let* receiver_balance = balance receiver.Eth_account.address in
  assert (receiver_balance = transfer_result.receiver_balance_after) ;
  let* sender_nonce =
    get_transaction_count full_evm_setup.evm_proxy_server sender.address
  in
  assert (sender_nonce = transfer_result.sender_nonce_after) ;
  let* tx_object = get_tx_object ~endpoint ~tx_hash:transfer_result.tx_hash in
  assert (tx_object = transfer_result.tx_object) ;
  let* tx_receipt =
    get_transaction_receipt ~full_evm_setup ~tx_hash:transfer_result.tx_hash
  in
  assert (tx_receipt = transfer_result.tx_receipt) ;
  unit

let make_transfer ?data ~value ~sender ~receiver full_evm_setup =
  let endpoint = Evm_proxy_server.endpoint full_evm_setup.evm_proxy_server in
  let balance account = Eth_cli.balance ~account ~endpoint in
  let* sender_balance_before = balance sender.Eth_account.address in
  let* receiver_balance_before = balance receiver.Eth_account.address in
  let* sender_nonce_before =
    get_transaction_count full_evm_setup.evm_proxy_server sender.address
  in
  let* tx_hash = send ~sender ~receiver ~value ?data full_evm_setup in
  let* () = check_tx_succeeded ~endpoint ~tx:tx_hash in
  let* sender_balance_after = balance sender.address in
  let* receiver_balance_after = balance receiver.address in
  let* sender_nonce_after =
    get_transaction_count full_evm_setup.evm_proxy_server sender.address
  in
  let* tx_object = get_tx_object ~endpoint ~tx_hash in
  let* tx_receipt = get_transaction_receipt ~full_evm_setup ~tx_hash in
  return
    {
      sender_balance_before;
      sender_balance_after;
      sender_nonce_before;
      sender_nonce_after;
      value;
      tx_hash;
      tx_object;
      tx_receipt;
      receiver_balance_before;
      receiver_balance_after;
    }

let transfer ?data protocol =
  let* full_evm_setup = setup_past_genesis ~admin:None protocol in
  let sender, receiver =
    (Eth_account.bootstrap_accounts.(0), Eth_account.bootstrap_accounts.(1))
  in
  let* {
         sender_balance_before;
         sender_balance_after;
         sender_nonce_before;
         sender_nonce_after;
         value;
         tx_object;
         receiver_balance_before;
         receiver_balance_after;
         _;
       } =
    make_transfer
      ?data
      ~value:Wei.(default_bootstrap_account_balance - one)
      ~sender
      ~receiver
      full_evm_setup
  in
  let* receipt =
    Eth_cli.get_receipt ~endpoint:full_evm_setup.endpoint ~tx:tx_object.hash
  in
  let fees =
    match receipt with
    | Some Transaction.{status = true; gasUsed; effectiveGasPrice; _} ->
        Int32.(to_string (mul gasUsed effectiveGasPrice)) |> Wei.of_string
    | _ -> Test.fail "Transaction didn't succeed"
  in
  Check.(
    Wei.(sender_balance_after = sender_balance_before - value - fees) Wei.typ)
    ~error_msg:
      "Unexpected sender balance after transfer, should be %R, but got %L" ;
  Check.(Wei.(receiver_balance_after = receiver_balance_before + value) Wei.typ)
    ~error_msg:
      "Unexpected receiver balance after transfer, should be %R, but got %L" ;
  Check.((sender_nonce_after = Int64.succ sender_nonce_before) int64)
    ~error_msg:
      "Unexpected sender nonce after transfer, should be %R, but got %L" ;
  (* Perform some sanity checks on the transaction object produced by the
     kernel. *)
  Check.((tx_object.from = sender.address) string)
    ~error_msg:"Unexpected transaction's sender" ;
  Check.((tx_object.to_ = Some receiver.address) (option string))
    ~error_msg:"Unexpected transaction's receiver" ;
  Check.((tx_object.value = value) Wei.typ)
    ~error_msg:"Unexpected transaction's value" ;
  unit

let test_l2_transfer =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "l2_transfer"]
    ~title:"Check L2 transfers are applied"
    transfer

let test_chunked_transaction =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "l2_transfer"; "chunked"]
    ~title:"Check L2 chunked transfers are applied"
  @@ transfer ~data:("0x" ^ String.make 12_000 'a')

let test_rpc_txpool_content =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "txpool_content"]
    ~title:"Check RPC txpool_content is available"
  @@ fun _protocol ->
  let* evm_proxy_server = setup_mockup () in
  (* The content of the txpool is not relevant for now, this test only checks
     the the RPC is correct, i.e. an object containing both the `pending` and
     `queued` fields, containing the correct objects: addresses pointing to a
     mapping of nonces to transactions. *)
  let* _result = Evm_proxy_server.txpool_content evm_proxy_server in
  unit

let test_rpc_web3_clientVersion =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "client_version"]
    ~title:"Check RPC web3_clientVersion"
  @@ fun _protocol ->
  let* evm_proxy_server = setup_mockup () in
  let* web3_clientVersion =
    Evm_proxy_server.(
      call_evm_rpc
        evm_proxy_server
        {method_ = "web3_clientVersion"; parameters = `A []})
  in
  let* server_version =
    evm_proxy_server_version evm_proxy_server |> Runnable.run
  in
  Check.(
    (JSON.(web3_clientVersion |-> "result" |> as_string)
    = JSON.as_string server_version)
      string)
    ~error_msg:"Expected version %%R, got %%L." ;
  unit

let test_rpc_web3_sha3 =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "sha3"]
    ~title:"Check RPC web3_sha3"
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  (* From the example provided in
     https://ethereum.org/en/developers/docs/apis/json-rpc/#web3_sha3 *)
  let input_data = "0x68656c6c6f20776f726c64" in
  let expected_reply =
    "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"
  in
  let* web3_sha3 =
    Evm_proxy_server.(
      call_evm_rpc
        evm_proxy_server
        {method_ = "web3_sha3"; parameters = `A [`String input_data]})
  in
  Check.((JSON.(web3_sha3 |-> "result" |> as_string) = expected_reply) string)
    ~error_msg:"Expected hash %%R, got %%L." ;
  unit

let test_simulate =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "simulate"]
    ~title:"A block can be simulated in the rollup node"
    (fun protocol ->
      let* {evm_proxy_server; sc_rollup_client; _} =
        setup_past_genesis ~admin:None protocol
      in
      let* json =
        Evm_proxy_server.call_evm_rpc
          evm_proxy_server
          {method_ = "eth_blockNumber"; parameters = `A []}
      in
      let block_number =
        JSON.(json |-> "result" |> as_string |> int_of_string)
      in
      let*! simulation_result =
        Sc_rollup_client.simulate
          ~insight_requests:
            [`Durable_storage_key ["evm"; "blocks"; "current"; "number"]]
          sc_rollup_client
          []
      in
      let simulated_block_number =
        match simulation_result.insights with
        | [insight] ->
            Option.map
              (fun hex -> `Hex hex |> Hex.to_string |> Z.of_bits |> Z.to_int)
              insight
        | _ -> None
      in
      Check.((simulated_block_number = Some (block_number + 1)) (option int))
        ~error_msg:"The simulation should advance one L2 block" ;
      unit)

let read_tx_from_file () =
  read_file (kernel_inputs_path ^ "/100-inputs-for-proxy")
  |> String.trim |> String.split_on_char '\n'
  |> List.map (fun line ->
         match String.split_on_char ' ' line with
         | [tx_raw; tx_hash] -> (tx_raw, tx_hash)
         | _ -> failwith "Unexpected tx_raw and tx_hash.")

let test_full_blocks =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "full_blocks"]
    ~title:
      "Check `evm_getBlockByNumber` with full blocks returns the correct \
       informations"
  @@ fun protocol ->
  let* {evm_proxy_server; sc_rollup_node; node; client; _} =
    setup_past_genesis ~admin:None protocol
  in
  let txs =
    read_tx_from_file ()
    |> List.filteri (fun i _ -> i < 5)
    |> List.map (fun (tx, _hash) -> tx)
  in
  let* _requests, receipt, _hashes =
    send_n_transactions ~sc_rollup_node ~node ~client ~evm_proxy_server txs
  in
  let* block =
    Evm_proxy_server.(
      call_evm_rpc
        evm_proxy_server
        {
          method_ = "eth_getBlockByNumber";
          parameters =
            `A [`String (Format.sprintf "%#lx" receipt.blockNumber); `Bool true];
        })
  in
  let block = block |> Evm_proxy_server.extract_result |> Block.of_json in
  let block_hash =
    match block.hash with
    | Some hash -> hash
    | None -> Test.fail "Expected a hash for the block"
  in
  let block_number = block.number in
  (match block.Block.transactions with
  | Block.Empty -> Test.fail "Expected a non empty block"
  | Block.Full transactions ->
      List.iteri
        (fun index
             ({blockHash; blockNumber; transactionIndex; _} :
               Transaction.transaction_object) ->
          Check.((block_hash = blockHash) string)
            ~error_msg:
              (sf "The transaction should be in block %%L but found %%R") ;
          Check.((block_number = blockNumber) int32)
            ~error_msg:
              (sf "The transaction should be in block %%L but found %%R") ;
          Check.((Int32.of_int index = transactionIndex) int32)
            ~error_msg:
              (sf "The transaction should be at index %%L but found %%R"))
        transactions
  | Block.Hash _ -> Test.fail "Block is supposed to contain transaction objects") ;
  unit

let test_latest_block =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "blocks"; "latest"]
    ~title:
      "Check `evm_getBlockByNumber` works correctly when asking for the \
       `latest`"
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  (* The first execution of the kernel actually builds two blocks: the genesis
     block and the block for the current inbox. As such, the latest block is
     always of level 1. *)
  let* latest_block =
    Evm_proxy_server.(
      call_evm_rpc
        evm_proxy_server
        {
          method_ = "eth_getBlockByNumber";
          parameters = `A [`String "latest"; `Bool false];
        })
  in
  let latest_block =
    latest_block |> Evm_proxy_server.extract_result |> Block.of_json
  in
  Check.((latest_block.Block.number = 1l) int32)
    ~error_msg:"Expected latest being block number %R, but got %L" ;
  unit

let test_eth_call_nullable_recipient =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "eth_call"; "null"]
    ~title:"Check `eth_call.to` input can be null"
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  let* call_result =
    Evm_proxy_server.(
      call_evm_rpc
        evm_proxy_server
        {
          method_ = "eth_call";
          parameters = `A [`O [("to", `Null)]; `String "latest"];
        })
  in
  (* Check the RPC returns a `result`. *)
  let _result = call_result |> Evm_proxy_server.extract_result in
  unit

let test_inject_100_transactions =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "bigger_blocks"]
    ~title:"Check blocks can contain more than 64 transactions"
  @@ fun protocol ->
  let* {evm_proxy_server; sc_rollup_node; node; client; _} =
    setup_past_genesis ~admin:None protocol
  in
  (* Retrieves all the messages and prepare them for the current rollup. *)
  let txs = read_tx_from_file () |> List.map (fun (tx, _hash) -> tx) in
  let* requests, receipt, _hashes =
    send_n_transactions ~sc_rollup_node ~node ~client ~evm_proxy_server txs
  in
  let* block_with_100tx =
    Evm_proxy_server.(
      call_evm_rpc
        evm_proxy_server
        {
          method_ = "eth_getBlockByNumber";
          parameters =
            `A
              [`String (Format.sprintf "%#lx" receipt.blockNumber); `Bool false];
        })
  in
  let block_with_100tx =
    block_with_100tx |> Evm_proxy_server.extract_result |> Block.of_json
  in
  (match block_with_100tx.Block.transactions with
  | Block.Empty -> Test.fail "Expected a non empty block"
  | Block.Full _ ->
      Test.fail "Block is supposed to contain only transaction hashes"
  | Block.Hash hashes ->
      Check.((List.length hashes = List.length requests) int)
        ~error_msg:"Expected %R transactions in the latest block, got %L") ;

  let* _level = next_evm_level ~sc_rollup_node ~node ~client in
  let* latest_evm_level =
    Evm_proxy_server.(
      call_evm_rpc
        evm_proxy_server
        {method_ = "eth_blockNumber"; parameters = `A []})
  in
  let latest_evm_level =
    latest_evm_level |> Evm_proxy_server.extract_result |> JSON.as_int32
  in
  (* At each loop, the kernel reads the previous block. Until the patch, the
     kernel failed to read the previous block if there was more than 64 hash,
     this test ensures it works by assessing new blocks are produced. *)
  Check.((latest_evm_level >= Int32.succ block_with_100tx.Block.number) int32)
    ~error_msg:
      "Expected a new block after the one with 100 transactions, but level \
       hasn't changed" ;
  unit

let test_eth_call_large =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "eth_call"; "simulate"; "large"]
    ~title:"eth_estimateGas with a large amount of data"
    (fun protocol ->
      (* setup *)
      let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
      let sender = Eth_account.bootstrap_accounts.(0) in

      (* large request *)
      let eth_call =
        [
          ("to", Ezjsonm.encode_string sender.address);
          ("data", Ezjsonm.encode_string ("0x" ^ String.make 12_000 'a'));
        ]
      in

      (* make call to proxy *)
      let* call_result =
        Evm_proxy_server.(
          call_evm_rpc
            evm_proxy_server
            {
              method_ = "eth_call";
              parameters = `A [`O eth_call; `String "latest"];
            })
      in

      (* Check the RPC returns a `result`. *)
      let r = call_result |> Evm_proxy_server.extract_result in
      Check.((JSON.as_string r = "0x") string)
        ~error_msg:"Expected result %R, but got %L" ;

      unit)

let test_estimate_gas =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "eth_estimategas"; "simulate"]
    ~title:"eth_estimateGas for contract creation"
    (fun protocol ->
      (* setup *)
      let* {evm_proxy_server; _} = setup_past_genesis protocol ~admin:None in

      (* large request *)
      let data = read_file simple_storage.bin in
      let eth_call = [("data", Ezjsonm.encode_string @@ "0x" ^ data)] in

      (* make call to proxy *)
      let* call_result =
        Evm_proxy_server.(
          call_evm_rpc
            evm_proxy_server
            {method_ = "eth_estimateGas"; parameters = `A [`O eth_call]})
      in

      (* Check the RPC returns a `result`. *)
      let r = call_result |> Evm_proxy_server.extract_result in
      Check.((JSON.as_int r = 23423) int)
        ~error_msg:"Expected result greater than %R, but got %L" ;

      unit)

let test_estimate_gas_additionnal_field =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "eth_estimategas"; "simulate"; "remix"]
    ~title:"eth_estimateGas allows additional fields"
    (fun protocol ->
      (* setup *)
      let* {evm_proxy_server; _} = setup_past_genesis protocol ~admin:None in

      (* large request *)
      let data = read_file simple_storage.bin in
      let eth_call =
        [
          ( "from",
            Ezjsonm.encode_string
            @@ "0x6ce4d79d4e77402e1ef3417fdda433aa744c6e1c" );
          ("data", Ezjsonm.encode_string @@ "0x" ^ data);
          ("value", Ezjsonm.encode_string @@ "0x0");
          (* for some reason remix adds the "type" field *)
          ("type", Ezjsonm.encode_string @@ "0x1");
        ]
      in

      (* make call to proxy *)
      let* call_result =
        Evm_proxy_server.(
          call_evm_rpc
            evm_proxy_server
            {method_ = "eth_estimateGas"; parameters = `A [`O eth_call]})
      in

      (* Check the RPC returns a `result`. *)
      let r = call_result |> Evm_proxy_server.extract_result in
      Check.((JSON.as_int r = 23423) int)
        ~error_msg:"Expected result greater than %R, but got %L" ;

      unit)

let test_eth_call_storage_contract_rollup_node =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "eth_call"; "simulate"]
    ~title:"Try to call a view (directly through proxy)"
    (fun protocol ->
      (* setup *)
      let* ({evm_proxy_server; endpoint; _} as evm_setup) =
        setup_past_genesis ~admin:None protocol
      in

      let sender = Eth_account.bootstrap_accounts.(0) in

      (* deploy contract *)
      let* address, tx = deploy ~contract:simple_storage ~sender evm_setup in
      let* () = check_tx_succeeded ~endpoint ~tx in
      Check.(
        (String.lowercase_ascii address
        = "0xd77420f73b4612a7a99dba8c2afd30a1886b0344")
          string
          ~error_msg:"Expected address to be %R but was %L.") ;

      (* craft request *)
      let data = "0x4e70b1dc" in
      let eth_call =
        [
          ("to", Ezjsonm.encode_string address);
          ("data", Ezjsonm.encode_string data);
        ]
      in

      (* make call to proxy *)
      let* call_result =
        Evm_proxy_server.(
          call_evm_rpc
            evm_proxy_server
            {
              method_ = "eth_call";
              parameters = `A [`O eth_call; `String "latest"];
            })
      in

      let r = call_result |> Evm_proxy_server.extract_result in
      Check.(
        (JSON.as_string r
       = "0x0000000000000000000000000000000000000000000000000000000000000000")
          string)
        ~error_msg:"Expected result %R, but got %L" ;

      let* tx = send_call_set_storage_simple address sender 42 evm_setup in
      let* () = check_tx_succeeded ~endpoint ~tx in

      (* make call to proxy *)
      let* call_result =
        Evm_proxy_server.(
          call_evm_rpc
            evm_proxy_server
            {
              method_ = "eth_call";
              parameters = `A [`O eth_call; `String "latest"];
            })
      in
      let r = call_result |> Evm_proxy_server.extract_result in
      Check.(
        (JSON.as_string r
       = "0x000000000000000000000000000000000000000000000000000000000000002a")
          string)
        ~error_msg:"Expected result %R, but got %L" ;
      unit)

let test_eth_call_storage_contract_proxy =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "simulate"]
    ~title:"Try to call a view (directly through rollup node)"
    (fun protocol ->
      let* ({sc_rollup_client; evm_proxy_server; _} as evm_setup) =
        setup_past_genesis ~admin:None protocol
      in

      let endpoint = Evm_proxy_server.endpoint evm_proxy_server in
      let sender = Eth_account.bootstrap_accounts.(0) in

      (* deploy contract *)
      let* address, tx = deploy ~contract:simple_storage ~sender evm_setup in
      let* () = check_tx_succeeded ~endpoint ~tx in

      Check.(
        (String.lowercase_ascii address
        = "0xd77420f73b4612a7a99dba8c2afd30a1886b0344")
          string
          ~error_msg:"Expected address to be %R but was %L.") ;

      let*! simulation_result =
        Sc_rollup_client.simulate
          ~insight_requests:
            [
              `Durable_storage_key ["evm"; "simulation_result"];
              `Durable_storage_key ["evm"; "simulation_status"];
            ]
          sc_rollup_client
          [
            Hex.to_string @@ `Hex "ff";
            Hex.to_string
            @@ `Hex
                 "ff0100e68094d77420f73b4612a7a99dba8c2afd30a1886b03448857040000000000008080844e70b1dc";
          ]
      in
      let expected_insights =
        [
          Some "0000000000000000000000000000000000000000000000000000000000000000";
          Some "01";
        ]
      in
      Check.(
        (simulation_result.insights = expected_insights) (list @@ option string))
        ~error_msg:"Expected result %R, but got %L" ;
      unit)

let test_eth_call_storage_contract_eth_cli =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "eth_call"; "simulate"]
    ~title:"Try to call a view through an ethereum client"
    (fun protocol ->
      (* setup *)
      let* ({evm_proxy_server; endpoint; sc_rollup_node; client; node; _} as
           evm_setup) =
        setup_past_genesis ~admin:None protocol
      in

      (* sanity *)
      let* call_result =
        Evm_proxy_server.(
          call_evm_rpc
            evm_proxy_server
            {
              method_ = "eth_call";
              parameters = `A [`O [("to", `Null)]; `String "latest"];
            })
      in
      (* Check the RPC returns a `result`. *)
      let _result = call_result |> Evm_proxy_server.extract_result in

      let sender = Eth_account.bootstrap_accounts.(0) in

      (* deploy contract send send 42 *)
      let* address, _tx = deploy ~contract:simple_storage ~sender evm_setup in
      let* tx = send_call_set_storage_simple address sender 42 evm_setup in
      let* () = check_tx_succeeded ~endpoint ~tx in

      (* make a call to proxy through eth-cli *)
      let call_num =
        Eth_cli.contract_call
          ~endpoint
          ~abi_label:simple_storage.label
          ~address
          ~method_call:"num()"
      in
      let* res =
        wait_for_application ~sc_rollup_node ~node ~client call_num ()
      in

      Check.((String.trim res = "42") string)
        ~error_msg:"Expected result %R, but got %L" ;
      unit)

let test_preinitialized_evm_kernel =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "administrator"; "config"]
    ~title:"Creates a kernel with an initialized administrator key"
  @@ fun protocol ->
  let dictator_key_path = Durable_storage_path.admin in
  let dictator_key = Eth_account.bootstrap_accounts.(0).address in
  let config =
    `Config
      Sc_rollup_helpers.Installer_kernel_config.
        [
          Set
            {
              value = Hex.(of_string dictator_key |> show);
              to_ = dictator_key_path;
            };
        ]
  in
  let* {sc_rollup_client; _} = setup_evm_kernel ~config ~admin:None protocol in
  let*! found_dictator_key_hex =
    Sc_rollup_client.inspect_durable_state_value
      sc_rollup_client
      ~pvm_kind:"wasm_2_0_0"
      ~operation:Sc_rollup_client.Value
      ~key:dictator_key_path
  in
  let found_dictator_key =
    Option.map
      (fun administrator -> Hex.to_string (`Hex administrator))
      found_dictator_key_hex
  in
  Check.((Some dictator_key = found_dictator_key) (option string))
    ~error_msg:
      (sf "Expected to read %%L as administrator key, but found %%R instead") ;
  unit

let deposit ~amount_mutez ~bridge ~depositor ~receiver ~sc_rollup_node
    ~sc_rollup_address ~node client =
  let* () =
    Client.transfer
      ~entrypoint:"deposit"
      ~arg:(sf "Pair %S %s" sc_rollup_address receiver)
      ~amount:amount_mutez
      ~giver:depositor.Account.public_key_hash
      ~receiver:bridge
      ~burn_cap:Tez.one
      client
  in
  let* () = Client.bake_for_and_wait client in

  let* _ = next_evm_level ~sc_rollup_node ~node ~client in
  unit

let check_balance ~receiver ~endpoint expected_balance =
  let* balance = Eth_cli.balance ~account:receiver ~endpoint in
  let balance = Wei.truncate_to_mutez balance in
  Check.((balance = Tez.to_mutez expected_balance) int)
    ~error_msg:(sf "Expected balance of %s should be %%R, but got %%L" receiver) ;
  unit

let test_deposit =
  Protocol.register_test ~__FILE__ ~tags:["evm"; "deposit"] ~title:"Deposit tez"
  @@ fun protocol ->
  let admin = Constant.bootstrap5 in
  let* {
         client;
         sc_rollup_address;
         l1_contracts;
         sc_rollup_node;
         node;
         endpoint;
         _;
       } =
    setup_evm_kernel ~admin:(Some admin) protocol
  in
  let {bridge; admin = _; exchanger = _} =
    match l1_contracts with
    | Some x -> x
    | None -> Test.fail ~__LOC__ "The test needs the L1 bridge"
  in

  let amount_mutez = Tez.of_mutez_int 100_000_000 in
  let receiver = "0x119811f34EF4491014Fbc3C969C426d37067D6A4" in

  let* () =
    deposit
      ~amount_mutez
      ~sc_rollup_address
      ~bridge
      ~depositor:admin
      ~receiver
      ~sc_rollup_node
      ~node
      client
  in
  check_balance ~receiver ~endpoint amount_mutez

let get_kernel_boot_wasm ~sc_rollup_client =
  let hooks : Process_hooks.t =
    let on_spawn _command _arguments = () in
    let on_log _output = Regression.capture "<boot.wasm>" in
    {on_spawn; on_log}
  in
  let*! kernel_boot_opt =
    Sc_rollup_client.inspect_durable_state_value
      sc_rollup_client
      ~hooks
      ~log_output:false
      ~pvm_kind:"wasm_2_0_0"
      ~operation:Sc_rollup_client.Value
      ~key:Durable_storage_path.kernel_boot_wasm
  in
  match kernel_boot_opt with
  | Some boot_wasm -> return boot_wasm
  | None -> failwith "Kernel `boot.wasm` should be accessible/readable."

let legacy_upgrade ~sc_rollup_node ~node ~client ~upgrade_nonce_bytes
    ~preimage_root_hash_bytes ~sc_rollup_address ~private_key =
  let* rollup_address_bytes =
    let address_opt =
      Tezos_crypto.Hashed.Smart_rollup_address.of_b58check_opt sc_rollup_address
    in
    match address_opt with
    | Some address ->
        return
        @@ Data_encoding.Binary.to_string_exn
             Tezos_crypto.Hashed.Smart_rollup_address.encoding
             address
    | None -> failwith "Unexpected smart rollup address."
  in
  let message_to_sign =
    rollup_address_bytes ^ upgrade_nonce_bytes ^ preimage_root_hash_bytes
  in
  let* secret_key =
    let sk = Hex.to_string (`Hex (Helpers.no_0x private_key)) in
    match
      Data_encoding.Binary.of_string_opt
        Tezos_crypto.Signature.Secp256k1.Secret_key.encoding
        sk
    with
    | Some sk -> return sk
    | None -> failwith "An invalid secret key was provided."
  in
  let signature =
    Data_encoding.Binary.to_string_exn Tezos_crypto.Signature.Secp256k1.encoding
    @@ Tezos_crypto.Signature.Secp256k1.sign_keccak256
         secret_key
         (String.to_bytes message_to_sign)
  in
  let upgrade_tag_bytes = "\003" in
  let full_external_message =
    Hex.show @@ Hex.of_string @@ rollup_address_bytes ^ upgrade_tag_bytes
    ^ upgrade_nonce_bytes ^ preimage_root_hash_bytes ^ signature
  in
  send_external_message_and_wait
    ~sc_rollup_node
    ~node
    ~client
    ~sender:Constant.bootstrap1.public_key_hash
    ~hex_msg:full_external_message

let gen_test_kernel_upgrade ?evm_setup ?rollup_address ?(should_fail = false)
    ?(nonce = 2) ~base_installee ~installee ?with_administrator
    ?expect_l1_failure ?legacy_dictator ?(admin = Constant.bootstrap1)
    ?(upgrador = admin) protocol =
  let* {
         node;
         client;
         sc_rollup_node;
         sc_rollup_client;
         sc_rollup_address;
         evm_proxy_server;
         l1_contracts;
         _;
       } =
    match evm_setup with
    | Some evm_setup -> return evm_setup
    | None ->
        setup_evm_kernel
          ?with_administrator
          ?legacy_dictator
          ~admin:(Some admin)
          protocol
  in
  let l1_contracts =
    match l1_contracts with
    | Some x -> x
    | None -> Test.fail "The test requires the l1 contracts."
  in
  let sc_rollup_address =
    Option.value ~default:sc_rollup_address rollup_address
  in
  let preimages_dir = Sc_rollup_node.data_dir sc_rollup_node // "wasm_2_0_0" in
  let* _, preimage_root_hash_opt =
    Sc_rollup_helpers.prepare_installer_kernel_gen
      ~preimages_dir
      ~base_installee
      ~display_root_hash:true
      installee
  in
  let preimage_root_hash_bytes =
    match preimage_root_hash_opt with
    | Some preimage_root_hash -> Hex.to_string @@ `Hex preimage_root_hash
    | None ->
        failwith
          "Couldn't obtain the root hash of the preimages of the chunked \
           kernel."
  in
  let upgrade_nonce_bytes = Helpers.u16_to_bytes nonce in
  let upgrade_payload =
    upgrade_nonce_bytes ^ preimage_root_hash_bytes |> Hex.of_string |> Hex.show
  in
  let* kernel_boot_wasm_before_upgrade =
    get_kernel_boot_wasm ~sc_rollup_client
  in
  let* expected_kernel_boot_wasm =
    if should_fail then return kernel_boot_wasm_before_upgrade
    else
      return @@ Hex.show @@ Hex.of_string
      @@ read_file (project_root // base_installee // (installee ^ ".wasm"))
  in
  let* () =
    match legacy_dictator with
    | Some dictator ->
        legacy_upgrade
          ~sc_rollup_node
          ~node
          ~client
          ~upgrade_nonce_bytes
          ~preimage_root_hash_bytes
          ~sc_rollup_address
          ~private_key:dictator.private_key
    | None ->
        let* () =
          Client.transfer
            ?expect_failure:expect_l1_failure
            ~amount:Tez.zero
            ~giver:upgrador.public_key_hash
            ~receiver:l1_contracts.admin
            ~arg:(sf {|Pair "%s" 0x%s|} sc_rollup_address upgrade_payload)
            ~burn_cap:Tez.one
            client
        in
        let* _ = next_evm_level ~sc_rollup_node ~node ~client in
        unit
  in
  let* kernel_boot_wasm_after_upgrade =
    get_kernel_boot_wasm ~sc_rollup_client
  in
  Check.((expected_kernel_boot_wasm = kernel_boot_wasm_after_upgrade) string)
    ~error_msg:(sf "Unexpected `boot.wasm`.") ;
  return
    ( sc_rollup_node,
      sc_rollup_client,
      node,
      client,
      evm_proxy_server,
      kernel_boot_wasm_before_upgrade )

let test_kernel_upgrade_to_debug =
  Protocol.register_test
    ~__FILE__
    ~tags:["debug"; "upgrade"]
    ~title:"Ensures EVM kernel's upgrade integrity to a debug kernel"
  @@ fun protocol ->
  let base_installee = "src/kernel_evm/kernel/tests/resources" in
  let installee = "debug_kernel" in
  let* _ = gen_test_kernel_upgrade ~base_installee ~installee protocol in
  unit

let test_kernel_upgrade_evm_to_evm =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "upgrade"]
    ~title:"Ensures EVM kernel's upgrade integrity to itself"
  @@ fun protocol ->
  let base_installee = "./" in
  let installee = "evm_kernel" in
  let* sc_rollup_node, _, node, client, evm_proxy_server, _ =
    gen_test_kernel_upgrade ~base_installee ~installee protocol
  in
  (* We ensure the upgrade went well by checking if the kernel still produces
     blocks. *)
  let endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  check_block_progression
    ~sc_rollup_node
    ~node
    ~client
    ~endpoint
    ~expected_block_level:2

let test_kernel_upgrade_wrong_key =
  Protocol.register_test
    ~__FILE__
    ~tags:["administrator"; "upgrade"]
    ~title:"Ensures EVM kernel's upgrade fails with a wrong administrator key"
  @@ fun protocol ->
  let base_installee = "src/kernel_evm/kernel/tests/resources" in
  let installee = "debug_kernel" in
  let* _ =
    gen_test_kernel_upgrade
      ~expect_l1_failure:true
      ~should_fail:true
      ~base_installee
      ~installee
      ~admin:Constant.bootstrap1
      ~upgrador:Constant.bootstrap2
      protocol
  in
  unit

let test_kernel_upgrade_wrong_nonce =
  Protocol.register_test
    ~__FILE__
    ~tags:["nonce"; "upgrade"]
    ~title:"Ensures EVM kernel's upgrade fails with a wrong upgrade nonce"
  @@ fun protocol ->
  let base_installee = "src/kernel_evm/kernel/tests/resources" in
  let installee = "debug_kernel" in
  let* _ =
    gen_test_kernel_upgrade
      ~nonce:3
      ~should_fail:true
      ~base_installee
      ~installee
      protocol
  in
  unit

let test_kernel_upgrade_wrong_rollup_address =
  Protocol.register_test
    ~__FILE__
    ~tags:["address"; "upgrade"]
    ~title:"Ensures EVM kernel's upgrade fails with a wrong rollup address"
  @@ fun protocol ->
  let base_installee = "src/kernel_evm/kernel/tests/resources" in
  let installee = "debug_kernel" in
  let* _ =
    gen_test_kernel_upgrade
      ~expect_l1_failure:true
      ~rollup_address:"sr1T13qeVewVm3tudQb8dwn8qRjptNo7KVkj"
      ~should_fail:true
      ~base_installee
      ~installee
      protocol
  in
  unit

let test_kernel_upgrade_no_dictator =
  Protocol.register_test
    ~__FILE__
    ~tags:["administrator"; "upgrade"]
    ~title:"Ensures EVM kernel's upgrade fails if there is no administrator"
  @@ fun protocol ->
  let base_installee = "src/kernel_evm/kernel/tests/resources" in
  let installee = "debug_kernel" in
  let* _ =
    gen_test_kernel_upgrade
      ~should_fail:true
      ~base_installee
      ~installee
      ~with_administrator:false
      protocol
  in
  unit

let test_kernel_upgrade_failing_migration =
  Protocol.register_test
    ~__FILE__
    ~tags:["migration"; "upgrade"]
    ~title:"Ensures EVM kernel's upgrade rollback when migration fails"
  @@ fun protocol ->
  let base_installee = "src/kernel_evm/kernel/tests/resources" in
  let installee = "failed_migration" in
  let* ( sc_rollup_node,
         sc_rollup_client,
         node,
         client,
         evm_proxy_server,
         original_kernel_boot_wasm ) =
    gen_test_kernel_upgrade ~base_installee ~installee protocol
  in
  (* Fallback mechanism is triggered, no block is produced at that level. *)
  let* _ = next_evm_level ~sc_rollup_node ~node ~client in
  (* We make sure that we can't read under the tmp file, after migration failed,
     everything is reverted. *)
  let*! tmp_dummy =
    Sc_rollup_client.inspect_durable_state_value
      sc_rollup_client
      ~pvm_kind:"wasm_2_0_0"
      ~operation:Sc_rollup_client.Value
      ~key:"/tmp/__dummy"
  in
  (match tmp_dummy with
  | Some _ -> failwith "Nothing should be readable under the temporary dir."
  | None -> ()) ;
  let* kernel_after_migration_failed = get_kernel_boot_wasm ~sc_rollup_client in
  (* The upgrade succeeded, but the fallback mechanism was activated, so the kernel
     after the upgrade/migration is still the previous one. *)
  Check.((original_kernel_boot_wasm = kernel_after_migration_failed) string)
    ~error_msg:(sf "Unexpected `boot.wasm` after migration failed.") ;
  (* We ensure that the fallback mechanism went well by checking if the
     kernel still produces blocks since it has booted back to the previous,
     original kernel. *)
  let endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  check_block_progression
    ~sc_rollup_node
    ~node
    ~client
    ~endpoint
    ~expected_block_level:2

let test_check_kernel_upgrade_nonce =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "upgrade"; "nonce"]
    ~title:"Ensures EVM kernel's upgrade nonce is bumped"
  @@ fun protocol ->
  let base_installee = "./" in
  let installee = "evm_kernel" in
  let* _, _, _, _, evm_proxy_server, _ =
    gen_test_kernel_upgrade ~base_installee ~installee protocol
  in
  let* upgrade_nonce_result =
    Evm_proxy_server.call_evm_rpc
      evm_proxy_server
      Evm_proxy_server.{method_ = "tez_upgradeNonce"; parameters = `Null}
  in
  let upgrade_nonce =
    upgrade_nonce_result |> Evm_proxy_server.extract_result |> JSON.as_int32
  in
  Check.((upgrade_nonce = Int32.of_int 2) int32)
    ~error_msg:(sf "Expected upgrade nonce should be %%R, but got %%L") ;
  unit

let send_raw_transaction_request raw_tx =
  Evm_proxy_server.
    {method_ = "eth_sendRawTransaction"; parameters = `A [`String raw_tx]}

let send_raw_transaction proxy_server raw_tx =
  let* response =
    Evm_proxy_server.call_evm_rpc
      proxy_server
      (send_raw_transaction_request raw_tx)
  in
  let hash =
    response |> Evm_proxy_server.extract_result |> JSON.as_string_opt
  in
  let error_message =
    response |> Evm_proxy_server.extract_error_message |> JSON.as_string_opt
  in
  match (hash, error_message) with
  | Some hash, _ -> return (Ok hash)
  | _, Some error_code -> return (Error error_code)
  | _ -> failwith "invalid response from eth_sendRawTransaction"

let test_rpc_sendRawTransaction =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "tx_hash"]
    ~title:
      "Ensure EVM proxy returns appropriate hash for any given transactions."
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  let txs = read_tx_from_file () |> List.filteri (fun i _ -> i < 5) in
  let* hashes =
    Lwt_list.map_p
      (fun (tx_raw, _) ->
        let* hash = send_raw_transaction evm_proxy_server tx_raw in
        let hash = Result.get_ok hash in
        return hash)
      txs
  in
  let expected_hashes =
    List.map (fun (_, expected_hash) -> expected_hash) txs
  in
  Check.((hashes = expected_hashes) (list string))
    ~error_msg:"Unexpected returned hash, should be %R, but got %L" ;
  unit

let by_block_arg_string by =
  match by with `Hash -> "Hash" | `Number -> "Number"

let get_transaction_by_block_arg_and_index_request ~by arg index =
  let by = by_block_arg_string by in
  Evm_proxy_server.
    {
      method_ = "eth_getTransactionByBlock" ^ by ^ "AndIndex";
      parameters = `A [`String arg; `String index];
    }

let get_transaction_by_block_arg_and_index ~by proxy_server block_hash index =
  let* transaction_object =
    Evm_proxy_server.call_evm_rpc
      proxy_server
      (get_transaction_by_block_arg_and_index_request ~by block_hash index)
  in
  return
    JSON.(
      transaction_object |-> "result" |> Transaction.transaction_object_of_json)

let test_rpc_getTransactionByBlockArgAndIndex ~by protocol =
  let* {evm_proxy_server; sc_rollup_node; node; client; _} =
    setup_past_genesis ~admin:None protocol
  in
  let txs = read_tx_from_file () |> List.filteri (fun i _ -> i < 3) in
  let* _, _, hashes =
    send_n_transactions
      ~sc_rollup_node
      ~node
      ~client
      ~evm_proxy_server
      (List.map fst txs)
  in
  Lwt_list.iter_s
    (fun transaction_hash ->
      let* receipt =
        wait_for_application
          ~sc_rollup_node
          ~node
          ~client
          (wait_for_transaction_receipt ~evm_proxy_server ~transaction_hash)
          ()
      in
      let block_arg, index =
        ( (match by with
          | `Hash -> receipt.blockHash
          | `Number -> Int32.to_string receipt.blockNumber),
          receipt.transactionIndex )
      in
      let* transaction_object =
        get_transaction_by_block_arg_and_index
          ~by
          evm_proxy_server
          block_arg
          (Int32.to_string index)
      in
      Check.(
        ((transaction_object.hash = transaction_hash) string)
          ~error_msg:"Incorrect transaction hash, should be %R, but got %L.") ;
      unit)
    hashes

let test_rpc_getTransactionByBlockHashAndIndex =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "get_transaction_by"; "block_hash_and_index"]
    ~title:"RPC method eth_getTransactionByBlockHashAndIndex"
  @@ test_rpc_getTransactionByBlockArgAndIndex ~by:`Hash

let test_rpc_getTransactionByBlockNumberAndIndex =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "get_transaction_by"; "block_number_and_index"]
    ~title:"RPC method eth_getTransactionByBlockNumberAndIndex"
  @@ test_rpc_getTransactionByBlockArgAndIndex ~by:`Number

type storage_migration_results = {
  transfer_result : transfer_result;
  block_result : Block.t;
  config_result : config_result;
}

(* This is the test generator that will trigger the sanity checks for migration
   tests.
   Note that:
   - it uses the latest version of the ghostnet EVM rollup as a starter kernel.
   - the upgrade of the kernel during the test will always target the latest one
     on master.
   - everytime a new path/rpc/object is stored in the kernel, a new sanity check
     MUST be generated. *)
let gen_kernel_migration_test ?(admin = Constant.bootstrap5) ~scenario_prior
    ~scenario_after protocol =
  let current_kernel_base_installee = "src/kernel_evm/kernel/tests/resources" in
  let current_kernel_installee = "ghostnet_evm_kernel" in
  let legacy_dictator = Eth_account.bootstrap_accounts.(0) in
  let* evm_setup =
    setup_past_genesis
      ~kernel_installee:
        {
          base_installee = current_kernel_base_installee;
          installee = current_kernel_installee;
        }
      ~admin:(Some admin)
      ~legacy_dictator
      ~ghostnet_storage_version:true
      protocol
  in
  (* Load the EVM rollup's storage and sanity check results. *)
  let* evm_proxy_server =
    Evm_proxy_server.init ~mode:`Production evm_setup.sc_rollup_node
  in
  let endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let* sanity_check =
    scenario_prior ~evm_setup:{evm_setup with evm_proxy_server; endpoint}
  in
  (* Upgrade the kernel. *)
  let next_kernel_base_installee = "./" in
  let next_kernel_installee = "evm_kernel" in
  let* _ =
    gen_test_kernel_upgrade
      ~evm_setup
      ~legacy_dictator
      ~base_installee:next_kernel_base_installee
      ~installee:next_kernel_installee
      protocol
  in
  let* _ =
    (* wait for the migration to be processed *)
    next_evm_level
      ~sc_rollup_node:evm_setup.sc_rollup_node
      ~node:evm_setup.node
      ~client:evm_setup.client
  in
  (* Check the values after the upgrade with [sanity_check] results. *)
  scenario_after ~evm_setup ~sanity_check

let test_kernel_migration =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "migration"; "upgrade"]
    ~title:"Ensures EVM kernel's upgrade succeed with potential migration(s)."
  @@ fun protocol ->
  let sender, receiver =
    (Eth_account.bootstrap_accounts.(0), Eth_account.bootstrap_accounts.(1))
  in
  let scenario_prior ~evm_setup =
    let* transfer_result =
      make_transfer
        ~value:Wei.(default_bootstrap_account_balance - one)
        ~sender
        ~receiver
        evm_setup
    in
    let* block_result = latest_block evm_setup in
    let* config_result = config_setup evm_setup in
    return {transfer_result; block_result; config_result}
  in
  let scenario_after ~evm_setup ~sanity_check =
    let* () =
      ensure_transfer_result_integrity
        ~sender
        ~receiver
        ~transfer_result:sanity_check.transfer_result
        evm_setup
    in
    let* () =
      ensure_block_integrity ~block_result:sanity_check.block_result evm_setup
    in
    ensure_config_setup_integrity
      ~config_result:sanity_check.config_result
      evm_setup
  in
  gen_kernel_migration_test ~scenario_prior ~scenario_after protocol

let test_deposit_dailynet =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "deposit"; "dailynet"]
    ~title:"deposit on dailynet"
  @@ fun protocol ->
  let bridge_address = "KT1QwBaLj5TRaGU3qkU4ZKKQ5mvNvyyzGBFv" in
  let exchanger_address = "KT1FHqsvc7vRS3u54L66DdMX4gb6QKqxJ1JW" in
  let rollup_address = "sr1RYurGZtN8KNSpkMcCt9CgWeUaNkzsAfXf" in

  let mockup_client = Client.create_with_mode Mockup in
  let make_bootstrap_contract ~address ~code ~storage ?typecheck () =
    let* code_json = Client.convert_script_to_json ~script:code mockup_client in
    let* storage_json =
      Client.convert_data_to_json ~data:storage ?typecheck mockup_client
    in
    let script : Ezjsonm.value =
      `O [("code", code_json); ("storage", storage_json)]
    in
    return
      Protocol.
        {delegate = None; amount = Tez.of_int 0; script; hash = Some address}
  in

  (* Creates the exchanger contract. *)
  let* exchanger_contract =
    make_bootstrap_contract
      ~address:exchanger_address
      ~code:(exchanger_path ())
      ~storage:"Unit"
      ()
  in
  (* Creates the bridge contract initialized with exchanger contract. *)
  let* bridge_contract =
    make_bootstrap_contract
      ~address:bridge_address
      ~code:(bridge_path ())
      ~storage:(sf "Pair %S None" exchanger_address)
      ()
  in

  (* Creates the EVM rollup that listens to the bootstrap smart contract exchanger. *)
  let* {
         bootstrap_smart_rollup = evm;
         smart_rollup_node_data_dir;
         smart_rollup_node_extra_args;
       } =
    setup_bootstrap_smart_rollup
      ~name:"evm"
      ~address:rollup_address
      ~parameters_ty:"pair (pair bytes (ticket unit)) (pair nat bytes)"
      ~base_installee:"./"
      ~installee:"evm_kernel"
      ~config:
        (`Path Base.(project_root // "src/kernel_evm/config/dailynet.yaml"))
      ()
  in

  (* Setup a chain where the EVM rollup, the exchanger contract and the bridge
     are all originated. *)
  let* node, client =
    setup_l1
      ~bootstrap_smart_rollups:[evm]
      ~bootstrap_contracts:[exchanger_contract; bridge_contract]
      protocol
  in

  let sc_rollup_node =
    Sc_rollup_node.create
      Operator
      node
      ~data_dir:smart_rollup_node_data_dir
      ~base_dir:(Client.base_dir client)
      ~default_operator:Constant.bootstrap1.public_key_hash
  in
  let* () = Client.bake_for_and_wait client in

  let* () =
    Sc_rollup_node.run
      sc_rollup_node
      rollup_address
      smart_rollup_node_extra_args
  in

  let* evm_proxy_server = Evm_proxy_server.init sc_rollup_node in
  let endpoint = Evm_proxy_server.endpoint evm_proxy_server in

  (* Deposit tokens to the EVM rollup. *)
  let amount_mutez = Tez.of_mutez_int 100_000_000 in
  let receiver = "0x119811f34EF4491014Fbc3C969C426d37067D6A4" in

  let* () =
    deposit
      ~amount_mutez
      ~bridge:bridge_address
      ~depositor:Constant.bootstrap2
      ~receiver
      ~sc_rollup_node
      ~sc_rollup_address:rollup_address
      ~node
      client
  in

  (* Check the balance in the EVM rollup. *)
  check_balance ~receiver ~endpoint amount_mutez

let test_rpc_sendRawTransaction_nonce_too_low =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "nonce"]
    ~title:"Returns an error if the nonce is too low"
  @@ fun protocol ->
  let* {evm_proxy_server; sc_rollup_node; node; client; _} =
    setup_past_genesis ~admin:None protocol
  in
  (* Nonce: 0 *)
  let raw_tx =
    "0xf86c80825208831e8480940000000000000000000000000000000000000000888ac7230489e8000080820a96a038294f867266c767aee6c3b54a0c444368fb8d5e90353219bce1da78de16aea4a018a7d3c58ddb1f6b33bad5dde106843acfbd6467e5df181d22270229dcfdf601"
  in
  let* result = send_raw_transaction evm_proxy_server raw_tx in
  let transaction_hash = Result.get_ok result in
  let* _ =
    wait_for_application
      ~sc_rollup_node
      ~node
      ~client
      (wait_for_transaction_receipt ~evm_proxy_server ~transaction_hash)
      ()
  in
  let* result = send_raw_transaction evm_proxy_server raw_tx in
  let error_message = Result.get_error result in
  Check.(
    ((error_message = "Nonce too low.") string)
      ~error_msg:"The transaction should fail") ;
  unit

let test_rpc_sendRawTransaction_nonce_too_high =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "nonce"]
    ~title:"Returns an error if the nonce is too high."
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  (* Nonce: 1 *)
  let raw_tx =
    "0xf86c01825208831e8480940000000000000000000000000000000000000000888ac7230489e8000080820a95a0a349864bedc9b84aea88cda197e96538c62c242286ead58eb7180a611f850237a01206525ff16ae5b708ee02b362f9b4d7565e0d7e9b4c536d7ef7dec81cda3ac7"
  in
  let* result = send_raw_transaction evm_proxy_server raw_tx in
  let error_message = Result.get_error result in
  Check.(
    ((error_message = "Nonce too high.") string)
      ~error_msg:"The transaction should fail") ;
  unit

let test_deposit_before_and_after_migration =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "migration"; "deposit"]
    ~title:"Deposit before and after migration"
  @@ fun protocol ->
  let admin = Constant.bootstrap5 in
  let receiver = "0x119811f34EF4491014Fbc3C969C426d37067D6A4" in
  let amount_mutez = Tez.of_mutez_int 50_000_000 in

  let scenario_prior
      ~evm_setup:
        {
          l1_contracts;
          sc_rollup_node;
          sc_rollup_address;
          node;
          client;
          endpoint;
          _;
        } =
    let {bridge; _} =
      match l1_contracts with Some x -> x | None -> assert false
    in
    let* () =
      deposit
        ~amount_mutez
        ~bridge
        ~depositor:admin
        ~receiver
        ~sc_rollup_node
        ~sc_rollup_address
        ~node
        client
    in
    check_balance ~receiver ~endpoint amount_mutez
  in
  let scenario_after
      ~evm_setup:
        {
          l1_contracts;
          sc_rollup_node;
          sc_rollup_address;
          node;
          client;
          endpoint;
          _;
        } ~sanity_check:_ =
    let {bridge; _} =
      match l1_contracts with Some x -> x | None -> assert false
    in
    let* () =
      deposit
        ~amount_mutez
        ~bridge
        ~depositor:admin
        ~receiver
        ~sc_rollup_node
        ~sc_rollup_address
        ~node
        client
    in
    check_balance ~receiver ~endpoint Tez.(amount_mutez + amount_mutez)
  in
  gen_kernel_migration_test ~admin ~scenario_prior ~scenario_after protocol

let test_block_storage_before_and_after_migration =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "migration"; "block"; "storage"]
    ~title:"Block storage before and after migration"
  @@ fun protocol ->
  let block_id = "1" in
  let scenario_prior ~evm_setup:{endpoint; _} =
    let* (block : Block.t) = Eth_cli.get_block ~block_id ~endpoint in
    return block
  in
  let scenario_after ~evm_setup:{endpoint; _} ~(sanity_check : Block.t) =
    let* (block : Block.t) = Eth_cli.get_block ~block_id ~endpoint in
    (* Compare fields stored before migration *)
    assert (block.number = sanity_check.number) ;
    assert (block.hash = sanity_check.hash) ;
    assert (block.timestamp = sanity_check.timestamp) ;
    assert (block.transactions = sanity_check.transactions) ;
    unit
  in
  gen_kernel_migration_test ~scenario_prior ~scenario_after protocol

let test_rpc_sendRawTransaction_invalid_chain_id =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "chain_id"]
    ~title:"Returns an error if the chainId is not correct."
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  (* Nonce: 0, chainId: 4242*)
  let raw_tx =
    "0xf86a8080831e8480940000000000000000000000000000000000000000888ac7230489e8000080822148a0e09f1fb4920f2e64a274b83d925890dd0b109fdf31f2811a781e918118daf34aa00f425e9a93bd92d710d3d323998b093a8c7d497d2af688c062a8099b076813e8"
  in
  let* result = send_raw_transaction evm_proxy_server raw_tx in
  let error_message = Result.get_error result in
  Check.(
    ((error_message = "Invalid chain id.") string)
      ~error_msg:"The transaction should fail") ;
  unit

let register_evm_migration ~protocols =
  test_kernel_migration protocols ;
  test_deposit_before_and_after_migration protocols ;
  test_block_storage_before_and_after_migration protocols

let test_reboot =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "reboot"; "loop"]
    ~title:"Check that the kernel can handle too many txs for a single run"
  @@ fun protocol ->
  let* {evm_proxy_server; sc_rollup_node; node; client; sc_rollup_client; _} =
    setup_past_genesis ~admin:None protocol
  in
  (* Retrieves all the messages and prepare them for the current rollup. *)
  let transfers =
    read_file (kernel_inputs_path ^ "/100-loops-transfers")
    |> String.trim |> String.split_on_char '\n'
  in
  let txs =
    read_file (kernel_inputs_path ^ "/100-loops")
    |> String.trim |> String.split_on_char '\n'
  in
  let* _, _transfer_receipt, _ =
    send_n_transactions
      ~sc_rollup_node
      ~node
      ~client
      ~evm_proxy_server
      transfers
  in
  let* requests, receipt, _hashes =
    send_n_transactions ~sc_rollup_node ~node ~client ~evm_proxy_server txs
  in
  let* block_with_many_txs =
    Evm_proxy_server.(
      call_evm_rpc
        evm_proxy_server
        {
          method_ = "eth_getBlockByNumber";
          parameters =
            `A
              [`String (Format.sprintf "%#lx" receipt.blockNumber); `Bool false];
        })
  in
  let block_with_many_txs =
    block_with_many_txs |> Evm_proxy_server.extract_result |> Block.of_json
  in
  (match block_with_many_txs.Block.transactions with
  | Block.Empty -> Test.fail "Expected a non empty block"
  | Block.Full _ ->
      Test.fail "Block is supposed to contain only transaction hashes"
  | Block.Hash hashes ->
      Check.((List.length hashes = List.length requests) int)
        ~error_msg:"Expected %R transactions in the latest block, got %L") ;
  let*! tick_number = Sc_rollup_client.ticks sc_rollup_client in
  let max_tick =
    Tezos_protocol_alpha.Protocol.Sc_rollup_wasm.V2_0_0.ticks_per_snapshot
  in
  Check.((tick_number > Z.to_int max_tick) int)
    ~error_msg:
      "Expected %L to be greater than %R, max nb of ticks per kernel run" ;
  (* a single run can't do more than 11_000_000_000 / 2000 gas units *)
  (* TODO: #6278
     RPC to fetch tick model / tick per gas *)
  Check.((block_with_many_txs.gasUsed > 5500000l) int32)
    ~error_msg:"Expected gas used %L to be superior to %R" ;
  unit

let block_transaction_count_by ~by arg =
  let method_ = "eth_getBlockTransactionCountBy" ^ by_block_arg_string by in
  Evm_proxy_server.{method_; parameters = `A [`String arg]}

let get_block_transaction_count_by proxy_server ~by arg =
  let* transaction_count =
    Evm_proxy_server.call_evm_rpc
      proxy_server
      (block_transaction_count_by ~by arg)
  in
  return JSON.(transaction_count |-> "result" |> as_int64)

let test_rpc_getBlockTransactionCountBy =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "get_block_transaction_count_by"]
    ~title:
      "RPC methods eth_getBlockTransactionCountByHash and \
       eth_getBlockTransactionCountByNumber"
  @@ fun protocol ->
  let* {evm_proxy_server; sc_rollup_node; node; client; _} =
    setup_past_genesis ~admin:None protocol
  in
  let evm_proxy_server_endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let txs = read_tx_from_file () |> List.filteri (fun i _ -> i < 5) in
  let* _, receipt, _ =
    send_n_transactions
      ~sc_rollup_node
      ~node
      ~client
      ~evm_proxy_server
      (List.map fst txs)
  in
  let* block =
    Eth_cli.get_block
      ~block_id:receipt.blockHash
      ~endpoint:evm_proxy_server_endpoint
  in
  let expected_count =
    match block.transactions with
    | Empty -> 0L
    | Hash l -> Int64.of_int @@ List.length l
    | Full l -> Int64.of_int @@ List.length l
  in
  let* transaction_count =
    get_block_transaction_count_by evm_proxy_server ~by:`Hash receipt.blockHash
  in
  Check.((transaction_count = expected_count) int64)
    ~error_msg:
      "Expected %R transactions with eth_getBlockTransactionCountByHash, but \
       got %L" ;
  let* transaction_count =
    get_block_transaction_count_by
      evm_proxy_server
      ~by:`Number
      (Int32.to_string receipt.blockNumber)
  in
  Check.((transaction_count = expected_count) int64)
    ~error_msg:
      "Expected %R transactions with eth_getBlockTransactionCountByNumber, but \
       got %L" ;
  unit

let uncle_count_by_block_arg_request ~by arg =
  let method_ = "eth_getUncleCountByBlock" ^ by_block_arg_string by in
  Evm_proxy_server.{method_; parameters = `A [`String arg]}

let get_uncle_count_by_block_arg proxy_server ~by arg =
  let* uncle_count =
    Evm_proxy_server.call_evm_rpc
      proxy_server
      (uncle_count_by_block_arg_request ~by arg)
  in
  return JSON.(uncle_count |-> "result" |> as_int64)

let test_rpc_getUncleCountByBlock =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "get_uncle_count_by_block"]
    ~title:
      "RPC methods eth_getUncleCountByBlockHash and \
       eth_getUncleCountByBlockNumber"
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  let evm_proxy_server_endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let* block =
    Eth_cli.get_block ~block_id:"0" ~endpoint:evm_proxy_server_endpoint
  in
  let* uncle_count =
    get_uncle_count_by_block_arg
      evm_proxy_server
      ~by:`Hash
      (Option.get block.hash)
  in
  Check.((uncle_count = Int64.zero) int64)
    ~error_msg:
      "Expected %R uncles with eth_getUncleCountByBlockHash, but got %L" ;
  let* uncle_count =
    get_uncle_count_by_block_arg
      evm_proxy_server
      ~by:`Number
      (Int32.to_string block.number)
  in
  Check.((uncle_count = Int64.zero) int64)
    ~error_msg:
      "Expected %R uncles with eth_getUncleCountByBlockNumber, but got %L" ;
  unit

let uncle_by_block_arg_and_index_request ~by arg index =
  let by = by_block_arg_string by in
  Evm_proxy_server.
    {
      method_ = "eth_getUncleByBlock" ^ by ^ "AndIndex";
      parameters = `A [`String arg; `String index];
    }

let get_uncle_by_block_arg_and_index ~by proxy_server arg index =
  let* block =
    Evm_proxy_server.call_evm_rpc
      proxy_server
      (uncle_by_block_arg_and_index_request ~by arg index)
  in
  let result = JSON.(block |-> "result") in
  if JSON.is_null result then return None
  else return @@ Some (result |> Block.of_json)

let test_rpc_getUncleByBlockArgAndIndex =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "get_uncle_by_block_arg_and_index"]
    ~title:
      "RPC methods eth_getUncleByBlockHashAndIndex and \
       eth_getUncleByBlockNumberAndIndex"
  @@ fun protocol ->
  let* {evm_proxy_server; _} = setup_past_genesis ~admin:None protocol in
  let evm_proxy_server_endpoint = Evm_proxy_server.endpoint evm_proxy_server in
  let block_id = "0" in
  let* block =
    Eth_cli.get_block ~block_id ~endpoint:evm_proxy_server_endpoint
  in
  let* uncle =
    get_uncle_by_block_arg_and_index
      ~by:`Hash
      evm_proxy_server
      (Option.get block.hash)
      block_id
  in
  assert (Option.is_none uncle) ;
  let* uncle =
    get_uncle_by_block_arg_and_index
      ~by:`Number
      evm_proxy_server
      (Int32.to_string block.number)
      block_id
  in
  assert (Option.is_none uncle) ;
  unit

let test_simulation_eip2200 =
  Protocol.register_test
    ~__FILE__
    ~tags:["evm"; "loop"; "simulation"; "eip2200"]
    ~title:"Simulation is EIP2200 resilient"
  @@ fun protocol ->
  let* ({sc_rollup_node; node; client; endpoint; _} as full_evm_setup) =
    setup_past_genesis ~admin:None protocol
  in
  let sender = Eth_account.bootstrap_accounts.(0) in
  let* loop_address, _tx = deploy ~contract:loop ~sender full_evm_setup in
  (* If we support EIP-2200, the simulation gives an amount of gas
     insufficient for the execution. As we do the simulation with an
     enormous gas limit, we never trigger EIP-2200. *)
  let call =
    Eth_cli.contract_send
      ~source_private_key:sender.private_key
      ~endpoint
      ~abi_label:loop.label
      ~address:loop_address
      ~method_call:"loop(5)"
  in
  let* _tx = wait_for_application ~sc_rollup_node ~node ~client call () in
  unit

let register_evm_proxy_server ~protocols =
  test_originate_evm_kernel protocols ;
  test_evm_proxy_server_connection protocols ;
  test_consistent_block_hashes protocols ;
  test_rpc_getBalance protocols ;
  test_rpc_getBlockByNumber protocols ;
  test_rpc_getBlockByHash protocols ;
  test_rpc_getTransactionCount protocols ;
  test_rpc_getTransactionCountBatch protocols ;
  test_rpc_batch protocols ;
  test_l2_block_size_non_zero protocols ;
  test_l2_blocks_progression protocols ;
  test_l2_transfer protocols ;
  test_chunked_transaction protocols ;
  test_rpc_txpool_content protocols ;
  test_rpc_web3_clientVersion protocols ;
  test_rpc_web3_sha3 protocols ;
  test_simulate protocols ;
  test_full_blocks protocols ;
  test_latest_block protocols ;
  test_eth_call_nullable_recipient protocols ;
  test_l2_deploy_simple_storage protocols ;
  test_l2_call_simple_storage protocols ;
  test_l2_deploy_erc20 protocols ;
  test_inject_100_transactions protocols ;
  test_eth_call_storage_contract_rollup_node protocols ;
  test_eth_call_storage_contract_proxy protocols ;
  test_eth_call_storage_contract_eth_cli protocols ;
  test_eth_call_large protocols ;
  test_preinitialized_evm_kernel protocols ;
  test_deposit protocols ;
  test_estimate_gas protocols ;
  test_estimate_gas_additionnal_field protocols ;
  test_kernel_upgrade_to_debug protocols ;
  test_kernel_upgrade_evm_to_evm protocols ;
  test_kernel_upgrade_wrong_key protocols ;
  test_kernel_upgrade_wrong_nonce protocols ;
  test_kernel_upgrade_wrong_rollup_address protocols ;
  test_kernel_upgrade_no_dictator protocols ;
  test_kernel_upgrade_failing_migration protocols ;
  test_check_kernel_upgrade_nonce protocols ;
  test_rpc_sendRawTransaction protocols ;
  test_deposit_dailynet protocols ;
  test_rpc_sendRawTransaction_nonce_too_low protocols ;
  test_rpc_sendRawTransaction_nonce_too_high protocols ;
  test_rpc_sendRawTransaction_invalid_chain_id protocols ;
  test_rpc_getTransactionByBlockHashAndIndex protocols ;
  test_rpc_getTransactionByBlockNumberAndIndex protocols ;
  test_reboot protocols ;
  test_rpc_getBlockTransactionCountBy protocols ;
  test_rpc_getUncleCountByBlock protocols ;
  test_rpc_getUncleByBlockArgAndIndex protocols ;
  test_simulation_eip2200 protocols

let register ~protocols =
  register_evm_proxy_server ~protocols ;
  register_evm_migration ~protocols
back to top