Arbitrum Stylus logo

Stylus by Example

Variables

In Solidity, there are 3 types of variables: local, state, and global. Local variables are not stored on the blockchain, while state variables are (and incur a much higher cost as a result). This is true of Arbitrum Stylus Rust smart contracts as well, although how they're defined is quite different.

In Rust, local variables are just ordinary variables you assign with let or let mut statements. Local variables are far cheaper than state variables, even on the EVM, however, Stylus local variables are more than 100x cheaper to allocate in memory than their Solidity equivalents.

Unlike Solidity, Rust was not built inherently with the blockchain in mind. It is a general purpose programming language. We therefore define specific storage types to explicitly denote values intended to be stored permanently as part of the contract's state. State variables cost the same to store as their Solidity equivalents.

Global variables in Solidity, such as msg.sender and block.timestamp, are available as function calls pulled in from the stylus_sdk with their Rust equivalents being msg::sender() and block::timestamp(), respectively. These variables provide information about the blockchain or the active transaction.

Learn more

src/lib.rs

1// Only run this as a WASM if the export-abi feature is not set.
2#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
3extern crate alloc;
4
5use stylus_sdk::alloy_primitives::{U16, U256};
6use stylus_sdk::prelude::*;
7use stylus_sdk::storage::{StorageAddress, StorageBool, StorageU256};
8use stylus_sdk::{block, console, msg};
9
10#[storage]
11#[entrypoint]
12pub struct Contract {
13    initialized: StorageBool,
14    owner: StorageAddress,
15    max_supply: StorageU256,
16}
17
18#[public]
19impl Contract {
20    // State variables are initialized in an `init` function.
21    pub fn init(&mut self) -> Result<(), Vec<u8>> {
22        // We check if contract has been initialized before.
23        // We return if so, we initialize if not.
24        let initialized = self.initialized.get();
25        if initialized {
26            return Ok(());
27        }
28        self.initialized.set(true);
29
30        // We set the contract owner to the caller,
31        // which we get from the global msg module
32        self.owner.set(msg::sender());
33        self.max_supply.set(U256::from(10_000));
34
35        Ok(())
36    }
37
38    pub fn do_something() -> Result<(), Vec<u8>> {
39        // Local variables are not saved to the blockchain
40        // 16-bit Rust integer
41        let _i = 456_u16;
42        // 16-bit int inferred from U16 Alloy primitive
43        let _j = U16::from(123);
44
45        // Here are some global variables
46        let _timestamp = block::timestamp();
47        let _amount = msg::value();
48
49        console!("Local variables: {_i}, {_j}");
50        console!("Global variables: {_timestamp}, {_amount}");
51
52        Ok(())
53    }
54}
1// Only run this as a WASM if the export-abi feature is not set.
2#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
3extern crate alloc;
4
5use stylus_sdk::alloy_primitives::{U16, U256};
6use stylus_sdk::prelude::*;
7use stylus_sdk::storage::{StorageAddress, StorageBool, StorageU256};
8use stylus_sdk::{block, console, msg};
9
10#[storage]
11#[entrypoint]
12pub struct Contract {
13    initialized: StorageBool,
14    owner: StorageAddress,
15    max_supply: StorageU256,
16}
17
18#[public]
19impl Contract {
20    // State variables are initialized in an `init` function.
21    pub fn init(&mut self) -> Result<(), Vec<u8>> {
22        // We check if contract has been initialized before.
23        // We return if so, we initialize if not.
24        let initialized = self.initialized.get();
25        if initialized {
26            return Ok(());
27        }
28        self.initialized.set(true);
29
30        // We set the contract owner to the caller,
31        // which we get from the global msg module
32        self.owner.set(msg::sender());
33        self.max_supply.set(U256::from(10_000));
34
35        Ok(())
36    }
37
38    pub fn do_something() -> Result<(), Vec<u8>> {
39        // Local variables are not saved to the blockchain
40        // 16-bit Rust integer
41        let _i = 456_u16;
42        // 16-bit int inferred from U16 Alloy primitive
43        let _j = U16::from(123);
44
45        // Here are some global variables
46        let _timestamp = block::timestamp();
47        let _amount = msg::value();
48
49        console!("Local variables: {_i}, {_j}");
50        console!("Global variables: {_timestamp}, {_amount}");
51
52        Ok(())
53    }
54}

Cargo.toml

1[package]
2name = "stylus_variable_example"
3version = "0.1.7"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
7
8[dependencies]
9alloy-primitives = "=0.7.6"
10alloy-sol-types = "=0.7.6"
11mini-alloc = "0.4.2"
12stylus-sdk = "0.6.0"
13hex = "0.4.3"
14
15[dev-dependencies]
16tokio = { version = "1.12.0", features = ["full"] }
17ethers = "2.0"
18eyre = "0.6.8"
19
20[features]
21export-abi = ["stylus-sdk/export-abi"]
22
23[lib]
24crate-type = ["lib", "cdylib"]
25
26[profile.release]
27codegen-units = 1
28strip = true
29lto = true
30panic = "abort"
31opt-level = "s"
1[package]
2name = "stylus_variable_example"
3version = "0.1.7"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
7
8[dependencies]
9alloy-primitives = "=0.7.6"
10alloy-sol-types = "=0.7.6"
11mini-alloc = "0.4.2"
12stylus-sdk = "0.6.0"
13hex = "0.4.3"
14
15[dev-dependencies]
16tokio = { version = "1.12.0", features = ["full"] }
17ethers = "2.0"
18eyre = "0.6.8"
19
20[features]
21export-abi = ["stylus-sdk/export-abi"]
22
23[lib]
24crate-type = ["lib", "cdylib"]
25
26[profile.release]
27codegen-units = 1
28strip = true
29lto = true
30panic = "abort"
31opt-level = "s"