Skip to main content

forge/cmd/
create.rs

1use crate::cmd::install;
2use alloy_chains::Chain;
3use alloy_consensus::{SignableTransaction, Signed};
4use alloy_dyn_abi::{DynSolValue, JsonAbiExt, Specifier};
5use alloy_json_abi::{Constructor, JsonAbi};
6use alloy_network::{Ethereum, EthereumWallet, Network, ReceiptResponse, TransactionBuilder};
7use alloy_primitives::{Address, Bytes, U256, hex};
8use alloy_provider::{PendingTransactionError, Provider, ProviderBuilder as AlloyProviderBuilder};
9use alloy_signer::{Signature, Signer};
10use alloy_transport::TransportError;
11use clap::{Parser, ValueHint};
12use eyre::{Context, ContextCompat, Result};
13use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs};
14use foundry_cli::{
15    opts::{BuildOpts, EthereumOpts, EtherscanOpts, TransactionOpts},
16    utils::{
17        LoadConfig, ResolvedLane, find_contract_artifacts, maybe_print_resolved_lane,
18        read_constructor_args_file, resolve_lane,
19    },
20};
21use foundry_common::{
22    FoundryTransactionBuilder,
23    compile::{self},
24    fmt::parse_tokens,
25    provider::ProviderBuilder,
26    shell,
27    tempo::TEMPO_BROWSER_GAS_BUFFER,
28};
29use foundry_compilers::{
30    ArtifactId, artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize,
31};
32use foundry_config::{
33    Config,
34    figment::{
35        self, Metadata, Profile,
36        value::{Dict, Map},
37    },
38    merge_impl_figment_convert,
39};
40use foundry_wallets::{
41    BrowserWalletOpts, TempoAccessKeyConfig, WalletSigner, wallet_browser::signer::BrowserSigner,
42};
43use serde_json::json;
44use std::{borrow::Borrow, marker::PhantomData, path::PathBuf, sync::Arc, time::Duration};
45use tempo_alloy::{TempoNetwork, contracts::precompiles::DEFAULT_FEE_TOKEN};
46
47merge_impl_figment_convert!(CreateArgs, build, eth);
48
49/// CLI arguments for `forge create`.
50#[derive(Clone, Debug, Parser)]
51pub struct CreateArgs {
52    /// The contract identifier in the form `<path>:<contractname>`.
53    contract: ContractInfo,
54
55    /// The constructor arguments.
56    #[arg(
57        long,
58        num_args(1..),
59        conflicts_with = "constructor_args_path",
60        value_name = "ARGS",
61        allow_hyphen_values = true,
62    )]
63    constructor_args: Vec<String>,
64
65    /// The path to a file containing the constructor arguments.
66    #[arg(
67        long,
68        value_hint = ValueHint::FilePath,
69        value_name = "PATH",
70    )]
71    constructor_args_path: Option<PathBuf>,
72
73    /// Broadcast the transaction.
74    #[arg(long)]
75    pub broadcast: bool,
76
77    /// Verify contract after creation.
78    #[arg(long)]
79    verify: bool,
80
81    /// Send via `eth_sendTransaction` using the `--from` argument or `$ETH_FROM` as sender
82    #[arg(long, requires = "from")]
83    unlocked: bool,
84
85    /// Prints the standard json compiler input if `--verify` is provided.
86    ///
87    /// The standard json compiler input can be used to manually submit contract verification in
88    /// the browser.
89    #[arg(long, requires = "verify")]
90    show_standard_json_input: bool,
91
92    /// Timeout to use for broadcasting transactions.
93    #[arg(long, env = "ETH_TIMEOUT")]
94    pub timeout: Option<u64>,
95
96    #[command(flatten)]
97    build: BuildOpts,
98
99    #[command(flatten)]
100    tx: TransactionOpts,
101
102    #[command(flatten)]
103    eth: EthereumOpts,
104
105    #[command(flatten)]
106    pub verifier: VerifierArgs,
107
108    #[command(flatten)]
109    retry: RetryArgs,
110
111    /// Browser wallet options
112    #[command(flatten)]
113    browser: BrowserWalletOpts,
114}
115
116impl CreateArgs {
117    /// Executes the command to create a contract
118    pub async fn run(mut self) -> Result<()> {
119        let (signer, tempo_access_key) = self.eth.wallet.maybe_signer().await?;
120
121        // Resolve chain early so we can dispatch to the correct network type.
122        if self.chain_id().is_none() {
123            let config = self.load_config()?;
124            let provider = ProviderBuilder::<Ethereum>::from_config(&config)?.build()?;
125            let chain_id = provider.get_chain_id().await?;
126            self.eth.etherscan.chain = Some(chain_id.into());
127        }
128
129        if tempo_access_key.is_some()
130            || self.tx.tempo.is_tempo()
131            || self.chain_id().is_some_and(|c| c.is_tempo())
132        {
133            self.run_generic::<TempoNetwork>(signer, tempo_access_key).await
134        } else {
135            self.run_generic::<Ethereum>(signer, None).await
136        }
137    }
138
139    async fn run_generic<N: Network>(
140        mut self,
141        pre_resolved_signer: Option<WalletSigner>,
142        access_key: Option<TempoAccessKeyConfig>,
143    ) -> Result<()>
144    where
145        N::TxEnvelope: From<Signed<N::UnsignedTx>>,
146        N::UnsignedTx: SignableTransaction<Signature>,
147        N::TransactionRequest: FoundryTransactionBuilder<N> + serde::Serialize,
148        N::ReceiptResponse: serde::Serialize,
149    {
150        let mut config = self.load_config()?;
151
152        // Install missing dependencies.
153        if install::install_missing_dependencies(&mut config).await && config.auto_detect_remappings
154        {
155            // need to re-configure here to also catch additional remappings
156            config = self.load_config()?;
157        }
158
159        // Find Project & Compile
160        let project = config.project()?;
161
162        let target_path = if let Some(ref mut path) = self.contract.path {
163            canonicalize(project.root().join(path))?
164        } else {
165            project.find_contract_path(&self.contract.name)?
166        };
167
168        let output = compile::compile_target(&target_path, &project, shell::is_json())?;
169
170        let (abi, bin, id) = find_contract_artifacts(output, &target_path, &self.contract.name)?;
171
172        let bin = match bin.object {
173            BytecodeObject::Bytecode(_) => bin.object,
174            _ => {
175                let link_refs = bin
176                    .link_references
177                    .iter()
178                    .flat_map(|(path, names)| {
179                        names.keys().map(move |name| format!("\t{name}: {path}"))
180                    })
181                    .collect::<Vec<String>>()
182                    .join("\n");
183                eyre::bail!(
184                    "Dynamic linking not supported in `create` command - deploy the following library contracts first, then provide the address to link at compile time\n{}",
185                    link_refs
186                )
187            }
188        };
189
190        // Add arguments to constructor
191        let params = if let Some(constructor) = &abi.constructor {
192            let constructor_args =
193                self.constructor_args_path.clone().map(read_constructor_args_file).transpose()?;
194            self.parse_constructor_args(
195                constructor,
196                constructor_args.as_deref().unwrap_or(&self.constructor_args),
197            )?
198        } else {
199            if !self.constructor_args.is_empty() || self.constructor_args_path.is_some() {
200                sh_warn!(
201                    "`{}` has no constructor; ignoring provided constructor arguments",
202                    self.contract.name
203                )?;
204            }
205            vec![]
206        };
207
208        let provider = ProviderBuilder::<N>::from_config(&config)?.build()?;
209
210        // Inject access key ID into TempoOpts so it's set before gas estimation.
211        if let Some(ref ak) = access_key {
212            self.tx.tempo.key_id = Some(ak.key_address);
213        }
214
215        // Resolve `--tempo.lane <name>` against the lanes file (default
216        // `<root>/tempo.lanes.toml`) and populate `self.tx.tempo.nonce_key` from the lane.
217        // Must happen before `self.deploy(...)` so `TempoOpts::apply` picks up the nonce_key.
218        let resolved_lane = resolve_lane(&mut self.tx.tempo, &config.root)?;
219
220        // Whether to broadcast the transaction or not
221        let dry_run = !self.broadcast;
222
223        // Launch browser signer if `--browser` flag is set
224        let browser = self.browser.run::<N>().await?;
225
226        if let Some(browser) = browser {
227            // Deploy with browser wallet
228            let deployer_address = browser.address();
229            self.deploy(
230                abi,
231                bin,
232                params,
233                provider,
234                deployer_address,
235                config.transaction_timeout,
236                id,
237                dry_run,
238                None,
239                Some(browser),
240                resolved_lane,
241            )
242            .await
243        } else if self.unlocked {
244            // Deploy with unlocked account
245            let sender = self.eth.wallet.from.expect("required");
246            self.deploy(
247                abi,
248                bin,
249                params,
250                provider,
251                sender,
252                config.transaction_timeout,
253                id,
254                dry_run,
255                None,
256                None,
257                resolved_lane,
258            )
259            .await
260        } else if let Some(ak) = access_key {
261            // Tempo keychain mode: sign with access key and send raw
262            let signer = match pre_resolved_signer {
263                Some(s) => s,
264                None => self.eth.wallet.signer().await?,
265            };
266            let deployer_address = ak.wallet_address;
267            self.deploy(
268                abi,
269                bin,
270                params,
271                provider,
272                deployer_address,
273                config.transaction_timeout,
274                id,
275                dry_run,
276                Some((signer, ak)),
277                None,
278                resolved_lane,
279            )
280            .await
281        } else {
282            // Deploy with signer
283            let signer = match pre_resolved_signer {
284                Some(s) => s,
285                None => self.eth.wallet.signer().await?,
286            };
287            let deployer = signer.address();
288            let provider = AlloyProviderBuilder::<_, _, N>::default()
289                .wallet(EthereumWallet::new(signer))
290                .connect_provider(provider);
291            self.deploy(
292                abi,
293                bin,
294                params,
295                provider,
296                deployer,
297                config.transaction_timeout,
298                id,
299                dry_run,
300                None,
301                None,
302                resolved_lane,
303            )
304            .await
305        }
306    }
307
308    /// Returns the resolved chain, if any.
309    const fn chain_id(&self) -> Option<Chain> {
310        self.eth.etherscan.chain
311    }
312
313    /// Ensures the verify command can be executed.
314    ///
315    /// This is supposed to check any things that might go wrong when preparing a verify request
316    /// before the contract is deployed. This should prevent situations where a contract is deployed
317    /// successfully, but we fail to prepare a verify request which would require manual
318    /// verification.
319    async fn verify_preflight_check(
320        &self,
321        constructor_args: Option<String>,
322        id: &ArtifactId,
323    ) -> Result<()> {
324        // NOTE: this does not represent the same `VerifyArgs` that would be sent after deployment,
325        // since we don't know the address yet.
326        let mut verify = VerifyArgs {
327            address: Default::default(),
328            contract: Some(self.contract.clone()),
329            compiler_version: Some(id.version.to_string()),
330            constructor_args,
331            constructor_args_path: None,
332            no_auto_detect: false,
333            use_solc: None,
334            num_of_optimizations: None,
335            etherscan: EtherscanOpts {
336                key: self.eth.etherscan.key.clone(),
337                chain: self.chain_id(),
338            },
339            rpc: Default::default(),
340            flatten: false,
341            force: false,
342            skip_is_verified_check: true,
343            watch: true,
344            retry: self.retry,
345            libraries: self.build.libraries.clone(),
346            root: None,
347            verifier: self.verifier.clone(),
348            via_ir: self.build.via_ir,
349            evm_version: self.build.compiler.evm_version,
350            show_standard_json_input: self.show_standard_json_input,
351            guess_constructor_args: false,
352            compilation_profile: Some(id.profile.clone()),
353            language: None,
354            creation_transaction_hash: None,
355        };
356
357        // Check config for Etherscan API Keys to avoid preflight check failing if no
358        // ETHERSCAN_API_KEY value set.
359        let config = verify.load_config()?;
360        verify.etherscan.key =
361            config.get_etherscan_config_with_chain(self.chain_id())?.map(|c| c.key);
362
363        let context = verify.resolve_context().await?;
364
365        verify.verification_provider()?.preflight_verify_check(verify, context).await?;
366        Ok(())
367    }
368
369    /// Deploys the contract
370    #[expect(clippy::too_many_arguments)]
371    async fn deploy<N: Network, P: Provider<N>>(
372        self,
373        abi: JsonAbi,
374        bin: BytecodeObject,
375        args: Vec<DynSolValue>,
376        provider: P,
377        deployer_address: Address,
378        timeout: u64,
379        id: ArtifactId,
380        dry_run: bool,
381        tempo_keychain: Option<(WalletSigner, TempoAccessKeyConfig)>,
382        browser_signer: Option<BrowserSigner<N>>,
383        resolved_lane: Option<ResolvedLane>,
384    ) -> Result<()>
385    where
386        N::TransactionRequest: FoundryTransactionBuilder<N> + serde::Serialize,
387        N::ReceiptResponse: serde::Serialize,
388    {
389        let chain = self.chain_id().context("chain ID not resolved")?;
390
391        let bin = bin.into_bytes().unwrap_or_default();
392        if bin.is_empty() {
393            eyre::bail!("no bytecode found in bin object for {}", self.contract.name)
394        }
395
396        let provider = Arc::new(provider);
397        let factory =
398            ContractFactory::<N, _>::new(abi.clone(), bin.clone(), provider.clone(), timeout);
399
400        let is_args_empty = args.is_empty();
401        let mut deployer =
402            factory.deploy_tokens(args.clone()).context("failed to deploy contract").map_err(|e| {
403                if is_args_empty {
404                    e.wrap_err("no arguments provided for contract constructor; consider --constructor-args or --constructor-args-path")
405                } else {
406                    e
407                }
408            })?;
409        let is_legacy = self.tx.legacy || chain.is_legacy();
410
411        deployer.tx.set_from(deployer_address);
412        deployer.tx.set_chain_id(chain.id());
413        // `to` field must be set explicitly, cannot be None.
414        if deployer.tx.to().is_none() {
415            deployer.tx.set_create();
416        }
417
418        // If Tempo chain fee token must be set
419        if chain.is_tempo() {
420            if let Some(fee_token) = self.tx.tempo.fee_token {
421                deployer.tx.set_fee_token(fee_token);
422            } else {
423                deployer.tx.set_fee_token(DEFAULT_FEE_TOKEN);
424            }
425        }
426
427        // Apply user-provided gas, fee, nonce, and Tempo options.
428        self.tx.apply::<N>(&mut deployer.tx, is_legacy);
429
430        // Convert the CREATE into an AA-compatible call entry since Tempo AA
431        // transactions use a `calls` list instead of `to`+`input`.
432        if chain.is_tempo() {
433            deployer.tx.convert_create_to_call();
434        }
435
436        // For keychain mode, set key_id and nonce_key before gas estimation.
437        if let Some((_, ref ak)) = tempo_keychain {
438            deployer.tx.set_key_id(ak.key_address);
439            if deployer.tx.nonce_key().is_none() {
440                deployer.tx.set_nonce_key(U256::ZERO);
441            }
442        }
443
444        // Fetch defaults from provider for values not specified by user.
445        if self.tx.nonce.is_none() && !self.tx.tempo.expiring_nonce {
446            deployer.tx.set_nonce(provider.get_transaction_count(deployer_address).await?);
447        }
448
449        maybe_print_resolved_lane(resolved_lane.as_ref(), deployer.tx.nonce().unwrap_or_default())?;
450
451        if let Some((_, ref ak)) = tempo_keychain {
452            deployer
453                .tx
454                .prepare_access_key_authorization(
455                    provider.as_ref(),
456                    ak.wallet_address,
457                    ak.key_address,
458                    ak.key_authorization.as_ref(),
459                )
460                .await?;
461        }
462
463        // set access list if specified
464        if let Some(access_list) = match self.tx.access_list {
465            None => None,
466            Some(None) => Some(provider.create_access_list(&deployer.tx).await?.access_list),
467            Some(Some(ref access_list)) => Some(access_list.clone()),
468        } {
469            deployer.tx.set_access_list(access_list);
470        }
471
472        if self.tx.gas_limit.is_none() {
473            let mut estimated = provider.estimate_gas(deployer.tx.clone()).await?;
474
475            // Browser wallets may sign with P256/WebAuthn instead of secp256k1, which
476            // costs more gas for signature verification on Tempo chains. Add a
477            // conservative buffer since we can't determine the signature type beforehand.
478            if browser_signer.is_some() && chain.is_tempo() {
479                estimated += TEMPO_BROWSER_GAS_BUFFER;
480            }
481
482            deployer.tx.set_gas_limit(estimated);
483        }
484
485        if is_legacy {
486            if self.tx.gas_price.is_none() {
487                deployer.tx.set_gas_price(provider.get_gas_price().await?);
488            }
489        } else if self.tx.gas_price.is_none() || self.tx.priority_gas_price.is_none() {
490            let estimate = provider.estimate_eip1559_fees().await.wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?;
491            if self.tx.priority_gas_price.is_none() {
492                deployer.tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas);
493            }
494            if self.tx.gas_price.is_none() {
495                deployer.tx.set_max_fee_per_gas(estimate.max_fee_per_gas);
496            }
497        }
498
499        // Before we actually deploy the contract we try check if the verify settings are valid
500        let mut constructor_args = None;
501        if self.verify {
502            if !args.is_empty() {
503                let encoded_args = abi
504                    .constructor()
505                    .ok_or_else(|| eyre::eyre!("could not find constructor"))?
506                    .abi_encode_input(&args)?;
507                constructor_args = Some(hex::encode(encoded_args));
508            }
509
510            self.verify_preflight_check(constructor_args.clone(), &id).await?;
511        }
512
513        if dry_run {
514            if shell::is_json() {
515                let output = json!({
516                    "contract": self.contract.name,
517                    "transaction": &deployer.tx,
518                    "abi":&abi
519                });
520                sh_println!("{}", serde_json::to_string_pretty(&output)?)?;
521            } else {
522                sh_warn!("Dry run enabled, not broadcasting transaction\n")?;
523
524                sh_println!("Contract: {}", self.contract.name)?;
525                sh_println!(
526                    "Transaction: {}",
527                    serde_json::to_string_pretty(&deployer.tx.clone())?
528                )?;
529                sh_println!("ABI: {}\n", serde_json::to_string_pretty(&abi)?)?;
530
531                sh_warn!(
532                    "To broadcast this transaction, add --broadcast to the previous command. See forge create --help for more."
533                )?;
534            }
535
536            return Ok(());
537        }
538
539        let tempo_sponsor = self.tx.tempo.sponsor_config().await?;
540        if let Some(sponsor) = &tempo_sponsor {
541            sponsor.attach_and_print::<N>(&mut deployer.tx, deployer_address).await?;
542        }
543
544        // Deploy the actual contract
545        let (deployed_contract, receipt) = if let Some(browser) = browser_signer {
546            // Browser wallet signs and sends the transaction
547            let tx_hash = browser.send_transaction_via_browser(deployer.tx).await?;
548
549            // Wait for the transaction to be confirmed, then fetch the receipt.
550            provider
551                .watch_pending_transaction(alloy_provider::PendingTransactionConfig::new(tx_hash))
552                .await?
553                .await?;
554
555            let receipt = provider
556                .get_transaction_receipt(tx_hash)
557                .await?
558                .ok_or_else(|| eyre::eyre!("could not get transaction receipt for {tx_hash}"))?;
559
560            if !receipt.status() {
561                eyre::bail!("deployment transaction failed (receipt status 0): {tx_hash}");
562            }
563
564            let address = receipt
565                .contract_address()
566                .ok_or_else(|| eyre::eyre!("contract was not deployed"))?;
567
568            (address, receipt)
569        } else if let Some((signer, ak)) = tempo_keychain {
570            // Tempo keychain mode: sign with access key provisioning and send raw
571            let raw_tx = deployer
572                .tx
573                .sign_with_access_key(
574                    &provider,
575                    &signer,
576                    ak.wallet_address,
577                    ak.key_address,
578                    ak.key_authorization.as_ref(),
579                )
580                .await?;
581
582            let receipt = provider
583                .send_raw_transaction(&raw_tx)
584                .await?
585                .with_required_confirmations(1)
586                .with_timeout(Some(Duration::from_secs(timeout)))
587                .get_receipt()
588                .await?;
589
590            let address = receipt
591                .contract_address()
592                .ok_or_else(|| eyre::eyre!("contract was not deployed"))?;
593
594            (address, receipt)
595        } else {
596            deployer.send_with_receipt().await?
597        };
598
599        let address = deployed_contract;
600        let tx_hash = receipt.transaction_hash();
601        if shell::is_json() {
602            let output = json!({
603                "deployer": deployer_address.to_string(),
604                "deployedTo": address.to_string(),
605                "transactionHash": tx_hash
606            });
607            sh_println!("{}", serde_json::to_string_pretty(&output)?)?;
608        } else {
609            sh_println!("Deployer: {deployer_address}")?;
610            sh_println!("Deployed to: {address}")?;
611            sh_println!("Transaction hash: {tx_hash:?}")?;
612        };
613
614        if !self.verify {
615            return Ok(());
616        }
617
618        sh_println!("Starting contract verification...")?;
619
620        let num_of_optimizations = if let Some(optimizer) = self.build.compiler.optimize {
621            optimizer.then(|| self.build.compiler.optimizer_runs.unwrap_or(200))
622        } else {
623            self.build.compiler.optimizer_runs
624        };
625
626        let verify = VerifyArgs {
627            address,
628            contract: Some(self.contract),
629            compiler_version: Some(id.version.to_string()),
630            constructor_args,
631            constructor_args_path: None,
632            no_auto_detect: false,
633            use_solc: None,
634            num_of_optimizations,
635            etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain) },
636            rpc: Default::default(),
637            flatten: false,
638            force: false,
639            skip_is_verified_check: true,
640            watch: true,
641            retry: self.retry,
642            libraries: self.build.libraries.clone(),
643            root: None,
644            verifier: self.verifier,
645            via_ir: self.build.via_ir,
646            evm_version: self.build.compiler.evm_version,
647            show_standard_json_input: self.show_standard_json_input,
648            guess_constructor_args: false,
649            compilation_profile: Some(id.profile.clone()),
650            language: None,
651            creation_transaction_hash: Some(tx_hash),
652        };
653        sh_println!("Waiting for {} to detect contract deployment...", verify.verifier.verifier)?;
654        verify.run().await
655    }
656
657    /// Parses the given constructor arguments into a vector of `DynSolValue`s, by matching them
658    /// against the constructor's input params.
659    ///
660    /// Returns a list of parsed values that match the constructor's input params.
661    fn parse_constructor_args(
662        &self,
663        constructor: &Constructor,
664        constructor_args: &[String],
665    ) -> Result<Vec<DynSolValue>> {
666        if constructor.inputs.len() != constructor_args.len() {
667            eyre::bail!(
668                "Constructor argument count mismatch: expected {} but got {}",
669                constructor.inputs.len(),
670                constructor_args.len()
671            );
672        }
673
674        let mut params = Vec::with_capacity(constructor.inputs.len());
675        for (input, arg) in constructor.inputs.iter().zip(constructor_args) {
676            // resolve the input type directly
677            let ty = input
678                .resolve()
679                .wrap_err_with(|| format!("Could not resolve constructor arg: input={input}"))?;
680            params.push((ty, arg));
681        }
682        let params = params.iter().map(|(ty, arg)| (ty, arg.as_str()));
683        parse_tokens(params).map_err(Into::into)
684    }
685}
686
687impl figment::Provider for CreateArgs {
688    fn metadata(&self) -> Metadata {
689        Metadata::named("Create Args Provider")
690    }
691
692    fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
693        let mut dict = Dict::default();
694        if let Some(timeout) = self.timeout {
695            dict.insert("transaction_timeout".to_string(), timeout.into());
696        }
697        Ok(Map::from([(Config::selected_profile(), dict)]))
698    }
699}
700
701/// `ContractFactory` is a [`DeploymentTxFactory`] object with an
702/// [`Arc`] middleware. This type alias exists to preserve backwards
703/// compatibility with less-abstract Contracts.
704///
705/// For full usage docs, see [`DeploymentTxFactory`].
706pub type ContractFactory<N, P> = DeploymentTxFactory<N, P>;
707
708/// Helper which manages the deployment transaction of a smart contract. It
709/// wraps a deployment transaction, and retrieves the contract address output
710/// by it.
711#[derive(Debug)]
712#[must_use = "ContractDeploymentTx does nothing unless you `send` it"]
713pub struct ContractDeploymentTx<N: Network, P, C> {
714    /// the actual deployer, exposed for overriding the defaults
715    pub deployer: Deployer<N, P>,
716    /// marker for the `Contract` type to create afterwards
717    ///
718    /// this type will be used to construct it via `From::from(Contract)`
719    _contract: PhantomData<C>,
720}
721
722impl<N: Network, P: Clone, C> Clone for ContractDeploymentTx<N, P, C> {
723    fn clone(&self) -> Self {
724        Self { deployer: self.deployer.clone(), _contract: self._contract }
725    }
726}
727
728impl<N: Network, P, C> From<Deployer<N, P>> for ContractDeploymentTx<N, P, C> {
729    fn from(deployer: Deployer<N, P>) -> Self {
730        Self { deployer, _contract: PhantomData }
731    }
732}
733
734/// Helper which manages the deployment transaction of a smart contract
735#[derive(Clone, Debug)]
736#[must_use = "Deployer does nothing unless you `send` it"]
737pub struct Deployer<N: Network, P> {
738    /// The deployer's transaction, exposed for overriding the defaults
739    pub tx: N::TransactionRequest,
740    client: P,
741    confs: usize,
742    timeout: u64,
743}
744
745impl<N: Network, P: Provider<N>> Deployer<N, P> {
746    /// Broadcasts the contract deployment transaction and after waiting for it to
747    /// be sufficiently confirmed (default: 1), it returns a tuple with the [`Address`] at the
748    /// deployed contract's address and the corresponding receipt.
749    pub async fn send_with_receipt(
750        self,
751    ) -> Result<(Address, N::ReceiptResponse), ContractDeploymentError> {
752        let receipt = self
753            .client
754            .borrow()
755            .send_transaction(self.tx)
756            .await?
757            .with_required_confirmations(self.confs as u64)
758            .with_timeout(Some(Duration::from_secs(self.timeout)))
759            .get_receipt()
760            .await?;
761
762        if !receipt.status() {
763            return Err(ContractDeploymentError::DeploymentFailed(receipt.transaction_hash()));
764        }
765
766        let address =
767            receipt.contract_address().ok_or(ContractDeploymentError::ContractNotDeployed)?;
768
769        Ok((address, receipt))
770    }
771}
772
773/// To deploy a contract to the Ethereum network, a [`ContractFactory`] can be
774/// created which manages the Contract bytecode and Application Binary Interface
775/// (ABI), usually generated from the Solidity compiler.
776#[derive(Clone, Debug)]
777pub struct DeploymentTxFactory<N: Network, P> {
778    client: P,
779    abi: JsonAbi,
780    bytecode: Bytes,
781    timeout: u64,
782    _network: PhantomData<N>,
783}
784
785impl<N: Network, P: Provider<N> + Clone> DeploymentTxFactory<N, P> {
786    /// Creates a factory for deployment of the Contract with bytecode, and the
787    /// constructor defined in the abi. The client will be used to send any deployment
788    /// transaction.
789    pub const fn new(abi: JsonAbi, bytecode: Bytes, client: P, timeout: u64) -> Self {
790        Self { client, abi, bytecode, timeout, _network: PhantomData }
791    }
792
793    /// Create a deployment tx using the provided tokens as constructor
794    /// arguments
795    pub fn deploy_tokens(
796        self,
797        params: Vec<DynSolValue>,
798    ) -> Result<Deployer<N, P>, ContractDeploymentError>
799    where
800        N::TransactionRequest: FoundryTransactionBuilder<N>,
801    {
802        // Encode the constructor args & concatenate with the bytecode if necessary
803        let data: Bytes = match (self.abi.constructor(), params.is_empty()) {
804            (None, false) => return Err(ContractDeploymentError::ConstructorError),
805            (None, true) => self.bytecode.clone(),
806            (Some(constructor), _) => {
807                let input: Bytes = constructor
808                    .abi_encode_input(&params)
809                    .map_err(ContractDeploymentError::DetokenizationError)?
810                    .into();
811                // Concatenate the bytecode and abi-encoded constructor call.
812                self.bytecode.iter().copied().chain(input).collect()
813            }
814        };
815
816        // create the tx object. Since we're deploying a contract, `to` is `None`
817        let mut tx = N::TransactionRequest::default();
818        tx.set_input(data);
819        Ok(Deployer { client: self.client.clone(), tx, confs: 1, timeout: self.timeout })
820    }
821}
822
823#[derive(thiserror::Error, Debug)]
824/// An Error which is thrown when interacting with a smart contract
825pub enum ContractDeploymentError {
826    #[error("constructor is not defined in the ABI")]
827    ConstructorError,
828    #[error(transparent)]
829    DetokenizationError(#[from] alloy_dyn_abi::Error),
830    #[error("contract was not deployed")]
831    ContractNotDeployed,
832    #[error("deployment transaction failed (receipt status 0): {0}")]
833    DeploymentFailed(alloy_primitives::TxHash),
834    #[error(transparent)]
835    RpcError(#[from] TransportError),
836}
837
838impl From<PendingTransactionError> for ContractDeploymentError {
839    fn from(_err: PendingTransactionError) -> Self {
840        Self::ContractNotDeployed
841    }
842}
843
844#[cfg(test)]
845mod tests {
846    use super::*;
847    use alloy_primitives::I256;
848
849    #[test]
850    fn can_parse_create() {
851        let args: CreateArgs = CreateArgs::parse_from([
852            "foundry-cli",
853            "src/Domains.sol:Domains",
854            "--verify",
855            "--retries",
856            "10",
857            "--delay",
858            "30",
859        ]);
860        assert_eq!(args.retry.retries, 10);
861        assert_eq!(args.retry.delay, 30);
862    }
863    #[test]
864    fn can_parse_chain_id() {
865        let args: CreateArgs = CreateArgs::parse_from([
866            "foundry-cli",
867            "src/Domains.sol:Domains",
868            "--verify",
869            "--retries",
870            "10",
871            "--delay",
872            "30",
873            "--chain-id",
874            "9999",
875        ]);
876        assert_eq!(args.chain_id().map(|c| c.id()), Some(9999));
877    }
878
879    #[test]
880    fn test_parse_constructor_args() {
881        let args: CreateArgs = CreateArgs::parse_from([
882            "foundry-cli",
883            "src/Domains.sol:Domains",
884            "--constructor-args",
885            "Hello",
886        ]);
887        let constructor: Constructor = serde_json::from_str(r#"{"type":"constructor","inputs":[{"name":"_name","type":"string","internalType":"string"}],"stateMutability":"nonpayable"}"#).unwrap();
888        let params = args.parse_constructor_args(&constructor, &args.constructor_args).unwrap();
889        assert_eq!(params, vec![DynSolValue::String("Hello".to_string())]);
890    }
891
892    #[test]
893    fn test_parse_tuple_constructor_args() {
894        let args: CreateArgs = CreateArgs::parse_from([
895            "foundry-cli",
896            "src/Domains.sol:Domains",
897            "--constructor-args",
898            "[(1,2), (2,3), (3,4)]",
899        ]);
900        let constructor: Constructor = serde_json::from_str(r#"{"type":"constructor","inputs":[{"name":"_points","type":"tuple[]","internalType":"struct Point[]","components":[{"name":"x","type":"uint256","internalType":"uint256"},{"name":"y","type":"uint256","internalType":"uint256"}]}],"stateMutability":"nonpayable"}"#).unwrap();
901        let _params = args.parse_constructor_args(&constructor, &args.constructor_args).unwrap();
902    }
903
904    #[test]
905    fn test_parse_int_constructor_args() {
906        let args: CreateArgs = CreateArgs::parse_from([
907            "foundry-cli",
908            "src/Domains.sol:Domains",
909            "--constructor-args",
910            "-5",
911        ]);
912        let constructor: Constructor = serde_json::from_str(r#"{"type":"constructor","inputs":[{"name":"_name","type":"int256","internalType":"int256"}],"stateMutability":"nonpayable"}"#).unwrap();
913        let params = args.parse_constructor_args(&constructor, &args.constructor_args).unwrap();
914        assert_eq!(params, vec![DynSolValue::Int(I256::unchecked_from(-5), 256)]);
915    }
916}