From 5a0727c7b4f19ccb8669e6cb0c47689c525734b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Wed, 30 Apr 2025 08:14:16 -0700 Subject: [PATCH 1/9] feat: add how-to guide for using inheritance in Stylus --- .../stylus/how-tos/using-inheritance.mdx | 533 ++++++++++++++++++ website/sidebars.js | 5 + 2 files changed, 538 insertions(+) create mode 100644 arbitrum-docs/stylus/how-tos/using-inheritance.mdx diff --git a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx new file mode 100644 index 0000000000..9147a7e2a3 --- /dev/null +++ b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx @@ -0,0 +1,533 @@ +--- +title: 'Using Inheritance Patterns in Stylus Contracts' +description: 'Learn how to implement inheritance patterns in your Stylus smart contracts' +author: anegg0 +sme: mahsamoosavi +content_type: how-to +sidebar_position: 1 +--- + +import CustomDetails from '@site/src/components/CustomDetails'; + +# Using Inheritance Patterns in Stylus Contracts + +This guide explains how to implement inheritance patterns in Stylus smart contracts. Inheritance allows you to create more modular, reusable code by extending existing contracts. + +## Introduction + +Inheritance is a key feature in object-oriented programming that allows a new class (or contract) to inherit properties and methods from an existing one. In Stylus, you can use inheritance to: + +- Reuse code across multiple contracts +- Implement a standardized interface +- Extend functionality of base contracts +- Create a hierarchical contract structure + +## Prerequisites + +Before starting, make sure you have: + +- [Rust](https://www.rust-lang.org/tools/install) installed +- [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) package manager +- Basic understanding of [Stylus](https://docs.arbitrum.io/stylus/stylus-quickstart) and [Rust](https://www.rust-lang.org/learn) +- [stylus-sdk](https://github.com/OffchainLabs/stylus-sdk-rs) installed + +## Basic Inheritance + +Here's how to implement basic inheritance in a Stylus contract: + + +```rust +use stylus_sdk::{prelude::*, storage::StorageU256}; + +// Base contract #[external] +impl BaseContract { #[storage] +struct Storage { +value: StorageU256, +} + + pub fn get_value(&self) -> Result> { + Ok(self.storage.value.get()) + } + + pub fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.value.set(new_value); + Ok(()) + } + +} + +// Derived contract #[external] +impl DerivedContract { #[storage] +struct Storage { +base: BaseContract, +additional_value: StorageU256, +} + + pub fn get_value(&self) -> Result> { + self.storage.base.get_value() + } + + pub fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.base.set_value(new_value) + } + + pub fn get_additional_value(&self) -> Result> { + Ok(self.storage.additional_value.get()) + } + + pub fn set_additional_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.additional_value.set(new_value); + Ok(()) + } + +} + +```` + + +In this example, `DerivedContract` inherits from `BaseContract` by including it as a field in its storage struct. This allows `DerivedContract` to reuse the functionality defined in `BaseContract` while adding its own additional functionality. + +## Using Traits for Inheritance + +Rust traits provide a powerful way to implement inheritance-like patterns in Stylus: + + +```rust +use stylus_sdk::{prelude::*, storage::StorageU256}; + +// Define a trait for common functionality +trait ValueOperations { + fn get_value(&self) -> Result>; + fn set_value(&mut self, value: U256) -> Result<(), Vec>; +} + +// Base contract implementing the trait +#[external] +impl BaseContract { + #[storage] + struct Storage { + value: StorageU256, + } +} + +// Implement the trait for the base contract +impl ValueOperations for BaseContract { + fn get_value(&self) -> Result> { + Ok(self.storage.value.get()) + } + + fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.value.set(new_value); + Ok(()) + } +} + +// Derived contract also implementing the trait +#[external] +impl DerivedContract { + #[storage] + struct Storage { + value: StorageU256, + additional_value: StorageU256, + } +} + +// Implement the trait for the derived contract +impl ValueOperations for DerivedContract { + fn get_value(&self) -> Result> { + Ok(self.storage.value.get()) + } + + fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.value.set(new_value); + Ok(()) + } +} + +// Add additional functionality +impl DerivedContract { + pub fn get_additional_value(&self) -> Result> { + Ok(self.storage.additional_value.get()) + } + + pub fn set_additional_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.additional_value.set(new_value); + Ok(()) + } +} +```` + + + +Using traits allows you to define a common interface that multiple contracts can implement, providing a form of inheritance. + +## Composition-Based Inheritance + +Composition is often preferred over inheritance in Rust. Here's how to use composition to achieve similar results: + + +```rust +use stylus_sdk::{prelude::*, storage::StorageU256}; + +// Base contract component +struct BaseContractComponent { +value: StorageU256, +} + +impl BaseContractComponent { +fn new(value: StorageU256) -> Self { +Self { value } +} + + fn get_value(&self) -> Result> { + Ok(self.value.get()) + } + + fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.value.set(new_value); + Ok(()) + } + +} + +// Main contract using composition #[external] +impl MainContract { #[storage] +struct Storage { +base_component_value: StorageU256, +additional_value: StorageU256, +} + + pub fn get_value(&self) -> Result> { + let base_component = BaseContractComponent::new(self.storage.base_component_value); + base_component.get_value() + } + + pub fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { + let mut base_component = BaseContractComponent::new(self.storage.base_component_value); + base_component.set_value(new_value) + } + + pub fn get_additional_value(&self) -> Result> { + Ok(self.storage.additional_value.get()) + } + + pub fn set_additional_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.additional_value.set(new_value); + Ok(()) + } + +} + +```` + + +With composition, you create a component that encapsulates the functionality you want to reuse, then include and delegate to it in your main contract. + +## Standard Contract Inheritance + +When implementing standard contracts like ERC-20 or ERC-721, inheritance can be particularly useful: + + +```rust +use stylus_sdk::{prelude::*, evm, msg, storage::*, alloy_primitives::Address}; + +// ERC-20 interface trait +trait ERC20Interface { + fn total_supply(&self) -> Result>; + fn balance_of(&self, account: Address) -> Result>; + fn transfer(&mut self, recipient: Address, amount: U256) -> Result>; + // Additional ERC-20 methods... +} + +// Base ERC-20 implementation +struct BaseERC20 { + total_supply: StorageU256, + balances: StorageMap, +} + +impl BaseERC20 { + fn new(total_supply: StorageU256, balances: StorageMap) -> Self { + Self { total_supply, balances } + } +} + +impl ERC20Interface for BaseERC20 { + fn total_supply(&self) -> Result> { + Ok(self.total_supply.get()) + } + + fn balance_of(&self, account: Address) -> Result> { + Ok(self.balances.get(account)) + } + + fn transfer(&mut self, recipient: Address, amount: U256) -> Result> { + let sender = msg::sender(); + let sender_balance = self.balances.get(sender); + + require(sender_balance >= amount, "Insufficient balance"); + + self.balances.insert(sender, sender_balance - amount); + self.balances.insert(recipient, self.balances.get(recipient) + amount); + + // Emit transfer event + evm::log(Transfer { from: sender, to: recipient, value: amount }); + + Ok(true) + } +} + +// Custom token extending the base ERC-20 +#[external] +impl CustomToken { + #[storage] + struct Storage { + total_supply: StorageU256, + balances: StorageMap, + // Additional fields specific to the custom token + paused: StorageBool, + } + + pub fn total_supply(&self) -> Result> { + let base = BaseERC20::new(self.storage.total_supply, self.storage.balances); + base.total_supply() + } + + pub fn balance_of(&self, account: Address) -> Result> { + let base = BaseERC20::new(self.storage.total_supply, self.storage.balances); + base.balance_of(account) + } + + pub fn transfer(&mut self, recipient: Address, amount: U256) -> Result> { + require(!self.storage.paused.get(), "Transfers are paused"); + + let mut base = BaseERC20::new(self.storage.total_supply, self.storage.balances); + base.transfer(recipient, amount) + } + + pub fn pause(&mut self) -> Result<(), Vec> { + require(msg::sender() == self.owner(), "Not authorized"); + self.storage.paused.set(true); + Ok(()) + } + + pub fn unpause(&mut self) -> Result<(), Vec> { + require(msg::sender() == self.owner(), "Not authorized"); + self.storage.paused.set(false); + Ok(()) + } + + fn owner(&self) -> Address { + // Logic to get the owner + Address::from([0; 20]) // Placeholder + } +} + +// Event struct +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Transfer { + pub from: Address, + pub to: Address, + pub value: U256, +} +```` + + + +This example shows how to implement an ERC-20 token with additional functionality by extending a base implementation. + +## Advanced Inheritance Patterns + +### Multi-Level Inheritance + +You can create multi-level inheritance by nesting contracts: + + +```rust +use stylus_sdk::{prelude::*, storage::StorageU256}; + +// Base contract #[external] +impl BaseContract { #[storage] +struct Storage { +base_value: StorageU256, +} + + pub fn get_base_value(&self) -> Result> { + Ok(self.storage.base_value.get()) + } + + pub fn set_base_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.base_value.set(new_value); + Ok(()) + } + +} + +// Intermediate contract #[external] +impl IntermediateContract { #[storage] +struct Storage { +base: BaseContract, +intermediate_value: StorageU256, +} + + pub fn get_base_value(&self) -> Result> { + self.storage.base.get_base_value() + } + + pub fn set_base_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.base.set_base_value(new_value) + } + + pub fn get_intermediate_value(&self) -> Result> { + Ok(self.storage.intermediate_value.get()) + } + + pub fn set_intermediate_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.intermediate_value.set(new_value); + Ok(()) + } + +} + +// Derived contract #[external] +impl DerivedContract { #[storage] +struct Storage { +intermediate: IntermediateContract, +derived_value: StorageU256, +} + + pub fn get_base_value(&self) -> Result> { + self.storage.intermediate.get_base_value() + } + + pub fn set_base_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.intermediate.set_base_value(new_value) + } + + pub fn get_intermediate_value(&self) -> Result> { + self.storage.intermediate.get_intermediate_value() + } + + pub fn set_intermediate_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.intermediate.set_intermediate_value(new_value) + } + + pub fn get_derived_value(&self) -> Result> { + Ok(self.storage.derived_value.get()) + } + + pub fn set_derived_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.derived_value.set(new_value); + Ok(()) + } + +} + +```` + + +### Multiple Inheritance + +Rust doesn't support traditional multiple inheritance, but you can achieve similar functionality using traits and composition: + + +```rust +use stylus_sdk::{prelude::*, storage::StorageU256}; + +// First trait +trait ValueOperations { + fn get_value(&self) -> Result>; + fn set_value(&mut self, value: U256) -> Result<(), Vec>; +} + +// Second trait +trait NameOperations { + fn get_name(&self) -> Result>; + fn set_name(&mut self, name: String) -> Result<(), Vec>; +} + +// Contract implementing both traits +#[external] +impl MultiContract { + #[storage] + struct Storage { + value: StorageU256, + // For simplicity, we're not implementing proper string storage here + // In a real contract, you would use StorageString or similar + } +} + +// Implement first trait +impl ValueOperations for MultiContract { + fn get_value(&self) -> Result> { + Ok(self.storage.value.get()) + } + + fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { + self.storage.value.set(new_value); + Ok(()) + } +} + +// Implement second trait (simplified) +impl NameOperations for MultiContract { + fn get_name(&self) -> Result> { + // Simplified implementation + Ok("MultiContract".to_string()) + } + + fn set_name(&mut self, _name: String) -> Result<(), Vec> { + // Simplified implementation + Ok(()) + } +} + +// Expose trait methods through the external interface +#[external] +impl MultiContractExternal for MultiContract { + pub fn get_value(&self) -> Result> { + ValueOperations::get_value(self) + } + + pub fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { + ValueOperations::set_value(self, new_value) + } + + pub fn get_name(&self) -> Result> { + NameOperations::get_name(self) + } + + pub fn set_name(&mut self, name: String) -> Result<(), Vec> { + NameOperations::set_name(self, name) + } +} +```` + + + +## Best Practices + +When using inheritance patterns in Stylus contracts, consider the following best practices: + +1. **Favor Composition Over Inheritance**: In Rust, composition is generally preferred over inheritance. Use composition when possible. + +2. **Use Traits for Interface Definitions**: Traits provide a way to define common interfaces that multiple contracts can implement. + +3. **Keep Storage Layout in Mind**: When extending contracts, be careful about storage layout to avoid conflicts. + +4. **Document Inheritance Relationships**: Clearly document the inheritance structure of your contracts. + +5. **Test Inherited Functionality**: Ensure that functionality inherited from base contracts is properly tested. + +6. **Consider Gas Efficiency**: Deep inheritance hierarchies can lead to increased gas costs. Keep your inheritance structure efficient. + +## Conclusion + +Inheritance patterns in Stylus contracts provide a powerful way to create modular, reusable code. By leveraging Rust's traits and composition, you can implement sophisticated contract hierarchies while maintaining the benefits of Rust's safety and performance. + +Remember that Rust's approach to inheritance differs from other languages like Solidity. Understanding these differences is key to writing effective Stylus contracts. + +## Next Steps + +- Explore examples of inheritance in the [Stylus SDK documentation](https://github.com/OffchainLabs/stylus-sdk-rs) +- Review implementations of standard contracts like ERC-20 and ERC-721 that use inheritance patterns +- Practice implementing your own contract hierarchies using the patterns shown in this guide diff --git a/website/sidebars.js b/website/sidebars.js index c80f1f0d2d..2882390498 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -632,6 +632,11 @@ const sidebars = { id: 'stylus/how-tos/caching-contracts', label: 'Cache contracts', }, + { + type: 'doc', + id: 'stylus/how-tos/using-inheritance', + label: 'Using Inheritance', + }, { type: 'doc', id: 'stylus/how-tos/verifying-contracts-arbiscan', From 7f199d585ea2fa6e15d67559c9554ae385fec18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Wed, 30 Apr 2025 10:09:24 -0700 Subject: [PATCH 2/9] Feature: Add VanillaAdmonition to Using Inheritance Page --- arbitrum-docs/stylus/how-tos/using-inheritance.mdx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx index 9147a7e2a3..e7de1a97b3 100644 --- a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx +++ b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx @@ -8,6 +8,8 @@ sidebar_position: 1 --- import CustomDetails from '@site/src/components/CustomDetails'; +import { VanillaAdmonition } from '@site/src/components/VanillaAdmonition/'; + # Using Inheritance Patterns in Stylus Contracts @@ -22,6 +24,11 @@ Inheritance is a key feature in object-oriented programming that allows a new cl - Extend functionality of base contracts - Create a hierarchical contract structure + +Stylus doesn't support contract multi-inheritance yet. + + + ## Prerequisites Before starting, make sure you have: From 9716e34064bccef6e8762035fe227914310aa7d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Thu, 1 May 2025 10:00:08 -0700 Subject: [PATCH 3/9] refactor: add more accurate implementation details + vanillaadmonition & customdetails components --- .../stylus/how-tos/using-inheritance.mdx | 550 ++++-------------- 1 file changed, 108 insertions(+), 442 deletions(-) diff --git a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx index e7de1a97b3..548fe1df2a 100644 --- a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx +++ b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx @@ -8,533 +8,199 @@ sidebar_position: 1 --- import CustomDetails from '@site/src/components/CustomDetails'; -import { VanillaAdmonition } from '@site/src/components/VanillaAdmonition/'; +import VanillaAdmonition from '@site/src/components/VanillaAdmonition'; +# How To Implement Inheritance Patterns in Stylus -# Using Inheritance Patterns in Stylus Contracts +Inheritance is a powerful design pattern that 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 inheritance in your Stylus smart contracts. -This guide explains how to implement inheritance patterns in Stylus smart contracts. Inheritance allows you to create more modular, reusable code by extending existing contracts. +## Overview -## Introduction +Stylus allows you to write smart contracts in Rust that are fully interoperable with EVM contracts. The inheritance model in Stylus aims to replicate the composition pattern found in Solidity, where types that implement the Router trait (provided by the `#[public]` macro) can be connected via inheritance. -Inheritance is a key feature in object-oriented programming that allows a new class (or contract) to inherit properties and methods from an existing one. In Stylus, you can use inheritance to: - -- Reuse code across multiple contracts -- Implement a standardized interface -- Extend functionality of base contracts -- Create a hierarchical contract structure - - -Stylus doesn't support contract multi-inheritance yet. + + Stylus doesn't currently support contract multi-inheritance, so you should design your contracts accordingly. +## Getting Started -## Prerequisites +Before implementing inheritance, ensure you have: -Before starting, make sure you have: +1. Installed the Rust toolchain +2. Installed `cargo-stylus` CLI tool: `cargo install --force cargo-stylus` +3. Set up a Stylus project: `cargo stylus new your_project_name` -- [Rust](https://www.rust-lang.org/tools/install) installed -- [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) package manager -- Basic understanding of [Stylus](https://docs.arbitrum.io/stylus/stylus-quickstart) and [Rust](https://www.rust-lang.org/learn) -- [stylus-sdk](https://github.com/OffchainLabs/stylus-sdk-rs) installed +## Basic Inheritance Pattern -## Basic Inheritance +The inheritance pattern in Stylus requires two main components: -Here's how to implement basic inheritance in a Stylus contract: +1. A storage structure using the `#[borrow]` annotation +2. An implementation block using the `#[inherit]` annotation - -```rust -use stylus_sdk::{prelude::*, storage::StorageU256}; +Let's walk through a practical example. -// Base contract #[external] -impl BaseContract { #[storage] -struct Storage { -value: StorageU256, -} +### Step 1: Define the Base Contract - pub fn get_value(&self) -> Result> { - Ok(self.storage.value.get()) - } +First, define your base contract that will be inherited: - pub fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.value.set(new_value); - Ok(()) - } + -} +```rust +use stylus_sdk::{alloy_primitives::U256, prelude::*}; -// Derived contract #[external] -impl DerivedContract { #[storage] -struct Storage { -base: BaseContract, -additional_value: StorageU256, +sol_storage! { + pub struct BaseContract { + uint256 value; + } } +#[public] +impl BaseContract { pub fn get_value(&self) -> Result> { - self.storage.base.get_value() + Ok(self.value.get()) } - + pub fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.base.set_value(new_value) - } - - pub fn get_additional_value(&self) -> Result> { - Ok(self.storage.additional_value.get()) - } - - pub fn set_additional_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.additional_value.set(new_value); + self.value.set(new_value); Ok(()) } - } +``` -```` -In this example, `DerivedContract` inherits from `BaseContract` by including it as a field in its storage struct. This allows `DerivedContract` to reuse the functionality defined in `BaseContract` while adding its own additional functionality. +### Step 2: Define the Child Contract with Inheritance -## Using Traits for Inheritance +Next, create your child contract that inherits from the base contract: -Rust traits provide a powerful way to implement inheritance-like patterns in Stylus: + - ```rust -use stylus_sdk::{prelude::*, storage::StorageU256}; - -// Define a trait for common functionality -trait ValueOperations { - fn get_value(&self) -> Result>; - fn set_value(&mut self, value: U256) -> Result<(), Vec>; -} - -// Base contract implementing the trait -#[external] -impl BaseContract { - #[storage] - struct Storage { - value: StorageU256, - } -} - -// Implement the trait for the base contract -impl ValueOperations for BaseContract { - fn get_value(&self) -> Result> { - Ok(self.storage.value.get()) - } - - fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.value.set(new_value); - Ok(()) +sol_storage! { + #[entrypoint] + pub struct ChildContract { + #[borrow] + BaseContract base_contract; + uint256 additional_value; } } -// Derived contract also implementing the trait -#[external] -impl DerivedContract { - #[storage] - struct Storage { - value: StorageU256, - additional_value: StorageU256, - } -} - -// Implement the trait for the derived contract -impl ValueOperations for DerivedContract { - fn get_value(&self) -> Result> { - Ok(self.storage.value.get()) - } - - fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.value.set(new_value); - Ok(()) - } -} - -// Add additional functionality -impl DerivedContract { +#[public] +#[inherit(BaseContract)] +impl ChildContract { pub fn get_additional_value(&self) -> Result> { - Ok(self.storage.additional_value.get()) + Ok(self.additional_value.get()) } - + pub fn set_additional_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.additional_value.set(new_value); + self.additional_value.set(new_value); Ok(()) } } -```` +``` -Using traits allows you to define a common interface that multiple contracts can implement, providing a form of inheritance. - -## Composition-Based Inheritance - -Composition is often preferred over inheritance in Rust. Here's how to use composition to achieve similar results: - - -```rust -use stylus_sdk::{prelude::*, storage::StorageU256}; +### How It Works -// Base contract component -struct BaseContractComponent { -value: StorageU256, -} +When you add the `#[entrypoint]` macro to your `ChildContract` struct, calls to the contract will first check if the requested method exists within `ChildContract`. If a matching function is not found, it will then try the `BaseContract`. Only after trying everything `ChildContract` inherits will the call revert. -impl BaseContractComponent { -fn new(value: StorageU256) -> Self { -Self { value } -} +The `#[borrow]` annotation in your storage declaration is crucial for inheritance to work properly, as it allows the child contract to access the base contract's storage. - fn get_value(&self) -> Result> { - Ok(self.value.get()) - } +## Method Overriding - fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.value.set(new_value); - Ok(()) - } +If both parent and child implement the same method, the one in the child will override the one in the parent, which won't be callable. This allows for patterns where you import a crate implementing a standard, like an ERC-20, and then add or override just the methods you want to without modifying the imported type. -} +For example: -// Main contract using composition #[external] -impl MainContract { #[storage] -struct Storage { -base_component_value: StorageU256, -additional_value: StorageU256, -} - - pub fn get_value(&self) -> Result> { - let base_component = BaseContractComponent::new(self.storage.base_component_value); - base_component.get_value() - } + +```rust +#[public] +#[inherit(BaseContract)] +impl ChildContract { + // This overrides the base_contract.set_value method pub fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { - let mut base_component = BaseContractComponent::new(self.storage.base_component_value); - base_component.set_value(new_value) - } - - pub fn get_additional_value(&self) -> Result> { - Ok(self.storage.additional_value.get()) - } - - pub fn set_additional_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.additional_value.set(new_value); + // Custom implementation + if new_value > U256::from(100) { + return Err("Value too large".as_bytes().to_vec()); + } + self.base_contract.set_value(new_value)?; Ok(()) } - } +``` -```` -With composition, you create a component that encapsulates the functionality you want to reuse, then include and delegate to it in your main contract. - -## Standard Contract Inheritance - -When implementing standard contracts like ERC-20 or ERC-721, inheritance can be particularly useful: - - -```rust -use stylus_sdk::{prelude::*, evm, msg, storage::*, alloy_primitives::Address}; - -// ERC-20 interface trait -trait ERC20Interface { - fn total_supply(&self) -> Result>; - fn balance_of(&self, account: Address) -> Result>; - fn transfer(&mut self, recipient: Address, amount: U256) -> Result>; - // Additional ERC-20 methods... -} - -// Base ERC-20 implementation -struct BaseERC20 { - total_supply: StorageU256, - balances: StorageMap, -} - -impl BaseERC20 { - fn new(total_supply: StorageU256, balances: StorageMap) -> Self { - Self { total_supply, balances } - } -} - -impl ERC20Interface for BaseERC20 { - fn total_supply(&self) -> Result> { - Ok(self.total_supply.get()) - } - - fn balance_of(&self, account: Address) -> Result> { - Ok(self.balances.get(account)) - } - - fn transfer(&mut self, recipient: Address, amount: U256) -> Result> { - let sender = msg::sender(); - let sender_balance = self.balances.get(sender); - - require(sender_balance >= amount, "Insufficient balance"); - - self.balances.insert(sender, sender_balance - amount); - self.balances.insert(recipient, self.balances.get(recipient) + amount); - - // Emit transfer event - evm::log(Transfer { from: sender, to: recipient, value: amount }); - - Ok(true) - } -} - -// Custom token extending the base ERC-20 -#[external] -impl CustomToken { - #[storage] - struct Storage { - total_supply: StorageU256, - balances: StorageMap, - // Additional fields specific to the custom token - paused: StorageBool, - } - - pub fn total_supply(&self) -> Result> { - let base = BaseERC20::new(self.storage.total_supply, self.storage.balances); - base.total_supply() - } - - pub fn balance_of(&self, account: Address) -> Result> { - let base = BaseERC20::new(self.storage.total_supply, self.storage.balances); - base.balance_of(account) - } - - pub fn transfer(&mut self, recipient: Address, amount: U256) -> Result> { - require(!self.storage.paused.get(), "Transfers are paused"); - - let mut base = BaseERC20::new(self.storage.total_supply, self.storage.balances); - base.transfer(recipient, amount) - } - - pub fn pause(&mut self) -> Result<(), Vec> { - require(msg::sender() == self.owner(), "Not authorized"); - self.storage.paused.set(true); - Ok(()) - } - - pub fn unpause(&mut self) -> Result<(), Vec> { - require(msg::sender() == self.owner(), "Not authorized"); - self.storage.paused.set(false); - Ok(()) - } - - fn owner(&self) -> Address { - // Logic to get the owner - Address::from([0; 20]) // Placeholder - } -} + + 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. + -// Event struct -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Transfer { - pub from: Address, - pub to: Address, - pub value: U256, -} -```` +## Storage Layout Considerations - + + Note that one exception to Stylus's storage layout guarantee is contracts which utilize inheritance. The current solution in Stylus using `#[borrow]` and `#[inherits(...)]` packs nested (inherited) structs into their own slots. This is consistent with regular struct nesting in Solidity, but not with inherited structs. + -This example shows how to implement an ERC-20 token with additional functionality by extending a base implementation. +This has important implications when upgrading from Solidity to Rust, as storage slots may not align the same way. -## Advanced Inheritance Patterns +## Working Example: ERC-20 Token with Inheritance -### Multi-Level Inheritance +A practical example of inheritance in Stylus is implementing an ERC-20 token with custom functionality. Here's how it works: -You can create multi-level inheritance by nesting contracts: + - ```rust -use stylus_sdk::{prelude::*, storage::StorageU256}; - -// Base contract #[external] -impl BaseContract { #[storage] -struct Storage { -base_value: StorageU256, +// Define the base ERC-20 functionality +struct StylusTokenParams; +impl Erc20Params for StylusTokenParams { + const NAME: &'static str = "StylusToken"; + const SYMBOL: &'static str = "STK"; + const DECIMALS: u8 = 18; } - pub fn get_base_value(&self) -> Result> { - Ok(self.storage.base_value.get()) - } - - pub fn set_base_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.base_value.set(new_value); - Ok(()) +sol_storage! { + #[entrypoint] + struct StylusToken { + #[borrow] + Erc20 erc20; } - -} - -// Intermediate contract #[external] -impl IntermediateContract { #[storage] -struct Storage { -base: BaseContract, -intermediate_value: StorageU256, } - pub fn get_base_value(&self) -> Result> { - self.storage.base.get_base_value() - } - - pub fn set_base_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.base.set_base_value(new_value) - } - - pub fn get_intermediate_value(&self) -> Result> { - Ok(self.storage.intermediate_value.get()) - } - - pub fn set_intermediate_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.intermediate_value.set(new_value); +#[public] +#[inherit(Erc20)] +impl StylusToken { + // Add custom functionality + pub fn mint(&mut self, value: U256) -> Result<(), Erc20Error> { + self.erc20.mint(msg::sender(), value)?; Ok(()) } - } +``` -// Derived contract #[external] -impl DerivedContract { #[storage] -struct Storage { -intermediate: IntermediateContract, -derived_value: StorageU256, -} - - pub fn get_base_value(&self) -> Result> { - self.storage.intermediate.get_base_value() - } - - pub fn set_base_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.intermediate.set_base_value(new_value) - } - - pub fn get_intermediate_value(&self) -> Result> { - self.storage.intermediate.get_intermediate_value() - } - - pub fn set_intermediate_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.intermediate.set_intermediate_value(new_value) - } - - pub fn get_derived_value(&self) -> Result> { - Ok(self.storage.derived_value.get()) - } - - pub fn set_derived_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.derived_value.set(new_value); - Ok(()) - } - -} - -```` -### Multiple Inheritance - -Rust doesn't support traditional multiple inheritance, but you can achieve similar functionality using traits and composition: +This example shows how to inherit from a generic ERC-20 implementation and add custom minting functionality. - -```rust -use stylus_sdk::{prelude::*, storage::StorageU256}; - -// First trait -trait ValueOperations { - fn get_value(&self) -> Result>; - fn set_value(&mut self, value: U256) -> Result<(), Vec>; -} +## Current Limitations -// Second trait -trait NameOperations { - fn get_name(&self) -> Result>; - fn set_name(&mut self, name: String) -> Result<(), Vec>; -} - -// Contract implementing both traits -#[external] -impl MultiContract { - #[storage] - struct Storage { - value: StorageU256, - // For simplicity, we're not implementing proper string storage here - // In a real contract, you would use StorageString or similar - } -} - -// Implement first trait -impl ValueOperations for MultiContract { - fn get_value(&self) -> Result> { - Ok(self.storage.value.get()) - } - - fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { - self.storage.value.set(new_value); - Ok(()) - } -} - -// Implement second trait (simplified) -impl NameOperations for MultiContract { - fn get_name(&self) -> Result> { - // Simplified implementation - Ok("MultiContract".to_string()) - } - - fn set_name(&mut self, _name: String) -> Result<(), Vec> { - // Simplified implementation - Ok(()) - } -} - -// Expose trait methods through the external interface -#[external] -impl MultiContractExternal for MultiContract { - pub fn get_value(&self) -> Result> { - ValueOperations::get_value(self) - } - - pub fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { - ValueOperations::set_value(self, new_value) - } - - pub fn get_name(&self) -> Result> { - NameOperations::get_name(self) - } - - pub fn set_name(&mut self, name: String) -> Result<(), Vec> { - NameOperations::set_name(self, name) - } -} -```` - - +1. Stylus doesn't support contract multi-inheritance yet +2. The storage layout for inherited contracts differs from Solidity's inheritance model +3. There's a risk of undetected selector collisions with functions from inherited contracts ## Best Practices -When using inheritance patterns in Stylus contracts, consider the following best practices: - -1. **Favor Composition Over Inheritance**: In Rust, composition is generally preferred over inheritance. Use composition when possible. - -2. **Use Traits for Interface Definitions**: Traits provide a way to define common interfaces that multiple contracts can implement. - -3. **Keep Storage Layout in Mind**: When extending contracts, be careful about storage layout to avoid conflicts. - -4. **Document Inheritance Relationships**: Clearly document the inheritance structure of your contracts. - -5. **Test Inherited Functionality**: Ensure that functionality inherited from base contracts is properly tested. - -6. **Consider Gas Efficiency**: Deep inheritance hierarchies can lead to increased gas costs. Keep your inheritance structure efficient. +1. Use `cargo expand` to examine the expanded code and verify inheritance is working as expected +2. Be cautious with method overriding since there are no explicit override keywords +3. Design your contracts with single inheritance in mind +4. Test thoroughly to ensure all inherited methods work correctly +5. Be aware of potential storage layout differences when migrating from Solidity ## Conclusion -Inheritance patterns in Stylus contracts provide a powerful way to create modular, reusable code. By leveraging Rust's traits and composition, you can implement sophisticated contract hierarchies while maintaining the benefits of Rust's safety and performance. - -Remember that Rust's approach to inheritance differs from other languages like Solidity. Understanding these differences is key to writing effective Stylus contracts. +Inheritance in Stylus provides a powerful way to compose contracts and reuse code. While it has some limitations compared to Solidity's inheritance model, it offers a familiar pattern for developers coming from Solidity and enables clean extension of existing functionality. -## Next Steps +By following the patterns outlined in this guide, you can create modular, extensible smart contracts in Rust that leverage inheritance while maintaining full EVM compatibility. -- Explore examples of inheritance in the [Stylus SDK documentation](https://github.com/OffchainLabs/stylus-sdk-rs) -- Review implementations of standard contracts like ERC-20 and ERC-721 that use inheritance patterns -- Practice implementing your own contract hierarchies using the patterns shown in this guide +For more information, refer to the [Stylus SDK documentation](https://docs.arbitrum.io/stylus/reference/rust-sdk-guide). From fcabafdede7603f31f162f64cb57c8b75ed8544f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Thu, 1 May 2025 10:35:18 -0700 Subject: [PATCH 4/9] fix: refactor components imports --- .../stylus/how-tos/using-inheritance.mdx | 150 +++++++++++++++--- 1 file changed, 128 insertions(+), 22 deletions(-) diff --git a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx index 548fe1df2a..99595edf78 100644 --- a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx +++ b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx @@ -7,8 +7,8 @@ content_type: how-to sidebar_position: 1 --- -import CustomDetails from '@site/src/components/CustomDetails'; -import VanillaAdmonition from '@site/src/components/VanillaAdmonition'; +import CustomDetails from '@site/src/components/CustomDetails'; +import { VanillaAdmonition } from '@site/src/components/VanillaAdmonition/'; # How To Implement Inheritance Patterns in Stylus @@ -16,10 +16,11 @@ Inheritance is a powerful design pattern that allows you to build upon existing ## Overview -Stylus allows you to write smart contracts in Rust that are fully interoperable with EVM contracts. The inheritance model in Stylus aims to replicate the composition pattern found in Solidity, where types that implement the Router trait (provided by the `#[public]` macro) can be connected via inheritance. +The Stylus SDK enables smart contract developers to write programs for Arbitrum chains in Rust that are fully interoperable with EVM contracts. The inheritance model in Stylus aims to replicate the composition pattern found in Solidity. Types that implement the Router trait (provided by the `#[public]` macro) can be connected via inheritance. - - Stylus doesn't currently support contract multi-inheritance, so you should design your contracts accordingly. + + Stylus doesn't currently support contract multi-inheritance yet, so you should design your + contracts accordingly. ## Getting Started @@ -30,14 +31,18 @@ Before implementing inheritance, ensure you have: 2. Installed `cargo-stylus` CLI tool: `cargo install --force cargo-stylus` 3. Set up a Stylus project: `cargo stylus new your_project_name` -## Basic Inheritance Pattern +## Understanding the Inheritance Model in Stylus -The inheritance pattern in Stylus requires two main components: +The inheritance pattern in Stylus requires two key components: 1. A storage structure using the `#[borrow]` annotation 2. An implementation block using the `#[inherit]` annotation -Let's walk through a practical example. +When you use these annotations properly, the child contract will be able to inherit the public methods from the parent contract. + +## Basic Inheritance Pattern + +Let's walk through a practical example of implementing inheritance in Stylus. ### Step 1: Define the Base Contract @@ -59,7 +64,7 @@ impl BaseContract { pub fn get_value(&self) -> Result> { Ok(self.value.get()) } - + pub fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { self.value.set(new_value); Ok(()) @@ -69,6 +74,8 @@ impl BaseContract { +In this example, we've created a simple base contract with a single state variable and two methods to get and set its value. + ### Step 2: Define the Child Contract with Inheritance Next, create your child contract that inherits from the base contract: @@ -91,7 +98,7 @@ impl ChildContract { pub fn get_additional_value(&self) -> Result> { Ok(self.additional_value.get()) } - + pub fn set_additional_value(&mut self, new_value: U256) -> Result<(), Vec> { self.additional_value.set(new_value); Ok(()) @@ -103,13 +110,19 @@ impl ChildContract { ### How It Works -When you add the `#[entrypoint]` macro to your `ChildContract` struct, calls to the contract will first check if the requested method exists within `ChildContract`. If a matching function is not found, it will then try the `BaseContract`. Only after trying everything `ChildContract` inherits will the call revert. +In the above code, when someone calls the `ChildContract` on a function defined in `BaseContract`, like `get_value()`, the function from `BaseContract` will be executed. -The `#[borrow]` annotation in your storage declaration is crucial for inheritance to work properly, as it allows the child contract to access the base contract's storage. +Here's the step-by-step process of how inheritance works in Stylus: + +1. The `#[entrypoint]` macro on `ChildContract` marks it as the entry point for Stylus execution +2. The `#[borrow]` annotation on the `BaseContract` field implements the `Borrow` trait, allowing the child to access the parent's storage +3. The `#[inherit(BaseContract)]` annotation on the implementation connects the child to the parent's methods through the Router trait + +When a method is called on `ChildContract`, it first checks if the requested method exists within `ChildContract`. If a matching function is not found, it will then try the `BaseContract`. Only after trying everything `ChildContract` inherits will the call revert. ## Method Overriding -If both parent and child implement the same method, the one in the child will override the one in the parent, which won't be callable. This allows for patterns where you import a crate implementing a standard, like an ERC-20, and then add or override just the methods you want to without modifying the imported type. +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. For example: @@ -121,7 +134,7 @@ For example: impl ChildContract { // This overrides the base_contract.set_value method pub fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { - // Custom implementation + // Custom implementation with validation if new_value > U256::from(100) { return Err("Value too large".as_bytes().to_vec()); } @@ -134,16 +147,94 @@ impl ChildContract { - 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. + Stylus does not currently contain explicit override or virtual keywords for explicitly marking + override functions. It is important, therefore, to carefully ensure that contracts are only + overriding the functions you intend to override. +## Advanced Inheritance Patterns + +### Chained Inheritance + +Inheritance can also be chained. `#[inherit(Erc20, Erc721)]` will inherit both Erc20 and Erc721, checking for methods in that order. Erc20 and Erc721 may also inherit other types themselves. Method resolution finds the first matching method by Depth First Search. + + + +```rust +#[public] +#[inherit(A, B, C)] +impl MyContract { + // Custom implementations here +} +``` + + + +When using chained inheritance, remember that method resolution follows the order specified in the `#[inherit]` annotation, from left to right, with depth-first search. + +### Generics and Inheritance + +Stylus also supports using generics with inheritance, which is particularly useful for creating configurable base contracts: + + + +```rust +pub trait Erc20Params { + const NAME: &'static str; + const SYMBOL: &'static str; + const DECIMALS: u8; +} + +sol_storage! { + pub struct Erc20 { + mapping(address => uint256) balances; + PhantomData phantom; // Zero-cost generic parameter + } +} + +// Implementation for the generic base contract +#[public] +impl Erc20 { + // Methods here +} + +// Usage in a child contract +struct MyTokenParams; +impl Erc20Params for MyTokenParams { + const NAME: &'static str = "MyToken"; + const SYMBOL: &'static str = "MTK"; + const DECIMALS: u8 = 18; +} + +sol_storage! { + #[entrypoint] + pub struct MyToken { + #[borrow] + Erc20 erc20; + } +} + +#[public] +#[inherit(Erc20)] +impl MyToken { + // Custom implementations here +} +``` + + + +This pattern allows consumers of generic base contracts like `Erc20` to choose immutable constants via specialization. + ## Storage Layout Considerations - Note that one exception to Stylus's storage layout guarantee is contracts which utilize inheritance. The current solution in Stylus using `#[borrow]` and `#[inherits(...)]` packs nested (inherited) structs into their own slots. This is consistent with regular struct nesting in Solidity, but not with inherited structs. + Note that one exception to Stylus's storage layout guarantee is contracts which utilize + inheritance. The current solution in Stylus using #[borrow] and #[inherits(...)] packs nested + (inherited) structs into their own slots. This is consistent with regular struct nesting in + solidity, but not inherited structs. -This has important implications when upgrading from Solidity to Rust, as storage slots may not align the same way. +This has important implications when upgrading from Solidity to Rust, as storage slots may not align the same way. The Stylus team plans to revisit this behavior in an upcoming release. ## Working Example: ERC-20 Token with Inheritance @@ -152,7 +243,7 @@ A practical example of inheritance in Stylus is implementing an ERC-20 token wit ```rust -// Define the base ERC-20 functionality +// Define the base ERC-20 functionality parameters struct StylusTokenParams; impl Erc20Params for StylusTokenParams { const NAME: &'static str = "StylusToken"; @@ -160,6 +251,7 @@ impl Erc20Params for StylusTokenParams { const DECIMALS: u8 = 18; } +// Storage definition with inheritance sol_storage! { #[entrypoint] struct StylusToken { @@ -168,6 +260,7 @@ sol_storage! { } } +// Implementation with inheritance #[public] #[inherit(Erc20)] impl StylusToken { @@ -181,21 +274,34 @@ impl StylusToken { -This example shows how to inherit from a generic ERC-20 implementation and add custom minting functionality. +This example shows how to inherit from a generic ERC-20 implementation and add custom functionality like minting. The pattern is very useful for token contracts where you need all the standard ERC-20 functionality but want to add custom features. + +## Current Limitations and Best Practices -## Current Limitations +### Limitations 1. Stylus doesn't support contract multi-inheritance yet 2. The storage layout for inherited contracts differs from Solidity's inheritance model 3. There's a risk of undetected selector collisions with functions from inherited contracts -## Best Practices +### Best Practices 1. Use `cargo expand` to examine the expanded code and verify inheritance is working as expected 2. Be cautious with method overriding since there are no explicit override keywords 3. Design your contracts with single inheritance in mind 4. Test thoroughly to ensure all inherited methods work correctly 5. Be aware of potential storage layout differences when migrating from Solidity +6. Consider using OpenZeppelin's Rust contracts for standardized implementations + +## Debugging Inheritance Issues + +If you encounter issues with inheritance in your Stylus contracts, try these approaches: + +1. Verify the `#[borrow]` annotation is correctly applied to the parent contract field +2. Ensure the `#[inherit]` annotation includes the correct parent contract type +3. Check for method name conflicts between parent and child contracts +4. Use the `cargo stylus check` command to verify your contract compiles correctly +5. Test individual methods from both the parent and child contracts to isolate issues ## Conclusion @@ -203,4 +309,4 @@ Inheritance in Stylus provides a powerful way to compose contracts and reuse cod By following the patterns outlined in this guide, you can create modular, extensible smart contracts in Rust that leverage inheritance while maintaining full EVM compatibility. -For more information, refer to the [Stylus SDK documentation](https://docs.arbitrum.io/stylus/reference/rust-sdk-guide). +For more information, refer to the [Stylus SDK documentation](https://docs.arbitrum.io/stylus/reference/rust-sdk-guide) and [Stylus by Example](https://docs.arbitrum.io/stylus-by-example/basic_examples/inheritance). From 8c83f0ecb76e72fc367f035354599a6f13845c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Thu, 1 May 2025 12:49:26 -0700 Subject: [PATCH 5/9] fix: add sentence case --- .../stylus/how-tos/using-inheritance.mdx | 71 ++++++++++++------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx index 99595edf78..33dcddc83d 100644 --- a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx +++ b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx @@ -12,18 +12,18 @@ import { VanillaAdmonition } from '@site/src/components/VanillaAdmonition/'; # How To Implement Inheritance Patterns in Stylus -Inheritance is a powerful design pattern that 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 inheritance 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 inheritance in your Stylus smart contracts. ## Overview -The Stylus SDK enables smart contract developers to write programs for Arbitrum chains in Rust that are fully interoperable with EVM contracts. The inheritance model in Stylus aims to replicate the composition pattern found in Solidity. Types that implement the Router trait (provided by the `#[public]` macro) can be connected via inheritance. +The inheritance model in Stylus aims to replicate the composition pattern found in Solidity. Types that implement the Router trait (provided by the `#[public]` macro) can be connected via inheritance. Stylus doesn't currently support contract multi-inheritance yet, so you should design your contracts accordingly. -## Getting Started +## Getting started Before implementing inheritance, ensure you have: @@ -31,20 +31,20 @@ Before implementing inheritance, ensure you have: 2. Installed `cargo-stylus` CLI tool: `cargo install --force cargo-stylus` 3. Set up a Stylus project: `cargo stylus new your_project_name` -## Understanding the Inheritance Model in Stylus +## Understanding the inheritance model in stylus -The inheritance pattern in Stylus requires two key components: +The inheritance pattern in Stylus requires two components: 1. A storage structure using the `#[borrow]` annotation 2. An implementation block using the `#[inherit]` annotation When you use these annotations properly, the child contract will be able to inherit the public methods from the parent contract. -## Basic Inheritance Pattern +## Basic inheritance pattern Let's walk through a practical example of implementing inheritance in Stylus. -### Step 1: Define the Base Contract +### Step 1: Define the base contract First, define your base contract that will be inherited: @@ -76,7 +76,7 @@ impl BaseContract { In this example, we've created a simple base contract with a single state variable and two methods to get and set its value. -### Step 2: Define the Child Contract with Inheritance +### Step 2: Define the child contract with inheritance Next, create your child contract that inherits from the base contract: @@ -108,7 +108,7 @@ impl ChildContract { -### How It Works +### How it works In the above code, when someone calls the `ChildContract` on a function defined in `BaseContract`, like `get_value()`, the function from `BaseContract` will be executed. @@ -120,7 +120,7 @@ Here's the step-by-step process of how inheritance works in Stylus: When a method is called on `ChildContract`, it first checks if the requested method exists within `ChildContract`. If a matching function is not found, it will then try the `BaseContract`. Only after trying everything `ChildContract` inherits will the call revert. -## Method Overriding +## 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. @@ -152,11 +152,25 @@ impl ChildContract { overriding the functions you intend to override. -## Advanced Inheritance Patterns +## Methods search order -### Chained Inheritance +When using inheritance, it's important to understand the order in which methods are searched: -Inheritance can also be chained. `#[inherit(Erc20, Erc721)]` will inherit both Erc20 and Erc721, checking for methods in that order. Erc20 and Erc721 may also inherit other types themselves. Method resolution finds the first matching method by Depth First Search. +1. The search starts in the type that uses the `#[entrypoint]` macro +2. If the method is not found, the search continues in the inherited types, in the order specified in the `#[inherit]` annotation +3. If the method is not found in any inherited type, the call reverts + +In a typical inheritance chain: +- Calling a method first searches in the child contract +- If not found there, it looks in the first parent specified in the `#[inherit]` list +- If still not found, it searches in the next parent in the list +- This continues until the method is found or all possibilities are exhausted + +## Advanced inheritance patterns + +### Chained inheritance + +Inheritance can be chained. When using `#[inherit(A, B, C)]`, the contract will inherit all three types, checking for methods in that order. Types A, B, and C may also inherit other types themselves. Method resolution follows a Depth First Search pattern. @@ -172,7 +186,7 @@ impl MyContract { When using chained inheritance, remember that method resolution follows the order specified in the `#[inherit]` annotation, from left to right, with depth-first search. -### Generics and Inheritance +### Generics and inheritance Stylus also supports using generics with inheritance, which is particularly useful for creating configurable base contracts: @@ -225,18 +239,18 @@ impl MyToken { This pattern allows consumers of generic base contracts like `Erc20` to choose immutable constants via specialization. -## Storage Layout Considerations +## Storage layout considerations Note that one exception to Stylus's storage layout guarantee is contracts which utilize - inheritance. The current solution in Stylus using #[borrow] and #[inherits(...)] packs nested + inheritance. The current solution in Stylus using #[borrow] and #[inherit(...)] packs nested (inherited) structs into their own slots. This is consistent with regular struct nesting in solidity, but not inherited structs. This has important implications when upgrading from Solidity to Rust, as storage slots may not align the same way. The Stylus team plans to revisit this behavior in an upcoming release. -## Working Example: ERC-20 Token with Inheritance +## Working example: ERC-20 token with inheritance A practical example of inheritance in Stylus is implementing an ERC-20 token with custom functionality. Here's how it works: @@ -269,6 +283,18 @@ impl StylusToken { self.erc20.mint(msg::sender(), value)?; Ok(()) } + + // Add more custom methods as needed + pub fn mint_to(&mut self, to: Address, value: U256) -> Result<(), Erc20Error> { + self.erc20.mint(to, value)?; + Ok(()) + } + + // Override parent methods if needed + pub fn burn(&mut self, value: U256) -> Result<(), Erc20Error> { + self.erc20.burn(msg::sender(), value)?; + Ok(()) + } } ``` @@ -276,7 +302,7 @@ impl StylusToken { This example shows how to inherit from a generic ERC-20 implementation and add custom functionality like minting. The pattern is very useful for token contracts where you need all the standard ERC-20 functionality but want to add custom features. -## Current Limitations and Best Practices +## Current limitations and best practices ### Limitations @@ -284,7 +310,7 @@ This example shows how to inherit from a generic ERC-20 implementation and add c 2. The storage layout for inherited contracts differs from Solidity's inheritance model 3. There's a risk of undetected selector collisions with functions from inherited contracts -### Best Practices +### Best practices 1. Use `cargo expand` to examine the expanded code and verify inheritance is working as expected 2. Be cautious with method overriding since there are no explicit override keywords @@ -293,7 +319,7 @@ This example shows how to inherit from a generic ERC-20 implementation and add c 5. Be aware of potential storage layout differences when migrating from Solidity 6. Consider using OpenZeppelin's Rust contracts for standardized implementations -## Debugging Inheritance Issues +## Debugging inheritance issues If you encounter issues with inheritance in your Stylus contracts, try these approaches: @@ -303,10 +329,5 @@ If you encounter issues with inheritance in your Stylus contracts, try these app 4. Use the `cargo stylus check` command to verify your contract compiles correctly 5. Test individual methods from both the parent and child contracts to isolate issues -## Conclusion - -Inheritance in Stylus provides a powerful way to compose contracts and reuse code. While it has some limitations compared to Solidity's inheritance model, it offers a familiar pattern for developers coming from Solidity and enables clean extension of existing functionality. - -By following the patterns outlined in this guide, you can create modular, extensible smart contracts in Rust that leverage inheritance while maintaining full EVM compatibility. For more information, refer to the [Stylus SDK documentation](https://docs.arbitrum.io/stylus/reference/rust-sdk-guide) and [Stylus by Example](https://docs.arbitrum.io/stylus-by-example/basic_examples/inheritance). From cf0ad93e2e4835fa129fe24a574974382a0eccd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Thu, 1 May 2025 12:50:53 -0700 Subject: [PATCH 6/9] fix: remove # title header --- arbitrum-docs/stylus/how-tos/using-inheritance.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx index 33dcddc83d..ee48d8381a 100644 --- a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx +++ b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx @@ -1,5 +1,5 @@ --- -title: 'Using Inheritance Patterns in Stylus Contracts' +title: 'Using inheritance patterns in stylus contracts' description: 'Learn how to implement inheritance patterns in your Stylus smart contracts' author: anegg0 sme: mahsamoosavi @@ -10,7 +10,6 @@ sidebar_position: 1 import CustomDetails from '@site/src/components/CustomDetails'; import { VanillaAdmonition } from '@site/src/components/VanillaAdmonition/'; -# How To Implement Inheritance Patterns in Stylus 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 inheritance in your Stylus smart contracts. From a87c677876c0b8d71cdad9f37fb549ea02dca053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Thu, 1 May 2025 13:27:03 -0700 Subject: [PATCH 7/9] feat: add more code comments to code blocks --- .../stylus/how-tos/using-inheritance.mdx | 120 ++++++++++++++++-- 1 file changed, 112 insertions(+), 8 deletions(-) diff --git a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx index ee48d8381a..56f15f1032 100644 --- a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx +++ b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx @@ -50,22 +50,42 @@ First, define your base contract that will be inherited: ```rust +// Import necessary components from the Stylus SDK +// - alloy_primitives::U256 for 256-bit unsigned integers (equivalent to uint256 in Solidity) +// - prelude contains common traits and macros used in most Stylus contracts use stylus_sdk::{alloy_primitives::U256, prelude::*}; +// Define the storage layout for our base contract +// sol_storage! is a macro that generates Rust structs with fields mapped to +// Solidity-equivalent storage slots and types sol_storage! { + // Public struct that will hold our contract's state pub struct BaseContract { + // This defines a uint256 field in storage, equivalent to Solidity's uint256 uint256 value; } } +// Mark this implementation block as containing public methods +// The #[public] macro makes these methods available to be called from other contracts #[public] impl BaseContract { + // Read-only function to retrieve the stored value + // - &self indicates this is a view function (doesn't modify state) + // - Returns either a U256 value or an error as Vec pub fn get_value(&self) -> Result> { + // Retrieve the value from storage and return it wrapped in Ok Ok(self.value.get()) } + // Mutable function to update the stored value + // - &mut self indicates this function can modify state + // - Takes a new_value parameter of type U256 + // - Returns either unit type () or an error pub fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { + // Update the storage value with the new value self.value.set(new_value); + // Return success (no error) Ok(()) } } @@ -82,26 +102,53 @@ Next, create your child contract that inherits from the base contract: ```rust +// Define the storage layout for our child contract that inherits from BaseContract sol_storage! { + // #[entrypoint] marks this struct as the main entry point for the contract + // When the contract is called, execution begins here #[entrypoint] pub struct ChildContract { + // #[borrow] enables the contract to borrow BaseContract's implementation + // This is crucial for inheritance - it implements the Borrow trait automatically + // Without this, the contract couldn't access BaseContract's methods #[borrow] BaseContract base_contract; + + // Additional state variable specific to the child contract + // This extends the parent contract's state uint256 additional_value; } } +// Define the public implementation for ChildContract +// #[public] makes these methods callable from other contracts or externally #[public] +// #[inherit(BaseContract)] connects ChildContract to BaseContract via the Router trait +// This allows ChildContract to inherit all BaseContract's public methods #[inherit(BaseContract)] impl ChildContract { + // Define a method specific to the child contract to get its additional value + // Similar to BaseContract.get_value() but for the child's own state pub fn get_additional_value(&self) -> Result> { + // Access the child-specific storage value Ok(self.additional_value.get()) } + // Define a method to set the additional value + // Similar to BaseContract.set_value() but for the child's own state pub fn set_additional_value(&mut self, new_value: U256) -> Result<(), Vec> { + // Update the child-specific storage value self.additional_value.set(new_value); + // Return success Ok(()) } + + // Note: ChildContract can also call methods on BaseContract like: + // self.base_contract.get_value() or self.base_contract.set_value() + + // Additionally, external callers can call BaseContract methods directly on ChildContract + // due to the #[inherit] annotation, e.g.: + // child_contract.get_value() - will call BaseContract.get_value() } ``` @@ -128,18 +175,34 @@ For example: ```rust +// Define implementation for ChildContract that will override a parent method #[public] +// Inherit from BaseContract to get access to its methods #[inherit(BaseContract)] impl ChildContract { - // This overrides the base_contract.set_value method + // This deliberately has the same name as BaseContract.set_value + // When this method is called, it will be chosen over the parent's implementation + // This is method overriding in Stylus pub fn set_value(&mut self, new_value: U256) -> Result<(), Vec> { - // Custom implementation with validation + // Add custom validation logic in the overridden method + // This demonstrates extending parent functionality with additional checks if new_value > U256::from(100) { + // Create a custom error message and convert it to bytes + // This will be returned as an error, causing the transaction to revert return Err("Value too large".as_bytes().to_vec()); } + + // If validation passes, call the parent's implementation + // This shows composition pattern - reusing parent logic while adding your own + // The ? operator unwraps the Result or returns early if it's an Err self.base_contract.set_value(new_value)?; + + // Return success if everything worked Ok(()) } + + // Note: Without explicit override keywords (like in Solidity), + // you must be careful about naming to avoid unintentional overrides } ``` @@ -256,44 +319,85 @@ A practical example of inheritance in Stylus is implementing an ERC-20 token wit ```rust -// Define the base ERC-20 functionality parameters +// Define the token parameters structure (no fields required, just for implementing the trait) +// This is a concrete implementation of the Erc20Params trait defined earlier struct StylusTokenParams; + +// Implement the ERC-20 parameters trait for our token +// This sets up the token's basic descriptive properties impl Erc20Params for StylusTokenParams { + // Token name - displayed in wallets and explorers const NAME: &'static str = "StylusToken"; + + // Token symbol/ticker - short identifier used on exchanges and UIs const SYMBOL: &'static str = "STK"; + + // Decimal precision - standard is 18 for ERC-20 tokens (like ETH) + // This means 1 token = 10^18 of the smallest unit const DECIMALS: u8 = 18; } -// Storage definition with inheritance +// Define the storage structure for our token contract sol_storage! { + // Mark this as the entry point for all contract calls #[entrypoint] struct StylusToken { + // Include the base ERC-20 implementation with our parameters + // The #[borrow] annotation is essential for inheritance to work #[borrow] Erc20 erc20; + + // We could add additional StylusToken-specific storage here + // For example: mapping(address => bool) minters; } } -// Implementation with inheritance +// Implement the public interface for our token contract #[public] +// Inherit all functionality from the ERC-20 implementation +// This gives our token standard methods like: +// - balanceOf +// - transfer +// - transferFrom +// - approve +// - allowance #[inherit(Erc20)] impl StylusToken { - // Add custom functionality + // Add custom mint functionality + // This lets the caller mint tokens for themselves pub fn mint(&mut self, value: U256) -> Result<(), Erc20Error> { + // Call the ERC-20 implementation's mint method + // msg::sender() gives the address of the caller + // The ? operator propagates any errors that might occur self.erc20.mint(msg::sender(), value)?; + + // Return success if everything worked Ok(()) } - // Add more custom methods as needed + // Add a method to mint tokens to a specific address + // This can be used for airdrops, rewards, etc. pub fn mint_to(&mut self, to: Address, value: U256) -> Result<(), Erc20Error> { + // Similar to mint, but with a specified recipient + // Could add custom logic here, like permission checks self.erc20.mint(to, value)?; Ok(()) } - // Override parent methods if needed + // Add a burn functionality to destroy tokens + // This could be used for deflationary mechanics pub fn burn(&mut self, value: U256) -> Result<(), Erc20Error> { + // Call the base implementation's burn method + // This could be extended with custom logic self.erc20.burn(msg::sender(), value)?; Ok(()) } + + // Could add more custom methods: + // - pausable functionality + // - role-based minting permissions + // - token recovery functions + // - etc. } ``` From 31e596f9c80cddfcb5d3a400ba3e03ad87bc5d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Thu, 1 May 2025 13:38:46 -0700 Subject: [PATCH 8/9] feat: more code comments --- .../stylus/how-tos/using-inheritance.mdx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx index 56f15f1032..5d804e5025 100644 --- a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx +++ b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx @@ -237,10 +237,23 @@ Inheritance can be chained. When using `#[inherit(A, B, C)]`, the contract will ```rust +// Define implementation for a contract with multiple inherited types #[public] +// Inherit from multiple contracts, specifying the search order +// When a method is called, it will first look in MyContract +// If not found, it will check A, then B, then C in that order #[inherit(A, B, C)] impl MyContract { - // Custom implementations here + // Custom implementations specific to MyContract + // These can override any methods from A, B, or C with the same name + + // For method resolution: + // - Calling my_contract.foo() will execute MyContract.foo() if it exists + // - Otherwise it will check A.foo(), then B.foo(), then C.foo() + // - If none exist, the call will revert + + // This chaining allows for sophisticated composition patterns + // but requires careful planning of the inheritance hierarchy } ``` From 628aa55618ba7780f7c059fe97d25286716b1456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Blanchemain?= Date: Thu, 8 May 2025 11:34:26 -0700 Subject: [PATCH 9/9] refactor: add customdetails + fix code blocks --- .../stylus/how-tos/using-inheritance.mdx | 108 +++++++++++++----- 1 file changed, 78 insertions(+), 30 deletions(-) diff --git a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx index 5d804e5025..25397db855 100644 --- a/arbitrum-docs/stylus/how-tos/using-inheritance.mdx +++ b/arbitrum-docs/stylus/how-tos/using-inheritance.mdx @@ -7,10 +7,9 @@ content_type: how-to sidebar_position: 1 --- -import CustomDetails from '@site/src/components/CustomDetails'; +import CustomDetails from '@site/src/components/CustomDetails'; import { VanillaAdmonition } from '@site/src/components/VanillaAdmonition/'; - 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 inheritance in your Stylus smart contracts. ## Overview @@ -26,9 +25,30 @@ The inheritance model in Stylus aims to replicate the composition pattern found Before implementing inheritance, ensure you have: -1. Installed the Rust toolchain -2. Installed `cargo-stylus` CLI tool: `cargo install --force cargo-stylus` -3. Set up a Stylus project: `cargo stylus new your_project_name` + + +Follow the instructions on [Rust Lang's installation page](https://www.rust-lang.org/tools/install) to install a complete Rust toolchain (v1.81 or newer) on your system. After installation, ensure you can access the programs `rustup`, `rustc`, and `cargo` from your preferred terminal application. + + + + + +In your terminal, run: + +```shell +cargo install --force cargo-stylus +``` + +Add WASM ([WebAssembly](https://webassembly.org/)) as a build target for the specific Rust toolchain you are using. The below example sets your default Rust toolchain to 1.81 as well as adding the WASM build target: + +```shell +rustup default 1.81 +rustup target add wasm32-unknown-unknown --toolchain 1.81 +``` + +You can verify that cargo stylus is installed by running `cargo stylus --help` in your terminal, which will return a list of helpful commands. + + ## Understanding the inheritance model in stylus @@ -66,7 +86,7 @@ sol_storage! { } } -// Mark this implementation block as containing public methods +// Mark this implementation block as containing public methods // The #[public] macro makes these methods available to be called from other contracts #[public] impl BaseContract { @@ -113,7 +133,7 @@ sol_storage! { // Without this, the contract couldn't access BaseContract's methods #[borrow] BaseContract base_contract; - + // Additional state variable specific to the child contract // This extends the parent contract's state uint256 additional_value; @@ -142,10 +162,10 @@ impl ChildContract { // Return success Ok(()) } - + // Note: ChildContract can also call methods on BaseContract like: // self.base_contract.get_value() or self.base_contract.set_value() - + // Additionally, external callers can call BaseContract methods directly on ChildContract // due to the #[inherit] annotation, e.g.: // child_contract.get_value() - will call BaseContract.get_value() @@ -191,16 +211,16 @@ impl ChildContract { // This will be returned as an error, causing the transaction to revert return Err("Value too large".as_bytes().to_vec()); } - + // If validation passes, call the parent's implementation // This shows composition pattern - reusing parent logic while adding your own // The ? operator unwraps the Result or returns early if it's an Err self.base_contract.set_value(new_value)?; - + // Return success if everything worked Ok(()) } - + // Note: Without explicit override keywords (like in Solidity), // you must be careful about naming to avoid unintentional overrides } @@ -223,6 +243,7 @@ When using inheritance, it's important to understand the order in which methods 3. If the method is not found in any inherited type, the call reverts In a typical inheritance chain: + - Calling a method first searches in the child contract - If not found there, it looks in the first parent specified in the `#[inherit]` list - If still not found, it searches in the next parent in the list @@ -246,12 +267,12 @@ Inheritance can be chained. When using `#[inherit(A, B, C)]`, the contract will impl MyContract { // Custom implementations specific to MyContract // These can override any methods from A, B, or C with the same name - + // For method resolution: // - Calling my_contract.foo() will execute MyContract.foo() if it exists // - Otherwise it will check A.foo(), then B.foo(), then C.foo() // - If none exist, the call will revert - + // This chaining allows for sophisticated composition patterns // but requires careful planning of the inheritance hierarchy } @@ -332,6 +353,24 @@ A practical example of inheritance in Stylus is implementing an ERC-20 token wit ```rust +// Import required dependencies +use stylus_sdk::{ + alloy_primitives::{Address, U256}, + prelude::*, +}; +use alloy_sol_types::sol; + +// Define error type for ERC20 operations +sol! { + /// Errors that can occur during ERC20 operations + enum Erc20Error { + /// Transfer amount exceeds balance + InsufficientBalance(); + /// Spender allowance too low + InsufficientAllowance(); + } +} + // Define the token parameters structure (no fields required, just for implementing the trait) // This is a concrete implementation of the Erc20Params trait defined earlier struct StylusTokenParams; @@ -341,10 +380,10 @@ struct StylusTokenParams; impl Erc20Params for StylusTokenParams { // Token name - displayed in wallets and explorers const NAME: &'static str = "StylusToken"; - + // Token symbol/ticker - short identifier used on exchanges and UIs const SYMBOL: &'static str = "STK"; - + // Decimal precision - standard is 18 for ERC-20 tokens (like ETH) // This means 1 token = 10^18 of the smallest unit const DECIMALS: u8 = 18; @@ -359,7 +398,7 @@ sol_storage! { // The #[borrow] annotation is essential for inheritance to work #[borrow] Erc20 erc20; - + // We could add additional StylusToken-specific storage here // For example: mapping(address => bool) minters; } @@ -370,7 +409,7 @@ sol_storage! { // Inherit all functionality from the ERC-20 implementation // This gives our token standard methods like: // - balanceOf -// - transfer +// - transfer // - transferFrom // - approve // - allowance @@ -380,32 +419,34 @@ impl StylusToken { // This lets the caller mint tokens for themselves pub fn mint(&mut self, value: U256) -> Result<(), Erc20Error> { // Call the ERC-20 implementation's mint method - // msg::sender() gives the address of the caller + // Using the VM context to get the sender address // The ? operator propagates any errors that might occur - self.erc20.mint(msg::sender(), value)?; - + self.erc20.mint(self.vm().msg_sender(), value)?; + // Return success if everything worked Ok(()) } - + // Add a method to mint tokens to a specific address // This can be used for airdrops, rewards, etc. pub fn mint_to(&mut self, to: Address, value: U256) -> Result<(), Erc20Error> { // Similar to mint, but with a specified recipient // Could add custom logic here, like permission checks + // For example, check if the sender has permission to mint + // if !is_minter(self.vm().msg_sender()) { return Err(...); } self.erc20.mint(to, value)?; Ok(()) } - + // Add a burn functionality to destroy tokens // This could be used for deflationary mechanics pub fn burn(&mut self, value: U256) -> Result<(), Erc20Error> { // Call the base implementation's burn method // This could be extended with custom logic - self.erc20.burn(msg::sender(), value)?; + self.erc20.burn(self.vm().msg_sender(), value)?; Ok(()) } - + // Could add more custom methods: // - pausable functionality // - role-based minting permissions @@ -422,9 +463,11 @@ This example shows how to inherit from a generic ERC-20 implementation and add c ### Limitations -1. Stylus doesn't support contract multi-inheritance yet -2. The storage layout for inherited contracts differs from Solidity's inheritance model +1. Stylus doesn't support Solidity-style multiple inheritance yet (though you can inherit from a chain of contracts) +2. The storage layout for inherited contracts differs from Solidity's inheritance model (Stylus packs nested structs into their own slots) 3. There's a risk of undetected selector collisions with functions from inherited contracts +4. No explicit override or virtual keywords for clearly marking overridden functions +5. The inheritance mechanism does not automatically emit events that would be emitted by the parent contract (you need to explicitly call the parent's methods) ### Best practices @@ -433,7 +476,10 @@ This example shows how to inherit from a generic ERC-20 implementation and add c 3. Design your contracts with single inheritance in mind 4. Test thoroughly to ensure all inherited methods work correctly 5. Be aware of potential storage layout differences when migrating from Solidity -6. Consider using OpenZeppelin's Rust contracts for standardized implementations +6. Always use `self.vm()` methods for accessing blockchain context (instead of deprecated functions like `msg::sender()`) +7. When overriding methods that emit events in the parent contract, make sure to explicitly call the parent method or re-emit the events +8. Use feature flags to control which contract is the entrypoint when working with multiple contracts +9. Consider using OpenZeppelin's Rust contracts for standardized implementations ## Debugging inheritance issues @@ -443,7 +489,9 @@ If you encounter issues with inheritance in your Stylus contracts, try these app 2. Ensure the `#[inherit]` annotation includes the correct parent contract type 3. Check for method name conflicts between parent and child contracts 4. Use the `cargo stylus check` command to verify your contract compiles correctly -5. Test individual methods from both the parent and child contracts to isolate issues - +5. Use `cargo clippy` to check for Rust-specific issues and stylus-specific lints +6. Test individual methods from both the parent and child contracts to isolate issues +7. If you encounter symbol collision errors with `mark_used`, ensure you're only compiling one contract as the entrypoint at a time using feature flags +8. For VM context errors, verify you're using `self.vm()` methods instead of deprecated global functions For more information, refer to the [Stylus SDK documentation](https://docs.arbitrum.io/stylus/reference/rust-sdk-guide) and [Stylus by Example](https://docs.arbitrum.io/stylus-by-example/basic_examples/inheritance).