Skip to main content

cast/cmd/
estimate.rs

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/// CLI arguments for `cast estimate`.
22#[derive(Debug, Parser)]
23pub struct EstimateArgs {
24    /// The destination of the transaction.
25    #[arg(value_parser = NameOrAddress::from_str)]
26    to: Option<NameOrAddress>,
27
28    /// The signature of the function to call.
29    sig: Option<String>,
30
31    /// The arguments of the function to call.
32    #[arg(allow_negative_numbers = true)]
33    args: Vec<String>,
34
35    /// The block height to query at.
36    ///
37    /// Can also be the tags earliest, finalized, safe, latest, or pending.
38    #[arg(long, short = 'B')]
39    block: Option<BlockId>,
40
41    /// Calculate the cost of a transaction using the network gas price.
42    ///
43    /// If not specified the amount of gas will be estimated.
44    #[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    /// Skip the EIP-7702 authorization disclosure confirmation.
60    #[arg(long)]
61    force: bool,
62
63    #[command(flatten)]
64    rpc: RpcOpts,
65}
66
67#[derive(Debug, Parser)]
68pub enum EstimateSubcommands {
69    /// Estimate gas cost to deploy a smart contract
70    #[command(name = "--create")]
71    Create {
72        /// The bytecode of contract
73        code: String,
74
75        /// The signature of the constructor
76        sig: Option<String>,
77
78        /// Constructor arguments
79        #[arg(allow_negative_numbers = true)]
80        args: Vec<String>,
81
82        /// Ether to send in the transaction
83        ///
84        /// Either specified in wei, or as a string with a unit type:
85        ///
86        /// Examples: 1ether, 10gwei, 0.01ether
87        #[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        // Bypass the shell verbosity layer so `--quiet` does not suppress the primary result.
176        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}