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.
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.
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}
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"