Skip to content

Commit

Permalink
Fungible examples (#23)
Browse files Browse the repository at this point in the history
* fix param types in burnable and mintable traits

* fungible example with our our traits

* fungible example with TokenInterface

* typo

* improve docs

* typo
  • Loading branch information
brozorec authored Jan 30, 2025
1 parent 481c97a commit bb6e79e
Show file tree
Hide file tree
Showing 19 changed files with 2,839 additions and 3 deletions.
10 changes: 10 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 @@ -18,6 +18,7 @@ syn = { version = "2.0", features = ["full"] }
# members
openzeppelin-pausable = { path = "contracts/utils/pausable" }
openzeppelin-pausable-macros = { path = "contracts/utils/pausable-macros" }
openzeppelin-fungible-token = { path = "contracts/token/fungible" }

[profile.release]
opt-level = "z"
Expand Down
4 changes: 2 additions & 2 deletions contracts/token/fungible/src/extensions/burnable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub trait FungibleBurnable {
///
/// We recommend using [`crate::burnable::burn()`] when implementing this
/// function.
fn burn(e: &Env, from: &Address, amount: i128);
fn burn(e: &Env, from: Address, amount: i128);

/// Destroys `amount` of tokens from `account`. Updates the total
/// supply accordingly.
Expand Down Expand Up @@ -71,7 +71,7 @@ pub trait FungibleBurnable {
///
/// We recommend using [`crate::burnable::burn_from()`] when implementing
/// this function.
fn burn_from(e: &Env, spender: &Address, from: &Address, amount: i128);
fn burn_from(e: &Env, spender: Address, from: Address, amount: i128);
}

// ################## EVENTS ##################
Expand Down
8 changes: 8 additions & 0 deletions contracts/token/fungible/src/extensions/metadata/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/// Unlike other extensions, `metadata` does not provide a separate trait
/// because the corresponding functions are already available in
/// [`crate::FungibleToken`].
///
/// The decision to keep `metadata` as a standalone extension allows developers
/// the flexibility to either use dynamic metadata functions or hardcode
/// values for `decimals`, `symbol`, and `name` when designing their token
/// contract.
mod storage;
pub use self::storage::{
decimals, get_metadata, name, set_metadata, symbol, Metadata, METADATA_KEY,
Expand Down
2 changes: 1 addition & 1 deletion contracts/token/fungible/src/extensions/mintable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub trait FungibleMintable {
///
/// IMPORTANT: Please do not forget that, you probably will want to have
/// some authorization controls for minting tokens.
fn mint(e: &Env, account: &Address, amount: i128);
fn mint(e: &Env, account: Address, amount: i128);
}
// ################## EVENTS ##################

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

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

[dependencies]
soroban-sdk = { workspace = true }
openzeppelin-pausable = { workspace = true }
openzeppelin-pausable-macros = { workspace = true }
openzeppelin-fungible-token = { workspace = true }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
143 changes: 143 additions & 0 deletions examples/fungible-pausable/src/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//! Fungible Pausable Example Contract.
//! This contract showcases how to integrate various OpenZeppelin modules to
//! build a fully SEP-41-compliant fungible token. It includes essential
//! features such as an emergency stop mechanism and controlled token minting by
//! the owner.
//!
//! To meet SEP-41 compliance, the contract must implement both
//! [`openzeppelin_fungible_token::fungible::FungibleToken`] and
//! [`openzeppelin_fungible_token::burnable::FungibleBurnable`].
use openzeppelin_fungible_token::{
self as fungible, burnable::FungibleBurnable, mintable::FungibleMintable, FungibleToken,
};
use openzeppelin_pausable::{self as pausable, Pausable};
use openzeppelin_pausable_macros::when_not_paused;
use soroban_sdk::{
contract, contracterror, contractimpl, panic_with_error, symbol_short, Address, Env, String,
Symbol,
};

pub const OWNER: Symbol = symbol_short!("OWNER");

#[contract]
pub struct ExampleContract;

#[contracterror]
pub enum ExampleContractError {
Unauthorized = 1,
}

#[contractimpl]
impl ExampleContract {
pub fn __constructor(e: &Env, owner: Address, initial_supply: i128) {
fungible::metadata::set_metadata(
e,
18,
String::from_str(e, "My Token"),
String::from_str(e, "TKN"),
);
fungible::mintable::mint(e, &owner, initial_supply);
e.storage().instance().set(&OWNER, &owner);
}
}

#[contractimpl]
impl Pausable for ExampleContract {
fn paused(e: &Env) -> bool {
pausable::paused(e)
}

fn pause(e: &Env, caller: Address) {
// When `ownable` module is available,
// the following checks should be equivalent to:
// `ownable::only_owner(&e);`
let owner: Address = e.storage().instance().get(&OWNER).expect("owner should be set");
if owner != caller {
panic_with_error!(e, ExampleContractError::Unauthorized)
}

pausable::pause(e, &caller);
}

fn unpause(e: &Env, caller: Address) {
// When `ownable` module is available,
// the following checks should be equivalent to:
// `ownable::only_owner(&e);`
let owner: Address = e.storage().instance().get(&OWNER).expect("owner should be set");
if owner != caller {
panic_with_error!(e, ExampleContractError::Unauthorized)
}

pausable::unpause(e, &caller);
}
}

#[contractimpl]
impl FungibleToken for ExampleContract {
fn total_supply(e: &Env) -> i128 {
fungible::total_supply(e)
}

fn balance(e: &Env, account: Address) -> i128 {
fungible::balance(e, &account)
}

fn allowance(e: &Env, owner: Address, spender: Address) -> i128 {
fungible::allowance(e, &owner, &spender)
}

#[when_not_paused]
fn transfer(e: &Env, from: Address, to: Address, amount: i128) {
fungible::transfer(e, &from, &to, amount);
}

#[when_not_paused]
fn transfer_from(e: &Env, spender: Address, from: Address, to: Address, amount: i128) {
fungible::transfer_from(e, &spender, &from, &to, amount);
}

fn approve(e: &Env, owner: Address, spender: Address, amount: i128, live_until_ledger: u32) {
fungible::approve(e, &owner, &spender, amount, live_until_ledger);
}

fn decimals(e: &Env) -> u32 {
fungible::metadata::decimals(e)
}

fn name(e: &Env) -> String {
fungible::metadata::name(e)
}

fn symbol(e: &Env) -> String {
fungible::metadata::symbol(e)
}
}

#[contractimpl]
impl FungibleBurnable for ExampleContract {
#[when_not_paused]
fn burn(e: &Env, from: Address, amount: i128) {
fungible::burnable::burn(e, &from, amount)
}

#[when_not_paused]
fn burn_from(e: &Env, spender: Address, from: Address, amount: i128) {
fungible::burnable::burn_from(e, &spender, &from, amount)
}
}

#[contractimpl]
impl FungibleMintable for ExampleContract {
#[when_not_paused]
fn mint(e: &Env, account: Address, amount: i128) {
// When `ownable` module is available,
// the following checks should be equivalent to:
// `ownable::only_owner(&e);`
let owner: Address = e.storage().instance().get(&OWNER).expect("owner should be set");
owner.require_auth();

fungible::mintable::mint(e, &account, amount);
}
}
142 changes: 142 additions & 0 deletions examples/fungible-pausable/src/contract_token_interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//! Fungible Pausable Example Contract.
//!
//! This contract replicates the functionality of the one in "contract.rs",
//! offering the same features. The key difference lies in how SEP-41 compliance
//! is achieved. The contract in "contract.rs" accomplishes this by implementing
//! [`openzeppelin_fungible_token::fungible::FungibleToken`] and
//! [`openzeppelin_fungible_token::burnable::FungibleBurnable`], whereas this
//! version directly implements [`soroban_sdk::token::TokenInterface`].
//!
//! Ultimately, it is up to the user to choose their preferred approach to
//! creating a SEP-41 token. We suggest the approach in "contract.rs" for better
//! organization of the code, consistency and ease of inspection/debugging.
use openzeppelin_fungible_token::{self as fungible, mintable::FungibleMintable};
use openzeppelin_pausable::{self as pausable, Pausable};
use openzeppelin_pausable_macros::when_not_paused;
use soroban_sdk::{
contract, contracterror, contractimpl, panic_with_error, symbol_short, token::TokenInterface,
Address, Env, String, Symbol,
};

pub const OWNER: Symbol = symbol_short!("OWNER");

#[contract]
pub struct ExampleContract;

#[contracterror]
pub enum ExampleContractError {
Unauthorized = 1,
}

#[contractimpl]
impl ExampleContract {
pub fn __constructor(e: &Env, owner: Address, initial_supply: i128) {
fungible::metadata::set_metadata(
e,
18,
String::from_str(e, "My Token"),
String::from_str(e, "TKN"),
);
fungible::mintable::mint(e, &owner, initial_supply);
e.storage().instance().set(&OWNER, &owner);
}

/// `TokenInterface` doesn't require implementing `total_supply()` because
/// of the need for backwards compatibility with Stellar classic assets.
pub fn total_supply(e: &Env) -> i128 {
fungible::total_supply(e)
}
}

#[contractimpl]
impl Pausable for ExampleContract {
fn paused(e: &Env) -> bool {
pausable::paused(e)
}

fn pause(e: &Env, caller: Address) {
// When `ownable` module is available,
// the following checks should be equivalent to:
// `ownable::only_owner(&e);`
let owner: Address = e.storage().instance().get(&OWNER).expect("owner should be set");
if owner != caller {
panic_with_error!(e, ExampleContractError::Unauthorized)
}

pausable::pause(e, &caller);
}

fn unpause(e: &Env, caller: Address) {
// When `ownable` module is available,
// the following checks should be equivalent to:
// `ownable::only_owner(&e);`
let owner: Address = e.storage().instance().get(&OWNER).expect("owner should be set");
if owner != caller {
panic_with_error!(e, ExampleContractError::Unauthorized)
}

pausable::unpause(e, &caller);
}
}

#[contractimpl]
impl TokenInterface for ExampleContract {
fn balance(e: Env, account: Address) -> i128 {
fungible::balance(&e, &account)
}

fn allowance(e: Env, owner: Address, spender: Address) -> i128 {
fungible::allowance(&e, &owner, &spender)
}

#[when_not_paused]
fn transfer(e: Env, from: Address, to: Address, amount: i128) {
fungible::transfer(&e, &from, &to, amount);
}

#[when_not_paused]
fn transfer_from(e: Env, spender: Address, from: Address, to: Address, amount: i128) {
fungible::transfer_from(&e, &spender, &from, &to, amount);
}

fn approve(e: Env, owner: Address, spender: Address, amount: i128, live_until_ledger: u32) {
fungible::approve(&e, &owner, &spender, amount, live_until_ledger);
}

#[when_not_paused]
fn burn(e: Env, from: Address, amount: i128) {
fungible::burnable::burn(&e, &from, amount)
}

#[when_not_paused]
fn burn_from(e: Env, spender: Address, from: Address, amount: i128) {
fungible::burnable::burn_from(&e, &spender, &from, amount)
}

fn decimals(e: Env) -> u32 {
fungible::metadata::decimals(&e)
}

fn name(e: Env) -> String {
fungible::metadata::name(&e)
}

fn symbol(e: Env) -> String {
fungible::metadata::symbol(&e)
}
}

#[contractimpl]
impl FungibleMintable for ExampleContract {
#[when_not_paused]
fn mint(e: &Env, account: Address, amount: i128) {
// When `ownable` module is available,
// the following checks should be equivalent to:
// `ownable::only_owner(&e);`
let owner: Address = e.storage().instance().get(&OWNER).expect("owner should be set");
owner.require_auth();

fungible::mintable::mint(e, &account, amount);
}
}
5 changes: 5 additions & 0 deletions examples/fungible-pausable/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#![no_std]

mod contract;
mod contract_token_interface;
mod test;
Loading

0 comments on commit bb6e79e

Please sign in to comment.