Stylus contracts
Stylus smart contracts are fully compatible with Solidity contracts on Arbitrum chains. They compile to WebAssembly and share the same EVM state trie as Solidity contracts, enabling seamless interoperability.
Contract Basics
A Stylus contract consists of three main components:
- Storage Definition: Defines the contract's persistent state
- Entrypoint: Marks the main contract struct that handles incoming calls
- Public Methods: Functions exposed to external callers via the
#[public]macro
Minimal Contract
Here's the simplest possible Stylus contract:
#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
extern crate alloc;
use stylus_sdk::prelude::*;
#[storage]
#[entrypoint]
pub struct HelloWorld;
#[public]
impl HelloWorld {
fn user_main(_input: Vec<u8>) -> ArbResult {
Ok(Vec::new())
}
}
This contract:
- Uses
#[storage]to define the contract struct (empty in this case) - Uses
#[entrypoint]to mark it as the contract's entry point - Uses
#[public]to expose theuser_mainfunction - Returns
ArbResult, which isResult<Vec<u8>, Vec<u8>>
Storage Definition
Stylus contracts use the sol_storage! macro or #[storage] attribute to define persistent storage that maps directly to Solidity storage slots.
Using sol_storage! (Solidity-style)
The sol_storage! macro lets you define storage using Solidity syntax:
use stylus_sdk::prelude::*;
use alloy_primitives::{Address, U256};
sol_storage! {
#[entrypoint]
pub struct Counter {
uint256 count;
address owner;
mapping(address => uint256) balances;
}
}
This creates a contract with:
- A
countfield of typeStorageU256 - An
ownerfield of typeStorageAddress - A
balancesmapping fromAddresstoStorageU256
Using #[storage] (Rust-style)
Alternatively, use the #[storage] attribute with explicit storage types:
use stylus_sdk::prelude::*;
use stylus_sdk::storage::{StorageU256, StorageAddress, StorageMap};
use alloy_primitives::{Address, U256};
#[storage]
#[entrypoint]
pub struct Counter {
count: StorageU256,
owner: StorageAddress,
balances: StorageMap<Address, StorageU256>,
}
Both approaches produce identical storage layouts and are fully interoperable with Solidity contracts using the same storage structure.
The #[entrypoint] Macro
The #[entrypoint] macro marks a struct as the contract's main entry point. It automatically implements the TopLevelStorage trait, which enables:
- Routing incoming calls to public methods
- Managing contract storage
- Handling reentrancy protection (unless the
reentrantfeature is enabled)
Key requirements:
- Exactly one struct per contract must have
#[entrypoint] - The struct must also have
#[storage]or be defined insol_storage! - The entrypoint struct represents the contract's root storage
Example:
sol_storage! {
#[entrypoint]
pub struct MyContract {
uint256 value;
}
}
The #[entrypoint] macro generates:
- An implementation of
TopLevelStoragefor the struct - A
user_entrypointfunction that Stylus calls when the contract receives a transaction - Method routing logic to dispatch calls to
#[public]methods
Public Methods with #[public]
The #[public] macro exposes Rust methods as external contract functions callable from Solidity, other Stylus contracts, or external callers.