Skip to main content

cast/cmd/
estimate.rs

1use super::{auth::confirm_and_build, print_result_line};
2use crate::tx::{CastTxBuilder, read_only_sender};
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    opts::{RpcOpts, TransactionOpts},
12    utils::{LoadConfig, parse_ether_value},
13};
14use foundry_common::{FoundryTransactionBuilder, provider::ProviderBuilder};
15use foundry_config::Config;
16use foundry_wallets::{BrowserWalletOpts, WalletOpts};
17use std::str::FromStr;
18use tempo_alloy::TempoNetwork;
19
20#[cfg(feature = "base")]
21use base_common_network::Base;
22
23/// CLI arguments for `cast estimate`.
24#[derive(Debug, Parser)]
25pub struct EstimateArgs {
26    /// The destination of the transaction.
27    #[arg(value_parser = NameOrAddress::from_str)]
28    to: Option<NameOrAddress>,
29
30    /// The signature of the function to call.
31    sig: Option<String>,
32
33    /// The arguments of the function to call.
34    #[arg(allow_negative_numbers = true)]
35    args: Vec<String>,
36
37    /// The block height to query at.
38    ///
39    /// Can also be the tags earliest, finalized, safe, latest, or pending.
40    #[arg(long, short = 'B')]
41    block: Option<BlockId>,
42
43    /// Calculate the cost of a transaction using the network gas price.
44    ///
45    /// If not specified the amount of gas will be estimated.
46    #[arg(long)]
47    cost: bool,
48
49    #[command(flatten)]
50    wallet: WalletOpts,
51
52    #[command(flatten)]
53    browser: BrowserWalletOpts,
54
55    #[command(subcommand)]
56    command: Option<EstimateSubcommands>,
57
58    #[command(flatten)]
59    tx: TransactionOpts,
60
61    /// Skip the EIP-7702 authorization disclosure confirmation.
62    #[arg(long)]
63    force: bool,
64
65    #[command(flatten)]
66    rpc: RpcOpts,
67}
68
69#[derive(Debug, Parser)]
70pub enum EstimateSubcommands {
71    /// Estimate gas cost to deploy a smart contract
72    #[command(name = "--create")]
73    Create {
74        /// The bytecode of contract
75        code: String,
76
77        /// The signature of the constructor
78        sig: Option<String>,
79
80        /// Constructor arguments
81        #[arg(allow_negative_numbers = true)]
82        args: Vec<String>,
83
84        /// Ether to send in the transaction
85        ///
86        /// Either specified in wei, or as a string with a unit type:
87        ///
88        /// Examples: 1ether, 10gwei, 0.01ether
89        #[arg(long, value_parser = parse_ether_value)]
90        value: Option<U256>,
91    },
92}
93
94impl EstimateArgs {
95    pub async fn run(self) -> Result<()> {
96        let config = self.rpc.load_config()?;
97        let requires_tempo = self.tx.tempo.is_tempo() || self.tx.tempo.session_id()?.is_some();
98        let network = super::resolve_transaction_network(&config, requires_tempo).await?;
99        if network.is_tempo() {
100            return self.run_with_network::<TempoNetwork>(config).await;
101        }
102        #[cfg(feature = "base")]
103        if network.is_base() {
104            super::validate_base_transaction_options(&self.tx)?;
105            return self.run_with_network::<Base>(config).await;
106        }
107        self.run_with_network::<Ethereum>(config).await
108    }
109
110    async fn run_with_network<N: Network>(self, config: Config) -> Result<()>
111    where
112        N::TransactionRequest: FoundryTransactionBuilder<N>,
113    {
114        let Self {
115            to,
116            mut sig,
117            mut args,
118            mut tx,
119            block,
120            cost,
121            wallet,
122            browser,
123            force,
124            rpc: _,
125            command,
126        } = self;
127
128        let provider = ProviderBuilder::<N>::from_config(&config)?.build()?;
129        let chain_id = match config.chain {
130            Some(chain) => chain.id(),
131            None => provider.get_chain_id().await?,
132        };
133        let (sender, is_browser) =
134            read_only_sender::<N>(&browser, wallet, &tx.tempo, chain_id).await?;
135
136        let code = if let Some(EstimateSubcommands::Create {
137            code,
138            sig: create_sig,
139            args: create_args,
140            value,
141        }) = command
142        {
143            sig = create_sig;
144            args = create_args;
145            if let Some(value) = value {
146                tx.value = Some(value);
147            }
148            Some(code)
149        } else {
150            None
151        };
152
153        let builder = CastTxBuilder::new(&provider, tx, &config)
154            .await?
155            .with_to(to)
156            .await?
157            .with_code_sig_and_args(code, sig, args)
158            .await?
159            .raw();
160        let Some(tx) = confirm_and_build(builder, sender, force, None, true).await? else {
161            return Ok(());
162        };
163
164        let tx = if is_browser { tx.browser_wallet_gas_estimation_request() } else { tx };
165        let gas = provider.estimate_gas(tx).block(block.unwrap_or_default()).await?;
166        if cost {
167            let cost = provider.get_gas_price().await? * gas as u128;
168            print_result_line(cost as f64 / 1e18)
169        } else {
170            print_result_line(gas)
171        }
172    }
173}