Smart Contracts – ERC 20
Rather than get too specialized in Stellar or any other ‘Crypto’, the ongoing theme here is to learn as much about Smart Contracts as possible AND improve our Rust programming/problem solving skills.
Futhermore, we’ll get plenty more practice with enums, public, private, set, get and tests in Rust.
One thing which we need to have knowledge of is is ERC 20
If your preference is for Bitcoin, don’t worry, “know thy enemy” is still not a bad mantra?
TL;DR;
https://okashi.dev/playground/cchacmzzwpexcluztzvklmgwjqfq
Smart Contracts
#![no_std]
pub mod owner {
use soroban_sdk::{contracterror, contracttype, panic_with_error, Address, Env, Symbol};
#[derive(Clone)]
#[contracttype]
enum OwnerKey {
Owner,
}
#[derive(Copy, Clone)]
#[contracterror]
#[repr(u32)]
pub enum OwnerError {
OnlyOwner = 1001,
}
pub fn has_owner(env: &Env) -> bool {
let key = OwnerKey::Owner;
env.storage().instance().has(&key)
}
pub fn get_owner(env: &Env) -> Option<Address> {
let key = OwnerKey::Owner;
env.storage().instance().get(&key)
}
pub fn set_owner(env: &Env, id: &Address) {
let key = OwnerKey::Owner;
env.storage().instance().set(&key, id);
env.events().publish((Symbol::new(env, "OwnerSet"),), id);
}
pub fn only_owner(env: &Env) {
let owner = get_owner(env);
if let Some(owner) = owner {
owner.require_auth();
} else {
panic_with_error!(env, OwnerError::OnlyOwner);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::testutils::Env;
#[test]
fn test_ownership() {
let env = Env::default();
let owner = Address::random(&env);
MyContract::init(env.clone(), owner.clone());
// Attempting a restricted action as the owner
owner.require_auth();
MyContract::restricted_action(env.clone());
// Unauthorized caller should fail
let other_user = Address::random(&env);
other_user.require_auth();
assert_panics!(MyContract::restricted_action(env.clone()));
}
}
pub mod owner
- Declares a module called
owner
. pub
makes it public, allowing other parts of the codebase (or external code) to use its contents.
Why No Public Functions Appear?
- Missing
#[contractimpl]
Macro- In Soroban, the
#[contractimpl]
macro is used to expose functions as part of the public API of the contract. - Without this macro, the functions are internal helpers, meaning they can be used only by other parts of the contract logic but not invoked directly via transactions.
- In Soroban, the
- Functions Are Just Helpers
- The
owner
module you provided is likely designed to encapsulate ownership-related utilities (e.g., checking or setting the owner). It’s not intended to be a standalone module but a dependency for another module that uses these utilities.
- The
#![no_std]
Doesn’t Affect Visibility- The
#![no_std]
attribute means the code doesn’t depend on Rust’s standard library but instead relies on the core library. It doesn’t prevent functions from being public or exposed.
- The
Stellar Deploy & Invoke
stellar contract deploy --source-account alice --wasm target/wasm32-unknown-unknown/release/increment.wasm --network testnet > id
stellar contract invoke --source-account alice --id $(cat id) --network testnet
stellar contract invoke –source-account alice –id $(cat id) –network testnet — init –owner CDF5AQSJP7XENGSSKFKXMLUFQO24T3C67PSOXTYBLBFP4J6MO4TMHW5A
stellar contract invoke –source-account alice –id $(cat id) –send=yes –network testnet — public_action
stellar contract invoke –source-account alice –id $(cat id) –send=yes –network testnet
Public action executed ✅
** Note the use of the “Enum as a Key” pattern in the code?
Using .has
in Soroban: Check Storage Key Existence
The .has
method in Soroban’s storage API is a simple yet powerful way to check if a specific key exists in a contract’s storage. It doesn’t retrieve the value or care about what’s stored—it simply confirms whether the key is present.
Why Use .has
?
- Initialization Checks: Verify if a contract has been initialized.
- Validation: Ensure data (e.g., owner address) exists before proceeding.
- Efficiency: Avoid unnecessary data retrieval or runtime errors from non-existent keys.
Example: Checking Initialization
fn is_initialized(env: &Env) -> bool {
env.storage().instance().has(&DataKey::Init)
}
Here, DataKey::Init
is an enum variant representing a storage key. If the key exists, the function returns true
.
Example: Prevent Overwrites
fn set_data(env: &Env, key: DataKey, value: u32) {
if env.storage().instance().has(&key) {
panic!("Key already set");
}
env.storage().instance().set(&key, &value);
}
By using .has
, you can safely confirm the presence of a key before storing new data, preventing accidental overwrites.
Conclusion
The .has
method is a lightweight tool to enhance contract logic, ensuring robust and efficient operations in Soroban.
In vanilla Rust, there isn’t a direct equivalent to Soroban’s .has
method because .has
is part of Soroban’s smart contract storage API, specifically designed for working with a blockchain-based storage model. However, you can achieve similar functionality in Rust when working with collections like HashMap
, BTreeMap
, or similar key-value storage types.
Here’s how you can mimic .has
in vanilla Rust:
Example with HashMap
use std::collections::HashMap;
fn main() {
let mut storage: HashMap<String, u32> = HashMap::new();
// Add a key-value pair
storage.insert("Init".to_string(), 1);
// Check if the key exists
if storage.contains_key("Init") {
println!("Key exists!");
} else {
println!("Key does not exist.");
}
}
Key Points:
contains_key
:- This method checks if a key exists in a collection like
HashMap
orBTreeMap
. - It returns
true
if the key is present, otherwisefalse
.
- This method checks if a key exists in a collection like
- Use Cases:
- Similar to
.has
in Soroban, you can usecontains_key
for existence checks before performing operations like retrieving or updating values.
- Similar to
Comparison to Soroban’s .has
:
- Similarities: Both check for the presence of a key.
- Differences: Soroban’s
.has
interacts with blockchain storage, whilecontains_key
works with in-memory data structures.
For blockchain-style persistent storage in vanilla Rust, you’d need a database like SQLite, RocksDB, or sled, but these would still require a manual .contains_key
or equivalent logic.