Skip to content

Commit 0398a95

Browse files
authored
(Feat Anvil): add geth like debug_traceCall api (#3990)
* add geth like debug_traceCall api * add debug trace call sanity check
1 parent 745b35e commit 0398a95

File tree

4 files changed

+131
-1
lines changed

4 files changed

+131
-1
lines changed

anvil/core/src/eth/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,14 @@ pub enum EthRequest {
263263
#[cfg_attr(feature = "serde", serde(default))] GethDebugTracingOptions,
264264
),
265265

266+
/// geth's `debug_traceCall` endpoint
267+
#[cfg_attr(feature = "serde", serde(rename = "debug_traceCall"))]
268+
DebugTraceCall(
269+
EthTransactionRequest,
270+
#[cfg_attr(feature = "serde", serde(default))] Option<BlockId>,
271+
#[cfg_attr(feature = "serde", serde(default))] GethDebugTracingOptions,
272+
),
273+
266274
/// Trace transaction endpoint for parity's `trace_transaction`
267275
#[cfg_attr(feature = "serde", serde(rename = "trace_transaction", with = "sequence"))]
268276
TraceTransaction(H256),
@@ -1100,6 +1108,29 @@ mod tests {
11001108
let _req = serde_json::from_value::<EthRequest>(value).unwrap();
11011109
}
11021110

1111+
#[test]
1112+
fn test_serde_debug_trace_call() {
1113+
let s = r#"{"method": "debug_traceCall", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}]}"#;
1114+
let value: serde_json::Value = serde_json::from_str(s).unwrap();
1115+
let _req = serde_json::from_value::<EthRequest>(value).unwrap();
1116+
1117+
let s = r#"{"method": "debug_traceCall", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "latest" }]}"#;
1118+
let value: serde_json::Value = serde_json::from_str(s).unwrap();
1119+
let _req = serde_json::from_value::<EthRequest>(value).unwrap();
1120+
1121+
let s = r#"{"method": "debug_traceCall", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "0x0" }]}"#;
1122+
let value: serde_json::Value = serde_json::from_str(s).unwrap();
1123+
let _req = serde_json::from_value::<EthRequest>(value).unwrap();
1124+
1125+
let s = r#"{"method": "debug_traceCall", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" }]}"#;
1126+
let value: serde_json::Value = serde_json::from_str(s).unwrap();
1127+
let _req = serde_json::from_value::<EthRequest>(value).unwrap();
1128+
1129+
let s = r#"{"method": "debug_traceCall", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "0x0" }, {"disableStorage": true}]}"#;
1130+
let value: serde_json::Value = serde_json::from_str(s).unwrap();
1131+
let _req = serde_json::from_value::<EthRequest>(value).unwrap();
1132+
}
1133+
11031134
#[test]
11041135
fn test_serde_eth_storage() {
11051136
let s = r#"{"method": "eth_getStorageAt", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "latest"]}"#;

anvil/src/eth/api.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ impl EthApi {
250250
EthRequest::DebugTraceTransaction(tx, opts) => {
251251
self.debug_trace_transaction(tx, opts).await.to_rpc_result()
252252
}
253+
// non eth-standard rpc calls
254+
EthRequest::DebugTraceCall(tx, block, opts) => {
255+
self.debug_trace_call(tx, block, opts).await.to_rpc_result()
256+
}
253257
EthRequest::TraceTransaction(tx) => self.trace_transaction(tx).await.to_rpc_result(),
254258
EthRequest::TraceBlock(block) => self.trace_block(block).await.to_rpc_result(),
255259
EthRequest::ImpersonateAccount(addr) => {
@@ -1266,6 +1270,30 @@ impl EthApi {
12661270
self.backend.debug_trace_transaction(tx_hash, opts).await
12671271
}
12681272

1273+
/// Returns traces for the transaction for geth's tracing endpoint
1274+
///
1275+
/// Handler for RPC call: `debug_traceCall`
1276+
pub async fn debug_trace_call(
1277+
&self,
1278+
request: EthTransactionRequest,
1279+
block_number: Option<BlockId>,
1280+
opts: GethDebugTracingOptions,
1281+
) -> Result<GethTrace> {
1282+
node_info!("debug_traceCall");
1283+
if opts.tracer.is_some() {
1284+
return Err(RpcError::invalid_params("non-default tracer not supported yet").into())
1285+
}
1286+
let block_request = self.block_request(block_number).await?;
1287+
let fees = FeeDetails::new(
1288+
request.gas_price,
1289+
request.max_fee_per_gas,
1290+
request.max_priority_fee_per_gas,
1291+
)?
1292+
.or_zero_fees();
1293+
1294+
self.backend.call_with_tracing(request, fees, Some(block_request), opts).await
1295+
}
1296+
12691297
/// Returns traces for the transaction hash via parity's tracing endpoint
12701298
///
12711299
/// Handler for RPC call: `trace_transaction`

anvil/src/eth/backend/mem/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,28 @@ impl Backend {
895895
Ok((exit_reason, out, gas_used, state))
896896
}
897897

898+
pub async fn call_with_tracing(
899+
&self,
900+
request: EthTransactionRequest,
901+
fee_details: FeeDetails,
902+
block_request: Option<BlockRequest>,
903+
opts: GethDebugTracingOptions,
904+
) -> Result<GethTrace, BlockchainError> {
905+
self.with_database_at(block_request, |state, block| {
906+
let mut inspector = Inspector::default().with_steps_tracing();
907+
let block_number = block.number;
908+
let mut evm = revm::EVM::new();
909+
evm.env = self.build_call_env(request, fee_details, block);
910+
evm.database(state);
911+
let (ExecutionResult { exit_reason, out, gas_used, .. }, _) =
912+
evm.inspect_ref(&mut inspector);
913+
let res = inspector.tracer.unwrap_or_default().traces.geth_trace(gas_used.into(), opts);
914+
trace!(target: "backend", "trace call return {:?} out: {:?} gas {} on block {}", exit_reason, out, gas_used, block_number);
915+
Ok(res)
916+
})
917+
.await?
918+
}
919+
898920
pub fn build_access_list_with_state<D>(
899921
&self,
900922
state: D,

anvil/tests/it/traces.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use anvil::{spawn, NodeConfig};
33
use ethers::{
44
contract::Contract,
55
prelude::{Action, ContractFactory, Middleware, Signer, SignerMiddleware, TransactionRequest},
6-
types::{ActionType, Address, Trace},
6+
types::{ActionType, Address, GethDebugTracingCallOptions, Trace},
77
utils::hex,
88
};
99
use ethers_solc::{project_util::TempProject, Artifact};
@@ -90,6 +90,55 @@ contract Contract {
9090
assert_eq!(traces[0].action_type, ActionType::Suicide);
9191
}
9292

93+
#[tokio::test(flavor = "multi_thread")]
94+
async fn test_transfer_debug_trace_call() {
95+
let prj = TempProject::dapptools().unwrap();
96+
prj.add_source(
97+
"Contract",
98+
r#"
99+
pragma solidity 0.8.13;
100+
contract Contract {
101+
address payable private owner;
102+
constructor() public {
103+
owner = payable(msg.sender);
104+
}
105+
function goodbye() public {
106+
selfdestruct(owner);
107+
}
108+
}
109+
"#,
110+
)
111+
.unwrap();
112+
113+
let mut compiled = prj.compile().unwrap();
114+
assert!(!compiled.has_compiler_errors());
115+
let contract = compiled.remove_first("Contract").unwrap();
116+
let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts();
117+
118+
let (_api, handle) = spawn(NodeConfig::test()).await;
119+
let provider = handle.ws_provider().await;
120+
let wallets = handle.dev_wallets().collect::<Vec<_>>();
121+
let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone()));
122+
123+
// deploy successfully
124+
let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client);
125+
let contract = factory.deploy(()).unwrap().send().await.unwrap();
126+
127+
let contract = Contract::new(
128+
contract.address(),
129+
abi.unwrap(),
130+
SignerMiddleware::new(handle.http_provider(), wallets[1].clone()),
131+
);
132+
let call = contract.method::<_, ()>("goodbye", ()).unwrap();
133+
134+
let traces = handle
135+
.http_provider()
136+
.debug_trace_call(call.tx, None, GethDebugTracingCallOptions::default())
137+
.await
138+
.unwrap();
139+
assert!(!traces.failed);
140+
}
141+
93142
// <https://github.com/foundry-rs/foundry/issues/2656>
94143
#[tokio::test(flavor = "multi_thread")]
95144
async fn test_trace_address_fork() {

0 commit comments

Comments
 (0)