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, find_contract_artifacts, read_constructor_args_file},
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#[derive(Clone, Debug, Parser)]
42pub struct CreateArgs {
43 contract: ContractInfo,
45
46 #[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 #[arg(
58 long,
59 value_hint = ValueHint::FilePath,
60 value_name = "PATH",
61 )]
62 constructor_args_path: Option<PathBuf>,
63
64 #[arg(long)]
66 pub broadcast: bool,
67
68 #[arg(long)]
70 verify: bool,
71
72 #[arg(long, requires = "from")]
74 unlocked: bool,
75
76 #[arg(long, requires = "verify")]
81 show_standard_json_input: bool,
82
83 #[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 pub async fn run(mut self) -> Result<()> {
106 let mut config = self.load_config()?;
107
108 if install::install_missing_dependencies(&mut config).await && config.auto_detect_remappings
110 {
111 config = self.load_config()?;
113 }
114
115 let project = config.project()?;
117
118 let target_path = if let Some(ref mut path) = self.contract.path {
119 canonicalize(project.root().join(path))?
120 } else {
121 project.find_contract_path(&self.contract.name)?
122 };
123
124 let output = compile::compile_target(&target_path, &project, shell::is_json())?;
125
126 let (abi, bin, id) = find_contract_artifacts(output, &target_path, &self.contract.name)?;
127
128 let bin = match bin.object {
129 BytecodeObject::Bytecode(_) => bin.object,
130 _ => {
131 let link_refs = bin
132 .link_references
133 .iter()
134 .flat_map(|(path, names)| {
135 names.keys().map(move |name| format!("\t{name}: {path}"))
136 })
137 .collect::<Vec<String>>()
138 .join("\n");
139 eyre::bail!(
140 "Dynamic linking not supported in `create` command - deploy the following library contracts first, then provide the address to link at compile time\n{}",
141 link_refs
142 )
143 }
144 };
145
146 let params = if let Some(constructor) = &abi.constructor {
148 let constructor_args =
149 self.constructor_args_path.clone().map(read_constructor_args_file).transpose()?;
150 self.parse_constructor_args(
151 constructor,
152 constructor_args.as_deref().unwrap_or(&self.constructor_args),
153 )?
154 } else {
155 vec![]
156 };
157
158 let provider = utils::get_provider(&config)?;
159
160 let chain_id = if let Some(chain_id) = self.chain_id() {
162 chain_id
163 } else {
164 provider.get_chain_id().await?
165 };
166
167 let dry_run = !self.broadcast;
169
170 if self.unlocked {
171 let sender = self.eth.wallet.from.expect("required");
173 self.deploy(
174 abi,
175 bin,
176 params,
177 provider,
178 chain_id,
179 sender,
180 config.transaction_timeout,
181 id,
182 dry_run,
183 )
184 .await
185 } else {
186 let signer = self.eth.wallet.signer().await?;
188 let deployer = signer.address();
189 let provider = ProviderBuilder::<_, _, AnyNetwork>::default()
190 .wallet(EthereumWallet::new(signer))
191 .connect_provider(provider);
192 self.deploy(
193 abi,
194 bin,
195 params,
196 provider,
197 chain_id,
198 deployer,
199 config.transaction_timeout,
200 id,
201 dry_run,
202 )
203 .await
204 }
205 }
206
207 fn chain_id(&self) -> Option<u64> {
209 self.eth.etherscan.chain.map(|chain| chain.id())
210 }
211
212 async fn verify_preflight_check(
219 &self,
220 constructor_args: Option<String>,
221 chain: u64,
222 id: &ArtifactId,
223 ) -> Result<()> {
224 let mut verify = VerifyArgs {
227 address: Default::default(),
228 contract: Some(self.contract.clone()),
229 compiler_version: Some(id.version.to_string()),
230 constructor_args,
231 constructor_args_path: None,
232 no_auto_detect: false,
233 use_solc: None,
234 num_of_optimizations: None,
235 etherscan: EtherscanOpts {
236 key: self.eth.etherscan.key.clone(),
237 chain: Some(chain.into()),
238 },
239 rpc: Default::default(),
240 flatten: false,
241 force: false,
242 skip_is_verified_check: true,
243 watch: true,
244 retry: self.retry,
245 libraries: self.build.libraries.clone(),
246 root: None,
247 verifier: self.verifier.clone(),
248 via_ir: self.build.via_ir,
249 evm_version: self.build.compiler.evm_version,
250 show_standard_json_input: self.show_standard_json_input,
251 guess_constructor_args: false,
252 compilation_profile: Some(id.profile.to_string()),
253 language: None,
254 creation_transaction_hash: None,
255 };
256
257 let config = verify.load_config()?;
260 verify.etherscan.key =
261 config.get_etherscan_config_with_chain(Some(chain.into()))?.map(|c| c.key);
262
263 let context = verify.resolve_context().await?;
264
265 verify.verification_provider()?.preflight_verify_check(verify, context).await?;
266 Ok(())
267 }
268
269 #[expect(clippy::too_many_arguments)]
271 async fn deploy<P: Provider<AnyNetwork>>(
272 self,
273 abi: JsonAbi,
274 bin: BytecodeObject,
275 args: Vec<DynSolValue>,
276 provider: P,
277 chain: u64,
278 deployer_address: Address,
279 timeout: u64,
280 id: ArtifactId,
281 dry_run: bool,
282 ) -> Result<()> {
283 let bin = bin.into_bytes().unwrap_or_default();
284 if bin.is_empty() {
285 eyre::bail!("no bytecode found in bin object for {}", self.contract.name)
286 }
287
288 let provider = Arc::new(provider);
289 let factory = ContractFactory::new(abi.clone(), bin.clone(), provider.clone(), timeout);
290
291 let is_args_empty = args.is_empty();
292 let mut deployer =
293 factory.deploy_tokens(args.clone()).context("failed to deploy contract").map_err(|e| {
294 if is_args_empty {
295 e.wrap_err("no arguments provided for contract constructor; consider --constructor-args or --constructor-args-path")
296 } else {
297 e
298 }
299 })?;
300 let is_legacy = self.tx.legacy || Chain::from(chain).is_legacy();
301
302 deployer.tx.set_from(deployer_address);
303 deployer.tx.set_chain_id(chain);
304 if deployer.tx.to.is_none() {
306 deployer.tx.set_create();
307 }
308 deployer.tx.set_nonce(if let Some(nonce) = self.tx.nonce {
309 Ok(nonce.to())
310 } else {
311 provider.get_transaction_count(deployer_address).await
312 }?);
313
314 if let Some(value) = self.tx.value {
316 deployer.tx.set_value(value);
317 }
318
319 if let Some(access_list) = match self.tx.access_list {
321 None => None,
322 Some(None) => Some(provider.create_access_list(&deployer.tx).await?.access_list),
323 Some(Some(ref access_list)) => Some(access_list.clone()),
324 } {
325 deployer.tx.set_access_list(access_list);
326 }
327
328 deployer.tx.set_gas_limit(if let Some(gas_limit) = self.tx.gas_limit {
329 Ok(gas_limit.to())
330 } else {
331 provider.estimate_gas(deployer.tx.clone()).await
332 }?);
333
334 if is_legacy {
335 let gas_price = if let Some(gas_price) = self.tx.gas_price {
336 gas_price.to()
337 } else {
338 provider.get_gas_price().await?
339 };
340 deployer.tx.set_gas_price(gas_price);
341 } else {
342 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.")?;
343 let priority_fee = if let Some(priority_fee) = self.tx.priority_gas_price {
344 priority_fee.to()
345 } else {
346 estimate.max_priority_fee_per_gas
347 };
348 let max_fee = if let Some(max_fee) = self.tx.gas_price {
349 max_fee.to()
350 } else {
351 estimate.max_fee_per_gas
352 };
353
354 deployer.tx.set_max_fee_per_gas(max_fee);
355 deployer.tx.set_max_priority_fee_per_gas(priority_fee);
356 }
357
358 let mut constructor_args = None;
360 if self.verify {
361 if !args.is_empty() {
362 let encoded_args = abi
363 .constructor()
364 .ok_or_else(|| eyre::eyre!("could not find constructor"))?
365 .abi_encode_input(&args)?;
366 constructor_args = Some(hex::encode(encoded_args));
367 }
368
369 self.verify_preflight_check(constructor_args.clone(), chain, &id).await?;
370 }
371
372 if dry_run {
373 if !shell::is_json() {
374 sh_warn!("Dry run enabled, not broadcasting transaction\n")?;
375
376 sh_println!("Contract: {}", self.contract.name)?;
377 sh_println!(
378 "Transaction: {}",
379 serde_json::to_string_pretty(&deployer.tx.clone())?
380 )?;
381 sh_println!("ABI: {}\n", serde_json::to_string_pretty(&abi)?)?;
382
383 sh_warn!(
384 "To broadcast this transaction, add --broadcast to the previous command. See forge create --help for more."
385 )?;
386 } else {
387 let output = json!({
388 "contract": self.contract.name,
389 "transaction": &deployer.tx,
390 "abi":&abi
391 });
392 sh_println!("{}", serde_json::to_string_pretty(&output)?)?;
393 }
394
395 return Ok(());
396 }
397
398 let (deployed_contract, receipt) = deployer.send_with_receipt().await?;
400
401 let address = deployed_contract;
402 if shell::is_json() {
403 let output = json!({
404 "deployer": deployer_address.to_string(),
405 "deployedTo": address.to_string(),
406 "transactionHash": receipt.transaction_hash
407 });
408 sh_println!("{}", serde_json::to_string_pretty(&output)?)?;
409 } else {
410 sh_println!("Deployer: {deployer_address}")?;
411 sh_println!("Deployed to: {address}")?;
412 sh_println!("Transaction hash: {:?}", receipt.transaction_hash)?;
413 };
414
415 if !self.verify {
416 return Ok(());
417 }
418
419 sh_println!("Starting contract verification...")?;
420
421 let num_of_optimizations = if let Some(optimizer) = self.build.compiler.optimize {
422 if optimizer { Some(self.build.compiler.optimizer_runs.unwrap_or(200)) } else { None }
423 } else {
424 self.build.compiler.optimizer_runs
425 };
426
427 let verify = VerifyArgs {
428 address,
429 contract: Some(self.contract),
430 compiler_version: Some(id.version.to_string()),
431 constructor_args,
432 constructor_args_path: None,
433 no_auto_detect: false,
434 use_solc: None,
435 num_of_optimizations,
436 etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) },
437 rpc: Default::default(),
438 flatten: false,
439 force: false,
440 skip_is_verified_check: true,
441 watch: true,
442 retry: self.retry,
443 libraries: self.build.libraries.clone(),
444 root: None,
445 verifier: self.verifier,
446 via_ir: self.build.via_ir,
447 evm_version: self.build.compiler.evm_version,
448 show_standard_json_input: self.show_standard_json_input,
449 guess_constructor_args: false,
450 compilation_profile: Some(id.profile.to_string()),
451 language: None,
452 creation_transaction_hash: Some(receipt.transaction_hash),
453 };
454 sh_println!("Waiting for {} to detect contract deployment...", verify.verifier.verifier)?;
455 verify.run().await
456 }
457
458 fn parse_constructor_args(
463 &self,
464 constructor: &Constructor,
465 constructor_args: &[String],
466 ) -> Result<Vec<DynSolValue>> {
467 if constructor.inputs.len() != constructor_args.len() {
468 eyre::bail!(
469 "Constructor argument count mismatch: expected {} but got {}",
470 constructor.inputs.len(),
471 constructor_args.len()
472 );
473 }
474
475 let mut params = Vec::with_capacity(constructor.inputs.len());
476 for (input, arg) in constructor.inputs.iter().zip(constructor_args) {
477 let ty = input
479 .resolve()
480 .wrap_err_with(|| format!("Could not resolve constructor arg: input={input}"))?;
481 params.push((ty, arg));
482 }
483 let params = params.iter().map(|(ty, arg)| (ty, arg.as_str()));
484 parse_tokens(params).map_err(Into::into)
485 }
486}
487
488impl figment::Provider for CreateArgs {
489 fn metadata(&self) -> Metadata {
490 Metadata::named("Create Args Provider")
491 }
492
493 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
494 let mut dict = Dict::default();
495 if let Some(timeout) = self.timeout {
496 dict.insert("transaction_timeout".to_string(), timeout.into());
497 }
498 Ok(Map::from([(Config::selected_profile(), dict)]))
499 }
500}
501
502pub type ContractFactory<P> = DeploymentTxFactory<P>;
508
509#[derive(Debug)]
513#[must_use = "ContractDeploymentTx does nothing unless you `send` it"]
514pub struct ContractDeploymentTx<P, C> {
515 pub deployer: Deployer<P>,
517 _contract: PhantomData<C>,
521}
522
523impl<P: Clone, C> Clone for ContractDeploymentTx<P, C> {
524 fn clone(&self) -> Self {
525 Self { deployer: self.deployer.clone(), _contract: self._contract }
526 }
527}
528
529impl<P, C> From<Deployer<P>> for ContractDeploymentTx<P, C> {
530 fn from(deployer: Deployer<P>) -> Self {
531 Self { deployer, _contract: PhantomData }
532 }
533}
534
535#[derive(Clone, Debug)]
537#[must_use = "Deployer does nothing unless you `send` it"]
538pub struct Deployer<P> {
539 pub tx: WithOtherFields<TransactionRequest>,
541 client: P,
542 confs: usize,
543 timeout: u64,
544}
545
546impl<P: Provider<AnyNetwork>> Deployer<P> {
547 pub async fn send_with_receipt(
551 self,
552 ) -> Result<(Address, AnyTransactionReceipt), ContractDeploymentError> {
553 let receipt = self
554 .client
555 .borrow()
556 .send_transaction(self.tx)
557 .await?
558 .with_required_confirmations(self.confs as u64)
559 .with_timeout(Some(Duration::from_secs(self.timeout)))
560 .get_receipt()
561 .await?;
562
563 let address =
564 receipt.contract_address.ok_or(ContractDeploymentError::ContractNotDeployed)?;
565
566 Ok((address, receipt))
567 }
568}
569
570#[derive(Clone, Debug)]
574pub struct DeploymentTxFactory<P> {
575 client: P,
576 abi: JsonAbi,
577 bytecode: Bytes,
578 timeout: u64,
579}
580
581impl<P: Provider<AnyNetwork> + Clone> DeploymentTxFactory<P> {
582 pub fn new(abi: JsonAbi, bytecode: Bytes, client: P, timeout: u64) -> Self {
586 Self { client, abi, bytecode, timeout }
587 }
588
589 pub fn deploy_tokens(
592 self,
593 params: Vec<DynSolValue>,
594 ) -> Result<Deployer<P>, ContractDeploymentError> {
595 let data: Bytes = match (self.abi.constructor(), params.is_empty()) {
597 (None, false) => return Err(ContractDeploymentError::ConstructorError),
598 (None, true) => self.bytecode.clone(),
599 (Some(constructor), _) => {
600 let input: Bytes = constructor
601 .abi_encode_input(¶ms)
602 .map_err(ContractDeploymentError::DetokenizationError)?
603 .into();
604 self.bytecode.iter().copied().chain(input).collect()
606 }
607 };
608
609 let tx = WithOtherFields::new(TransactionRequest::default().input(data.into()));
611
612 Ok(Deployer { client: self.client.clone(), tx, confs: 1, timeout: self.timeout })
613 }
614}
615
616#[derive(thiserror::Error, Debug)]
617pub enum ContractDeploymentError {
619 #[error("constructor is not defined in the ABI")]
620 ConstructorError,
621 #[error(transparent)]
622 DetokenizationError(#[from] alloy_dyn_abi::Error),
623 #[error("contract was not deployed")]
624 ContractNotDeployed,
625 #[error(transparent)]
626 RpcError(#[from] TransportError),
627}
628
629impl From<PendingTransactionError> for ContractDeploymentError {
630 fn from(_err: PendingTransactionError) -> Self {
631 Self::ContractNotDeployed
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638 use alloy_primitives::I256;
639
640 #[test]
641 fn can_parse_create() {
642 let args: CreateArgs = CreateArgs::parse_from([
643 "foundry-cli",
644 "src/Domains.sol:Domains",
645 "--verify",
646 "--retries",
647 "10",
648 "--delay",
649 "30",
650 ]);
651 assert_eq!(args.retry.retries, 10);
652 assert_eq!(args.retry.delay, 30);
653 }
654 #[test]
655 fn can_parse_chain_id() {
656 let args: CreateArgs = CreateArgs::parse_from([
657 "foundry-cli",
658 "src/Domains.sol:Domains",
659 "--verify",
660 "--retries",
661 "10",
662 "--delay",
663 "30",
664 "--chain-id",
665 "9999",
666 ]);
667 assert_eq!(args.chain_id(), Some(9999));
668 }
669
670 #[test]
671 fn test_parse_constructor_args() {
672 let args: CreateArgs = CreateArgs::parse_from([
673 "foundry-cli",
674 "src/Domains.sol:Domains",
675 "--constructor-args",
676 "Hello",
677 ]);
678 let constructor: Constructor = serde_json::from_str(r#"{"type":"constructor","inputs":[{"name":"_name","type":"string","internalType":"string"}],"stateMutability":"nonpayable"}"#).unwrap();
679 let params = args.parse_constructor_args(&constructor, &args.constructor_args).unwrap();
680 assert_eq!(params, vec![DynSolValue::String("Hello".to_string())]);
681 }
682
683 #[test]
684 fn test_parse_tuple_constructor_args() {
685 let args: CreateArgs = CreateArgs::parse_from([
686 "foundry-cli",
687 "src/Domains.sol:Domains",
688 "--constructor-args",
689 "[(1,2), (2,3), (3,4)]",
690 ]);
691 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();
692 let _params = args.parse_constructor_args(&constructor, &args.constructor_args).unwrap();
693 }
694
695 #[test]
696 fn test_parse_int_constructor_args() {
697 let args: CreateArgs = CreateArgs::parse_from([
698 "foundry-cli",
699 "src/Domains.sol:Domains",
700 "--constructor-args",
701 "-5",
702 ]);
703 let constructor: Constructor = serde_json::from_str(r#"{"type":"constructor","inputs":[{"name":"_name","type":"int256","internalType":"int256"}],"stateMutability":"nonpayable"}"#).unwrap();
704 let params = args.parse_constructor_args(&constructor, &args.constructor_args).unwrap();
705 assert_eq!(params, vec![DynSolValue::Int(I256::unchecked_from(-5), 256)]);
706 }
707}