-
Notifications
You must be signed in to change notification settings - Fork 2.3k
feat(cast): validate-auth #12634
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
0xferrous
wants to merge
1
commit into
foundry-rs:master
Choose a base branch
from
0xferrous:push-mylruvmswxuw
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+503
−4
Open
feat(cast): validate-auth #12634
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,4 +25,5 @@ pub mod run; | |
| pub mod send; | ||
| pub mod storage; | ||
| pub mod txpool; | ||
| pub mod validate_auth; | ||
| pub mod wallet; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| use std::collections::HashMap; | ||
|
|
||
| use alloy_consensus::{Transaction, TxEnvelope}; | ||
| use alloy_eips::BlockId; | ||
| use alloy_network::{AnyTxEnvelope, TransactionResponse}; | ||
| use alloy_primitives::{Address, B256}; | ||
| use alloy_provider::Provider; | ||
| use foundry_cli::{ | ||
| opts::RpcOpts, | ||
| utils::{self, LoadConfig}, | ||
| }; | ||
|
|
||
| #[derive(Debug, clap::Parser)] | ||
| pub struct ValidateAuthArgs { | ||
| /// Transaction hash. | ||
| tx_hash: B256, | ||
|
|
||
| #[command(flatten)] | ||
| rpc: RpcOpts, | ||
| } | ||
|
|
||
| impl ValidateAuthArgs { | ||
| pub async fn run(self) -> eyre::Result<()> { | ||
| let config = self.rpc.load_config()?; | ||
| let provider = utils::get_provider(&config)?; | ||
|
|
||
| let tx = provider | ||
| .get_transaction_by_hash(self.tx_hash) | ||
| .await? | ||
| .ok_or_else(|| eyre::eyre!("tx not found: {:?}", self.tx_hash))?; | ||
|
|
||
| // Get block info for nonce calculation | ||
| let block_number = | ||
| tx.block_number.ok_or_else(|| eyre::eyre!("transaction is not yet mined"))?; | ||
| let tx_index = | ||
| tx.transaction_index.ok_or_else(|| eyre::eyre!("transaction index not available"))?; | ||
|
|
||
| // Fetch the block to get all transactions up to this one | ||
| let block = provider | ||
| .get_block_by_number(block_number.into()) | ||
| .full() | ||
| .await? | ||
| .ok_or_else(|| eyre::eyre!("block not found: {}", block_number))?; | ||
|
|
||
| // Build a map of address -> running nonce from txs in this block up to and including | ||
| // our tx | ||
| let mut running_nonces: HashMap<Address, u64> = HashMap::new(); | ||
| for block_tx in block.transactions.txns().take((tx_index + 1) as usize) { | ||
| let from = block_tx.from(); | ||
| let nonce = block_tx.nonce(); | ||
| // Track the next expected nonce (current nonce + 1) | ||
| running_nonces.insert(from, nonce + 1); | ||
| } | ||
|
|
||
| let chain_id = provider.get_chain_id().await?; | ||
|
|
||
| // Extract authorization list from EIP-7702 transaction | ||
| let auth_list = match &*tx.inner.inner { | ||
| AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(signed_tx)) => { | ||
| signed_tx.tx().authorization_list.clone() | ||
| } | ||
| _ => { | ||
| eyre::bail!("transaction is not an EIP-7702 transaction"); | ||
| } | ||
| }; | ||
|
|
||
| sh_println!("Transaction: {}", self.tx_hash)?; | ||
| sh_println!("Block: {} (tx index: {})", block_number, tx_index)?; | ||
| sh_println!()?; | ||
|
|
||
| if auth_list.is_empty() { | ||
| sh_println!("Authorization list is empty")?; | ||
| } else { | ||
| for (i, auth) in auth_list.iter().enumerate() { | ||
| let valid_chain = auth.chain_id == chain_id || auth.chain_id == 0; | ||
| sh_println!("Authorization #{}", i)?; | ||
| sh_println!(" Decoded:")?; | ||
| sh_println!(" Chain ID: {}", auth.chain_id,)?; | ||
| sh_println!(" Address: {}", auth.address)?; | ||
| sh_println!(" Nonce: {}", auth.nonce)?; | ||
| sh_println!(" r: {}", auth.r())?; | ||
| sh_println!(" s: {}", auth.s())?; | ||
| sh_println!(" v: {}", auth.y_parity())?; | ||
|
|
||
| match auth.recover_authority() { | ||
| Ok(authority) => { | ||
| sh_println!(" Recovered Authority: {}", authority)?; | ||
|
|
||
| sh_println!(" Validation Status:")?; | ||
| sh_println!( | ||
| " Chain: {}", | ||
| if valid_chain { | ||
| "VALID".to_string() | ||
| } else { | ||
| format!("INVALID (expected: 0 or {chain_id})") | ||
| } | ||
| )?; | ||
|
|
||
| // Get the expected nonce at time of tx execution | ||
| let expected_nonce = if let Some(&nonce) = running_nonces.get(&authority) { | ||
| nonce | ||
| } else { | ||
| // Fetch nonce at block - 1 (state before this block) | ||
| let prev_block = BlockId::number(block_number - 1); | ||
| provider.get_transaction_count(authority).block_id(prev_block).await? | ||
| }; | ||
|
|
||
| let valid_nonce = auth.nonce == expected_nonce; | ||
| if valid_nonce { | ||
| sh_println!(" Nonce: VALID")?; | ||
| } else { | ||
| sh_println!( | ||
| " Nonce: INVALID (expected: {}, got: {})", | ||
| expected_nonce, | ||
| auth.nonce | ||
| )?; | ||
| } | ||
|
|
||
| // If authorization was valid, update running nonce for subsequent auths | ||
| if valid_chain && valid_nonce { | ||
| running_nonces.insert(authority, expected_nonce + 1); | ||
| } | ||
|
|
||
| // Check if the authority's code was set to the delegated address | ||
| let code = provider.get_code_at(authority).await?; | ||
| if code.is_empty() { | ||
| sh_println!(" Code Status: No delegation (account has no code)")?; | ||
| } else if code.len() == 23 && code[0..3] == [0xef, 0x01, 0x00] { | ||
| // EIP-7702 delegation designator: 0xef0100 followed by 20-byte | ||
| // address | ||
| let delegated_to = Address::from_slice(&code[3..23]); | ||
| if delegated_to == auth.address { | ||
| sh_println!( | ||
| " Code Status: ACTIVE (delegated to {})", | ||
| delegated_to | ||
| )?; | ||
| } else { | ||
| sh_println!( | ||
| " Code Status: SUPERSEDED (currently delegated to {})", | ||
| delegated_to | ||
| )?; | ||
| } | ||
| } else { | ||
| sh_println!( | ||
| " Code Status: Account has contract code (not a delegation)" | ||
| )?; | ||
| } | ||
| } | ||
| Err(e) => { | ||
| sh_println!(" Authority: UNKNOWN")?; | ||
| sh_println!(" Signature: INVALID ({})", e)?; | ||
| } | ||
| } | ||
| sh_println!()?; | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is being validated here?
just the auth itself?
because for this we only need to fetch the tx itself and perform recovery on this, then this can be simplified
this info is also already included in cast tx (only displays the recovered auth if valid)
so I'm not actually sure how exactly this new command is different
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the
recoveredAuthorityin itself tells you whether the authorization was valid because, the signature can be valid, but the nonce may be outdated or too ahead(there is only one acceptable nonce), the chain id can be invalid, etc. Essentially, my intention was to determine which authorizations got "executed", and which got rejected, and this is my attempt at displaying that information.I think it shows the decoded data and recovered authority from the signature, but it doesn't show the the validity. I had to refer to etherscan for instance when I couldn't figure out why my eoa was not getting delegated
