forge/cmd/
create.rs

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