Stylus in Action: Lifecycle of a Stylus Contract
This past February, we at Offchain Labs announced Stylus, an upgrade to the Arbitrum tech stack that will be ready later this year. Since then, we’ve been hard at work building the technology; we’ve been ambitiously tackling one of the most crucial aspects of the Stylus stack — its fraud prover — and we’re excited to unveil the progress we’ve made. Today, we’ll dive deeper into the technical details as we go step-by-step through the lifecycle of an example Stylus program.
First, a bit of background for newcomers:
Background: What Stylus Can Do And Why It’s Cool
Stylus is a new programming environment for chains built on the Arbitrum Nitro stack that takes advantage of Arbitrum Nitro’s unique fraud-proving technology. Users will be able to deploy contracts written in new programming languages, including Rust, C, C++, and potentially many more, side-by-side with EVM contracts on the same chain.
In Stylus, EVM contracts behave exactly as they do on Ethereum. We maintain Arbitrum’s full EVM equivalence and add a second, coequal virtual machine to handle these new languages. The two VMs are synchronously composable with each other; together, they define the system’s state transition function. We call this paradigm EVM+ since everything is entirely additive. And to boot, Stylus programs are over an order of magnitude faster than their EVM/Solidity counterparts, dramatically lowering fees.
Before we dig into the details of how Stylus works, let’s review the smart contract lifecycle in Nitro, Arbitrum’s current tech stack.
More Background: How Nitro Currently Works
In brief, the lifecycle of a Nitro contract looks like this:
1. Coding: A developer writes a smart contract in an EVM-supported smart contract language, like Solidity (or Vyper, Yul, etc.).
2. Compilation: The developer then compiles their contract to EVM bytecode and uploads it to the Arbitrum chain via a deployment.
3. Execution: When the smart contract is called, the Nitro VM loads the bytecode from the database and executes its instructions via Geth’s EVM.
Note that so far, this process is identical to that of Ethereum. And for most situations, the story ends here.
A new capability introduced by layer 2s like Arbitrum is ‘proving’. To qualify as an Optimistic Rollup, we need the ability for the underlying layer 1 to enforce the safety of the chain’s state updates. Or more specifically, we need the ability to prove the invalidity of a state update (and in turn reject it) via smart contracts on Ethereum. These so-called “fraud proofs” are at the core of Arbitrum’s technical architecture:
4. Proving: To prove an invalid state update (if the need arises), the Nitro VM is compiled into a WebAssembly (WASM) program, a modern binary format popularized by its use in web browsers like Chrome and Firefox, where sandboxing and other security features are paramount. Via an interactive dissection process, honest Arbitrum validators narrow down the invalid state update to a single invalid step of WASM execution, which is then checked on chain via a so-called “one-step proof.”
(See “why nitro” for more on design rationale.)
Stylus in Action
With the stage set, we’re now ready to walk through the lifecycle of a Stylus contract, which we call a “Stylus program”. We’ll go through the same steps that we did for Nitro contracts: coding, compilation, execution, and proving.
First, we need to code up a smart contract. In principle, Stylus can support programs written in any programming language that can be compiled into WASM. In practice, some high-level languages generate far more performant WASMs than others. Initially, we’ll be offering support for Rust, C++, C, and “BF”.
For our example, we’ll use this C implementation of secp256r1, a cryptographic primitive used in secure enclaves in mobile devices of interest to wallet developers. secp256r1 currently has no precompile support on Ethereum or Arbitrum Nitro-based chains.
Stylus-supported languages include an “Ethereum API” — libraries for performing EVM-specific functionality like checking a contract’s balance, accessing the current block, and calling other contracts. In short, virtually everything one might want to do in Solidity can be done in Stylus. In fact, the only EVM opcode functionality that isn’t available is that of Ethereum’s likely-soon-deprecated
CALLCODE. Users can still access these behaviors via Solidity, where full EVM equivalence is guaranteed.
In this particular case, however, the secp256r1 signature verifier is a pure function; thus we can use the implementation we found in the wild with no modifications! Our Stylus contract code, which simply calls the verifier, looks like this:
And that’s the entire contract, save for the library we link in as-is with Clang! One may wonder why we used C. In general, users will want to design their contracts in more modern languages like Rust or C++. As an example, we also deployed a Rust version using the p256 crate — again without modifications. We’re still working on Rust language features, but below is something quite similar to what we deployed:
For contracts centered around pure cryptography though, people generally want to use official reference implementations, which are often written in C. Hence, it’s important to include C compatibility in the initial release so that these libraries can be deployed to Arbitrum chains.
Compilation of a Stylus program happens in two steps:
First, the code is compiled off-chain by the developer using any off-the-shelf WASM compiler; in this case, our contract is written in C, so we’ll use Clang. The WASM is then posted on the Arbitrum chain in compressed form. With this on-chain commitment to the code, the contract’s behavior is defined deterministically. However, the contract cannot yet be called (at this stage, calls to it will simply revert.)
Next, the user interacts with
ArbWasm, a new precompile for managing user programs, and calls the
compileProgram method. At this point each Arbitrum node lowers the WASM to their machine’s native assembly, producing an efficient executable tailored just for their hardware. Crucially, during this step, the WASM is instrumented to guarantee that it and any native equivalents can be safely executed and deterministically fraud proven within Arbitrum’s VM.
Included is the instrumentation that adds gas metering. Each WASM opcode is assigned a cost, which is measured in “ink”, a unit similar to Ethereum’s gas but thousands of times smaller. We do this because WASM execution is so much faster, that for every op the EVM does, potentially tens of thousands of WASM ops can be executed. This is true even for the simplest instructions like
ADD, which in the EVM requires that the interpreter pop two items off the stack, add them together, and then push the result, touching all sorts of memory along the way. In WASM, we just do the
ADD natively in a single step, and efficiently space out ink charging so as to avoid doing it every instruction.
Additional instrumentation exists to enforce depth-checking (important for deterministic stack overflows), memory charging, and other mechanisms needed to ensure computational resources are paid for and that results are universal.
To understand the importance of this step, it’s worth pausing to emphasize a key difference here between Stylus and Nitro. In Nitro, we run a particular program — namely nitro-geth — which validators compile to WASM for proving fraud. We say that nitro-geth is “trusted” in the sense that we assume it is well-behaved: it won’t enter infinite loops, try to access memory that’s out of bounds, or otherwise do anything malicious — no matter what the user’s transaction says to do. These are the same assumptions anyone running Geth on L1 makes and are reasonable given Geth’s long history as the most popular Ethereum execution client.
Whereas with Stylus, the instrumentation step enforces safety directly at the WASM level. Hence, instead of only being able to run a particular, trusted WASM, we can now run arbitrary “untrusted” WASMs.
This is the core of how Stylus enables broader, EVM+ smart contract support.
With our contract now fully compiled, we’re ready for execution. Stylus contracts are executed via our fork of Wasmer, the premier WASM runtime. Executing native code via Wasmer is much faster than executing with Geth, translating to significant gas savings than equivalent operations in the EVM.
Nitro’s EVM-style contracts are still executed in the same EVM-equivalent way that they are currently. When a contract is called, the system detects whether it’s an EVM contract or a WASM program via a special EOF-inspired contract header and then executes it using its corresponding runtime, nitro-geth or stylus-wasmer, respectively. Nitro and Stylus contracts are part of the same state machine; they access and modify the same state trie, and can make cross-contract calls to each other. A developer never has to consider whether the contract they are calling was written in Solidity or Rust. It’s all interoperable.
Developers working strictly with Solidity contracts don’t have to adapt their workflows or best practices with the Stylus upgrade and can incorporate high-efficiency Stylus libraries at their choosing.
Recall that Nitro can run in two different modes. The code is compiled both to native code and, separately, to WASM; in the normal/happy case, it uses the native code for execution, whereas for proving, it uses the WASM program for interactive fraud proofs on L1.
The Stylus proving model is a natural extension of Nitro’s fraud prover, “Arbitrator”. Just as validators bisect over Geth’s execution history, now too do they bisect over user programs. Due to the aforementioned instrumentation step during compilation, we’re able to guarantee both the termination and determinism of this process. After a logarithmic-bounded number of moves, a single opcode is identified and proven, regardless of if it’s part of Geth or novel user WASM.
None of this is theoretical by the way, as we’re excited to announce that we’ve written a complete, working fraud prover for Stylus! This includes proving fraud over all of the functionality described in this article. Our remaining work will now primarily focus on language features and tooling, along with some efficiency improvements to the current system and pricing.
Stylus is an exciting new paradigm for layer 2 smart contracts. Over the coming months, we’re excited to unveil more information, release a testnet (sign up here to be among the first to access), and — before the end of the year — release the source code that can be deployed on any Arbitrum-nitro based chain.