arb_stylus/
lib.rs

1//! Stylus WASM smart contract runtime.
2//!
3//! Provides the execution pipeline for Stylus programs: WASM compilation
4//! and caching, ink metering, host I/O functions, and EVM interop.
5
6pub mod cache;
7pub mod config;
8pub mod env;
9pub mod error;
10pub mod evm_api;
11pub mod evm_api_impl;
12#[allow(unused_mut)]
13pub mod host;
14pub mod ink;
15pub mod meter;
16pub mod middleware;
17pub mod native;
18pub mod pages;
19pub mod pricing;
20pub mod run;
21pub mod trace;
22
23pub use cache::InitCache;
24pub use config::{CompileConfig, StylusConfig};
25pub use evm_api::EvmApi;
26pub use evm_api_impl::StylusEvmApi;
27pub use ink::{Gas, Ink};
28pub use meter::{MachineMeter, MeteredMachine, STYLUS_ENTRY_POINT};
29pub use native::NativeInstance;
30pub use run::RunProgram;
31
32/// Prefix bytes that identify a Stylus WASM program in contract bytecode.
33///
34/// The discriminant is `[0xEF, 0xF0, 0x00]`. The `0xEF` byte conflicts with
35/// EIP-3541, so EIP-3541 must be disabled for Stylus-era blocks to allow
36/// deployment. The third byte `0x00` is the EOF version marker.
37pub const STYLUS_DISCRIMINANT: [u8; 3] = [0xEF, 0xF0, 0x00];
38
39/// Returns `true` if the bytecode is a Stylus WASM program.
40///
41/// Checks for the 3-byte discriminant prefix `[0xEF, 0xF0, 0x00]`.
42pub fn is_stylus_program(bytecode: &[u8]) -> bool {
43    bytecode.len() >= 4 && bytecode[..3] == STYLUS_DISCRIMINANT
44}
45
46/// Strips the 4-byte Stylus prefix from contract bytecode.
47///
48/// Returns `(stripped_bytecode, version_byte)` or an error if the bytecode
49/// is too short or doesn't have the Stylus discriminant.
50pub fn strip_stylus_prefix(bytecode: &[u8]) -> Result<(&[u8], u8), &'static str> {
51    if bytecode.len() < 4 {
52        return Err("bytecode too short for Stylus prefix");
53    }
54    if bytecode[..3] != STYLUS_DISCRIMINANT {
55        return Err("bytecode does not have Stylus discriminant");
56    }
57    let version = bytecode[3];
58    Ok((&bytecode[4..], version))
59}
60
61/// Root Stylus program prefix: `[0xEF, 0xF0, 0x02]`.
62pub const STYLUS_ROOT_DISCRIMINANT: [u8; 3] = [0xEF, 0xF0, 0x02];
63
64/// Fragment prefix: `[0xEF, 0xF0, 0x01]`.
65pub const STYLUS_FRAGMENT_DISCRIMINANT: [u8; 3] = [0xEF, 0xF0, 0x01];
66
67/// Returns `true` if the bytecode is a classic Stylus program (`[0xEF, 0xF0, 0x00, ...]`).
68pub fn is_stylus_classic(bytecode: &[u8]) -> bool {
69    bytecode.len() > 3 && bytecode[..3] == STYLUS_DISCRIMINANT
70}
71
72/// Returns `true` if the bytecode is a Stylus root program (`[0xEF, 0xF0, 0x02, ...]`).
73pub fn is_stylus_root(bytecode: &[u8]) -> bool {
74    bytecode.len() > 3 && bytecode[..3] == STYLUS_ROOT_DISCRIMINANT
75}
76
77/// Returns `true` if the bytecode is a Stylus fragment (`[0xEF, 0xF0, 0x01, ...]`).
78pub fn is_stylus_fragment(bytecode: &[u8]) -> bool {
79    bytecode.len() > 3 && bytecode[..3] == STYLUS_FRAGMENT_DISCRIMINANT
80}
81
82/// Returns `true` if the bytecode is a deployable Stylus program.
83pub fn is_stylus_deployable(bytecode: &[u8], arbos_version: u64) -> bool {
84    use arb_chainspec::arbos_version as av;
85    if arbos_version < av::ARBOS_VERSION_STYLUS {
86        return false;
87    }
88    if arbos_version < av::ARBOS_VERSION_STYLUS_CONTRACT_LIMIT {
89        return is_stylus_classic(bytecode);
90    }
91    is_stylus_classic(bytecode) || is_stylus_root(bytecode)
92}
93
94/// Decompress a Stylus WASM program from its contract bytecode.
95/// The bytecode format is `[0xEF, 0xF0, 0x00, dict_byte, ...compressed_wasm]`.
96/// Returns the decompressed WASM bytes.
97pub fn decompress_wasm(bytecode: &[u8]) -> eyre::Result<Vec<u8>> {
98    if bytecode.len() < 4 || bytecode[..3] != STYLUS_DISCRIMINANT {
99        eyre::bail!("not a Stylus program");
100    }
101    let dict_byte = bytecode[3];
102    let compressed = &bytecode[4..];
103
104    let dict = match dict_byte {
105        0 => nitro_brotli::Dictionary::Empty,
106        1 => nitro_brotli::Dictionary::StylusProgram,
107        _ => eyre::bail!("unsupported dictionary type: {dict_byte}"),
108    };
109
110    nitro_brotli::decompress(compressed, dict)
111        .map_err(|e| eyre::eyre!("brotli decompression failed: {e:?}"))
112}
113
114/// Activate a Stylus program.
115///
116/// `wasm` must be the decompressed WASM bytes (call `decompress_wasm` first).
117/// `gas` is decremented by the activation cost.
118pub fn activate_program(
119    wasm: &[u8],
120    codehash: &[u8; 32],
121    stylus_version: u16,
122    arbos_version: u64,
123    page_limit: u16,
124    debug: bool,
125    gas: &mut u64,
126) -> eyre::Result<arbos::programs::types::ActivationResult> {
127    let codehash_bytes32 = nitro_arbutil::Bytes32(*codehash);
128    let (_module, stylus_data) = nitro_prover::machine::Module::activate(
129        wasm,
130        &codehash_bytes32,
131        stylus_version,
132        arbos_version,
133        page_limit,
134        debug,
135        gas,
136    )?;
137
138    Ok(arbos::programs::types::ActivationResult {
139        module_hash: alloy_primitives::B256::from(_module.hash().0),
140        init_gas: stylus_data.init_cost,
141        cached_init_gas: stylus_data.cached_init_cost,
142        asm_estimate: stylus_data.asm_estimate,
143        footprint: stylus_data.footprint,
144    })
145}