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.

smart contracts

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

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.
  2. 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.
  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

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

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:

  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.