Arbitrum Stylus logo

Stylus by Example

Vending Machine

An example project for writing Arbitrum Stylus programs in Rust using the stylus-sdk. It includes a Rust implementation of a vending machine Ethereum smart contract.

  • distribute Cupcakes to any given address
  • count Cupcakes balance of any given address

Here is the interface for Vending Machine.

1interface IVendingMachine {
2    // Function to distribute a cupcake to a user
3    function giveCupcakeTo(address userAddress) external returns (bool);
4
5    // Getter function for the cupcake balance of a user
6    function getCupcakeBalanceFor(address userAddress) external view returns (uint);
7}
1interface IVendingMachine {
2    // Function to distribute a cupcake to a user
3    function giveCupcakeTo(address userAddress) external returns (bool);
4
5    // Getter function for the cupcake balance of a user
6    function getCupcakeBalanceFor(address userAddress) external view returns (uint);
7}

Example implementation of the Vending Machine contract written in Rust.

src/lib.rs

1// Allow `cargo stylus export-abi` to generate a main function if the "export-abi" feature is enabled.
2#![cfg_attr(not(feature = "export-abi"), no_main)]
3extern crate alloc;
4
5// Use an efficient WASM allocator for memory management.
6#[global_allocator]
7static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT;
8
9use alloy_primitives::{Address, Uint};
10// Import items from the SDK. The prelude contains common traits and macros.
11use stylus_sdk::alloy_primitives::U256;
12use stylus_sdk::prelude::*;
13use stylus_sdk::{block, console};
14
15// Define persistent storage using the Solidity ABI.
16// `VendingMachine` will be the entrypoint for the contract.
17sol_storage! {
18    #[entrypoint]
19    pub struct VendingMachine {
20        // Mapping from user addresses to their cupcake balances.
21        mapping(address => uint256) cupcake_balances;
22        // Mapping from user addresses to the last time they received a cupcake.
23        mapping(address => uint256) cupcake_distribution_times;
24    }
25}
26
27// Declare that `VendingMachine` is a contract with the following external methods.
28#[external]
29impl VendingMachine {
30    // Give a cupcake to the specified user if they are eligible (i.e., if at least 5 seconds have passed since their last cupcake).
31    pub fn give_cupcake_to(&mut self, user_address: Address) -> bool {
32        // Get the last distribution time for the user.
33        let last_distribution = self.cupcake_distribution_times.get(user_address);
34        // Calculate the earliest next time the user can receive a cupcake.
35        let five_seconds_from_last_distribution = last_distribution + U256::from(5);
36
37        // Get the current block timestamp.
38        let current_time = block::timestamp();
39        // Check if the user can receive a cupcake.
40        let user_can_receive_cupcake =
41            five_seconds_from_last_distribution <= Uint::<256, 4>::from(current_time);
42
43        if user_can_receive_cupcake {
44            // Increment the user's cupcake balance.
45            let mut balance_accessor = self.cupcake_balances.setter(user_address);
46            let balance = balance_accessor.get() + U256::from(1);
47            balance_accessor.set(balance);
48
49            // Update the distribution time to the current time.
50            let mut time_accessor = self.cupcake_distribution_times.setter(user_address);
51            let new_distribution_time = block::timestamp();
52            time_accessor.set(Uint::<256, 4>::from(new_distribution_time));
53            return true;
54        } else {
55            // User must wait before receiving another cupcake.
56            console!(
57                "HTTP 429: Too Many Cupcakes (you must wait at least 5 seconds between cupcakes)"
58            );
59            return false;
60        }
61    }
62
63    // Get the cupcake balance for the specified user.
64    #[view]
65    pub fn get_cupcake_balance_for(&self, user_address: Address) -> Uint<256, 4> {
66        // Return the user's cupcake balance from storage.
67        return self.cupcake_balances.get(user_address);
68    }
69}
1// Allow `cargo stylus export-abi` to generate a main function if the "export-abi" feature is enabled.
2#![cfg_attr(not(feature = "export-abi"), no_main)]
3extern crate alloc;
4
5// Use an efficient WASM allocator for memory management.
6#[global_allocator]
7static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT;
8
9use alloy_primitives::{Address, Uint};
10// Import items from the SDK. The prelude contains common traits and macros.
11use stylus_sdk::alloy_primitives::U256;
12use stylus_sdk::prelude::*;
13use stylus_sdk::{block, console};
14
15// Define persistent storage using the Solidity ABI.
16// `VendingMachine` will be the entrypoint for the contract.
17sol_storage! {
18    #[entrypoint]
19    pub struct VendingMachine {
20        // Mapping from user addresses to their cupcake balances.
21        mapping(address => uint256) cupcake_balances;
22        // Mapping from user addresses to the last time they received a cupcake.
23        mapping(address => uint256) cupcake_distribution_times;
24    }
25}
26
27// Declare that `VendingMachine` is a contract with the following external methods.
28#[external]
29impl VendingMachine {
30    // Give a cupcake to the specified user if they are eligible (i.e., if at least 5 seconds have passed since their last cupcake).
31    pub fn give_cupcake_to(&mut self, user_address: Address) -> bool {
32        // Get the last distribution time for the user.
33        let last_distribution = self.cupcake_distribution_times.get(user_address);
34        // Calculate the earliest next time the user can receive a cupcake.
35        let five_seconds_from_last_distribution = last_distribution + U256::from(5);
36
37        // Get the current block timestamp.
38        let current_time = block::timestamp();
39        // Check if the user can receive a cupcake.
40        let user_can_receive_cupcake =
41            five_seconds_from_last_distribution <= Uint::<256, 4>::from(current_time);
42
43        if user_can_receive_cupcake {
44            // Increment the user's cupcake balance.
45            let mut balance_accessor = self.cupcake_balances.setter(user_address);
46            let balance = balance_accessor.get() + U256::from(1);
47            balance_accessor.set(balance);
48
49            // Update the distribution time to the current time.
50            let mut time_accessor = self.cupcake_distribution_times.setter(user_address);
51            let new_distribution_time = block::timestamp();
52            time_accessor.set(Uint::<256, 4>::from(new_distribution_time));
53            return true;
54        } else {
55            // User must wait before receiving another cupcake.
56            console!(
57                "HTTP 429: Too Many Cupcakes (you must wait at least 5 seconds between cupcakes)"
58            );
59            return false;
60        }
61    }
62
63    // Get the cupcake balance for the specified user.
64    #[view]
65    pub fn get_cupcake_balance_for(&self, user_address: Address) -> Uint<256, 4> {
66        // Return the user's cupcake balance from storage.
67        return self.cupcake_balances.get(user_address);
68    }
69}

Cargo.toml

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