As Ethereum scaling solutions mature, Arbitrum Stylus stands out as a fascinating innovation that lets developers write smart contracts in Rust. This offers significant performance improvements and brings Rust's robust type system and memory safety guarantees to the blockchain world.
But as any experienced smart contract developer knows, thorough testing is non-negotiable. That's where Motsu comes in – a testing framework designed specifically for Stylus contracts that feels both familiar to Rust developers and conceptually similar to tools like Hardhat and Foundry that Solidity developers know well.
In this tutorial, we'll explore how to effectively test your Stylus contracts using Motsu. If you're coming from a Solidity background with experience in tools like Foundry or Hardhat, you'll find many parallels that will help you get up to speed quickly.
You can find the complete working project with all examples in our GitHub repository here: https://github.com/0xNeshi/motsu-tutorial
Motsu addresses a core challenge in testing Stylus smart contracts: simulating the blockchain environment. Just as Hardhat provides a JavaScript environment for testing Solidity contracts, and Foundry provides a Solidity-native testing experience, Motsu delivers a pure Rust testing experience for Stylus contracts by mocking the vm affordances. Unlike Hardhat or Foundry, which spin up lightweight blockchain nodes to execute tests, Motsu takes a different approach by directly intercepting and mocking Stylus host functions at the Wasm level. This enables fast, isolated tests without requiring a full blockchain runtime, making it ideal for unit testing Stylus contracts purely in Rust.
The name "Motsu" (持つ, Japanese for "to hold") cleverly references "holding a stylus in our hand" – a fitting metaphor for a tool that gives you control over your Stylus contract tests.
Before diving into Motsu, make sure your project is properly configured. Below are the dependencies that we’re going to need for this project:
Let's start with a simple example. If you've written tests in Rust before, the structure will look familiar, with one key difference: instead of using #[test], we use #[motsu::test].
Here's a basic test for our Vault contract:
This is conceptually similar to how Foundry's setUp() function prepares your test environment, but in a more Rust-idiomatic way through function parameters.
Motsu provides two types for representing blockchain accounts:
You can use either as parameters in your test functions:
Unless you need to access the private key or the underlying signer in your tests we recommend using Address as it is more lightweight.
When debugging tests, having consistently named accounts can help track issues. Motsu provides a FromTag trait that is included in its prelude to create deterministic addresses from string identifiers, and this trait is what’s used to inject test parameters:
The core of Motsu testing revolves around the Contract<T> type, which represents a deployed instance of your smart contract.
To call functions on your contract:
Imagine we added a payable deposit function to our Vault that accepts the underlying gas token:
Smart contracts often emit events that you'll want to verify in your tests.
First, let’s update our deposit function to emit an event on a successful deposit:
Testing failure scenarios is just as important as testing successful operations. Let’s add a decrease_balance function to Vault that reverts with an error on balance underflow:
These methods ensure that the contract state is properly reverted when transactions fail, similar to how real blockchain transactions behave. In case any of the above methods panic, the panic messages will be pretty-printed for clarity, with addresses being replaced with appropriate tags if that’s how they were instantiated.
One of Motsu's powerful features is the ability to test interactions between multiple contracts. This is essential for complex DeFi applications or any system with multiple interacting components.
Let’s set up a simple proxy contract implementation:
Sometimes you need to simulate specific blockchain conditions. Motsu lets you modify certain environment variables like chain ID, which lets you simulate a chain fork.
Let's put everything together by testing an ERC-20 token implementation.
We’ll inherit openzeppelin-stylus library’s ERC-20 implementation, so let’s add the necessary dependency to our Cargo.toml:
Based on the patterns we've explored, here are some best practices for testing with Motsu:
Motsu provides a powerful, Rust-native testing environment for Arbitrum Stylus contracts. While conceptually similar to tools like Hardhat and Foundry, it leverages Rust's type system to provide a more integrated testing experience.
For developers coming from Solidity, the mental model is quite transferable - you're still working with contracts, addresses, and transactions. But Motsu's design takes advantage of Rust features to make tests more concise and easier to reason about.
As you continue developing with Stylus, keep exploring Motsu's capabilities. The time invested in writing thorough tests will pay dividends in the reliability and security of your smart contracts.
Happy testing!