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

Cargo.toml

1[package]
2name = "stylus_cupcake_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_cupcake_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"