-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
Showing
19 changed files
with
2,839 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
142
examples/fungible-pausable/src/contract_token_interface.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#![no_std] | ||
|
||
mod contract; | ||
mod contract_token_interface; | ||
mod test; |
Oops, something went wrong.