Skip to content

Commit 4b40d59

Browse files
klkvrrplusq
authored andcommitted
fix: respect --auth in cast call and cast estimate (foundry-rs#9120)
* fix: respect --auth in cast call and cast estimate * access list parser
1 parent 0780d02 commit 4b40d59

File tree

5 files changed

+87
-51
lines changed

5 files changed

+87
-51
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cast/bin/tx.rs

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,18 @@ use alloy_network::{
55
};
66
use alloy_primitives::{hex, Address, Bytes, TxKind, U256};
77
use alloy_provider::Provider;
8-
use alloy_rlp::Decodable;
98
use alloy_rpc_types::{AccessList, Authorization, TransactionInput, TransactionRequest};
109
use alloy_serde::WithOtherFields;
1110
use alloy_signer::Signer;
1211
use alloy_transport::Transport;
13-
use cast::revm::primitives::SignedAuthorization;
14-
use eyre::{Result, WrapErr};
12+
use eyre::Result;
1513
use foundry_cli::{
16-
opts::TransactionOpts,
14+
opts::{CliAuthorizationList, TransactionOpts},
1715
utils::{self, parse_function_args},
1816
};
1917
use foundry_common::ens::NameOrAddress;
2018
use foundry_config::{Chain, Config};
2119
use foundry_wallets::{WalletOpts, WalletSigner};
22-
use serde_json;
2320

2421
/// Different sender kinds used by [`CastTxBuilder`].
2522
pub enum SenderKind<'a> {
@@ -134,10 +131,10 @@ pub struct CastTxBuilder<T, P, S> {
134131
tx: WithOtherFields<TransactionRequest>,
135132
legacy: bool,
136133
blob: bool,
137-
auth: Option<String>,
134+
auth: Option<CliAuthorizationList>,
138135
chain: Chain,
139136
etherscan_api_key: Option<String>,
140-
access_list: Option<Option<String>>,
137+
access_list: Option<Option<AccessList>>,
141138
state: S,
142139
_t: std::marker::PhantomData<T>,
143140
}
@@ -319,24 +316,32 @@ where
319316
self.tx.set_from(from);
320317
self.tx.set_chain_id(self.chain.id());
321318

322-
if !fill {
323-
return Ok((self.tx, self.state.func));
324-
}
319+
let tx_nonce = if let Some(nonce) = self.tx.nonce {
320+
nonce
321+
} else {
322+
let nonce = self.provider.get_transaction_count(from).await?;
323+
if fill {
324+
self.tx.nonce = Some(nonce);
325+
}
326+
nonce
327+
};
325328

326-
if let Some(access_list) = match self.access_list {
329+
self.resolve_auth(sender, tx_nonce).await?;
330+
331+
if let Some(access_list) = match self.access_list.take() {
327332
None => None,
328333
// --access-list provided with no value, call the provider to create it
329334
Some(None) => Some(self.provider.create_access_list(&self.tx).await?.access_list),
330335
// Access list provided as a string, attempt to parse it
331-
Some(Some(ref s)) => Some(
332-
serde_json::from_str::<AccessList>(s)
333-
.map(AccessList::from)
334-
.wrap_err("Failed to parse access list from string")?,
335-
),
336+
Some(Some(access_list)) => Some(access_list),
336337
} {
337338
self.tx.set_access_list(access_list);
338339
}
339340

341+
if !fill {
342+
return Ok((self.tx, self.state.func));
343+
}
344+
340345
if self.legacy && self.tx.gas_price.is_none() {
341346
self.tx.gas_price = Some(self.provider.get_gas_price().await?);
342347
}
@@ -361,16 +366,6 @@ where
361366
}
362367
}
363368

364-
let nonce = if let Some(nonce) = self.tx.nonce {
365-
nonce
366-
} else {
367-
let nonce = self.provider.get_transaction_count(from).await?;
368-
self.tx.nonce = Some(nonce);
369-
nonce
370-
};
371-
372-
self.resolve_auth(sender, nonce).await?;
373-
374369
if self.tx.gas.is_none() {
375370
self.tx.gas = Some(self.provider.estimate_gas(&self.tx).await?);
376371
}
@@ -379,25 +374,27 @@ where
379374
}
380375

