Skip to content

Commit b53e6a9

Browse files
authored
refactor: signatures identifier, trace decoding (#10282)
* refactor: signatures identifier * nit * cleanup * dedup fallback decoding * fix * chore: clippy * feat: ignore non ABI calldata * feat: skip decoding create traces * fixes * fixes * chore: use CallTraceNode directly * chore: etherscan code dedup * chore: more filtering
1 parent 7825a06 commit b53e6a9

File tree

23 files changed

+651
-514
lines changed

23 files changed

+651
-514
lines changed

crates/cast/src/args.rs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,10 @@ use foundry_common::{
2020
selectors::{
2121
decode_calldata, decode_event_topic, decode_function_selector, decode_selectors,
2222
import_selectors, parse_signatures, pretty_calldata, ParsedSignatures, SelectorImportData,
23-
SelectorType,
23+
SelectorKind,
2424
},
2525
shell, stdin,
2626
};
27-
use foundry_config::Config;
2827
use std::time::Instant;
2928

3029
/// Run the `cast` command-line interface.
@@ -209,11 +208,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
209208
let data = data.strip_prefix("0x").unwrap_or(data.as_str());
210209
let selector = data.get(..64).unwrap_or_default();
211210
let identified_event =
212-
SignaturesIdentifier::new(Config::foundry_cache_dir(), false)?
213-
.write()
214-
.await
215-
.identify_event(&hex::decode(selector)?)
216-
.await;
211+
SignaturesIdentifier::new(false)?.identify_event(selector.parse()?).await;
217212
if let Some(event) = identified_event {
218213
let _ = sh_println!("{}", event.signature());
219214
let data = data.get(64..).unwrap_or_default();
@@ -235,11 +230,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
235230
let data = data.strip_prefix("0x").unwrap_or(data.as_str());
236231
let selector = data.get(..8).unwrap_or_default();
237232
let identified_error =
238-
SignaturesIdentifier::new(Config::foundry_cache_dir(), false)?
239-
.write()
240-
.await
241-
.identify_error(&hex::decode(selector)?)
242-
.await;
233+
SignaturesIdentifier::new(false)?.identify_error(selector.parse()?).await;
243234
if let Some(error) = identified_error {
244235
let _ = sh_println!("{}", error.signature());
245236
error
@@ -385,9 +376,12 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
385376
let max_mutability_len = functions.iter().map(|r| r.2.len()).max().unwrap_or(0);
386377

387378
let resolve_results = if resolve {
388-
let selectors_it = functions.iter().map(|r| &r.0);
389-
let ds = decode_selectors(SelectorType::Function, selectors_it).await?;
390-
ds.into_iter().map(|v| v.unwrap_or_default().join("|")).collect()
379+
let selectors = functions
380+
.iter()
381+
.map(|&(selector, ..)| SelectorKind::Function(selector))
382+
.collect::<Vec<_>>();
383+
let ds = decode_selectors(&selectors).await?;
384+
ds.into_iter().map(|v| v.join("|")).collect()
391385
} else {
392386
vec![]
393387
};
@@ -501,7 +495,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
501495
// 4Byte
502496
CastSubcommand::FourByte { selector } => {
503497
let selector = stdin::unwrap_line(selector)?;
504-
let sigs = decode_function_selector(&selector).await?;
498+
let sigs = decode_function_selector(selector).await?;
505499
if sigs.is_empty() {
506500
eyre::bail!("No matching function signatures found for selector `{selector}`");
507501
}
@@ -514,7 +508,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
514508
let calldata = stdin::unwrap_line(calldata)?;
515509

516510
if calldata.len() == 10 {
517-
let sigs = decode_function_selector(&calldata).await?;
511+
let sigs = decode_function_selector(calldata.parse()?).await?;
518512
if sigs.is_empty() {
519513
eyre::bail!("No matching function signatures found for calldata `{calldata}`");
520514
}
@@ -544,7 +538,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
544538

545539
CastSubcommand::FourByteEvent { topic } => {
546540
let topic = stdin::unwrap_line(topic)?;
547-
let sigs = decode_event_topic(&topic).await?;
541+
let sigs = decode_event_topic(topic).await?;
548542
if sigs.is_empty() {
549543
eyre::bail!("No matching event signatures found for topic `{topic}`");
550544
}

crates/cast/src/lib.rs

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use alloy_network::AnyNetwork;
99
use alloy_primitives::{
1010
hex,
1111
utils::{keccak256, ParseUnits, Unit},
12-
Address, Keccak256, TxHash, TxKind, B256, I256, U256,
12+
Address, Keccak256, Selector, TxHash, TxKind, B256, I256, U256,
1313
};
1414
use alloy_provider::{
1515
network::eip2718::{Decodable2718, Encodable2718},
@@ -367,22 +367,16 @@ impl<P: Provider<AnyNetwork>> Cast<P> {
367367
}
368368

369369
async fn block_field_as_num<B: Into<BlockId>>(&self, block: B, field: String) -> Result<U256> {
370-
let block = block.into();
371-
let block_field = Self::block(
370+
Self::block(
372371
self,
373-
block,
372+
block.into(),
374373
false,
375374
// Select only select field
376375
Some(field),
377376
)
378-
.await?;
379-
380-
let ret = if block_field.starts_with("0x") {
381-
U256::from_str_radix(strip_0x(&block_field), 16).expect("Unable to convert hex to U256")
382-
} else {
383-
U256::from_str_radix(&block_field, 10).expect("Unable to convert decimal to U256")
384-
};
385-
Ok(ret)
377+
.await?
378+
.parse()
379+
.map_err(Into::into)
386380
}
387381

388382
pub async fn base_fee<B: Into<BlockId>>(&self, block: B) -> Result<U256> {
@@ -2132,15 +2126,16 @@ impl SimpleCast {
21322126
/// # Example
21332127
///
21342128
/// ```
2129+
/// use alloy_primitives::fixed_bytes;
21352130
/// use cast::SimpleCast as Cast;
21362131
///
21372132
/// let bytecode = "6080604052348015600e575f80fd5b50600436106026575f3560e01c80632125b65b14602a575b5f80fd5b603a6035366004603c565b505050565b005b5f805f60608486031215604d575f80fd5b833563ffffffff81168114605f575f80fd5b925060208401356001600160a01b03811681146079575f80fd5b915060408401356001600160e01b03811681146093575f80fd5b80915050925092509256";
21382133
/// let functions = Cast::extract_functions(bytecode)?;
2139-
/// assert_eq!(functions, vec![("0x2125b65b".to_string(), "uint32,address,uint224".to_string(), "pure")]);
2134+
/// assert_eq!(functions, vec![(fixed_bytes!("0x2125b65b"), "uint32,address,uint224".to_string(), "pure")]);
21402135
/// # Ok::<(), eyre::Report>(())
21412136
/// ```
2142-
pub fn extract_functions(bytecode: &str) -> Result<Vec<(String, String, &str)>> {
2143-
let code = hex::decode(strip_0x(bytecode))?;
2137+
pub fn extract_functions(bytecode: &str) -> Result<Vec<(Selector, String, &str)>> {
2138+
let code = hex::decode(bytecode)?;
21442139
let info = evmole::contract_info(
21452140
evmole::ContractInfoArgs::new(&code)
21462141
.with_selectors()
@@ -2153,7 +2148,7 @@ impl SimpleCast {
21532148
.into_iter()
21542149
.map(|f| {
21552150
(
2156-
hex::encode_prefixed(f.selector),
2151+
f.selector.into(),
21572152
f.arguments
21582153
.expect("arguments extraction was requested")
21592154
.into_iter()
@@ -2180,7 +2175,7 @@ impl SimpleCast {
21802175
/// let tx_envelope = Cast::decode_raw_transaction(&tx)?;
21812176
/// # Ok::<(), eyre::Report>(())
21822177
pub fn decode_raw_transaction(tx: &str) -> Result<TxEnvelope> {
2183-
let tx_hex = hex::decode(strip_0x(tx))?;
2178+
let tx_hex = hex::decode(tx)?;
21842179
let tx = TxEnvelope::decode_2718(&mut tx_hex.as_slice())?;
21852180
Ok(tx)
21862181
}

crates/cast/src/opts.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::cmd::{
55
mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs,
66
txpool::TxPoolSubcommands, wallet::WalletSubcommands,
77
};
8-
use alloy_primitives::{Address, B256, U256};
8+
use alloy_primitives::{Address, Selector, B256, U256};
99
use alloy_rpc_types::BlockId;
1010
use clap::{Parser, Subcommand, ValueHint};
1111
use eyre::Result;
@@ -646,7 +646,7 @@ pub enum CastSubcommand {
646646
#[command(name = "4byte", visible_aliases = &["4", "4b"])]
647647
FourByte {
648648
/// The function selector.
649-
selector: Option<String>,
649+
selector: Option<Selector>,
650650
},
651651

652652
/// Decode ABI-encoded calldata using <https://openchain.xyz>.
@@ -661,7 +661,7 @@ pub enum CastSubcommand {
661661
FourByteEvent {
662662
/// Topic 0
663663
#[arg(value_name = "TOPIC_0")]
664-
topic: Option<String>,
664+
topic: Option<B256>,
665665
},
666666

667667
/// Upload the given signatures to <https://openchain.xyz>.

crates/cast/src/tx.rs

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -459,23 +459,18 @@ where
459459

460460
/// Helper function that tries to decode custom error name and inputs from error payload data.
461461
async fn decode_execution_revert(data: &RawValue) -> Result<Option<String>> {
462-
if let Some(err_data) = serde_json::from_str::<String>(data.get())?.strip_prefix("0x") {
463-
let Some(selector) = err_data.get(..8) else { return Ok(None) };
464-
465-
if let Some(known_error) = SignaturesIdentifier::new(Config::foundry_cache_dir(), false)?
466-
.write()
467-
.await
468-
.identify_error(&hex::decode(selector)?)
469-
.await
470-
{
471-
let mut decoded_error = known_error.name.clone();
472-
if !known_error.inputs.is_empty() {
473-
if let Ok(error) = known_error.decode_error(&hex::decode(err_data)?) {
474-
write!(decoded_error, "({})", format_tokens(&error.body).format(", "))?;
475-
}
462+
let err_data = serde_json::from_str::<Bytes>(data.get())?;
463+
let Some(selector) = err_data.get(..4) else { return Ok(None) };
464+
if let Some(known_error) =
465+
SignaturesIdentifier::new(false)?.identify_error(selector.try_into().unwrap()).await
466+
{
467+
let mut decoded_error = known_error.name.clone();
468+
if !known_error.inputs.is_empty() {
469+
if let Ok(error) = known_error.decode_error(&err_data) {
470+
write!(decoded_error, "({})", format_tokens(&error.body).format(", "))?;
476471
}
477-
return Ok(Some(decoded_error))
478472
}
473+
return Ok(Some(decoded_error))
479474
}
480475
Ok(None)
481476
}

crates/cast/tests/cli/main.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2179,11 +2179,9 @@ contract CounterInExternalLibScript is Script {
21792179
.tx_hash();
21802180

21812181
// Cache project selectors.
2182-
cmd.forge_fuse().set_current_dir(prj.root());
21832182
cmd.forge_fuse().args(["selectors", "cache"]).assert_success();
21842183

21852184
// Assert cast with local artifacts can decode external lib signature.
2186-
cmd.cast_fuse().set_current_dir(prj.root());
21872185
cmd.cast_fuse()
21882186
.args(["run", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()])
21892187
.assert_success()

crates/cast/tests/cli/selectors.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ transfer(address,uint256)
2020

2121
casttest!(fourbyte_invalid, |_prj, cmd| {
2222
cmd.args(["4byte", "0xa9059c"]).assert_failure().stderr_eq(str![[r#"
23-
Error: Invalid selector 0xa9059c: expected 10 characters (including 0x prefix).
23+
error: invalid value '0xa9059c' for '[SELECTOR]': invalid string length
24+
25+
For more information, try '--help'.
2426
2527
"#]]);
2628
});

crates/chisel/src/dispatcher.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
use alloy_json_abi::{InternalType, JsonAbi};
1414
use alloy_primitives::{hex, Address};
1515
use forge_fmt::FormatterConfig;
16-
use foundry_config::{Config, RpcEndpointUrl};
16+
use foundry_config::RpcEndpointUrl;
1717
use foundry_evm::{
1818
decode::decode_console_logs,
1919
traces::{
@@ -925,9 +925,8 @@ impl ChiselDispatcher {
925925
) -> eyre::Result<CallTraceDecoder> {
926926
let mut decoder = CallTraceDecoderBuilder::new()
927927
.with_labels(result.labeled_addresses.clone())
928-
.with_signature_identifier(SignaturesIdentifier::new(
929-
Config::foundry_cache_dir(),
930-
session_config.foundry_config.offline,
928+
.with_signature_identifier(SignaturesIdentifier::from_config(
929+
&session_config.foundry_config,
931930
)?)
932931
.build();
933932

@@ -965,7 +964,7 @@ impl ChiselDispatcher {
965964
for (kind, trace) in &mut result.traces {
966965
// Display all Setup + Execution traces.
967966
if matches!(kind, TraceKind::Setup | TraceKind::Execution) {
968-
decode_trace_arena(trace, decoder).await?;
967+
decode_trace_arena(trace, decoder).await;
969968
sh_println!("{}", render_trace_arena(trace))?;
970969
}
971970
}

crates/cli/src/utils/cmd.rs

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use alloy_json_abi::JsonAbi;
22
use alloy_primitives::Address;
33
use eyre::{Result, WrapErr};
4-
use foundry_common::{compile::ProjectCompiler, fs, shell, ContractsByArtifact, TestFunctionExt};
4+
use foundry_common::{
5+
compile::ProjectCompiler, fs, selectors::SelectorKind, shell, ContractsByArtifact,
6+
TestFunctionExt,
7+
};
58
use foundry_compilers::{
69
artifacts::{CompactBytecode, Settings},
710
cache::{CacheEntry, CompilerCache},
@@ -16,7 +19,7 @@ use foundry_evm::{
1619
traces::{
1720
debug::{ContractSources, DebugTraceIdentifier},
1821
decode_trace_arena,
19-
identifier::{CachedSignatures, SignaturesIdentifier, TraceIdentifiers},
22+
identifier::{SignaturesCache, SignaturesIdentifier, TraceIdentifiers},
2023
render_trace_arena_inner, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces,
2124
},
2225
};
@@ -364,10 +367,7 @@ pub async fn handle_traces(
364367

365368
let mut builder = CallTraceDecoderBuilder::new()
366369
.with_labels(labels.chain(config_labels))
367-
.with_signature_identifier(SignaturesIdentifier::new(
368-
Config::foundry_cache_dir(),
369-
config.offline,
370-
)?);
370+
.with_signature_identifier(SignaturesIdentifier::from_config(config)?);
371371
let mut identifier = TraceIdentifiers::new().with_etherscan(config, chain)?;
372372
if let Some(contracts) = &known_contracts {
373373
builder = builder.with_known_contracts(contracts);
@@ -416,7 +416,7 @@ pub async fn print_traces(
416416
}
417417

418418
for (_, arena) in traces {
419-
decode_trace_arena(arena, decoder).await?;
419+
decode_trace_arena(arena, decoder).await;
420420
sh_println!("{}", render_trace_arena_inner(arena, verbose, state_changes))?;
421421
}
422422

@@ -437,34 +437,21 @@ pub async fn print_traces(
437437

438438
/// Traverse the artifacts in the project to generate local signatures and merge them into the cache
439439
/// file.
440-
pub fn cache_local_signatures(output: &ProjectCompileOutput, cache_path: PathBuf) -> Result<()> {
441-
let path = cache_path.join("signatures");
442-
let mut cached_signatures = CachedSignatures::load(cache_path);
443-
output.artifacts().for_each(|(_, artifact)| {
440+
pub fn cache_local_signatures(output: &ProjectCompileOutput, cache_dir: &Path) -> Result<()> {
441+
let path = cache_dir.join("signatures");
442+
let mut signatures = SignaturesCache::load(&path);
443+
for (_, artifact) in output.artifacts() {
444444
if let Some(abi) = &artifact.abi {
445-
for func in abi.functions() {
446-
cached_signatures.functions.insert(func.selector().to_string(), func.signature());
447-
}
448-
for event in abi.events() {
449-
cached_signatures
450-
.events
451-
.insert(event.selector().to_string(), event.full_signature());
452-
}
453-
for error in abi.errors() {
454-
cached_signatures.errors.insert(error.selector().to_string(), error.signature());
455-
}
456-
// External libraries doesn't have functions included in abi, but `methodIdentifiers`.
457-
if let Some(method_identifiers) = &artifact.method_identifiers {
458-
method_identifiers.iter().for_each(|(signature, selector)| {
459-
cached_signatures
460-
.functions
461-
.entry(format!("0x{selector}"))
462-
.or_insert(signature.to_string());
463-
});
464-
}
445+
signatures.extend_from_abi(abi);
465446
}
466-
});
467447

468-
fs::write_json_file(&path, &cached_signatures)?;
448+
// External libraries don't have functions included in the ABI, but `methodIdentifiers`.
449+
if let Some(method_identifiers) = &artifact.method_identifiers {
450+
signatures.extend(method_identifiers.iter().filter_map(|(signature, selector)| {
451+
Some((SelectorKind::Function(selector.parse().ok()?), signature.clone()))
452+
}));
453+
}
454+
}
455+
signatures.save(&path);
469456
Ok(())
470457
}

crates/common/src/provider/runtime_transport.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ impl RuntimeTransport {
138138
/// Connects the underlying transport, depending on the URL scheme.
139139
pub async fn connect(&self) -> Result<InnerTransport, RuntimeTransportError> {
140140
match self.url.scheme() {
141-
"http" | "https" => self.connect_http().await,
141+
"http" | "https" => self.connect_http(),
142142
"ws" | "wss" => self.connect_ws().await,
143143
"file" => self.connect_ipc().await,
144144
_ => Err(RuntimeTransportError::BadScheme(self.url.scheme().to_string())),
@@ -190,7 +190,7 @@ impl RuntimeTransport {
190190
}
191191

192192
/// Connects to an HTTP [alloy_transport_http::Http] transport.
193-
async fn connect_http(&self) -> Result<InnerTransport, RuntimeTransportError> {
193+
fn connect_http(&self) -> Result<InnerTransport, RuntimeTransportError> {
194194
let client = self.reqwest_client()?;
195195
Ok(InnerTransport::Http(Http::with_client(client, self.url.clone())))
196196
}
@@ -351,7 +351,7 @@ mod tests {
351351
let transport = RuntimeTransportBuilder::new(url.clone())
352352
.with_headers(vec!["User-Agent: test-agent".to_string()])
353353
.build();
354-
let inner = transport.connect_http().await.unwrap();
354+
let inner = transport.connect_http().unwrap();
355355

356356
match inner {
357357
InnerTransport::Http(http) => {

0 commit comments

Comments
 (0)