@@ -5,9 +5,9 @@ use crate::{
55 Cheatcode , Cheatcodes , CheatcodesExecutor , CheatsCtxt , Result , Vm :: * , inspector:: exec_create,
66} ;
77use alloy_dyn_abi:: DynSolType ;
8- use alloy_json_abi:: ContractObject ;
8+ use alloy_json_abi:: { ContractObject , JsonAbi } ;
99use alloy_network:: { Network , ReceiptResponse } ;
10- use alloy_primitives:: { Bytes , U256 , hex, map:: Entry } ;
10+ use alloy_primitives:: { Bytes , FixedBytes , U256 , hex, map:: Entry } ;
1111use alloy_sol_types:: SolValue ;
1212use dialoguer:: { Input , Password } ;
1313use forge_script_sequence:: { BroadcastReader , TransactionWithMetadata } ;
@@ -310,6 +310,16 @@ impl Cheatcode for getDeployedCodeCall {
310310 }
311311}
312312
313+ impl Cheatcode for getSelectorsCall {
314+ fn apply < FEN : FoundryEvmNetwork > ( & self , state : & mut Cheatcodes < FEN > ) -> Result {
315+ let Self { artifactPath : path } = self ;
316+ let abi = get_artifact_abi ( state, path) ?;
317+ let selectors: Vec < FixedBytes < 4 > > =
318+ abi. functions ( ) . map ( |func| func. selector ( ) . into ( ) ) . collect ( ) ;
319+ Ok ( selectors. abi_encode ( ) )
320+ }
321+ }
322+
313323impl Cheatcode for deployCode_0Call {
314324 fn apply_full < FEN : FoundryEvmNetwork > (
315325 & self ,
@@ -602,6 +612,136 @@ fn get_artifact_code<FEN: FoundryEvmNetwork>(
602612 maybe_bytecode. ok_or_else ( || fmt_err ! ( "no bytecode for contract; is it abstract or unlinked?" ) )
603613}
604614
615+ /// Returns the ABI of a matching artifact from the given path.
616+ ///
617+ /// See [`get_artifact_code`] for the supported path formats.
618+ fn get_artifact_abi < FEN : FoundryEvmNetwork > (
619+ state : & Cheatcodes < FEN > ,
620+ path : & str ,
621+ ) -> Result < JsonAbi > {
622+ if path. ends_with ( ".json" ) {
623+ // Read JSON artifact directly from disk.
624+ let path = state. config . ensure_path_allowed ( path, FsAccessKind :: Read ) ?;
625+ let data = fs:: read_to_string ( path) ?;
626+ let json: serde_json:: Value = serde_json:: from_str ( & data) ?;
627+ let abi = json. get ( "abi" ) . ok_or_else ( || fmt_err ! ( "no `abi` field in artifact JSON" ) ) ?;
628+ return serde_json:: from_value ( abi. clone ( ) ) . map_err ( |e| fmt_err ! ( "{e}" ) ) ;
629+ }
630+
631+ let mut parts = path. split ( ':' ) ;
632+
633+ let mut file = None ;
634+ let mut contract_name = None ;
635+ let mut version = None ;
636+
637+ let path_or_name = parts. next ( ) . unwrap ( ) ;
638+ if path_or_name. contains ( '.' ) {
639+ file = Some ( PathBuf :: from ( path_or_name) ) ;
640+ if let Some ( name_or_version) = parts. next ( ) {
641+ if name_or_version. contains ( '.' ) {
642+ version = Some ( name_or_version) ;
643+ } else {
644+ contract_name = Some ( name_or_version) ;
645+ version = parts. next ( ) ;
646+ }
647+ }
648+ } else {
649+ contract_name = Some ( path_or_name) ;
650+ version = parts. next ( ) ;
651+ }
652+
653+ let version = if let Some ( version) = version {
654+ Some ( Version :: parse ( version) . map_err ( |e| fmt_err ! ( "failed parsing version: {e}" ) ) ?)
655+ } else {
656+ None
657+ } ;
658+
659+ // Use available artifacts list if present.
660+ if let Some ( artifacts) = & state. config . available_artifacts {
661+ let filtered = artifacts
662+ . iter ( )
663+ . filter ( |( id, _) | {
664+ let id_name = id. name . split ( '.' ) . next ( ) . unwrap ( ) ;
665+
666+ if let Some ( path) = & file
667+ && !id. source . ends_with ( path)
668+ {
669+ return false ;
670+ }
671+ if let Some ( name) = contract_name
672+ && id_name != name
673+ {
674+ return false ;
675+ }
676+ if let Some ( ref version) = version
677+ && ( id. version . minor != version. minor
678+ || id. version . major != version. major
679+ || id. version . patch != version. patch )
680+ {
681+ return false ;
682+ }
683+ true
684+ } )
685+ . collect :: < Vec < _ > > ( ) ;
686+
687+ let artifact = match & filtered[ ..] {
688+ [ ] => None ,
689+ [ artifact] => Some ( Ok ( * artifact) ) ,
690+ filtered => {
691+ let mut filtered = filtered. to_vec ( ) ;
692+ Some (
693+ state
694+ . config
695+ . running_artifact
696+ . as_ref ( )
697+ . and_then ( |running| {
698+ filtered. retain ( |( id, _) | id. version == running. version ) ;
699+ if filtered. len ( ) == 1 {
700+ return Some ( filtered[ 0 ] ) ;
701+ }
702+ filtered. retain ( |( id, _) | id. profile == running. profile ) ;
703+ ( filtered. len ( ) == 1 ) . then ( || filtered[ 0 ] )
704+ } )
705+ . ok_or_else ( || fmt_err ! ( "multiple matching artifacts found" ) ) ,
706+ )
707+ }
708+ } ;
709+
710+ if let Some ( artifact) = artifact {
711+ let artifact = artifact?;
712+ return Ok ( artifact. 1 . abi . clone ( ) ) ;
713+ }
714+ }
715+
716+ // Fallback: construct path manually and read JSON from disk.
717+ let path_in_artifacts = match ( file. map ( |f| f. to_string_lossy ( ) . to_string ( ) ) , contract_name) {
718+ ( Some ( file) , Some ( contract_name) ) => {
719+ PathBuf :: from ( format ! ( "{file}/{contract_name}.json" ) )
720+ }
721+ ( None , Some ( contract_name) ) => {
722+ PathBuf :: from ( format ! ( "{contract_name}.sol/{contract_name}.json" ) )
723+ }
724+ ( Some ( file) , None ) => {
725+ let name = file. replace ( ".sol" , "" ) ;
726+ PathBuf :: from ( format ! ( "{file}/{name}.json" ) )
727+ }
728+ _ => bail ! ( "invalid artifact path" ) ,
729+ } ;
730+
731+ let path = state. config . paths . artifacts . join ( path_in_artifacts) ;
732+ let path = state. config . ensure_path_allowed ( path, FsAccessKind :: Read ) ?;
733+ let data = fs:: read_to_string ( path) . map_err ( |e| {
734+ if state. config . available_artifacts . is_some ( ) {
735+ fmt_err ! ( "no matching artifact found" )
736+ } else {
737+ e. into ( )
738+ }
739+ } ) ?;
740+ let json: serde_json:: Value = serde_json:: from_str ( & data) ?;
741+ let abi = json. get ( "abi" ) . ok_or_else ( || fmt_err ! ( "no `abi` field in artifact JSON" ) ) ?;
742+ serde_json:: from_value ( abi. clone ( ) ) . map_err ( |e| fmt_err ! ( "{e}" ) )
743+ }
744+
605745impl Cheatcode for ffiCall {
606746 fn apply < FEN : FoundryEvmNetwork > ( & self , state : & mut Cheatcodes < FEN > ) -> Result {
607747 let Self { commandInput : input } = self ;
0 commit comments