Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fungible Token #13

Merged
merged 10 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading