Activation
Stylus contracts undergo a two-step process to become executable on Arbitrum chains: deployment and activation. This guide explains both steps, the distinction between them, and how to manage the activation process.
Overview
Unlike traditional EVM contracts that become immediately executable after deployment, Stylus contracts require an additional activation step:
- Deployment: Stores the compressed WASM bytecode onchain at a contract address
- Activation: Converts the bytecode into an executable Stylus program by registering it with the ArbWasm precompile
Why two steps?
- Gas optimization: Activation involves one-time processing and caching that would be expensive to repeat on every call
- Code reuse: Multiple contracts can share the same activated codehash, reducing activation costs
- Version management: Allows the chain to track which Stylus protocol version a contract targets
Deployment vs activation
| Aspect | Deployment | Activation |
|---|---|---|
| Purpose | Store compressed WASM onchain | Register program with ArbWasm |
| Transaction count | one transaction | one transaction (separate) |
| Cost type | Standard EVM deployment gas | Data fee (WASM-specific cost) |
| When required | Always - stores the code | Always - makes code executable |
| Reversible | No | No (but can expire) |
| Who can call | Anyone with funds | Anyone (after deployment) |
| Can be skipped | No | No (unless already activated) |
Contract state
A Stylus contract can be in one of these states:
pub enum ContractStatus {
/// Contract already exists onchain and is activated
Active { code: Vec<u8> },
/// Contract is deployed but not yet activated
/// Ready to activate with the given data fee
Ready { code: Vec<u8>, fee: U256 },
}
The Activation process
Step 1: Build and process WASM
Before deployment, your Rust contract is compiled and processed:
cargo stylus check
This performs:
- Compile Rust to WASM: Using
wasm32-unknown-unknowntarget - Process WASM binary:
- Remove dangling references
- Add project hash metadata
- Strip unnecessary custom sections
- Brotli compression: Maximum compression (level 11)
- Add EOF prefix:
EFF00000(identifies Stylus programs) - Size validation: Compressed code must be ≤ 24KB
WASM Processing Pipeline:
Figure 1: WASM binary processing pipeline showing transformation from raw binary to deployment-ready compressed code.
Step 2: Deploy the contract
Deployment creates a transaction that stores your processed WASM onchain:
cargo stylus deploy \
--private-key-path=key.txt \
--endpoint="https://sepolia-rollup.arbitrum.io/rpc"
What happens during deployment:
- Generate deployment bytecode: Create EVM initcode with embedded compressed WASM
- Estimate gas: Calculate deployment transaction gas cost
- Send deployment transaction: To the Stylus deployer contract
- Extract contract address: From the transaction receipt
Deployment Bytecode Structure:
EVM Initcode Prelude (43 bytes):
┌─────────────────────────────────────┐
│ 0x7f PUSH32 <code_len> │ Push code length
│ 0x80 DUP1 │ Duplicate length
│ 0x60 PUSH1 <prelude_length> │ Push prelude length
│ 0x60 PUSH1 0x00 │ Push 0
│ 0x39 CODECOPY │ Copy code to memory
│ 0x60 PUSH1 0x00 │ Push 0
│ 0xf3 RETURN │ Return code
│ 0x00 <version_byte> │ Stylus version
└─────────────────────────────────────┘
↓
<compressed_wasm_code>
Step 3: Calculate activation fee
Before activating, the data fee must be calculated:
// Simulated via state overrides (no transaction sent)
let data_fee = calculate_activation_fee(contract_address);
// Apply bump percentage for safety (default: 20%)
let final_fee = data_fee * (1 + bump_percent / 100);
Data fee calculation:
- Uses state override simulation to estimate the fee
- No actual transaction sent during estimation
- Configurable bump percentage protects against variance (default: 20%)
- The fee is paid in ETH when activating
Step 4: Activate the contract
Activation registers your contract with the ArbWasm precompile:
# Automatic activation (default)
cargo stylus deploy --private-key-path=key.txt
# Or manual activation
cargo stylus activate \
--address=0x1234... \
--private-key-path=key.txt
What happens during activation:
- Call ArbWasm precompile: At address
0x0000000000000000000000000000000000000071 - Send activation transaction:
ArbWasm.activateProgram{value: dataFee}(contractAddress)
- ArbWasm processes the code:
- Validates WASM formatting
- Checks against the protocol version
- Stores the activation metadata
- Emits a
ProgramActivatedevent
- Returns activation info:
returns (uint16 version, uint256 actualDataFee)
Using cargo-stylus
The cargo-stylus CLI tool simplifies the deployment and activation workflow.
Basic deployment (automatic activation)
By default, cargo stylus deploy handles both steps:
cargo stylus deploy \
--private-key-path=wallet.txt \
--endpoint="https://sepolia-rollup.arbitrum.io/rpc"
Output:
Building contract...
Compressing WASM...
Deploying contract to 0x1234567890abcdef...
Deployment transaction: 0xabcd...
Contract deployed at: 0x1234567890abcdef
Activating contract...
Activation transaction: 0xef12...
Contract activated successfully!
Deploy without activation
To deploy but skip activation:
cargo stylus deploy \
--private-key-path=wallet.txt \
--no-activate
This is useful when:
- You want to inspect the contract before activating
- Someone else will handle the activation
- You're testing deployment workflows
Manual activation
Activate a previously deployed contract:
cargo stylus activate \
--address=0x1234567890abcdef \
--private-key-path=wallet.txt \
--endpoint="https://sepolia-rollup.arbitrum.io/rpc"
Check contract status
Before deploying, check if a contract with the same code is already activated:
cargo stylus check \
--endpoint="https://sepolia-rollup.arbitrum.io/rpc"
Possible outcomes:
- Code already activated: You can reuse the existing deployment
- Ready to activate: Shows estimated data fee
- Validation errors: Displays issues that must be fixed
Deployment with constructors
If your contract has a constructor, provide arguments during deployment:
#[public]
impl MyContract {
#[constructor]
pub fn constructor(&mut self, initial_value: U256, owner: Address) {
self.value.set(initial_value);
self.owner.set(owner);
}
}
Deploy with constructor arguments:
cargo stylus deploy \
--private-key-path=wallet.txt \
--constructor-args 42 0x1234567890abcdef1234567890abcdef12345678
With payable constructor:
#[constructor]
#[payable]
pub fn constructor(&mut self) {
let value = self.vm().msg_value();
self.initial_balance.set(value);
}
cargo stylus deploy \
--private-key-path=wallet.txt \
--constructor-value=1000000000000000000 # 1 ETH in wei
The ArbWasm precompile
Activation is handled by the ArbWasm precompile at address 0x0000000000000000000000000000000000000071.