From 19b9d018c09cb60f2bea52c78114e303a6a057c5 Mon Sep 17 00:00:00 2001 From: 0xferrous <0xferrous@proton.me> Date: Mon, 11 Aug 2025 07:01:57 +0000 Subject: [PATCH] feat(forge-selectors-list): add --no-group option --- crates/forge/src/cmd/selectors.rs | 84 +++++++++++++++++++----- crates/forge/tests/cli/test_cmd.rs | 100 +++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 16 deletions(-) diff --git a/crates/forge/src/cmd/selectors.rs b/crates/forge/src/cmd/selectors.rs index 3ced02aee0b8a..cd8009df642a2 100644 --- a/crates/forge/src/cmd/selectors.rs +++ b/crates/forge/src/cmd/selectors.rs @@ -11,7 +11,7 @@ use foundry_common::{ selectors::{SelectorImportData, import_selectors}, }; use foundry_compilers::{artifacts::output_selection::ContractOutputSelection, info::ContractInfo}; -use std::fs::canonicalize; +use std::{collections::BTreeMap, fs::canonicalize}; /// CLI arguments for `forge selectors`. #[derive(Clone, Debug, Parser)] @@ -56,6 +56,9 @@ pub enum SelectorsSubcommands { #[command(flatten)] project_paths: ProjectPathOpts, + + #[arg(long, help = "Do not group the selectors by contract in separate tables.")] + no_group: bool, }, /// Find if a selector is present in the project @@ -225,7 +228,7 @@ impl SelectorsSubcommands { sh_println!("\n{table}\n")?; } } - Self::List { contract, project_paths } => { + Self::List { contract, project_paths, no_group } => { sh_println!("Listing selectors for contracts in the project...")?; let build_args = BuildOpts { project_paths, @@ -273,41 +276,90 @@ impl SelectorsSubcommands { let mut artifacts = artifacts.into_iter().peekable(); - while let Some((contract, artifact)) = artifacts.next() { - let abi = artifact.abi.ok_or_else(|| eyre::eyre!("Unable to fetch abi"))?; - if abi.functions.is_empty() && abi.events.is_empty() && abi.errors.is_empty() { - continue; + #[derive(PartialEq, PartialOrd, Eq, Ord)] + enum SelectorType { + Function, + Event, + Error, + } + impl std::fmt::Display for SelectorType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Function => write!(f, "Function"), + Self::Event => write!(f, "Event"), + Self::Error => write!(f, "Error"), + } } + } - sh_println!("{contract}")?; + let mut selectors = + BTreeMap::>>::new(); - let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); + for (contract, artifact) in artifacts.by_ref() { + let abi = artifact.abi.ok_or_else(|| eyre::eyre!("Unable to fetch abi"))?; - table.set_header(["Type", "Signature", "Selector"]); + let contract_selectors = selectors.entry(contract.clone()).or_default(); for func in abi.functions() { let sig = func.signature(); let selector = func.selector(); - table.add_row(["Function", &sig, &hex::encode_prefixed(selector)]); + contract_selectors + .entry(SelectorType::Function) + .or_default() + .push((hex::encode_prefixed(selector), sig)); } for event in abi.events() { let sig = event.signature(); let selector = event.selector(); - table.add_row(["Event", &sig, &hex::encode_prefixed(selector)]); + contract_selectors + .entry(SelectorType::Event) + .or_default() + .push((hex::encode_prefixed(selector), sig)); } for error in abi.errors() { let sig = error.signature(); let selector = error.selector(); - table.add_row(["Error", &sig, &hex::encode_prefixed(selector)]); + contract_selectors + .entry(SelectorType::Error) + .or_default() + .push((hex::encode_prefixed(selector), sig)); } + } - sh_println!("\n{table}\n")?; + if no_group { + let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); + table.set_header(["Type", "Signature", "Selector", "Contract"]); + + for (contract, contract_selectors) in selectors { + for (selector_type, selectors) in contract_selectors { + for (selector, sig) in selectors { + table.add_row([ + selector_type.to_string(), + sig, + selector, + contract.to_string(), + ]); + } + } + } - if artifacts.peek().is_some() { - sh_println!()? + sh_println!("\n{table}")?; + } else { + for (idx, (contract, contract_selectors)) in selectors.into_iter().enumerate() { + sh_println!("{}{contract}", if idx == 0 { "" } else { "\n" })?; + let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); + table.set_header(["Type", "Signature", "Selector"]); + + for (selector_type, selectors) in contract_selectors { + for (selector, sig) in selectors { + table.add_row([selector_type.to_string(), sig, selector]); + } + } + sh_println!("\n{table}")?; } } } diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 6a446a381751b..887ee5e56925a 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -3411,6 +3411,106 @@ Selectors successfully uploaded to OpenChain "#]]); }); +forgetest_init!(selectors_list_cmd, |prj, cmd| { + prj.add_source( + "Counter.sol", + r" +contract Counter { + uint256 public number; + event Incremented(uint256 newNumber); + error IncrementError(); + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} + ", + ) + .unwrap(); + + prj.add_source( + "CounterV2.sol", + r" +contract CounterV2 { + uint256 public number; + + function setNumberV2(uint256 newNumber) public { + number = newNumber; + } + + function incrementV2() public { + number++; + } +} + ", + ) + .unwrap(); + + cmd.args(["selectors", "list"]).assert_success().stdout_eq(str![[r#" +Listing selectors for contracts in the project... +Counter + +╭----------+----------------------+--------------------------------------------------------------------╮ +| Type | Signature | Selector | ++======================================================================================================+ +| Function | increment() | 0xd09de08a | +|----------+----------------------+--------------------------------------------------------------------| +| Function | number() | 0x8381f58a | +|----------+----------------------+--------------------------------------------------------------------| +| Function | setNumber(uint256) | 0x3fb5c1cb | +|----------+----------------------+--------------------------------------------------------------------| +| Event | Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 | +|----------+----------------------+--------------------------------------------------------------------| +| Error | IncrementError() | 0x46544c04 | +╰----------+----------------------+--------------------------------------------------------------------╯ + +CounterV2 + +╭----------+----------------------+------------╮ +| Type | Signature | Selector | ++==============================================+ +| Function | incrementV2() | 0x49365a69 | +|----------+----------------------+------------| +| Function | number() | 0x8381f58a | +|----------+----------------------+------------| +| Function | setNumberV2(uint256) | 0xb525b68c | +╰----------+----------------------+------------╯ + +"#]]); + + cmd.forge_fuse() + .args(["selectors", "list", "--no-group"]) + .assert_success() + .stdout_eq(str![[r#" +Listing selectors for contracts in the project... + +╭----------+----------------------+--------------------------------------------------------------------+-----------╮ +| Type | Signature | Selector | Contract | ++==================================================================================================================+ +| Function | increment() | 0xd09de08a | Counter | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Function | number() | 0x8381f58a | Counter | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Function | setNumber(uint256) | 0x3fb5c1cb | Counter | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Event | Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 | Counter | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Error | IncrementError() | 0x46544c04 | Counter | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Function | incrementV2() | 0x49365a69 | CounterV2 | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Function | number() | 0x8381f58a | CounterV2 | +|----------+----------------------+--------------------------------------------------------------------+-----------| +| Function | setNumberV2(uint256) | 0xb525b68c | CounterV2 | +╰----------+----------------------+--------------------------------------------------------------------+-----------╯ + +"#]]); +}); + // tests `interceptInitcode` function forgetest_init!(intercept_initcode, |prj, cmd| { prj.wipe_contracts();