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