Skip to content

Commit

Permalink
Burnable (#15)
Browse files Browse the repository at this point in the history
* burnable trait implementation
  • Loading branch information
ozgunozerk authored Jan 24, 2025
1 parent 6655643 commit 17556a7
Show file tree
Hide file tree
Showing 26 changed files with 2,242 additions and 193 deletions.
8 changes: 0 additions & 8 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,3 @@ body:
attributes:
label: Expected behavior
description: What should have happened instead?
- type: checkboxes
id: terms
attributes:
label: Contribution Guidelines
description: By submitting this issue, you agree to follow our [Contribution Guidelines](https://github.com/OpenZeppelin/soroban-contracts/blob/main/CONTRIBUTING.md)
options:
- label: I agree to follow this project's Contribution Guidelines
required: true
8 changes: 0 additions & 8 deletions .github/ISSUE_TEMPLATE/core_implementation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,3 @@ body:
label: What is the key feature we're aiming to implement?
validations:
required: true
- type: checkboxes
id: terms
attributes:
label: Contribution Guidelines
description: By submitting this issue, you agree to follow our [Contribution Guidelines](https://github.com/OpenZeppelin/soroban-contracts/blob/main/CONTRIBUTING.md)
options:
- label: I agree to follow this project's Contribution Guidelines
required: true
8 changes: 0 additions & 8 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,3 @@ body:
description: Is your feature request related to a specific problem? Is it just a crazy idea? Tell us about it!
validations:
required: true
- type: checkboxes
id: terms
attributes:
label: Contribution Guidelines
description: By submitting this issue, you agree to follow our [Contribution Guidelines](https://github.com/OpenZeppelin/soroban-contracts/blob/main/CONTRIBUTING.md)
options:
- label: I agree to follow this project's Contribution Guidelines
required: true
92 changes: 92 additions & 0 deletions contracts/token/fungible/src/extensions/burnable/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
pub mod storage;
mod tests;

use soroban_sdk::{contractclient, symbol_short, Address, Env};

/// Burnable Trait for Fungible Token
///
/// The `Burnable` trait extends the `FungibleToken` trait to provide the
/// capability to burn tokens. This trait is designed to be used in conjunction
/// with the `FungibleToken` trait.
///
/// To fully comply with the SEP-41 specification one have to implement the
/// this `Burnable` trait along with the `[FungibleToken]` trait.
/// SEP-41 mandates support for token burning to be considered compliant.
///
/// Excluding the `burn` functionality from the `[FungibleToken]` trait
/// is a deliberate design choice to accommodate flexibility and customization
/// for various smart contract use cases.
#[contractclient(name = "FungibleBurnableTokenClient")]
pub trait FungibleBurnable {
/// Destroys `amount` of tokens from `account`. Updates the total
/// supply accordingly.
///
/// # Arguments
///
/// * `e` - Access to the Soroban environment.
/// * `from` - The account whose tokens are destroyed.
/// * `amount` - The amount of tokens to burn.
///
/// # Errors
///
/// * [`crate::fungible::FungibleTokenError::InsufficientBalance`] - When
/// attempting to burn more tokens than `from` current balance.
///
/// # Events
///
/// * topics - `["burn", from: Address]`
/// * data - `[amount: i128]`
///
/// # Notes
///
/// We recommend using the [`crate::extensions::burnable::storage::burn()`]
/// function the `storage` module when implementing this function.
fn burn(e: &Env, from: &Address, amount: i128);

/// Destroys `amount` of tokens from `account`. Updates the total
/// supply accordingly.
///
/// # Arguments
///
/// * `e` - Access to the Soroban environment.
/// * `spender` - The address authorized to burn the tokens.
/// * `from` - The account whose tokens are destroyed.
/// * `amount` - The amount of tokens to burn.
///
/// # Errors
///
/// * [`crate::fungible::FungibleTokenError::InsufficientBalance`] - When
/// attempting to burn more tokens than `from` current balance.A
/// * [`crate::fungible::FungibleTokenError::InsufficientAllowance`] - When
/// attempting to burn more tokens than `from` allowance.
///
/// # Events
///
/// * topics - `["burn", from: Address]`
/// * data - `[amount: i128]`
///
/// # Notes
///
/// We recommend using the [`crate::extensions::burnable::storage::burn()`]
/// function the `storage` module when implementing this function.
fn burn_from(e: &Env, spender: &Address, from: &Address, amount: i128);
}

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

/// Emits an event indicating a burn of tokens.
///
/// # Arguments
///
/// * `e` - Access to Soroban environment.
/// * `from` - The address holding the tokens.
/// * `amount` - The amount of tokens to be burned.
///
/// # Events
///
/// * topics - `["burn", from: Address]`
/// * data - `[amount: i128]`
pub fn emit_burn(e: &Env, from: &Address, amount: i128) {
let topics = (symbol_short!("burn"), from);
e.events().publish(topics, amount)
}
64 changes: 64 additions & 0 deletions contracts/token/fungible/src/extensions/burnable/storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use soroban_sdk::{Address, Env};

use crate::{
extensions::burnable::emit_burn,
storage::{spend_allowance, update},
};

/// Destroys `amount` of tokens from `account`. Updates the total
/// supply accordingly.
///
/// # Arguments
///
/// * `e` - Access to the Soroban environment.
/// * `from` - The account whose tokens are destroyed.
/// * `amount` - The amount of tokens to burn.
///
/// # Errors
///
/// * [`crate::fungible::FungibleTokenError::InsufficientBalance`] - When
/// attempting to burn more tokens than `from` current balance.
///
/// # Events
///
/// * topics - `["burn", from: Address]`
/// * data - `[amount: i128]`
pub fn burn(e: &Env, from: &Address, amount: i128) {
from.require_auth();
update(e, Some(from), None, amount);
emit_burn(e, from, amount);
}

/// Destroys `amount` of tokens from `account` using the allowance mechanism.
/// `amount`is then deducted from `spender` allowance.
/// Updates the total supply accordingly.
///
/// # Arguments
///
/// * `e` - Access to the Soroban environment.
/// * `spender` - The address authorizing the transfer, and having its
/// allowance.
/// * `from` - The account whose tokens are destroyed.
/// * `amount` - The amount of tokens to burn.
///
/// # Errors
///
/// * [`crate::fungible::FungibleTokenError::InsufficientBalance`] - When
/// attempting to burn more tokens than `from` current balance.
/// * [`FungibleTokenError::InsufficientAllowance`] - When attempting to burn
/// more tokens than `spender`s current allowance.
///
/// # Events
///
/// * topics - `["burn", from: Address]`
/// * data - `[amount: i128]`
///
/// # Notes
///
/// Authorization for `spender` is required.
pub fn burn_from(e: &Env, spender: &Address, from: &Address, amount: i128) {
spender.require_auth();
spend_allowance(e, from, spender, amount);
update(e, Some(from), None, amount);
emit_burn(e, from, amount);
}
93 changes: 93 additions & 0 deletions contracts/token/fungible/src/extensions/burnable/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#![cfg(test)]

extern crate std;

use soroban_sdk::{contract, testutils::Address as _, Address, Env};

use crate::{
extensions::burnable::storage::{burn, burn_from},
storage::{allowance, approve, balance, mint, total_supply},
};

#[contract]
struct MockContract;

#[test]
fn burn_works() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let account = Address::generate(&e);
e.as_contract(&address, || {
mint(&e, &account, 100);
burn(&e, &account, 50);
assert_eq!(balance(&e, &account), 50);
assert_eq!(total_supply(&e), 50);
});
}

#[test]
fn burn_with_allowance_works() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let owner = Address::generate(&e);
let spender = Address::generate(&e);
e.as_contract(&address, || {
mint(&e, &owner, 100);
approve(&e, &owner, &spender, 30, 1000);
burn_from(&e, &spender, &owner, 30);
assert_eq!(balance(&e, &owner), 70);
assert_eq!(balance(&e, &spender), 0);
assert_eq!(total_supply(&e), 70);
});
}

#[test]
#[should_panic(expected = "Error(Contract, #1)")]
fn burn_with_insufficient_balance_panics() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let account = Address::generate(&e);
e.as_contract(&address, || {
mint(&e, &account, 100);
assert_eq!(balance(&e, &account), 100);
assert_eq!(total_supply(&e), 100);
burn(&e, &account, 101);
});
}

#[test]
#[should_panic(expected = "Error(Contract, #2)")]
fn burn_with_no_allowance_panics() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let owner = Address::generate(&e);
let spender = Address::generate(&e);
e.as_contract(&address, || {
mint(&e, &owner, 100);
assert_eq!(balance(&e, &owner), 100);
assert_eq!(total_supply(&e), 100);
burn_from(&e, &spender, &owner, 50);
});
}

#[test]
#[should_panic(expected = "Error(Contract, #2)")]
fn burn_with_insufficient_allowance_panics() {
let e = Env::default();
e.mock_all_auths();
let address = e.register(MockContract, ());
let owner = Address::generate(&e);
let spender = Address::generate(&e);
e.as_contract(&address, || {
mint(&e, &owner, 100);
approve(&e, &owner, &spender, 50, 100);
assert_eq!(allowance(&e, &owner, &spender), 50);
assert_eq!(balance(&e, &owner), 100);
assert_eq!(total_supply(&e), 100);
burn_from(&e, &spender, &owner, 60);
});
}
1 change: 1 addition & 0 deletions contracts/token/fungible/src/extensions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod burnable;
Loading

0 comments on commit 17556a7

Please sign in to comment.