Skip to content

Commit

Permalink
Fungible Token (#13)
Browse files Browse the repository at this point in the history
* init fungible

* finize fungible trait and storage

* improve mod doc

* change owner to from in transfers

* add metadata functions to trait and fix docs

* add tests for storage functions

* add storage bumps

* remove bump_instance

* change visibility of EXTEND consts to public
  • Loading branch information
brozorec authored Jan 23, 2025
1 parent a976840 commit 6655643
Show file tree
Hide file tree
Showing 27 changed files with 5,045 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "2"
members = [
"contracts/utils/*",
"contracts/token/*",
"examples/*",
]

Expand Down
17 changes: 17 additions & 0 deletions contracts/token/fungible/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "openzeppelin-fungible-token"
edition.workspace = true
license.workspace = true
repository.workspace = true
publish = false
version.workspace = true

[lib]
crate-type = ["lib", "cdylib"]
doctest = false

[dependencies]
soroban-sdk = { workspace = true }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
238 changes: 238 additions & 0 deletions contracts/token/fungible/src/fungible.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
use soroban_sdk::{contractclient, contracterror, symbol_short, Address, Env, String};

/// Vanilla Fungible Token Trait
///
/// The `FungibleToken` trait defines the core functionality for fungible
/// tokens, adhering to SEP-41. It provides a standard interface for managing
/// balances, allowances, and metadata associated with fungible tokens.
/// Additionally, this trait includes the `total_supply()` function, which is
/// not part of SEP-41 but is commonly used in token contracts.
///
/// To fully comply with the SEP-41 specification one have to implement the
/// `Burnable` trait in addition to this one. SEP-41 mandates support for token
/// burning to be considered compliant.
#[contractclient(name = "FungibleTokenClient")]
pub trait FungibleToken {
/// Returns the total amount of tokens in circulation.
///
/// # Arguments
///
/// * `e` - Access to the Soroban environment.
///
/// # Notes
///
/// We recommend using the [`crate::storage::total_supply()`] function from
/// the `storage` module when implementing this function.
fn total_supply(e: Env) -> i128;

/// Returns the amount of tokens held by `account`.
///
/// # Arguments
///
/// * `e` - Access to the Soroban environment.
/// * `account` - The address for which the balance is being queried.
///
/// # Notes
///
/// We recommend using the [`crate::storage::balance()`] function from
/// the `storage` module when implementing this function.
fn balance(e: Env, account: Address) -> i128;

/// Returns the amount of tokens a `spender` is allowed to spend on behalf
/// of an `owner`.
///
/// # Arguments
///
/// * `e` - Access to Soroban environment.
/// * `owner` - The address holding the tokens.
/// * `spender` - The address authorized to spend the tokens.
///
/// # Notes
///
/// We recommend using the [`crate::storage::allowance()`] function from
/// the `storage` module when implementing this function.
fn allowance(e: Env, owner: Address, spender: Address) -> i128;

/// Transfers a `value` amount of tokens from `from` to `to`.
///
/// # Arguments
///
/// * `e` - Access to Soroban environment.
/// * `from` - The address holding the tokens.
/// * `to` - The address receiving the transferred tokens.
/// * `value` - The value of tokens to be transferred.
///
/// # Errors
///
/// * [`FungibleTokenError::InsufficientBalance`] - When attempting to
/// transfer more tokens than `from` current balance.
///
/// # Events
///
/// * topics - `["transfer", from: Address, to: Address]`
/// * data - `[value: i128]`
///
/// # Notes
///
/// We recommend using the [`crate::storage::transfer()`] function from
/// the `storage` module when implementing this function.
fn transfer(e: Env, from: Address, to: Address, value: i128);

/// Transfers a `value` amount of tokens from `from` to `to` using the
/// allowance mechanism. `value` is then deducted from `spender allowance.
///
/// # Arguments
///
/// * `e` - Access to Soroban environment.
/// * `spender` - The address authorizing the transfer, and having its
/// allowance consumed during the transfer.
/// * `from` - The address holding the tokens which will be transferred.
/// * `to` - The address receiving the transferred tokens.
/// * `value` - The amount of tokens to be transferred.
///
/// # Errors
///
/// * [`FungibleTokenError::InsufficientBalance`] - When attempting to
/// transfer more tokens than `from` current balance.
/// * [`FungibleTokenError::InsufficientAllowance`] - When attempting to
/// transfer more tokens than `spender` current allowance.
///
///
/// # Events
///
/// * topics - `["transfer", from: Address, to: Address]`
/// * data - `[value: i128]`
///
/// # Notes
///
/// We recommend using the [`crate::storage::transfer_from()`] function from
/// the `storage` module when implementing this function.
fn transfer_from(e: Env, spender: Address, from: Address, to: Address, value: i128);

/// Sets the amount of tokens a `spender` is allowed to spend on behalf of
/// an `owner`. Overrides any existing allowance set between `spender` and
/// `owner`.
///
/// # Arguments
///
/// * `e` - Access to Soroban environment.
/// * `owner` - The address holding the tokens.
/// * `spender` - The address authorized to spend the tokens.
/// * `value` - The amount of tokens made available to `spender`.
/// * `live_until_ledger` - The ledger number at which the allowance
/// expires.
///
/// # Errors
///
/// * [`FungibleTokenError::InvalidLiveUntilLedger`] - Occurs when
/// attempting to set `live_until_ledger` that is less than the current
/// ledger number and greater than `0`.
///
/// # Events
///
/// * topics - `["approve", from: Address, spender: Address]`
/// * data - `[value: i128, live_until_ledger: u32]`
///
/// # Notes
///
/// We recommend using the [`crate::storage::approve()`] function from
/// the `storage` module when implementing this function.
fn approve(e: Env, owner: Address, spender: Address, value: i128, live_until_ledger: u32);

/// Returns the number of decimals used to represent amounts of this token.
///
/// # Arguments
///
/// * `e` - Access to Soroban environment.
///
/// # Notes
///
/// We recommend using the [`crate::metadata::decimals()`] function from
/// the `metadata` module when implementing this function.
fn decimals(e: Env) -> u32;

/// Returns the name for this token.
///
/// # Arguments
///
/// * `e` - Access to Soroban environment.
///
/// # Notes
///
/// We recommend using the [`crate::metadata::name()`] function from
/// the `metadata` module when implementing this function.
fn name(e: Env) -> String;

/// Returns the symbol for this token.
///
/// # Arguments
///
/// * `e` - Access to Soroban environment.
///
/// # Notes
///
/// We recommend using the [`crate::metadata::symbol()`] function from
/// the `metadata` module when implementing this function.
fn symbol(e: Env) -> String;
}

// ################## ERRORS ##################

#[contracterror]
#[repr(u32)]
pub enum FungibleTokenError {
/// Indicates an error related to the current balance of account from which
/// tokens are expected to be transferred.
InsufficientBalance = 1,
/// Indicates a failure with the allowance mechanism when a given spender
/// doesn't have enough allowance.
InsufficientAllowance = 2,
/// Indicates an invalid value for `live_until_ledger` when setting an
/// allowance.
InvalidLiveUntilLedger = 3,
}

// ################## EVENTS ##################

/// Emits an event indicating a transfer of tokens.
///
/// # Arguments
///
/// * `e` - Access to Soroban environment.
/// * `from` - The address holding the tokens.
/// * `to` - The address receiving the transferred tokens.
/// * `value` - The value of tokens to be transferred.
///
/// # Events
///
/// * topics - `["transfer", from: Address, to: Address]`
/// * data - `[value: i128]`
pub fn emit_transfer(e: &Env, from: &Address, to: &Address, value: i128) {
let topics = (symbol_short!("transfer"), from, to);
e.events().publish(topics, value)
}

/// Emits an event indicating an allowance was set.
///
/// # Arguments
///
/// * `e` - Access to Soroban environment.
/// * `owner` - The address holding the tokens.
/// * `spender` - The address authorized to spend the tokens.
/// * `value` - The amount of tokens made available to `spender`.
/// * `live_until_ledger` - The ledger number at which the allowance expires.
///
/// # Events
///
/// * topics - `["approve", owner: Address, spender: Address]`
/// * data - `[value: i128, live_until_ledger: u32]`
pub fn emit_approve(
e: &Env,
owner: &Address,
spender: &Address,
value: i128,
live_until_ledger: u32,
) {
let topics = (symbol_short!("approve"), owner, spender);
e.events().publish(topics, (value, live_until_ledger))
}
64 changes: 64 additions & 0 deletions contracts/token/fungible/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! # Fungible Token Contract Module.
//!
//! Implements utilities for handling fungible tokens in a Soroban contract.
//!
//! This module provides essential storage functionalities required for managing
//! balances, allowances, and total supply of fungible tokens.
//!
//! ## Design Overview
//!
//! This module is structured to provide flexibility to developers by splitting
//! functionalities into higher-level and lower-level operations:
//!
//! - **High-Level Functions**: These include all necessary checks,
//! verifications, authorizations, state-changing logic, and event emissions.
//! They simplify usage by handling core logic securely. Users can directly
//! call these functions for typical token operations without worrying about
//! implementation details.
//!
//! - **Low-Level Functions**: These offer granular control for developers who
//! need to compose their own workflows. Such functions expose internal
//! mechanisms and require the caller to handle verifications and
//! authorizations manually.
//!
//! By offering this dual-layered approach, developers can choose between
//! convenience and customization, depending on their project requirements.
//!
//! ## Base Module and Extensions
//!
//! The base module implements:
//!
//! - Total supply management
//! - Transfers and allowances
//!
//! To extend functionality, the module supports the following optional
//! features:
//!
//! - Metadata: Provides additional information about the token, such as name,
//! symbol, and decimals.
//! - Mintable: Allows authorized entities to mint new tokens and increase the
//! total supply.
//! - Burnable: Enables token holders to destroy their tokens, reducing the
//! total supply.
//!
//! ## Compatibility and Compliance
//!
//! The module is designed to ensure full compatibility with SEP-0041, making it
//! easy to integrate into Soroban-based applications. It also closely mirrors
//! the Ethereum ERC-20 standard, facilitating cross-ecosystem familiarity and
//! ease of use.
//!
//! ## Notes for Developers
//!
//! - **Security Considerations**: While high-level functions handle necessary
//! checks, users of low-level functions must take extra care to ensure
//! correctness and security.
//! - **Composable Design**: The modular structure encourages developers to
//! extend functionality by combining provided primitives or creating custom
//! extensions.
#![no_std]

pub mod fungible;
pub mod storage;

mod test;
Loading

0 comments on commit 6655643

Please sign in to comment.