Skip to main content

ArbOS technical reference

This page provides a technical reference for ArbOS internals. For a conceptual overview of ArbOS and its role in the Arbitrum stack, see ArbOS.

How precompiles work

A precompile consists of a Solidity interface in contracts/src/precompiles/ and a corresponding Golang implementation in precompiles/. Using Geth's ABI generator, solgen/gen.go generates solgen/go/precompilesgen/precompilesgen.go, which collects the ABI data of the precompiles. The runtime installer uses this generated file to check the type safety of each precompile's implementer.

The installer uses runtime reflection to ensure each implementer has all the right methods and signatures. This reflection includes restricting access to stateful objects, such as the EVM and statedb, based on their declared purity. Additionally, the installer verifies and populates event function pointers, enabling each precompile to emit logs and determine its gas cost. Additional configurations, such as restricting a precompile's methods to be callable only by the chain owner, are possible by adding precompile wrappers like ownerOnly and debugOnly to their installation entry.

Completion of calling, dispatching, and recording of precompile methods occurs via runtime reflection, which avoids any human error that manually parsing and writing bytes could introduce, and uses Geth's stable APIs for packing and unpacking values.

Each time a transaction calls a method of a child chain-specific precompile, a call context gets created to track and record the gas burnt. For convenience, it also provides access to the public fields of the underlying TxProcessor. Because sub-transactions could revert without updates to this struct, the TxProcessor only makes public what is safe, such as the amount of parent chain calldata paid by the top-level transaction.

For a complete list of precompiles, refer to the precompile references.

ArbOS state

ArbOS's state is viewed and modified via ArbosState objects, which provide convenient abstractions for working with the underlying data of its backingStorage. The backing storage's keyed subspace strategy makes ArbosState's convenient getters and setters possible, minimizing the need to work directly with the specific keys and values of the underlying storage's stateDB.

Because two ArbosState objects with the same backingStorage contain and mutate the same underlying state, different ArbosState objects can provide different views of ArbOS's contents. Burner objects, which track gas usage while working with the ArbosState, provide the internal mechanism. Some are read-only, causing transactions to revert with vm.ErrWriteProtection when a mutating request is issued. Others demand that the caller have elevated privileges. Meanwhile, others dynamically charge users when doing stateful work. This view is chosen for safety when OpenArbosState() creates the object and may never change.

arbosVersion, updgradeVersion and upgradeTimestamp ArbOS upgrades are scheduled to happen when finalizing the first block after the upgradeTimestamp.

Most of ArbOS's state exists to facilitate its precompiles. The parts that aren't are detailed below.

blockhashes

This component maintains the last 256 parent chain block hashes in a circular buffer. This component allows the TxProcessor to implement the BLOCKHASH and NUMBER opcodes and supports the precompile methods that involve the Outbox. To avoid changing the ArbOS state outside of a transaction, blocks made from messages with a new parent chain–block number update this info during an InternalTxUpdateL1BlockNumber ArbitrumInternalTx included as the first transaction of the block.

l1PricingState

In addition to supporting the ArbAggregator precompile, the parent chain pricing state provides tools for determining the parent chain component of a transaction's gas costs. This part of the state tracks the total amount of funds collected from transactions in parent chain gas fees and the funds spent by batch posters to post data breaches on the parent chain.

Based on this information, ArbOS maintains a parent chain data fee, which is also tracked in this state and determines how much the parent chain fee will cost. ArbOS dynamically adjusts this value so that fees collected are approximately equal to batch posting costs. For more details about parent chain pricing, see Parent chain pricing.

l2PricingState

The child chain pricing state tracks child chain resource usage to determine a reasonable child chain gas price. This process considers various factors, including user demand, the state of Geth, and the computational gas target. The primary mechanism for doing so consists of a pair of pools, one larger than the other, that drain as child chain–specific resources are consumed and filled as time passes. Parent chain-specific resources, such as parent chain calldata, are not accounted for in the pools since they do not directly impact the computational workload of network actors. Instead, the design of the gas target mechanism regulates execution resources to ensure consistent system performance and synchronization.

While much of this state is accessible through the ArbGasInfo and ArbOwner precompiles, most changes are automatic and happen during block production and the transaction hooks. Each transaction in an incoming message removes the parent chain component of the gas it consumes from the pool. Afterward, the message's timestamp informs the pricing mechanism of the time passed as ArbOS finalizes the block.

ArbOS's larger gas pool determines the per-block gas limit, setting a dynamic upper limit on the amount of compute gas a child chain block may have. This limit is always enforced, though it's done in the GasChargingHook for the first transaction to avoid sharp decreases in the parent chain gas price from over-inflating the compute component purchased to above the gas limit. Enforcing this improves UX by allowing the first transaction to succeed rather than requiring a resubmission. Because the first transaction reduces the space left in the block, subsequent transactions do not use this strategy and may fail due to compute-component inflation. This space is acceptable because such transactions occur only when the system is under heavy load. The result is that the user's transaction is dropped without charges since the state transition fails early. Those trusting the Sequencer can rely on the automatic resubmission of a transaction in such a scenario.

