stylus_tools/core/deployment/
deployer.rs

1// Copyright 2025, Offchain Labs, Inc.
2// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/licenses/COPYRIGHT.md
3
4use crate::core::deployment::prelude::DeploymentCalldata;
5use crate::core::deployment::DeploymentError;
6use crate::core::deployment::DeploymentError::NoContractAddress;
7use alloy::dyn_abi::{DynSolValue, JsonAbiExt, Specifier};
8use alloy::json_abi::Constructor;
9use alloy::primitives::B256;
10use alloy::rpc::types::TransactionReceipt;
11use alloy::{
12    primitives::{address, Address, U256},
13    providers::Provider,
14    sol,
15    sol_types::SolCall,
16    sol_types::SolEvent,
17};
18use eyre::{Context, ErrReport};
19
20pub const ADDRESS: Address = address!("cEcba2F1DC234f70Dd89F2041029807F8D03A990");
21
22sol! {
23    #[sol(rpc)]
24    interface StylusDeployer {
25        event ContractDeployed(address deployedContract);
26
27        function deploy(
28            bytes calldata bytecode,
29            bytes calldata initData,
30            uint256 initValue,
31            bytes32 salt
32        ) public payable returns (address);
33    }
34
35    function stylus_constructor();
36}
37
38#[derive(Debug, thiserror::Error)]
39pub enum DeployerError {
40    #[error("rpc error: {0}")]
41    Rpc(#[from] alloy::transports::RpcError<alloy::transports::TransportErrorKind>),
42
43    #[error("deployment failed during gas estimation")]
44    GasEstimationFailure,
45}
46
47pub async fn parse_tx_calldata(
48    contract_code: &[u8],
49    constructor: &Constructor,
50    constructor_value: U256,
51    constructor_args: Vec<String>,
52    deployer_salt: B256,
53    provider: &impl Provider,
54) -> Result<Vec<u8>, ErrReport> {
55    let mut arg_values = Vec::<DynSolValue>::with_capacity(constructor_args.len());
56    for (arg, param) in constructor_args.iter().zip(constructor.inputs.iter()) {
57        let ty = param
58            .resolve()
59            .wrap_err_with(|| format!("could not resolve constructor arg: {param}"))?;
60        let value = ty
61            .coerce_str(arg)
62            .wrap_err_with(|| format!("could not parse constructor arg: {param}"))?;
63        arg_values.push(value);
64    }
65
66    let calldata_args = constructor.abi_encode_input_raw(&arg_values)?;
67
68    let mut constructor_calldata = Vec::from(stylus_constructorCall::SELECTOR);
69    constructor_calldata.extend(calldata_args);
70
71    let tx_calldata = StylusDeployer::new(Address::ZERO, provider)
72        .deploy_call(
73            DeploymentCalldata::new(contract_code).into(),
74            constructor_calldata.into(),
75            constructor_value,
76            deployer_salt,
77        )
78        .calldata()
79        .to_vec();
80    Ok(tx_calldata)
81}
82
83/// Gets the Stylus-contract address that was deployed using the deployer.
84pub fn get_address_from_receipt(receipt: &TransactionReceipt) -> Result<Address, DeploymentError> {
85    receipt
86        .clone()
87        .into_inner()
88        .logs()
89        .iter()
90        .find(|log| match log.topics().first() {
91            Some(topic) => topic.0 == StylusDeployer::ContractDeployed::SIGNATURE_HASH,
92            None => false,
93        })
94        .map(|log| {
95            if log.data().data.len() != 32 {
96                return Err(NoContractAddress("from ContractDeployed log".to_string()));
97            }
98            Ok(Address::from_slice(&log.data().data[12..32]))
99        })
100        .unwrap_or_else(|| Err(NoContractAddress("from receipt logs".to_string())))
101}