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//!
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}
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"