Smart Contracts – ERC 20

Rather than get too specialized in Stellar’s syntax and proprietary code, the ongoing theme here is to learn as much about Smart Contracts as possible AND also improve our Rust programming/problem solving skills.

Learn the concepts rather than the nitty gritty syntax which may change right?

Futhermore, we’ll get plenty more practice with enums, public, private, set, get, and tests in Rust.

Disclaimer: I have used chatGPT for assistance, but this article was made by me an actual *human* 😊

smart contracts

One thing which is useful is knowledge of 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

how to handle ownership in a contract

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?

  1. 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. You can test this in Okashi.dev, by just removing the #[contractimpl] – the code still compiles, but the function is no longer available!
  2. Functions Are Just Helpers
    • The owner module is 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.
  3. #![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.

Stellar Deploy & Invoke

Here’s a collection of stellar-cli command examples, I’ve included them as sometimes it’s tricky to pass the correct args. There are some good videos on “Meetings” on the Stellar.org site where you can pick up tips like this.

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?

https://github.com/RGGH/bond

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

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:

  1. contains_key:
    • This method checks if a key exists in a collection like HashMap or BTreeMap.
    • It returns true if the key is present, otherwise false.
  2. Use Cases:
    • Similar to .has in Soroban, you can use contains_key for existence checks before performing operations like retrieving or updating values.

Comparison to Soroban’s .has:

  • Similarities: Both check for the presence of a key.
  • Differences: Soroban’s .has interacts with blockchain storage, while contains_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.

What’s coming next on this site?

Blockchain voting!

The code is working, I’ll be back soon with the article!

blockchain voting results
blockchain voting
stellar contract

https://github.com/RGGH/voting