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#[derive(Clone, Debug, Parser)]
51pub struct CreateArgs {
52 contract: ContractInfo,
54
55 #[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 #[arg(
67 long,
68 value_hint = ValueHint::FilePath,
69 value_name = "PATH",
70 )]
71 constructor_args_path: Option<PathBuf>,
72
73 #[arg(long)]
75 pub broadcast: bool,
76
77 #[arg(long)]
79 verify: bool,
80
81 #[arg(long, requires = "from")]
83 unlocked: bool,
84
85 #[arg(long, requires = "verify")]
90 show_standard_json_input: bool,
91
92 #[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 #[command(flatten)]
113 browser: BrowserWalletOpts,
114}
115
116impl CreateArgs {
117 pub async fn run(mut self) -> Result<()> {
119 let (signer, tempo_access_key) = self.eth.wallet.maybe_signer().await?;
120
121 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 if install::install_missing_dependencies(&mut config).await && config.auto_detect_remappings
154 {
155 config = self.load_config()?;
157 }
158
159 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 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 if let Some(ref ak) = access_key {
212 self.tx.tempo.key_id = Some(ak.key_address);
213 }
214
215 let resolved_lane = resolve_lane(&mut self.tx.tempo, &config.root)?;
219
220 let dry_run = !self.broadcast;
222
223 let browser = self.browser.run::<N>().await?;
225
226 if let Some(browser) = browser {
227 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 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 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 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 const fn chain_id(&self) -> Option<Chain> {
310 self.eth.etherscan.chain
311 }
312
313 async fn verify_preflight_check(
320 &self,
321 constructor_args: Option<String>,
322 id: &ArtifactId,
323 ) -> Result<()> {
324 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 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 #[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 if deployer.tx.to().is_none() {
415 deployer.tx.set_create();
416 }
417
418 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 self.tx.apply::<N>(&mut deployer.tx, is_legacy);
429
430 if chain.is_tempo() {
433 deployer.tx.convert_create_to_call();
434 }
435
436 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 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 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 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 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 let (deployed_contract, receipt) = if let Some(browser) = browser_signer {
546 let tx_hash = browser.send_transaction_via_browser(deployer.tx).await?;
548
549 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 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 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 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
701pub type ContractFactory<N, P> = DeploymentTxFactory<N, P>;
707
708#[derive(Debug)]
712#[must_use = "ContractDeploymentTx does nothing unless you `send` it"]
713pub struct ContractDeploymentTx<N: Network, P, C> {
714 pub deployer: Deployer<N, P>,
716 _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#[derive(Clone, Debug)]
736#[must_use = "Deployer does nothing unless you `send` it"]
737pub struct Deployer<N: Network, P> {
738 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 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#[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 pub const fn new(abi: JsonAbi, bytecode: Bytes, client: P, timeout: u64) -> Self {
790 Self { client, abi, bytecode, timeout, _network: PhantomData }
791 }
792
793 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 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(¶ms)
809 .map_err(ContractDeploymentError::DetokenizationError)?
810 .into();
811 self.bytecode.iter().copied().chain(input).collect()
813 }
814 };
815
816 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)]
824pub 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}