We need a per-block gas limit because arbitrator WAVM execution is much slower than native transaction execution. This limit means there can only be so much gas, roughly translating to wall-block time, in a child chain block. It also allows ArbOS to limit the size of blocks should demand continue to surge even as prices rise.

ArbOS's per-block gas limit is distinct from Geth's block limit, which ArbOS sets sufficiently high never to run out. This approach is safe since Geth's block limit exists to constrain the work done per block, which ArbOS already does via its own per-block gas limit. Though it'll never run out, a block's transactions use the same Geth gas pool to maintain the invariant that the pool decreases monotonically after each transaction. Block headers use the Geth block limit for internal consistency and to ensure gas estimation works. They are distinct from the gasLeft variable, which ephemerally exists outside of the global state to keep child chain blocks from exceeding ArbOS's per-block gas limit and to deduct space where the state transition failed or used negligible amounts of compute gas. ArbOS does not need to persist gasLeft because its pool induces a revert, and transactions use the Geth block limit during EVM execution.

Stylus-specific differences

This section details how Stylus integrates into the State Transition Function (STF), covering execution flow, messaging handling, caching, and interactions with ArbOS and Geth.

1. Execution flow of a Stylus transaction

When a Transaction interacts with a Stylus contract, its execution follows a distinct path compared to EVM transactions:

  • Transaction submission and routing
    • The transaction is included in a child chain block by the Sequencer.
    • Geth processes the transaction and determines its target contract.
    • If the target is a Stylus contract, ArbOS routes execution to the WASM runtime instead of the EVM.
  • Stylus execution within ArbOS
    • ArbOS retrieves the Stylus program from its cache (stylus/src/cache.rs) or loads it from storage if not cached.
    • The WebAssembly System Interface (Go-WASI) initializes a secure execution environment.
    • The WASM module executes within ArbOS, processing instructions efficiently and calling host I/O functions.
  • Host I/O operations for blockchain state access
    • Stylus contracts do not use EVM opcodes. Instead, they interact with the blockchain through host I/O calls handled by ArbOS.
    • These include storage access (TLOAD TSTORE), arithmetic operations (MULMOD, ADDMOD), and context retrieval (GETCALLER, GETCALLVALUE).
    • ArbOS ensures these operations are efficient and compatible with Ethereum's state model.
    • State commitment and finalization
      • Once execution is complete, ArbOS finalizes storage changes and updates logs and receipts.
      • Geth processes the final transaction result and commits it to the state tree.

This process bypasses the EVM interpreter entirely, allowing Stylus contracts to execute significantly faster than their Solidity counterparts.

2. Stylus caching and gas pricing

  • Stylus gas pricing model Unlike standard EVM gas pricing, Stylus pricing follows a multi-dimensional cost model, incorporating:
    • Ink cost (memory and execution cost)
      • Measure in Ink units (Stylus's equivalent of computational gas).
      • Ink pricing varies based on execution complexity, memory usage, and computation steps.
      • Complex WASM operations consume more Ink, directly impacting execution costs.
    • Opcode pricing
      • WASM instructions are assigned individual execution costs similar to EVM opcodes.
      • Heavy computation opcodes are priced higher.
      • Cheap opcodes (e.g., simple arithmetic, bitwise operations) have minimal costs.
    • Host I/O pricing
      • Stylus introduces fine-grained pricing for different I/O calls:
        • Storage read/writes: Priced based on access pattern and data size.
        • Precompile calls: Stylus-specific precompiles have fixed execution costs.
        • External calls to EVM contracts: Encapsulated within ArbOS transaction handling, with additional gas considerations.

Stylus caching

Stylus contracts leverage an advanced caching system to minimize execution overhead within ArbOS:

  • LRU (Least Recently Used) caching: Keeps the most recently accessed Stylus contracts in memory for fast execution.
  • Persistent long-term caching: Caching for selected contracts may occur across blocks based on an economic auction model.
  • Init costs and execution pricing: Instead of a flat gas cost, Stylus contracts have dynamic execution costs based on WASM complexity. ArbOS maintains pricing parameters (initCost, cachedCost) that are adjusted based on future WASM execution optimizations.

3. Interaction with ArbOS and Geth

Execution StageHandled By
Transaction submissionGeth (identifies target contract)
Stylus executionArbOS (switches to WASM runtime)
Host I/O callsArbOS (handles storage, call data, context retrieval)
State commitmentGeth and ArbOS (finalizes updates, commits to state)

4. Go-WASI and co-threads in Stylus execution

ArbOS executes Stylus contracts using Go-WASI, a WASM-compatible runtime with custom optimizations for Arbitrum. Key features include:

  • Memory management: WASM modules execute in a sandboxed environment with strict memory allocation policies.
  • Co-threads for efficient execution: Instead of traditional synchronous execution, Stylus employs co-threading, enabling lightweight task switching and parallelism where possible.
  • Deterministic execution: Ensures that Stylus contracts remain fully deterministic and compatible with Ethereum's consensus model.

These optimizations make Stylus an extremely efficient execution environment, capable of outperforming the EVM while maintaining security and compatibility with Ethereum's state model.