1use super::auth::confirm_auth_rpc_disclosure;
2use crate::tx::{CastTxBuilder, SenderKind};
3use alloy_ens::NameOrAddress;
4use alloy_network::{Ethereum, Network};
5use alloy_primitives::U256;
6use alloy_provider::Provider;
7use alloy_rpc_types::BlockId;
8use clap::Parser;
9use eyre::Result;
10use foundry_cli::{
11 json::print_scalar,
12 opts::{RpcOpts, TransactionOpts},
13 utils::{LoadConfig, parse_ether_value},
14};
15use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder, shell};
16use foundry_wallets::{BrowserWalletOpts, WalletOpts};
17use serde::Serialize;
18use std::{fmt::Display, str::FromStr};
19use tempo_alloy::TempoNetwork;
20
21#[derive(Debug, Parser)]
23pub struct EstimateArgs {
24 #[arg(value_parser = NameOrAddress::from_str)]
26 to: Option<NameOrAddress>,
27
28 sig: Option<String>,
30
31 #[arg(allow_negative_numbers = true)]
33 args: Vec<String>,
34
35 #[arg(long, short = 'B')]
39 block: Option<BlockId>,
40
41 #[arg(long)]
45 cost: bool,
46
47 #[command(flatten)]
48 wallet: WalletOpts,
49
50 #[command(flatten)]
51 browser: BrowserWalletOpts,
52
53 #[command(subcommand)]
54 command: Option<EstimateSubcommands>,
55
56 #[command(flatten)]
57 tx: TransactionOpts,
58
59 #[arg(long)]
61 force: bool,
62
63 #[command(flatten)]
64 rpc: RpcOpts,
65}
66
67#[derive(Debug, Parser)]
68pub enum EstimateSubcommands {
69 #[command(name = "--create")]
71 Create {
72 code: String,
74
75 sig: Option<String>,
77
78 #[arg(allow_negative_numbers = true)]
80 args: Vec<String>,
81
82 #[arg(long, value_parser = parse_ether_value)]
88 value: Option<U256>,
89 },
90}
91
92impl EstimateArgs {
93 pub async fn run(self) -> Result<()> {
94 if self.tx.tempo.is_tempo() {
95 self.run_with_network::<TempoNetwork>().await
96 } else {
97 self.run_with_network::<Ethereum>().await
98 }
99 }
100
101 pub async fn run_with_network<N: Network>(self) -> Result<()>
102 where
103 N::TransactionRequest: FoundryTransactionBuilder<N>,
104 {
105 let Self {
106 to,
107 mut sig,
108 mut args,
109 mut tx,
110 block,
111 cost,
112 wallet,
113 browser,
114 force,
115 rpc,
116 command,
117 } = self;
118
119 let config = rpc.load_config()?;
120 let provider = ProviderBuilder::<N>::from_config(&config)?.build()?;
121 let browser = browser.run::<N>().await?;
122 let sender = if let Some(browser) = &browser {
123 browser.address().into()
124 } else {
125 SenderKind::from_wallet_opts(wallet).await?
126 };
127
128 let code = if let Some(EstimateSubcommands::Create {
129 code,
130 sig: create_sig,
131 args: create_args,
132 value,
133 }) = command
134 {
135 sig = create_sig;
136 args = create_args;
137 if let Some(value) = value {
138 tx.value = Some(value);
139 }
140 Some(code)
141 } else {
142 None
143 };
144
145 let builder = CastTxBuilder::new(&provider, tx, &config)
146 .await?
147 .with_to(to)
148 .await?
149 .with_code_sig_and_args(code, sig, args)
150 .await?
151 .raw();
152 if builder.has_auth() && !confirm_auth_rpc_disclosure(&builder, &sender, force)? {
153 return Ok(());
154 }
155 let (tx, _) = builder.build(sender).await?;
156
157 let tx = if browser.is_some() { tx.browser_wallet_gas_estimation_request() } else { tx };
158 let gas = provider.estimate_gas(tx).block(block.unwrap_or_default()).await?;
159 if cost {
160 let gas_price_wei = provider.get_gas_price().await?;
161 let cost = gas_price_wei * gas as u128;
162 let cost_eth = cost as f64 / 1e18;
163 print_estimate_result(cost_eth)?;
164 } else {
165 print_estimate_result(gas)?;
166 }
167 Ok(())
168 }
169}
170
171fn print_estimate_result(value: impl Serialize + Display) -> Result<()> {
172 if shell::is_json() {
173 print_scalar(value)
174 } else {
175 let mut shell = shell::Shell::get();
177 let out = shell.out();
178 writeln!(out, "{value}")?;
179 out.flush()?;
180 Ok(())
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn parse_estimate_value() {
190 let args: EstimateArgs = EstimateArgs::parse_from(["foundry-cli", "--value", "100"]);
191 assert!(args.tx.value.is_some());
192 }
193}