Composition and trait-based routing model
Learn how to implement trait-based composition in your Stylus smart contracts
Inheritance allows you to build upon existing smart contract functionality without duplicating code. In Stylus, the Rust SDK provides tools to implement inheritance patterns similar to Solidity, but with some important differences. This guide walks you through implementing trait-based composition in your Stylus smart contracts.
There's no direct equivalent of inheritance in Rust, but the following will show you the Rust-way of achieving similar results.
Overview
The Stylus SDK offers trait-based composition using traits and the #[implements] annotation. This approach follows Rust's composition patterns and provides stronger type safety.
Getting started
Before implementing trait-based composition, ensure you have:
Trait-based composition model
The recommended approach to inheritance in Stylus uses traits and the #[implements] annotation, which follows Rust's standard composition patterns:
Basic example of trait-based composition
How trait-based composition works
The trait-based composition model follows these principles:
- Define traits that represent interfaces (similar to Solidity interfaces)
- Implement these traits for your contract
- Use the
#[implements(...)]attribute to tell the Stylus SDK which traits your contract implements - The router will connect incoming calls to the appropriate implementation
This approach is aligned with Rust's composition patterns and offers better type safety.
Method overriding
If both parent and child implement the same method, the one in the child will override the one in the parent. This allows for customizing inherited functionality.
Stylus does not currently contain explicit override or virtual keywords for marking override functions. It is important to carefully ensure that contracts are only overriding the functions you intend to override.
ABI export considerations with trait-based composition
When using trait-based composition, you need to be careful about function selectors to ensure correct ABI generation. Due to how Rust handles traits, you may need to explicitly set selectors for methods to match Solidity's expected function signatures.
When implementing traits with methods that have matching names, you must manually use the #[selector(name = "ActualName")] attribute to avoid method selector collisions. This is particularly important when implementing standard interfaces like ERC-20 or ERC-721.
The Stylus SDK generates ABIs based on the methods that are available at the entrypoint contract. When using trait-based composition, make sure that all methods you want exposed in the ABI are properly included through the #[implements] attribute.
Methods search order
When using trait-based composition, it's important to understand the order in which methods are searched:
- The search starts in the type that uses the
#[entrypoint]macro - If the method is not found, the search continues in the implemented traits, in the order specified in the
#[implements]annotation - If the method is not found in any implemented trait, the call reverts
In a typical composition chain:
- Calling a method first searches in the contract itself
- If not found there, it looks in the first trait specified in the inheritance list
- If still not found, it searches in the next trait in the list
- This continues until the method is found or all possibilities are exhausted
How to optimize Stylus WASM binaries
A guide on optimizing Stylus WASM program sizes
How to verify Stylus contracts on Arbiscan
This page provides a step-by-step guide to verifying Stylus contracts on Arbiscan, including contract details, source code submission, and handling previously verified contracts.