Skip to main content

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, 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/// 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).await && config.auto_detect_remappings
110        {
111            // need to re-configure here to also catch additional remappings
112            config = self.load_config()?;
113        }
114
115        // Find Project & Compile
116        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        // Add arguments to constructor
147        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        // respect chain, if set explicitly via cmd args
161        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        // Whether to broadcast the transaction or not
168        let dry_run = !self.broadcast;
169
170        if self.unlocked {
171            // Deploy with unlocked account
172            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            // Deploy with signer
187            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    /// Returns the provided chain id, if any.
208    fn chain_id(&self) -> Option<u64> {
209        self.eth.etherscan.chain.map(|chain| chain.id())
210    }
211
212    /// Ensures the verify command can be executed.
213    ///
214    /// This is supposed to check any things that might go wrong when preparing a verify request
215    /// before the contract is deployed. This should prevent situations where a contract is deployed
216    /// successfully, but we fail to prepare a verify request which would require manual
217    /// verification.
218    async fn verify_preflight_check(
219        &self,
220        constructor_args: Option<String>,
221        chain: u64,
222        id: &ArtifactId,
223    ) -> Result<()> {
224        // NOTE: this does not represent the same `VerifyArgs` that would be sent after deployment,
225        // since we don't know the address yet.
226        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        // Check config for Etherscan API Keys to avoid preflight check failing if no
258        // ETHERSCAN_API_KEY value set.
259        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    /// Deploys the contract
270    #[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        // `to` field must be set explicitly, cannot be None.
305        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        // set tx value if specified
315        if let Some(value) = self.tx.value {
316            deployer.tx.set_value(value);
317        }
318
319        // set access list if specified
320        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        // Before we actually deploy the contract we try check if the verify settings are valid
359        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        // Deploy the actual contract
399        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    /// Parses the given constructor arguments into a vector of `DynSolValue`s, by matching them
459    /// against the constructor's input params.
460    ///
461    /// Returns a list of parsed values that match the constructor's input params.
462    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            // resolve the input type directly
478            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
502/// `ContractFactory` is a [`DeploymentTxFactory`] object with an
503/// [`Arc`] middleware. This type alias exists to preserve backwards
504/// compatibility with less-abstract Contracts.
505///
506/// For full usage docs, see [`DeploymentTxFactory`].
507pub type ContractFactory<P> = DeploymentTxFactory<P>;
508
509/// Helper which manages the deployment transaction of a smart contract. It
510/// wraps a deployment transaction, and retrieves the contract address output
511/// by it.
512#[derive(Debug)]
513#[must_use = "ContractDeploymentTx does nothing unless you `send` it"]
514pub struct ContractDeploymentTx<P, C> {
515    /// the actual deployer, exposed for overriding the defaults
516    pub deployer: Deployer<P>,
517    /// marker for the `Contract` type to create afterwards
518    ///
519    /// this type will be used to construct it via `From::from(Contract)`
520    _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/// Helper which manages the deployment transaction of a smart contract
536#[derive(Clone, Debug)]
537#[must_use = "Deployer does nothing unless you `send` it"]
538pub struct Deployer<P> {
539    /// The deployer's transaction, exposed for overriding the defaults
540    pub tx: WithOtherFields<TransactionRequest>,
541    client: P,
542    confs: usize,
543    timeout: u64,
544}
545
546impl<P: Provider<AnyNetwork>> Deployer<P> {
547    /// Broadcasts the contract deployment transaction and after waiting for it to
548    /// be sufficiently confirmed (default: 1), it returns a tuple with the [`Address`] at the
549    /// deployed contract's address and the corresponding [`AnyTransactionReceipt`].
550    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/// To deploy a contract to the Ethereum network, a [`ContractFactory`] can be
571/// created which manages the Contract bytecode and Application Binary Interface
572/// (ABI), usually generated from the Solidity compiler.
573#[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    /// Creates a factory for deployment of the Contract with bytecode, and the
583    /// constructor defined in the abi. The client will be used to send any deployment
584    /// transaction.
585    pub fn new(abi: JsonAbi, bytecode: Bytes, client: P, timeout: u64) -> Self {
586        Self { client, abi, bytecode, timeout }
587    }
588
589    /// Create a deployment tx using the provided tokens as constructor
590    /// arguments
591    pub fn deploy_tokens(
592        self,
593        params: Vec<DynSolValue>,
594    ) -> Result<Deployer<P>, ContractDeploymentError> {
595        // Encode the constructor args & concatenate with the bytecode if necessary
596        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(&params)
602                    .map_err(ContractDeploymentError::DetokenizationError)?
603                    .into();
604                // Concatenate the bytecode and abi-encoded constructor call.
605                self.bytecode.iter().copied().chain(input).collect()
606            }
607        };
608
609        // create the tx object. Since we're deploying a contract, `to` is `None`
610        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)]
617/// An Error which is thrown when interacting with a smart contract
618pub 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}