Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ scopeguard = "1.2.0"
serde = { version = "1.0.203", features = ["derive"] }
serde-ext = { path = "crates/serde-ext" }
serde_json = { version = "1.0.117", features = ["raw_value", "unbounded_depth"] }
serde_stacker = "0.1.14"
serde_with = "3.8.1"
sha2 = "0.10"
shared = { path = "crates/shared" }
Expand Down
1 change: 1 addition & 0 deletions crates/autopilot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ rust_decimal = { workspace = true }
s3 = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_stacker = { workspace = true }
serde_with = { workspace = true }
shared = { workspace = true }
simulator = { workspace = true }
Expand Down
11 changes: 9 additions & 2 deletions crates/autopilot/src/domain/blockchain.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use eth_domain_types as eth;
use {eth_domain_types as eth, serde::Deserialize};

/// Originated from the blockchain transaction input data.
pub type Calldata = alloy::primitives::Bytes;

// Note: normally we would first deserialize into a DTO and then
// convert that DTO into the domain type to decouple the wire
// format from the domain representation. However, in this case
// we are dealing with an object that's potentially very deeply
// nested. To not run into stack overflows we therefore directly
// deserialize into the domain type.
/// Call frames of a transaction.
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, Deserialize)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that CallFrame derives Deserialize and is deserialized directly, the From<alloy::rpc::types::trace::geth::CallFrame> impl below (lines 26–35) becomes dead code — its only caller was the GethTrace::CallTracer(call_frame) => call_frame.into() arm that this PR removes. Nothing else in the workspace uses it (the settlement tests build CallFrame { .. } struct literals directly). Since it's a trait impl on public types the compiler won't flag it, so it'll silently rot. Suggest removing it.

Fix this →

pub struct CallFrame {
/// The address of the call initiator.
pub from: eth::Address,
Expand All @@ -13,6 +19,7 @@ pub struct CallFrame {
/// Calldata input.
pub input: Calldata,
/// Recorded child calls.
#[serde(default)]
pub calls: Vec<CallFrame>,
}

Expand Down
27 changes: 14 additions & 13 deletions crates/autopilot/src/infra/blockchain/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use {
self::contracts::Contracts,
crate::{boundary, domain},
crate::{
boundary,
domain::{self, blockchain::CallFrame},
},
alloy::{
providers::Provider,
rpc::types::{
TransactionReceipt,
trace::geth::{GethDebugBuiltInTracerType, GethDebugTracingOptions, GethTrace},
trace::geth::{GethDebugBuiltInTracerType, GethDebugTracingOptions},
},
},
anyhow::bail,
chain::Chain,
eth_domain_types as eth,
ethrpc::{Web3, block_stream::CurrentBlockWatcher},
Expand Down Expand Up @@ -115,7 +117,7 @@ impl Ethereum {
&self,
hash: eth::TxId,
) -> Result<domain::blockchain::Transaction, Error> {
let (receipt, traces): (Option<TransactionReceipt>, GethTrace) = tokio::try_join!(
let (receipt, traces): (Option<TransactionReceipt>, CallFrame) = tokio::try_join!(
self.web3
.provider
.get_transaction_receipt(hash.0)
Expand Down Expand Up @@ -148,7 +150,7 @@ impl Ethereum {
/// default recursion limit. This is needed to support transactions with
/// exceptionally deep call stacks. (e.g.
/// <https://dashboard.tenderly.co/tx/0x5200aab12fbe4e0aef019748bf0f79266155fbbea00557bf1071fa2859e7eb9b>)
async fn fetch_debug_trace(provider: &impl Provider, hash: eth::TxId) -> Result<GethTrace, Error> {
async fn fetch_debug_trace(provider: &impl Provider, hash: eth::TxId) -> Result<CallFrame, Error> {
let params = serde_json::value::to_raw_value(&(
hash.0,
GethDebugTracingOptions::new_tracer(GethDebugBuiltInTracerType::CallTracer),
Expand All @@ -161,19 +163,18 @@ async fn fetch_debug_trace(provider: &impl Provider, hash: eth::TxId) -> Result<

let mut de = serde_json::Deserializer::from_str(raw.get());
de.disable_recursion_limit();
GethTrace::deserialize(&mut de).map_err(Error::DeserializationFailed)
// use [`serde_stacker`] to dynamically grow the stack in a vector to avoid
// blowing the stack size limit when recursing through the object
let de = serde_stacker::Deserializer::new(&mut de);

CallFrame::deserialize(de).map_err(Error::DeserializationFailed)
Comment thread
MartinquaXD marked this conversation as resolved.
}

fn into_domain(
receipt: TransactionReceipt,
trace: GethTrace,
trace: CallFrame,
timestamp: u64,
) -> anyhow::Result<domain::blockchain::Transaction> {
let trace_calls = match trace {
GethTrace::CallTracer(call_frame) => call_frame.into(),
trace => bail!("unsupported trace call {trace:?}"),
};

Ok(domain::blockchain::Transaction {
hash: receipt.transaction_hash.into(),
from: receipt.from,
Expand All @@ -184,7 +185,7 @@ fn into_domain(
gas: receipt.gas_used.into(),
gas_price: receipt.effective_gas_price.into(),
timestamp: u32::try_from(timestamp)?,
trace_calls,
trace_calls: trace,
})
}

Expand Down
Loading