Arbitrum Stylus logo

Stylus by Example

VM affordances

The Stylus Rust SDK contains several modules for interacting with the Virtual Machine (VM). In modern Stylus contracts, you’ll typically access these affordances through the contract’s VM handle via self.vm(). Some helpers still exist as free functions, but the self.vm() pattern keeps your code consistent in WASM and native tests.

Let's see an example:

1#[public]
2impl Example {
3    pub fn callvalue(&self) -> U256 {
4        // preferred: read msg.value from the VM handle
5        self.vm().msg_value()
6    }
7}
1#[public]
2impl Example {
3    pub fn callvalue(&self) -> U256 {
4        // preferred: read msg.value from the VM handle
5        self.vm().msg_value()
6    }
7}

This page lists the modules that are available, as well as the methods within those modules.


block

Allows you to inspect the current block:

  • basefee: gets the basefee of the current block
  • chainid: gets the unique chain identifier of the Arbitrum chain
  • coinbase: gets the coinbase of the current block, which on Arbitrum chains is the L1 batch poster's address
  • gas_limit: gets the gas limit of the current block
  • number: gets a bounded estimate of the L1 block number at which the sequencer sequenced the transaction
  • timestamp: gets a bounded estimate of the Unix timestamp at which the sequencer sequenced the transaction
1// VM-handle access (preferred)
2let basefee   = self.vm().block_basefee();
3let chainid   = self.vm().block_chainid();
4let coinbase  = self.vm().block_coinbase();
5let gas_limit = self.vm().block_gas_limit();
6let number    = self.vm().block_number();
7let timestamp = self.vm().block_timestamp();
1// VM-handle access (preferred)
2let basefee   = self.vm().block_basefee();
3let chainid   = self.vm().block_chainid();
4let coinbase  = self.vm().block_coinbase();
5let gas_limit = self.vm().block_gas_limit();
6let number    = self.vm().block_number();
7let timestamp = self.vm().block_timestamp();

contract

Allows you to inspect the contract itself:

  • address: gets the address of the current program
  • args: reads the invocation's calldata. The entrypoint macro uses this under the hood
  • balance: gets the balance of the current program
  • output: writes the contract's return data. The entrypoint macro uses this under the hood
  • read_return_data: copies the bytes of the last EVM call or deployment return result (copies the overlapping portion if out of bounds)
  • return_data_len: returns the length of the last EVM call or deployment return result, or 0 if neither have happened
1use stylus_sdk::contract;
2
3let address = contract::address();
4let balance = contract::balance();
5let _len    = contract::return_data_len();
6// contract::args(), contract::output(), contract::read_return_data() as needed
1use stylus_sdk::contract;
2
3let address = contract::address();
4let balance = contract::balance();
5let _len    = contract::return_data_len();
6// contract::args(), contract::output(), contract::read_return_data() as needed

crypto

Allows you to access VM-accelerated cryptographic functions:

  • keccak: efficiently computes the [keccak256] hash of the given preimage
1use stylus_sdk::crypto;
2use stylus_sdk::alloy_primitives::address;
3
4let preimage = address!("361594F5429D23ECE0A88E4fBE529E1c49D524d8");
5let hash = crypto::keccak(&preimage);
1use stylus_sdk::crypto;
2use stylus_sdk::alloy_primitives::address;
3
4let preimage = address!("361594F5429D23ECE0A88E4fBE529E1c49D524d8");
5let hash = crypto::keccak(&preimage);

evm

Allows you to access affordances for the Ethereum Virtual Machine:

  • gas_left: gets the amount of gas remaining
  • ink_left: gets the amount of ink remaining
  • log: emits a typed alloy log (requires a VM handle)
  • pay_for_memory_grow: exists to force the compiler to import this symbol (consumes gas if called)
  • raw_log: emits an EVM log from raw topics and data (most users prefer typed logs)
1// gas/ink via VM handle
2let gas_left = self.vm().evm_gas_left();
3let ink_left = self.vm().ink_left();
4
5// typed event logging via evm::log with VM handle
6use stylus_sdk::evm;
7use stylus_sdk::alloy_primitives::Address;
8use stylus_sdk::alloy_sol_types::sol;
9
10sol! {
11    event Transfer(address indexed from, address indexed to, uint256 value);
12}
13
14evm::log(
15    self.vm(),
16    Transfer {
17        from: Address::ZERO,
18        to: some_address,
19        value: some_value,
20    },
21);
22
23// raw_log also accepts the VM handle as first argument
24// evm::raw_log(self.vm(), topics, &data);
1// gas/ink via VM handle
2let gas_left = self.vm().evm_gas_left();
3let ink_left = self.vm().ink_left();
4
5// typed event logging via evm::log with VM handle
6use stylus_sdk::evm;
7use stylus_sdk::alloy_primitives::Address;
8use stylus_sdk::alloy_sol_types::sol;
9
10sol! {
11    event Transfer(address indexed from, address indexed to, uint256 value);
12}
13
14evm::log(
15    self.vm(),
16    Transfer {
17        from: Address::ZERO,
18        to: some_address,
19        value: some_value,
20    },
21);
22
23// raw_log also accepts the VM handle as first argument
24// evm::raw_log(self.vm(), topics, &data);

msg

Allows you to inspect the current call:

  • reentrant: whether the current call is reentrant
  • sender: the address of the account that called the program (semantics match EVM CALLER, including under DELEGATECALL)
  • value: the ETH value in wei sent to the program
1let reentrant = self.vm().reentrant();
2let sender    = self.vm().msg_sender();
3let value     = self.vm().msg_value();
1let reentrant = self.vm().reentrant();
2let sender    = self.vm().msg_sender();
3let value     = self.vm().msg_value();

tx

Allows you to inspect the current transaction:

  • gas_price: gas price in wei per gas (equals basefee on Arbitrum chains)
  • gas_to_ink: converts EVM gas to ink
  • ink_price: price of ink in EVM gas basis points
  • ink_to_gas: converts ink to EVM gas
  • origin: the top-level sender of the transaction (EVM ORIGIN)
1use stylus_sdk::tx;
2
3let gas_price = tx::gas_price();
4let gas_to_ink = tx::gas_to_ink();
5let ink_price = tx::ink_price();
6let ink_to_gas = tx::ink_to_gas();
7let origin = tx::origin();
1use stylus_sdk::tx;
2
3let gas_price = tx::gas_price();
4let gas_to_ink = tx::gas_to_ink();
5let ink_price = tx::ink_price();
6let ink_to_gas = tx::ink_to_gas();
7let origin = tx::origin();

Learn More