stylus_tools/core/deployment/
mod.rs1use crate::core::activation;
5use crate::core::activation::ActivationError;
6use crate::core::cache::format_gas;
7use crate::core::deployment::deployer::{
8 get_address_from_receipt, parse_tx_calldata, DeployerError,
9};
10use crate::core::deployment::DeploymentError::{
11 InvalidConstructor, NoContractAddress, ReadConstructorFailure,
12};
13use crate::ops::activate::print_gas_estimate;
14use crate::ops::get_constructor_signature;
15use crate::{
16 core::{
17 check::{check_contract, CheckConfig},
18 project::contract::{Contract, ContractStatus},
19 },
20 utils::color::{Color, DebugColor},
21};
22use alloy::json_abi::StateMutability::Payable;
23use alloy::{
24 network::TransactionBuilder,
25 primitives::{Address, TxHash, U256},
26 providers::{Provider, WalletProvider},
27 rpc::types::{TransactionReceipt, TransactionRequest},
28};
29
30use alloy::primitives::B256;
31use prelude::DeploymentCalldata;
32
33pub mod deployer;
34pub mod prelude;
35
36#[derive(Debug, Default)]
37pub struct DeploymentConfig {
38 pub check: CheckConfig,
39 pub max_fee_per_gas_gwei: Option<u128>,
40 pub estimate_gas: bool,
41 pub no_activate: bool,
42 pub constructor_value: U256,
43 pub deployer_address: Address,
44 pub constructor_args: Vec<String>,
45 pub deployer_salt: B256,
46}
47
48#[derive(Debug)]
49pub struct DeploymentRequest {
50 tx: TransactionRequest,
51 max_fee_per_gas_wei: Option<u128>,
52}
53
54impl DeploymentRequest {
55 pub fn new_with_args(
56 sender: Address,
57 deployer: Address,
58 tx_value: U256,
59 tx_calldata: Vec<u8>,
60 max_fee_per_gas_wei: Option<u128>,
61 ) -> Self {
62 Self {
63 tx: TransactionRequest::default()
64 .with_to(deployer)
65 .with_from(sender)
66 .with_value(tx_value)
67 .with_input(tx_calldata),
68 max_fee_per_gas_wei,
69 }
70 }
71 pub fn new(sender: Address, code: &[u8], max_fee_per_gas_wei: Option<u128>) -> Self {
72 Self {
73 tx: TransactionRequest::default()
74 .with_from(sender)
75 .with_deploy_code(DeploymentCalldata::new(code)),
76 max_fee_per_gas_wei,
77 }
78 }
79
80 pub async fn estimate_gas(&self, provider: &impl Provider) -> Result<u64, DeploymentError> {
81 Ok(provider.estimate_gas(self.tx.clone()).await?)
82 }
83
84 pub async fn exec(
85 self,
86 provider: &impl Provider,
87 ) -> Result<TransactionReceipt, DeploymentError> {
88 let gas = self.estimate_gas(provider).await?;
89 let max_fee_per_gas = self.fee_per_gas(provider).await?;
90
91 let mut tx = self.tx;
92 tx.gas = Some(gas);
93 tx.max_fee_per_gas = Some(max_fee_per_gas);
94 tx.max_priority_fee_per_gas = Some(0);
95
96 let tx = provider.send_transaction(tx).await?;
97 let tx_hash = *tx.tx_hash();
98 debug!(@grey, "sent deploy tx: {}", tx_hash.debug_lavender());
99
100 let receipt = tx
101 .get_receipt()
102 .await
103 .or(Err(DeploymentError::FailedToComplete))?;
104 if !receipt.status() {
105 return Err(DeploymentError::Reverted { tx_hash });
106 }
107
108 Ok(receipt)
109 }
110
111 async fn fee_per_gas(&self, provider: &impl Provider) -> Result<u128, DeploymentError> {
112 match self.max_fee_per_gas_wei {
113 Some(wei) => Ok(wei),
114 None => Ok(provider.get_gas_price().await?),
115 }
116 }
117}
118
119#[derive(Debug, thiserror::Error)]
120pub enum DeploymentError {
121 #[error("rpc error: {0}")]
122 Rpc(#[from] alloy::transports::RpcError<alloy::transports::TransportErrorKind>),
123
124 #[error("{0}")]
125 Check(#[from] crate::core::check::CheckError),
126
127 #[error("tx failed to complete")]
128 FailedToComplete,
129 #[error("failed to get balance")]
130 FailedToGetBalance,
131 #[error(
132 "not enough funds in account {} to pay for data fee\n\
133 balance {} < {}\n\
134 please see the Quickstart guide for funding new accounts:\n{}",
135 .from_address.red(),
136 .balance.red(),
137 format!("{} wei", .data_fee).red(),
138 "https://docs.arbitrum.io/stylus/stylus-quickstart".yellow(),
139 )]
140 NotEnoughFunds {
141 from_address: Address,
142 balance: U256,
143 data_fee: U256,
144 },
145 #[error("deploy tx reverted {}", .tx_hash.debug_red())]
146 Reverted { tx_hash: TxHash },
147 #[error("{0}")]
148 DeployerFailure(#[from] DeployerError),
149 #[error("{0}")]
150 ActivationFailure(#[from] ActivationError),
151 #[error("missing address: {0}")]
152 NoContractAddress(String),
153 #[error("failed to get constructor signature")]
154 ReadConstructorFailure,
155 #[error("invalid constructor: {0}")]
156 InvalidConstructor(String),
157}
158
159pub async fn deploy(
161 contract: &Contract,
162 config: &DeploymentConfig,
163 provider: &(impl Provider + WalletProvider),
164) -> Result<(), DeploymentError> {
165 let status = check_contract(contract, None, &config.check, provider).await?;
166 let from_address = provider.default_signer_address();
167 debug!(@grey, "sender address: {}", from_address.debug_lavender());
168 let data_fee = status.suggest_fee() + config.constructor_value;
169
170 if let ContractStatus::Ready { .. } = status {
171 let balance = provider
173 .get_balance(from_address)
174 .await
175 .map_err(|_| DeploymentError::FailedToGetBalance)?;
176 if balance < data_fee {
177 return Err(DeploymentError::NotEnoughFunds {
178 from_address,
179 balance,
180 data_fee,
181 });
182 }
183 }
184
185 let constructor = get_constructor_signature(contract.package.name.as_str())
186 .map_err(|_| ReadConstructorFailure)?;
187
188 let req = match &constructor {
189 None => DeploymentRequest::new(from_address, status.code(), config.max_fee_per_gas_gwei),
190 Some(constructor) => {
191 if constructor.state_mutability != Payable && !config.constructor_value.is_zero() {
192 return Err(InvalidConstructor(
193 "attempting to send Ether to non-payable constructor".to_string(),
194 ));
195 }
196 if config.constructor_args.len() != constructor.inputs.len() {
197 return Err(InvalidConstructor(format!(
198 "mismatch number of constructor arguments (want {:?} ({}); got {})",
199 constructor.inputs,
200 constructor.inputs.len(),
201 config.constructor_args.len(),
202 )));
203 }
204
205 let tx_calldata = parse_tx_calldata(
206 status.code(),
207 constructor,
208 config.constructor_value,
209 config.constructor_args.clone(),
210 config.deployer_salt,
211 &provider,
212 )
213 .await
214 .map_err(|err| InvalidConstructor(err.to_string()))?;
215
216 DeploymentRequest::new_with_args(
217 from_address,
218 config.deployer_address,
219 data_fee,
220 tx_calldata,
221 config.max_fee_per_gas_gwei,
222 )
223 }
224 };
225
226 if config.estimate_gas {
227 let gas = req
228 .estimate_gas(&provider)
229 .await
230 .or(Err(DeployerError::GasEstimationFailure))?;
231 let gas_price = req
232 .fee_per_gas(&provider)
233 .await
234 .or(Err(DeployerError::GasEstimationFailure))?;
235 print_gas_estimate("deployment", gas, gas_price)
236 .or(Err(DeployerError::GasEstimationFailure))?;
237 let nonce = provider.get_transaction_count(from_address).await?;
239 let _ = from_address.create(nonce);
240 return Ok(());
241 }
242 let receipt = req.exec(&provider).await?;
243
244 let contract_addr = match &constructor {
245 None => receipt
246 .contract_address
247 .ok_or(NoContractAddress("in receipt".to_string())),
248 Some(_) => get_address_from_receipt(&receipt),
249 }?;
250
251 info!(@grey, "deployed code at address: {}", contract_addr.debug_lavender());
252 debug!(@grey, "gas used: {}", format_gas(receipt.gas_used.into()));
253 info!(@grey, "deployment tx hash: {}", receipt.transaction_hash.debug_lavender());
254
255 if constructor.is_none() {
256 if matches!(status, ContractStatus::Active { .. }) {
257 greyln!("wasm already activated!")
258 } else if config.no_activate {
259 mintln!(
260 r#"NOTE:
261 You must activate the stylus contract before calling it. To do so, we recommend running:
262 cargo stylus activate --address {}"#,
263 hex::encode(contract_addr)
264 )
265 } else {
266 activation::activate_contract(contract_addr, &config.check.activation, provider)
267 .await?;
268 }
269 }
270
271 mintln!(
272 r#"NOTE:
273 We recommend running cargo stylus cache bid {} 0 to cache your activated contract in ArbOS.
274 Cached contracts benefit from cheaper calls.
275 To read more about the Stylus contract cache, see:
276 https://docs.arbitrum.io/stylus/how-tos/caching-contracts"#,
277 hex::encode(contract_addr)
278 );
279
280 Ok(())
281}