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#[derive(Debug, Parser)]
24pub struct AccessListArgs {
25 #[arg(
27 value_name = "TO",
28 value_parser = NameOrAddress::from_str
29 )]
30 to: Option<NameOrAddress>,
31
32 #[arg(value_name = "SIG")]
34 sig: Option<String>,
35
36 #[arg(value_name = "ARGS", allow_negative_numbers = true)]
38 args: Vec<String>,
39
40 #[arg(
42 long,
43 conflicts_with_all = &["sig", "args"]
44 )]
45 data: Option<String>,
46
47 #[arg(long, short = 'B')]
51 block: Option<BlockId>,
52
53 #[command(flatten)]
54 tx: TransactionOpts,
55
56 #[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}