stylus_tools/core/
activation.rs

1// Copyright 2025, Offchain Labs, Inc.
2// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/licenses/COPYRIGHT.md
3
4//! Contract acitvation.
5//!
6//! See the [Arbitrum Docs](https://docs.arbitrum.io/stylus/concepts/how-it-works#activation) for
7//! details on contract activation.
8
9use alloy::{
10    primitives::{utils::parse_ether, Address, Bytes, U256},
11    providers::{Provider, WalletProvider},
12    rpc::types::{
13        state::{AccountOverride, StateOverride},
14        TransactionReceipt,
15    },
16};
17
18use crate::{
19    precompiles,
20    utils::{
21        bump_data_fee,
22        color::{DebugColor, GREY, LAVENDER},
23        format_data_fee,
24    },
25};
26
27#[derive(Debug)]
28pub struct ActivationConfig {
29    pub data_fee_bump_percent: u64,
30}
31
32impl Default for ActivationConfig {
33    fn default() -> Self {
34        Self {
35            data_fee_bump_percent: 20,
36        }
37    }
38}
39
40#[derive(Debug, thiserror::Error)]
41pub enum ActivationError {
42    #[error("{0}")]
43    Contract(alloy::contract::Error),
44    #[error("{0}")]
45    PendingTransaction(#[from] alloy::providers::PendingTransactionError),
46    #[error("{0}")]
47    Rpc(#[from] alloy::transports::RpcError<alloy::transports::TransportErrorKind>),
48
49    #[error(
50        "Contract could not be activated as it is missing an entrypoint. \
51         Please ensure that your contract has an #[entrypoint] defined on your main struct"
52    )]
53    MissingEntrypoint,
54}
55
56impl From<alloy::contract::Error> for ActivationError {
57    fn from(err: alloy::contract::Error) -> Self {
58        if err.to_string().contains("pay_for_memory_grow") {
59            Self::MissingEntrypoint
60        } else {
61            Self::Contract(err)
62        }
63    }
64}
65
66/// Activates an already deployed Stylus contract by address.
67pub async fn activate_contract(
68    address: Address,
69    config: &ActivationConfig,
70    provider: &(impl Provider + WalletProvider),
71) -> Result<TransactionReceipt, ActivationError> {
72    let code = provider.get_code_at(address).await?;
73    let from_address = provider.default_signer_address();
74    let data_fee = data_fee(code, address, config, &provider).await?;
75
76    let receipt = precompiles::arb_wasm(&provider)
77        .activateProgram(address)
78        .from(from_address)
79        .value(data_fee)
80        .send()
81        .await?
82        .get_receipt()
83        .await?;
84
85    info!(@grey,
86        "successfully activated contract 0x{} with tx {}",
87        hex::encode(address),
88        hex::encode(receipt.transaction_hash).debug_lavender()
89    );
90
91    Ok(receipt)
92}
93
94/// Checks Stylus contract activation, returning the data fee.
95pub async fn data_fee(
96    code: impl Into<Bytes>,
97    address: Address,
98    config: &ActivationConfig,
99    provider: &impl Provider,
100) -> Result<U256, ActivationError> {
101    let arbwasm = precompiles::arb_wasm(provider);
102    let random_sender_addr = Address::random();
103    let spoofed_sender_account = AccountOverride::default().with_balance(U256::MAX);
104    let spoofed_code = AccountOverride::default().with_code(code);
105    let state_override = StateOverride::from_iter([
106        (address, spoofed_code),
107        (random_sender_addr, spoofed_sender_account),
108    ]);
109
110    let result = arbwasm
111        .activateProgram(address)
112        .state(state_override)
113        .from(random_sender_addr)
114        .value(parse_ether("1").unwrap())
115        .call()
116        .await?;
117
118    let data_fee = result.dataFee;
119    let bump = config.data_fee_bump_percent;
120    let adjusted = bump_data_fee(data_fee, bump);
121    info!(@grey,
122        "wasm data fee: {} {GREY}(originally {}{GREY} with {LAVENDER}{bump}%{GREY} bump)",
123        format_data_fee(adjusted),
124        format_data_fee(data_fee)
125    );
126
127    Ok(adjusted)
128}
129
130/// Estimate gas cost for Stylus contract activation.
131pub async fn estimate_gas(
132    address: Address,
133    config: &ActivationConfig,
134    provider: &(impl Provider + WalletProvider),
135) -> Result<u64, ActivationError> {
136    let code = provider.get_code_at(address).await?;
137    let from_address = provider.default_signer_address();
138    let data_fee = data_fee(code, address, config, &provider).await?;
139
140    let gas = precompiles::arb_wasm(&provider)
141        .activateProgram(address)
142        .from(from_address)
143        .value(data_fee)
144        .estimate_gas()
145        .await?;
146
147    Ok(gas)
148}