From 3cb33d8e39783e8bca0d9904d8a0eae8c03aee17 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Wed, 23 Jul 2025 10:22:33 +0200 Subject: [PATCH 1/2] Etherlink/Tezt: nested delegatecalls contracts --- .../nested_delegatecalls.sol | 40 +++++++++++++++++++ etherlink/tezt/lib/solidity_contracts.ml | 24 +++++++++++ 2 files changed, 64 insertions(+) create mode 100644 etherlink/kernel_latest/solidity_examples/nested_delegatecalls.sol diff --git a/etherlink/kernel_latest/solidity_examples/nested_delegatecalls.sol b/etherlink/kernel_latest/solidity_examples/nested_delegatecalls.sol new file mode 100644 index 000000000000..e1057c9aff8f --- /dev/null +++ b/etherlink/kernel_latest/solidity_examples/nested_delegatecalls.sol @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2025 Functori +// +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract D { + event Context(address msgSender, address thisAddress); + + function logContext() public { + emit Context(msg.sender, address(this)); + } +} + +contract C { + function callD(address dAddr) public { + (bool success, ) = dAddr.call( + abi.encodeWithSignature("logContext()") + ); + require(success, "call to D failed"); + } +} + +contract B { + function callC(address cAddr, address dAddr) public { + (bool success, ) = cAddr.call( + abi.encodeWithSignature("callD(address)", dAddr) + ); + require(success, "call to C failed"); + } +} + +contract A { + function callB(address bAddr, address cAddr, address dAddr) public { + (bool success, ) = bAddr.delegatecall( + abi.encodeWithSignature("callC(address,address)", cAddr, dAddr) + ); + require(success, "delegatecall to B failed"); + } +} + diff --git a/etherlink/tezt/lib/solidity_contracts.ml b/etherlink/tezt/lib/solidity_contracts.ml index c34cbeaeb570..49e379217819 100644 --- a/etherlink/tezt/lib/solidity_contracts.ml +++ b/etherlink/tezt/lib/solidity_contracts.ml @@ -438,6 +438,30 @@ let eip2930_storage_access = ~label:"storageaccess" ~contract:"StorageAccess" +let nested_delegatecalls_A = + compile_contract + ~source:(solidity_contracts_path ^ "/nested_delegatecalls.sol") + ~label:"nested_delegatecalls_A" + ~contract:"A" + +let nested_delegatecalls_B = + compile_contract + ~source:(solidity_contracts_path ^ "/nested_delegatecalls.sol") + ~label:"nested_delegatecalls_B" + ~contract:"B" + +let nested_delegatecalls_C = + compile_contract + ~source:(solidity_contracts_path ^ "/nested_delegatecalls.sol") + ~label:"nested_delegatecalls_C" + ~contract:"C" + +let nested_delegatecalls_D = + compile_contract + ~source:(solidity_contracts_path ^ "/nested_delegatecalls.sol") + ~label:"nested_delegatecalls_D" + ~contract:"D" + module Precompile = struct let withdrawal = "0xff00000000000000000000000000000000000001" -- GitLab From 20369726d836200d911d1a6f032caa6ec2109020 Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Wed, 23 Jul 2025 10:22:45 +0200 Subject: [PATCH 2/2] Etherlink/Tezt: calltracer can trace nested delegatecalls with appropriate sender addresses for each call --- etherlink/tezt/tests/evm_sequencer.ml | 91 +++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index dc6366c16d84..9e3c74d29fc3 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -8788,6 +8788,96 @@ let test_trace_transaction_calltracer_deposit = in unit +let test_trace_transaction_calltracer_on_nested_delegatecalls = + register_all + ~__FILE__ + ~kernels:[Latest] + ~tags:["evm"; "rpc"; "trace"; "call_trace"; "nested_delegatecalls"] + ~title: + "debug_traceTransaction with calltracer can trace nested delegatecalls \ + with appropriate sender addresses for each call" + ~da_fee:Wei.zero + ~maximum_allowed_ticks:100_000_000_000_000L + ~time_between_blocks:Nothing + ~use_revm:activate_revm_registration + @@ fun {sequencer; evm_version; _} _protocol -> + let endpoint = Evm_node.endpoint sequencer in + let source_private_key, address_eoa = + Eth_account. + ( bootstrap_accounts.(0).private_key, + String.lowercase_ascii bootstrap_accounts.(0).address ) + in + let init_contract ~contract = + let open Solidity_contracts in + let* contract = contract evm_version in + let* () = Eth_cli.add_abi ~label:contract.label ~abi:contract.abi () in + let* address, _ = + send_transaction_to_sequencer + (Eth_cli.deploy + ~source_private_key + ~endpoint + ~abi:contract.label + ~bin:contract.bin) + sequencer + in + return (String.lowercase_ascii address, contract.label) + in + let* address_A, label_A = + init_contract ~contract:Solidity_contracts.nested_delegatecalls_A + in + let* address_B, _ = + init_contract ~contract:Solidity_contracts.nested_delegatecalls_B + in + let* address_C, _ = + init_contract ~contract:Solidity_contracts.nested_delegatecalls_C + in + let* address_D, _ = + init_contract ~contract:Solidity_contracts.nested_delegatecalls_D + in + let* _ = produce_block sequencer in + let* transaction_hash = + send_transaction_to_sequencer + (Eth_cli.contract_send + ~source_private_key + ~endpoint + ~abi_label:label_A + ~address:address_A + ~method_call: + (Format.sprintf + "callB(\"%s\",\"%s\",\"%s\")" + address_B + address_C + address_D)) + sequencer + in + let* _ = produce_block sequencer in + let*@ trace_result = + Rpc.trace_transaction + ~tracer:"callTracer" + ~transaction_hash + ~tracer_config:[("withLog", `Bool true); ("onlyTopCall", `Bool false)] + sequencer + in + assert (JSON.(trace_result |-> "type" |> as_string) = "CALL") ; + assert (JSON.(trace_result |-> "from" |> as_string) = address_eoa) ; + assert (JSON.(trace_result |-> "to" |> as_string) = address_A) ; + let call_list = JSON.(trace_result |-> "calls" |> as_list) in + let delegatecall_1 = List.hd call_list in + assert (JSON.(delegatecall_1 |-> "type" |> as_string) = "DELEGATECALL") ; + assert (JSON.(delegatecall_1 |-> "from" |> as_string) = address_A) ; + assert (JSON.(delegatecall_1 |-> "to" |> as_string) = address_B) ; + let delegatecall_1_call_list = JSON.(delegatecall_1 |-> "calls" |> as_list) in + let delegatecall_2 = List.hd delegatecall_1_call_list in + assert (JSON.(delegatecall_2 |-> "type" |> as_string) = "CALL") ; + assert (JSON.(delegatecall_2 |-> "from" |> as_string) = address_A) ; + assert (JSON.(delegatecall_2 |-> "to" |> as_string) = address_C) ; + let delegatecall_2_call_list = JSON.(delegatecall_2 |-> "calls" |> as_list) in + let delegatecall_3 = List.hd delegatecall_2_call_list in + assert (JSON.(delegatecall_3 |-> "type" |> as_string) = "CALL") ; + assert (JSON.(delegatecall_3 |-> "from" |> as_string) = address_C) ; + assert (JSON.(delegatecall_3 |-> "to" |> as_string) = address_D) ; + unit + let test_miner = let sequencer_pool_address = String.lowercase_ascii "0x8aaD6553Cf769Aa7b89174bE824ED0e53768ed70" @@ -13527,6 +13617,7 @@ let () = test_trace_transaction_calltracer_on_simple_transfer protocols ; test_trace_transaction_calltracer_precompiles protocols ; test_trace_transaction_calltracer_deposit protocols ; + test_trace_transaction_calltracer_on_nested_delegatecalls [Alpha] ; test_debug_print_store_schemas () ; test_man () ; test_describe_config () ; -- GitLab