381376
/// Parses the passed --auth value and sets the authorization list on the transaction.
382-
async fn resolve_auth(&mut self, sender: SenderKind<'_>, nonce: u64) -> Result<()> {
383-
let Some(auth) = &self.auth else { return Ok(()) };
384-
385-
let auth = hex::decode(auth)?;
386-
let auth = if let Ok(address) = Address::try_from(auth.as_slice()) {
387-
let auth =
388-
Authorization { chain_id: U256::from(self.chain.id()), nonce: nonce + 1, address };
389-
390-
let Some(signer) = sender.as_signer() else {
391-
eyre::bail!("No signer available to sign authorization");
392-
};
393-
let signature = signer.sign_hash(&auth.signature_hash()).await?;
394-
395-
auth.into_signed(signature)
396-
} else if let Ok(auth) = SignedAuthorization::decode(&mut auth.as_ref()) {
397-
auth
398-
} else {
399-
eyre::bail!("Failed to decode authorization");
377+
async fn resolve_auth(&mut self, sender: SenderKind<'_>, tx_nonce: u64) -> Result<()> {
378+
let Some(auth) = self.auth.take() else { return Ok(()) };
379+
380+
let auth = match auth {
381+
CliAuthorizationList::Address(address) => {
382+
let auth = Authorization {
383+
chain_id: U256::from(self.chain.id()),
384+
nonce: tx_nonce + 1,
385+
address,
386+
};
387+
388+
let Some(signer) = sender.as_signer() else {
389+
eyre::bail!("No signer available to sign authorization");
390+
};
391+
let signature = signer.sign_hash(&auth.signature_hash()).await?;
392+
393+
auth.into_signed(signature)
394+
}
395+
CliAuthorizationList::Signed(auth) => auth,
400396
};
397+
401398
self.tx.set_authorization_list(vec![auth]);
402399

403400
Ok(())

crates/cli/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ foundry-wallets.workspace = true
2222

2323
foundry-compilers = { workspace = true, features = ["full"] }
2424

25+
alloy-eips.workspace = true
2526
alloy-dyn-abi.workspace = true
2627
alloy-json-abi.workspace = true
2728
alloy-primitives.workspace = true
2829
alloy-provider.workspace = true
30+
alloy-rlp.workspace = true
2931
alloy-transport.workspace = true
3032
alloy-chains.workspace = true
3133

@@ -43,6 +45,7 @@ tokio = { workspace = true, features = ["macros"] }
4345
tracing-subscriber = { workspace = true, features = ["registry", "env-filter"] }
4446
tracing.workspace = true
4547
yansi.workspace = true
48+
serde_json.workspace = true
4649

4750
tracing-tracy = { version = "0.11", optional = true }
4851

crates/cli/src/opts/transaction.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,36 @@
1-
use crate::utils::parse_ether_value;
2-
use alloy_primitives::{U256, U64};
1+
use std::str::FromStr;
2+
3+
use crate::utils::{parse_ether_value, parse_json};
4+
use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization};
5+
use alloy_primitives::{hex, Address, U256, U64};
6+
use alloy_rlp::Decodable;
37
use clap::Parser;
4-
use serde::Serialize;
58

6-
#[derive(Clone, Debug, Serialize, Parser)]
9+
/// CLI helper to parse a EIP-7702 authorization list.
10+
/// Can be either a hex-encoded signed authorization or an address.
11+
#[derive(Clone, Debug)]
12+
pub enum CliAuthorizationList {
13+
/// If an address is provided, we sign the authorization delegating to provided address.
14+
Address(Address),
15+
/// If RLP-encoded authorization is provided, we decode it and attach to transaction.
16+
Signed(SignedAuthorization),
17+
}
18+
19+
impl FromStr for CliAuthorizationList {
20+
type Err = eyre::Error;
21+
22+
fn from_str(s: &str) -> Result<Self, Self::Err> {
23+
if let Ok(addr) = Address::from_str(s) {
24+
Ok(Self::Address(addr))
25+
} else if let Ok(auth) = SignedAuthorization::decode(&mut hex::decode(s)?.as_ref()) {
26+
Ok(Self::Signed(auth))
27+
} else {
28+
eyre::bail!("Failed to decode authorization")
29+
}
30+
}
31+
}
32+
33+
#[derive(Clone, Debug, Parser)]
734
#[command(next_help_heading = "Transaction options")]
835
pub struct TransactionOpts {
936
/// Gas limit for the transaction.
@@ -61,15 +88,15 @@ pub struct TransactionOpts {
6188
///
6289
/// Can be either a hex-encoded signed authorization or an address.
6390
#[arg(long, conflicts_with_all = &["legacy", "blob"])]
64-
pub auth: Option<String>,
91+
pub auth: Option<CliAuthorizationList>,
6592

6693
/// EIP-2930 access list.
6794
///
6895
/// Accepts either a JSON-encoded access list or an empty value to create the access list
6996
/// via an RPC call to `eth_createAccessList`. To retrieve only the access list portion, use
7097
/// the `cast access-list` command.
71-
#[arg(long)]
72-
pub access_list: Option<Option<String>>,
98+
#[arg(long, value_parser = parse_json::<AccessList>)]
99+
pub access_list: Option<Option<AccessList>>,
73100
}
74101

75102
#[cfg(test)]

crates/cli/src/utils/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use alloy_transport::Transport;
55
use eyre::{ContextCompat, Result};
66
use foundry_common::provider::{ProviderBuilder, RetryProvider};
77
use foundry_config::{Chain, Config};
8+
use serde::de::DeserializeOwned;
89
use std::{
910
ffi::OsStr,
1011
future::Future,
@@ -133,6 +134,11 @@ pub fn parse_ether_value(value: &str) -> Result<U256> {
133134
})
134135
}
135136

137+
/// Parses a `T` from a string using [`serde_json::from_str`].
138+
pub fn parse_json<T: DeserializeOwned>(value: &str) -> serde_json::Result<T> {
139+
serde_json::from_str(value)
140+
}
141+
136142
/// Parses a `Duration` from a &str
137143
pub fn parse_delay(delay: &str) -> Result<Duration> {
138144
let delay = if delay.ends_with("ms") {

0 commit comments

Comments
 (0)