-
Notifications
You must be signed in to change notification settings - Fork 406
Adding structure of a contract to Rust SDK section #2095
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
da1866a
Adding structure of a contract to Rust SDK section
chrisco512 41be796
Merge branch 'master' into stylus-project-structure
anegg0 12eaa73
fix: rename file extension to `mdx`
anegg0 f5b2784
fix: reformat
anegg0 e80672b
Update arbitrum-docs/stylus/reference/project-structure.mdx
chrisco512 9d01c38
Update arbitrum-docs/stylus/reference/project-structure.mdx
chrisco512 98bf03a
Update arbitrum-docs/stylus/reference/project-structure.mdx
chrisco512 f680a6c
Update arbitrum-docs/stylus/reference/project-structure.mdx
chrisco512 93fbd44
Update arbitrum-docs/stylus/reference/project-structure.mdx
chrisco512 9ec92ea
Update arbitrum-docs/stylus/reference/project-structure.mdx
chrisco512 d7ecdd7
Update arbitrum-docs/stylus/reference/project-structure.mdx
chrisco512 ce5e695
Addressing GH comments
chrisco512 c27f304
Merge branch 'master' into stylus-project-structure
anegg0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
--- | ||
title: 'Structure of a Rust Contract' | ||
description: 'A quick overview of how contracts are structured with the Stylus Rust SDK' | ||
author: chrisco | ||
sme: chrisco | ||
sidebar_position: 1 | ||
target_audience: Developers using the Stylus Rust SDK to write and deploy smart contracts. | ||
--- | ||
|
||
Contracts in Rust are similar to contracts in Solidity. Each contract can contain declarations of State Variables, Functions, Function Modifiers, Events, Errors, Struct Types, and Enum Types. In addition, Rust contracts can import third-party packages from [crates.io](https://crates.io) as dependencies and use them for advanced functionality. | ||
|
||
## Project Layout | ||
|
||
In the most basic example, this is how a Rust contract will be organized. The simplest way to get going with a new project is to follow the [Quickstart](https://docs.arbitrum.io/stylus/quickstart) guide, or if you've already installed all dependencies, just run `cargo stylus new <YOUR_PROJECT_NAME>` from your terminal to begin a new project. Once installed, your project will include the following required files: | ||
|
||
```shell | ||
- src | ||
- lib.rs | ||
- main.rs | ||
- Cargo.toml | ||
- rust-toolchain.toml | ||
``` | ||
|
||
`src/lib.rs` is the root module of your contract's code. Here, you can import utilities or methods from internal or external modules, define the data layout of your contract's state variables, and define your contract's public API. This module must define a root data struct with the `#[entrypoint]` macro and provide an impl block annotated with `#[public]` to define public or external methods. See [First App](https://stylus-by-example.org/basic_examples/first_app) for an example of this. These macros are used to maintain [Solidity ABI](https://docs.soliditylang.org/en/v0.8.19/abi-spec.html#basic-design) compatibility to ensure that Rust contracts work with existing Solidity libraries and tooling. | ||
|
||
`src/main.rs` is typically auto-generated by [cargo-stylus](https://github.com/OffchainLabs/cargo-stylus) and does not usually need to be modified. Its purpose is to assist with the generation of [JSON describing](https://docs.soliditylang.org/en/v0.8.19/abi-spec.html#json) your contract's public interface, for use with automated tooling and frontend frameworks. | ||
|
||
`Cargo.toml` is standard file that Rust projects use to define a package's name, repository location, etc as well as import dependencies and define feature and build flags. From here you can define required dependencies such as the [Stylus SDK](https://crates.io/crates/stylus-sdk) itself or import third-party packages from [crates.io](https://crates.io). See [First Steps with Cargo](https://doc.rust-lang.org/cargo/getting-started/first-steps.html) if you are new to Rust. | ||
chrisco512 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
`rust-toolchain.toml` is used by public blockchain explorers, like [Arbiscan](https://arbiscan.io/), to assist with source code verification. To ensure that source code can be compiled deterministically, we use this file to include relevant metadata like what version of Rust was used. | ||
chrisco512 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Your contract may also include other dot files (such as `.gitignore`, `.env`, etc), markdown files for docs, or additional subfolders. | ||
|
||
## State Variables | ||
chrisco512 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Like Solidity, Rust contracts are able to define _state variables_. These are variables which are stored on the chain's _state trie_, which is essentially the chain's database. They differ from standard Rust variables in that they must implement the `Storage` trait from the Stylus SDK. This trait is used to layout the data in the trie in a Solidity-compatible fashion. The Stylus SDK provides Storage types for all Solidity primitives out-of-the-box, such as `StorageAddress`, `StorageU256`, etc. See [storage module](https://docs.rs/stylus-sdk/latest/stylus_sdk/storage/index.html#structs) for more information. | ||
|
||
When working with state variables, you can either use Rust-style syntax or Solidity-style syntax to define your data schema. The `#[storage]` macro is used to define Rust-style state variables while `sol_storage!` macro is used for Solidity-style state variables. Both styles may have more than one struct but must annotate a single struct as the root struct with `#[entrypoint]` macro. Below are examples of each. | ||
|
||
**Rust-style Schema** | ||
|
||
```rust | ||
use stylus_sdk::{prelude::*, storage::{StorageU256, StorageAddress}}; | ||
|
||
#[storage] | ||
#[entrypoint] | ||
pub struct MyContract { | ||
owner: StorageAddress, | ||
version: StorageU256, | ||
} | ||
``` | ||
|
||
**Solidity-style Schema** | ||
|
||
```rust | ||
use stylus_sdk::{prelude::*}; | ||
|
||
sol_storage! { | ||
#[entrypoint] | ||
pub struct MyContract { | ||
owner: address, | ||
chrisco512 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
version: uint256, | ||
chrisco512 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
``` | ||
|
||
To read from state or write to it, getters and setters are used: | ||
chrisco512 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```rust | ||
let new_count = self.count.get() + U256::from(1); | ||
self.count.set(new_count); | ||
``` | ||
|
||
## Functions | ||
|
||
Contract functions are defined by providing an `impl` block for your contract's `#[entrypoint]` struct and annotating that block with `#[public]` to make the functions part of the contract's public API. The first parameter of each function is `&self`, which references the struct annotated with `#[entrypoint]`, it's used for reading state variables. By default, methods are view-only and cannot mutate state. To make a function mutable and able to alter state, `&mut self` must be used. Internal methods can be defined on a separate impl block for the struct that is not annotated with `#[public]`. Internal methods can access state. | ||
|
||
```rust | ||
// Defines the public, external methods for your contract | ||
// This impl block must be for the #[entrypoint] struct defined prior | ||
#[public] | ||
impl Counter { | ||
// By annotating first arg with &self, this indicates a view function | ||
pub fn get(&self) -> U256 { | ||
self.count.get() | ||
} | ||
|
||
// By annotating with &mut self, this is a mutable public function | ||
pub fn set_count(&mut self, count: U256) { | ||
chrisco512 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.count.set(count); | ||
} | ||
} | ||
|
||
// Internal methods (NOT part of public API) | ||
impl Counter { | ||
fn add(a: U256, b: U256) -> U256 { | ||
a + b | ||
} | ||
} | ||
``` | ||
|
||
## Modules | ||
|
||
Modules are a way to organize code into logical units. While your contract must have a `lib.rs` which defines your entrypoint struct, you can also define utility functions, structs, enums, etc, in modules and import them in to use in your contract methods. | ||
chrisco512 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
For example, with this file structure: | ||
|
||
``` | ||
- src | ||
- lib.rs | ||
- main.rs | ||
- utils | ||
- mod.rs | ||
- Cargo.toml | ||
- rust-toolchain.toml | ||
``` | ||
|
||
In `lib.rs`: | ||
|
||
```rust | ||
// import module | ||
mod utils; | ||
|
||
// ..other code | ||
const score = utils::check_score(); | ||
``` | ||
|
||
See [Defining modules](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html) in the Rust book for more info on modules and how to use them. | ||
|
||
## Importing Packages | ||
chrisco512 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Rust has a robust package manager for managing dependencies and importing 3rd party libraries to use in your smart contracts. These packages (called crates in Rust) are located at [crates.io](https://crates.io). To make use of a dependency in your code, you'll need to complete these steps: | ||
chrisco512 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Add the package name and version to your `Cargo.toml`: | ||
|
||
```toml | ||
# Cargo.toml | ||
[package] | ||
# ...package info here | ||
|
||
[dependencies] | ||
rust_decimal = "1.36.0" | ||
``` | ||
|
||
Import the package into your contract: | ||
|
||
```rust | ||
// lib.rs | ||
use rust_decimal_macros::dec; | ||
``` | ||
|
||
Use imported types in your contract: | ||
|
||
```rust | ||
// create a fixed point Decimal value | ||
let price = dec!(72.00); | ||
``` | ||
|
||
See [Using public Rust crates](https://docs.arbitrum.io/stylus/recommended-libraries#using-public-rust-crates) for more important details on using public Rust crates as well as a curated list of crates that tend to work well for smart contract development. | ||
|
||
## Events | ||
|
||
Events are used to publicly log values to the EVM. They can be useful for users to understand what occurred during a transaction while inspecting a transaction on a public explorer, like [Arbiscan](https://arbiscan.io/). | ||
|
||
```rust | ||
sol! { | ||
event HighestBidIncreased(address bidder, uint256 amount); | ||
} | ||
|
||
#[public] | ||
impl AuctionContract { | ||
pub fn bid() { | ||
// ... | ||
evm::log(HighestBidIncreased { | ||
bidder: Address::from([0x11; 20]), | ||
amount: U256::from(42), | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
## Errors | ||
|
||
Errors allow you to define descriptive names for failure situations. These can be useful for debugging or providing users with helpful information for why a transaction may have failed. | ||
|
||
```rust | ||
sol! { | ||
error NotEnoughFunds(uint256 request, uint256 available); | ||
} | ||
|
||
#[derive(SolidityError)] | ||
pub enum TokenErrors { | ||
NotEnoughFunds(NotEnoughFunds), | ||
} | ||
|
||
#[public] | ||
impl Token { | ||
pub fn transfer(&mut self, to: Address, amount: U256) -> Result<(), TokenErrors> { | ||
const balance = self.balances.get(msg::sender()); | ||
if (balance < amount) { | ||
return Err(TokenErrors::NotEnoughFunds(NotEnoughFunds { | ||
request: amount, | ||
available: balance, | ||
})); | ||
} | ||
// .. other code here | ||
} | ||
} | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.