Skip to main content

cast/cmd/
access_list.rs

1use super::auth::confirm_and_build;
2use crate::tx::{CastTxBuilder, read_only_sender};
3use alloy_ens::NameOrAddress;
4use alloy_network::{Ethereum, Network};
5use alloy_provider::Provider;
6use alloy_rpc_types::BlockId;
7use clap::Parser;
8use eyre::Result;
9use foundry_cli::{
10    opts::{RpcOpts, TransactionOpts},
11    utils::LoadConfig,
12};
13use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder, shell};
14use foundry_config::Config;
15use foundry_wallets::{BrowserWalletOpts, WalletOpts};
16use std::str::FromStr;
17use tempo_alloy::TempoNetwork;
18
19#[cfg(feature = "base")]
20use base_common_network::Base;
21
22/// CLI arguments for `cast access-list`.
23#[derive(Debug, Parser)]
24pub struct AccessListArgs {
25    /// The destination of the transaction.
26    #[arg(
27        value_name = "TO",
28        value_parser = NameOrAddress::from_str
29    )]
30    to: Option<NameOrAddress>,
31
32    /// The signature of the function to call.
33    #[arg(value_name = "SIG")]
34    sig: Option<String>,
35
36    /// The arguments of the function to call.
37    #[arg(value_name = "ARGS", allow_negative_numbers = true)]
38    args: Vec<String>,
39
40    /// Raw hex-encoded data for the transaction. Used instead of `SIG` and `ARGS`.
41    #[arg(
42        long,
43        conflicts_with_all = &["sig", "args"]
44    )]
45    data: Option<String>,
46
47    /// The block height to query at.
48    ///
49    /// Can also be the tags earliest, finalized, safe, latest, or pending.
50    #[arg(long, short = 'B')]
51    block: Option<BlockId>,
52
53    #[command(flatten)]
54    tx: TransactionOpts,
55
56    /// Skip the EIP-7702 authorization disclosure confirmation.
57    #[arg(long)]
58    force: bool,
59
60    #[command(flatten)]
61    rpc: RpcOpts,
62
63    #[command(flatten)]
64    wallet: WalletOpts,
65
66    #[command(flatten)]
67    browser: BrowserWalletOpts,
68}
69
70impl AccessListArgs {
71    pub async fn run(self) -> Result<()> {
72        let config = self.rpc.load_config()?;
73        let requires_tempo = self.tx.tempo.is_tempo() || self.tx.tempo.session_id()?.is_some();
74        let network = super::resolve_transaction_network(&config, requires_tempo).await?;
75        if network.is_tempo() {
76            return self.run_with_network::<TempoNetwork>(config).await;
77        }
78        #[cfg(feature = "base")]
79        if network.is_base() {
80            super::validate_base_transaction_options(&self.tx)?;
81            return self.run_with_network::<Base>(config).await;
82        }
83        self.run_with_network::<Ethereum>(config).await
84    }
85
86    async fn run_with_network<N: Network + Unpin>(self, config: Config) -> Result<()>
87    where
88        N::TransactionRequest: FoundryTransactionBuilder<N>,
89    {
90        let Self { to, sig, args, data, tx, force, rpc: _, wallet, browser, block } = self;
91
92        let provider = ProviderBuilder::<N>::from_config(&config)?.build()?;
93        let chain_id = match config.chain {
94            Some(chain) => chain.id(),
95            None => provider.get_chain_id().await?,
96        };
97        let (sender, _) = read_only_sender::<N>(&browser, wallet, &tx.tempo, chain_id).await?;
98
99        let builder = CastTxBuilder::new(&provider, tx, &config)
100            .await?
101            .with_to(to)
102            .await?
103            .with_code_sig_and_args(None, data.or(sig), args)
104            .await?
105            .raw();
106        let Some(tx) = confirm_and_build(builder, sender, force, None, true).await? else {
107            return Ok(());
108        };
109
110        let access_list =
111            provider.create_access_list(&tx).block_id(block.unwrap_or_default()).await?;
112        let access_list = if shell::is_json() {
113            serde_json::to_string(&access_list)?
114        } else {
115            let mut s =
116                vec![format!("gas used: {}", access_list.gas_used), "access list:".to_string()];
117            for al in access_list.access_list.0 {
118                s.push(format!("- address: {}", al.address.to_checksum(None)));
119                if !al.storage_keys.is_empty() {
120                    s.push("  keys:".to_string());
121                    for key in al.storage_keys {
122                        s.push(format!("    {key:?}"));
123                    }
124                }
125            }
126            s.join("\n")
127        };
128        sh_println!("{access_list}")?;
129        Ok(())
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use clap::error::ErrorKind;
137
138    #[test]
139    fn data_conflicts_with_sig_and_args() {
140        let err = AccessListArgs::try_parse_from([
141            "foundry-cli",
142            "0x0000000000000000000000000000000000000001",
143            "transfer(address,uint256)",
144            "0x0000000000000000000000000000000000000002",
145            "1",
146            "--data",
147            "0x1234",
148        ])
149        .unwrap_err();
150
151        assert_eq!(err.kind(), ErrorKind::ArgumentConflict);
152    }
153}