Stylus SDK

repository·main·Indexed 43 days ago

https://github.com/offchainlabs/stylus-sdk-rs

A framework for writing high-performance, WebAssembly-based smart contracts for Arbitrum chains using Rust. It provides ABI equivalence with Ethereum and includes the cargo-stylus CLI tool for building, verifying, and deploying WASM contracts.

Tokens
45.5K
Snippets
168
Records
202
Agent score
53%

What's inside Stylus SDK

  1. Overview of the Stylus SDK

    main

    The Stylus SDK allows developers to write smart contracts for Arbitrum chains using the Rust programming language. These programs are compiled to WebAssembly (Wasm) and execute on-chain alongside Solidity smart contracts.

    Key capabilities include:

    • EVM-interoperability: Stylus programs can call and be called by Solidity contracts due to ABI equivalence. Existing Solidity DEXs can interact with Rust tokens without modification.
    • Performance: Stylus programs are designed to be faster and cheaper than traditional Solidity contracts.
    • #[no_std] Support: The entire SDK is available in #[no_std] mode, allowing developers to opt out of the Rust standard library to reduce binary size, which is useful for pure-compute tasks like cryptography.
  2. How to unit test Stylus contracts with TestVM

    main

    To unit test Stylus contracts in native Rust without a real EVM or Arbitrum chain, use the TestVM provided by the stylus_sdk::testing module.

    Requirement for Testability: For a contract to be testable with TestVM, it must access host methods through the HostAccess trait rather than calling stylus_sdk::msg functions directly. Instead of stylus_sdk::msg::value(), use self.vm().msg_value(). This allows the TestVM to inject mocked values into the contract's execution context.

    Basic Workflow:

    1. Import stylus_sdk::testing::* in your test module.
    2. Initialize a TestVM using TestVM::default().
    3. Initialize your contract from the VM using ContractName::from(&vm) (all Stylus storage types implement From<VM>).
    4. Perform actions on the contract and use vm methods to mock environment variables like msg_value.
    #[cfg(test)]
    mod test {
        use super::*;
        use stylus_sdk::testing::*;
    
        #[test]
        fn test_counter() {
            let vm = TestVM::default();
            let mut contract = Counter::from(&vm);
    
            // Test basic logic
            contract.increment();
            assert_eq!(U256::from(1), contract.number());
    
            // Mock msg_value and test
            vm.set_value(U256::from(2));
            contract.add_from_msg_value();
            assert_eq!(U256::from(3), contract.number());
        }
    }
  3. Optimize Stylus contract binary sizes with reproducible wasm-opt

    main

    Stylus contract WASM binaries must fit within a 24Kb Brotli-compressed limit. To ensure your contract fits while maintaining reproducible builds, you can pin a specific wasm-opt (Binaryen) version and set optimization flags in your Stylus.toml file.

    How it works

    • Opt-in: A contract is only optimized if it contains a [wasm-opt] table in Stylus.toml.
    • Reproducibility: The version and flags are included in the project_hash. When using the default Docker-based deployment, cargo-stylus installs the pinned version automatically, ensuring the deployed bytes are reproducible.
    • Local Builds: If running commands like check or deploy --no-verify locally, cargo-stylus will use the wasm-opt found on your PATH and will error if the version does not match your pinned version.
    • Bypassing: Using the --wasm-file flag with commands bypasses the [wasm-opt] recipe; you must optimize such files manually before passing them to cargo-stylus.
    [wasm-opt]
    version = "131"     # pinned Binaryen version
    flags = ["-Oz"]     # passed verbatim to wasm-opt
  4. How to write Solidity-equivalent contracts in Rust

    main

    The SDK provides tools to abstract away Ethereum's storage layout, allowing you to write idiomatic Rust that behaves like a Solidity contract.

    Key features include:

    • sol_storage! macro: Generates storage-backed Rust structs.
    • #[entrypoint] attribute: Marks a struct as a Stylus contract entrypoint.
    • #[public] attribute: Defines language-agnostic methods and entrypoints.
    • Storage Caching: Uses generic, storage-backed types with optimal caching.
    • Alloy Integration: Uses powerful primitive types backed by the Alloy library (e.g., U256).
    • Automatic Interface Export: Automatically exports Solidity interfaces to ensure interoperability.
    use stylus_sdk::{alloy_primitives::U256, prelude::*};
    
    // Generate Solidity-equivalent, Rust structs backed by storage.
    sol_storage! {
      #[entrypoint]
      pub struct Counter {
        uint256 number;
      }
    }
    
    #[public]
    impl Counter {
      // Gets the number value from storage.
      pub fn number(&self) -> Result<U256, Vec<u8>> {
        Ok(self.number.get())
      }
    
      // Sets a number in storage to a user-specified value.
      pub fn set_number(&mut self, new_number: U256) -> Result<(), Vec<u8>> {
        self.number.set(new_number);
        Ok(())
      }
    }
  5. Use the Stylus SingleCall contract pattern

    main

    The SingleCall contract pattern provides a single entry point via an execute method. This method accepts a target address and data bytes, acting as a proxy that passes the provided data directly to the target address. This pattern is useful for consolidating multiple interactions into a single call structure.

    // Conceptual signature of the execute method
    fn execute(&mut self, target: Address, data: Bytes) {
        // Passes data to the target
    }
  6. How to interact with other Stylus contracts using the Caller pattern

    main
    A Stylus Rust contract (the 'caller') can interact with another deployed Stylus Rust contract (the 'callee') by importing the callee's crate as a dependency in its Cargo.toml. This pattern allows for seamless, type-safe contract-to-contract calls within the Stylus ecosystem.
  7. How to call one Stylus contract from another

    main
    To allow a Stylus Rust contract (the caller) to interact with another deployed Stylus Rust contract (the callee), you can import the callee's crate directly as a dependency in the caller's project. This pattern enables seamless contract-to-contract interaction within the Stylus ecosystem.
  8. Getting started with Stylus development

    main

    To begin developing and deploying WebAssembly programs to Arbitrum, use the following resources:

    • For Rust developers: Use the Stylus Hello World starter template to create a new project from a working example.
    • For deployment: Use the Cargo Stylus CLI Tool to deploy your compiled Stylus programs on-chain.
    • For non-Rust developers: The Stylus VM supports any language that compiles to WebAssembly. Official SDK ports are available for C/C++ and AssemblyScript.
  9. Set up environment variables for SingleCall demo

    main

    Before running the demo scripts, you must configure your .env file with the necessary credentials and contract addresses.

    1. Copy the template: cp .env.example .env
    2. Populate PRIVATE_KEY with a developer wallet key (ensure it has Sepolia ETH on Arbitrum Sepolia).
    3. Set COUNTER_CONTRACT_ADDRESS with the address obtained after deploying the Solidity Counter contract.

    Warning: Never use a personal account containing real assets for development. Use a fresh developer wallet.

    cp .env.example .env
  10. Use ERC-20 helper scripts to mint tokens

    main

    The ERC-20 helper scripts allow you to quickly mint a specific amount of tokens from an ERC-20 contract. By default, the script mints 1000 tokens from the contract address specified in your .env file.

    To use these scripts, follow these steps:

    1. Install dependencies: Run yarn to install the necessary Node.js packages.
    2. Configure environment: Copy the .env.example file to .env and fill in the required contract and network details.
    3. Execute minting: Run yarn mint to trigger the minting process.
    yarn
    cp .env.example .env
    yarn mint
  11. Create a new Stylus project

    main

    Use cargo stylus new to scaffold a new project. This command clones a starter project (like stylus-hello-world) which includes a sample Counter contract.

    • To create a standard project: cargo stylus new <PROJECT_NAME>
    • To create a barebones project with only a Stylus entrypoint: cargo stylus new --minimal <PROJECT_NAME>
    # Create a standard project
    cargo stylus new my-contract
    
    # Create a minimal project
    cargo stylus new --minimal my-minimal-contract