@@ -4,16 +4,20 @@ use crate::{
44 opts:: { Cast as CastArgs , CastSubcommand , ToBaseArgs } ,
55 traces:: identifier:: SignaturesIdentifier ,
66} ;
7- use alloy_consensus:: transaction:: { Recovered , SignerRecoverable } ;
7+ use alloy_consensus:: {
8+ Transaction , TxEnvelope ,
9+ transaction:: { Recovered , SignerRecoverable } ,
10+ } ;
811use alloy_dyn_abi:: { DynSolValue , ErrorExt , EventExt } ;
912use alloy_eips:: eip7702:: SignedAuthorization ;
1013use alloy_ens:: { ProviderEnsExt , namehash} ;
14+ use alloy_network:: { AnyTxEnvelope , TransactionResponse } ;
1115use alloy_primitives:: { Address , B256 , eip191_hash_message, hex, keccak256} ;
1216use alloy_provider:: Provider ;
1317use alloy_rpc_types:: { BlockId , BlockNumberOrTag :: Latest } ;
1418use clap:: { CommandFactory , Parser } ;
1519use clap_complete:: generate;
16- use eyre:: Result ;
20+ use eyre:: { Result , WrapErr } ;
1721use foundry_cli:: { utils, utils:: LoadConfig } ;
1822use foundry_common:: {
1923 abi:: { get_error, get_event} ,
@@ -26,7 +30,7 @@ use foundry_common::{
2630 } ,
2731 shell, stdin,
2832} ;
29- use std:: time:: Instant ;
33+ use std:: { collections :: HashMap , time:: Instant } ;
3034
3135/// Run the `cast` command-line interface.
3236pub fn run ( ) -> Result < ( ) > {
@@ -751,6 +755,147 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
751755 let auth: SignedAuthorization = serde_json:: from_str ( & auth) ?;
752756 sh_println ! ( "{}" , auth. recover_authority( ) ?) ?;
753757 }
758+ CastSubcommand :: ValidateAuth { tx_hash, rpc } => {
759+ let config = rpc. load_config ( ) ?;
760+ let provider = utils:: get_provider ( & config) ?;
761+
762+ let tx_hash = tx_hash. parse :: < B256 > ( ) . wrap_err ( "invalid tx hash" ) ?;
763+ let tx = provider
764+ . get_transaction_by_hash ( tx_hash)
765+ . await ?
766+ . ok_or_else ( || eyre:: eyre!( "tx not found: {:?}" , tx_hash) ) ?;
767+
768+ // Get block info for nonce calculation
769+ let block_number =
770+ tx. block_number . ok_or_else ( || eyre:: eyre!( "transaction is not yet mined" ) ) ?;
771+ let tx_index = tx
772+ . transaction_index
773+ . ok_or_else ( || eyre:: eyre!( "transaction index not available" ) ) ?;
774+
775+ // Fetch the block to get all transactions up to this one
776+ let block = provider
777+ . get_block_by_number ( block_number. into ( ) )
778+ . full ( )
779+ . await ?
780+ . ok_or_else ( || eyre:: eyre!( "block not found: {}" , block_number) ) ?;
781+
782+ // Build a map of address -> running nonce from txs in this block up to and including
783+ // our tx
784+ let mut running_nonces: HashMap < Address , u64 > = HashMap :: new ( ) ;
785+ for block_tx in block. transactions . txns ( ) . take ( ( tx_index + 1 ) as usize ) {
786+ let from = block_tx. from ( ) ;
787+ let nonce = block_tx. nonce ( ) ;
788+ // Track the next expected nonce (current nonce + 1)
789+ running_nonces. insert ( from, nonce + 1 ) ;
790+ }
791+
792+ let chain_id = provider. get_chain_id ( ) . await ?;
793+
794+ // Extract authorization list from EIP-7702 transaction
795+ let auth_list = match & * tx. inner . inner {
796+ AnyTxEnvelope :: Ethereum ( TxEnvelope :: Eip7702 ( signed_tx) ) => {
797+ signed_tx. tx ( ) . authorization_list . clone ( )
798+ }
799+ _ => {
800+ eyre:: bail!( "transaction is not an EIP-7702 transaction" ) ;
801+ }
802+ } ;
803+
804+ sh_println ! ( "Transaction: {}" , tx_hash) ?;
805+ sh_println ! ( "Block: {} (tx index: {})" , block_number, tx_index) ?;
806+ sh_println ! ( ) ?;
807+
808+ if auth_list. is_empty ( ) {
809+ sh_println ! ( "Authorization list is empty" ) ?;
810+ } else {
811+ for ( i, auth) in auth_list. iter ( ) . enumerate ( ) {
812+ let valid_chain = auth. chain_id == chain_id || auth. chain_id == 0 ;
813+ sh_println ! ( "Authorization #{}" , i) ?;
814+ sh_println ! ( " Decoded:" ) ?;
815+ sh_println ! ( " Chain ID: {}" , auth. chain_id, ) ?;
816+ sh_println ! ( " Address: {}" , auth. address) ?;
817+ sh_println ! ( " Nonce: {}" , auth. nonce) ?;
818+ sh_println ! ( " r: {}" , auth. r( ) ) ?;
819+ sh_println ! ( " s: {}" , auth. s( ) ) ?;
820+ sh_println ! ( " v: {}" , auth. y_parity( ) ) ?;
821+
822+ match auth. recover_authority ( ) {
823+ Ok ( authority) => {
824+ sh_println ! ( " Recovered Authority: {}" , authority) ?;
825+
826+ sh_println ! ( " Validation Status:" ) ?;
827+ sh_println ! (
828+ " Chain: {}" ,
829+ if valid_chain {
830+ "VALID" . to_string( )
831+ } else {
832+ format!( "INVALID (expected: 0 or {chain_id})" )
833+ }
834+ ) ?;
835+
836+ // Get the expected nonce at time of tx execution
837+ let expected_nonce =
838+ if let Some ( & nonce) = running_nonces. get ( & authority) {
839+ nonce
840+ } else {
841+ // Fetch nonce at block - 1 (state before this block)
842+ let prev_block = BlockId :: number ( block_number - 1 ) ;
843+ provider
844+ . get_transaction_count ( authority)
845+ . block_id ( prev_block)
846+ . await ?
847+ } ;
848+
849+ let valid_nonce = auth. nonce == expected_nonce;
850+ if valid_nonce {
851+ sh_println ! ( " Nonce: VALID" ) ?;
852+ } else {
853+ sh_println ! (
854+ " Nonce: INVALID (expected: {}, got: {})" ,
855+ expected_nonce,
856+ auth. nonce
857+ ) ?;
858+ }
859+
860+ // If authorization was valid, update running nonce for subsequent auths
861+ if valid_chain && valid_nonce {
862+ running_nonces. insert ( authority, expected_nonce + 1 ) ;
863+ }
864+
865+ // Check if the authority's code was set to the delegated address
866+ let code = provider. get_code_at ( authority) . await ?;
867+ if code. is_empty ( ) {
868+ sh_println ! ( " Code Status: No delegation (account has no code)" ) ?;
869+ } else if code. len ( ) == 23 && code[ 0 ..3 ] == [ 0xef , 0x01 , 0x00 ] {
870+ // EIP-7702 delegation designator: 0xef0100 followed by 20-byte
871+ // address
872+ let delegated_to = Address :: from_slice ( & code[ 3 ..23 ] ) ;
873+ if delegated_to == auth. address {
874+ sh_println ! (
875+ " Code Status: ACTIVE (delegated to {})" ,
876+ delegated_to
877+ ) ?;
878+ } else {
879+ sh_println ! (
880+ " Code Status: SUPERSEDED (currently delegated to {})" ,
881+ delegated_to
882+ ) ?;
883+ }
884+ } else {
885+ sh_println ! (
886+ " Code Status: Account has contract code (not a delegation)"
887+ ) ?;
888+ }
889+ }
890+ Err ( e) => {
891+ sh_println ! ( " Authority: UNKNOWN" ) ?;
892+ sh_println ! ( " Signature: INVALID ({})" , e) ?;
893+ }
894+ }
895+ sh_println ! ( ) ?;
896+ }
897+ }
898+ }
754899 CastSubcommand :: TxPool { command } => command. run ( ) . await ?,
755900 CastSubcommand :: Erc20Token { command } => command. run ( ) . await ?,
756901 CastSubcommand :: DAEstimate ( cmd) => {
0 commit comments