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

feat: burn and mint #150

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
36 changes: 36 additions & 0 deletions pallets/api/src/fungibles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,30 +156,30 @@
let id: AssetIdParameterOf<T> = id.into();

// If the new value is equal to the current allowance, do nothing.
let return_weight = if value == current_allowance {
Self::weight_approve(0, 0)
}
// If the new value is greater than the current allowance, approve the difference
// because `approve_transfer` works additively (see `pallet-assets`).
else if value > current_allowance {
AssetsOf::<T>::approve_transfer(
origin,
id,
spender,
value.saturating_sub(current_allowance),
)
.map_err(|e| e.with_weight(Self::weight_approve(1, 0)))?;
Self::weight_approve(1, 0)
} else {
// If the new value is less than the current allowance, cancel the approval and set the new value
AssetsOf::<T>::cancel_approval(origin.clone(), id.clone(), spender.clone())
.map_err(|e| e.with_weight(Self::weight_approve(0, 1)))?;
if value.is_zero() {
return Ok(Some(Self::weight_approve(0, 1)).into());
}
AssetsOf::<T>::approve_transfer(origin, id, spender, value)?;
Self::weight_approve(1, 1)
};

Check warning on line 182 in pallets/api/src/fungibles/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

`if` chain can be rewritten with `match`

warning: `if` chain can be rewritten with `match` --> pallets/api/src/fungibles/mod.rs:159:24 | 159 | let return_weight = if value == current_allowance { | _________________________________^ 160 | | Self::weight_approve(0, 0) 161 | | } 162 | | // If the new value is greater than the current allowance, approve the difference ... | 181 | | Self::weight_approve(1, 1) 182 | | }; | |_____________^ | = help: consider rewriting the `if` chain to use `cmp` and `match` = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain = note: `#[warn(clippy::comparison_chain)]` on by default
Ok(Some(return_weight).into())
}

Expand Down Expand Up @@ -293,6 +293,42 @@
pub fn clear_metadata(origin: OriginFor<T>, id: AssetIdOf<T>) -> DispatchResult {
AssetsOf::<T>::clear_metadata(origin, id.into())
}

/// Creates `amount` tokens and assigns them to `account`, increasing the total supply.
///
/// # Parameters
/// - `id` - The ID of the asset.
/// - `owner` - The account to be credited with the created tokens.
/// - `value` - The number of tokens to mint.
#[pallet::call_index(19)]
#[pallet::weight(AssetsWeightInfoOf::<T>::mint())]
pub fn mint(
origin: OriginFor<T>,
id: AssetIdOf<T>,
account: AccountIdOf<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
let account = T::Lookup::unlookup(account);
AssetsOf::<T>::mint(origin, id.into(), account, amount)
}

/// Destroys `amount` tokens from `account`, reducing the total supply.
///
/// # Parameters
/// - `id` - The ID of the asset.
/// - `owner` - The account from which the tokens will be destroyed.
/// - `value` - The number of tokens to destroy.
#[pallet::call_index(20)]
#[pallet::weight(AssetsWeightInfoOf::<T>::burn())]
pub fn burn(
origin: OriginFor<T>,
id: AssetIdOf<T>,
account: AccountIdOf<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
let account = T::Lookup::unlookup(account);
AssetsOf::<T>::burn(origin, id.into(), account, amount)
}
}

impl<T: Config> Pallet<T> {
Expand Down
36 changes: 30 additions & 6 deletions pallets/api/src/fungibles/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ fn create_works() {
#[test]
fn start_destroy_works() {
new_test_ext().execute_with(|| {
create_asset(ALICE, ASSET, 100);
create_asset(ALICE, ASSET);
assert_ok!(Fungibles::start_destroy(signed(ALICE), ASSET));
});
}
Expand All @@ -119,7 +119,7 @@ fn set_metadata_works() {
let name = vec![42];
let symbol = vec![42];
let decimals = 42;
create_asset(ALICE, ASSET, 100);
create_asset(ALICE, ASSET);
assert_ok!(Fungibles::set_metadata(
signed(ALICE),
ASSET,
Expand Down Expand Up @@ -147,6 +147,30 @@ fn clear_metadata_works() {
});
}

#[test]
fn mint_works() {
Daanvdplas marked this conversation as resolved.
Show resolved Hide resolved
new_test_ext().execute_with(|| {
let amount: Balance = 100 * UNIT;
create_asset(ALICE, ASSET);
let balance_before_mint = Assets::balance(ASSET, &BOB);
assert_ok!(Fungibles::mint(signed(ALICE), ASSET, BOB, amount));
let balance_after_mint = Assets::balance(ASSET, &BOB);
assert_eq!(balance_after_mint, balance_before_mint + amount);
});
}

#[test]
fn burn_works() {
Daanvdplas marked this conversation as resolved.
Show resolved Hide resolved
new_test_ext().execute_with(|| {
let amount: Balance = 100 * UNIT;
create_asset_and_mint_to(ALICE, ASSET, BOB, amount);
let balance_before_burn = Assets::balance(ASSET, &BOB);
assert_ok!(Fungibles::burn(signed(ALICE), ASSET, BOB, amount));
let balance_after_burn = Assets::balance(ASSET, &BOB);
assert_eq!(balance_after_burn, balance_before_burn - amount);
});
}

#[test]
fn total_supply_works() {
new_test_ext().execute_with(|| {
Expand Down Expand Up @@ -193,7 +217,7 @@ fn token_metadata_works() {
#[test]
fn asset_exists_works() {
new_test_ext().execute_with(|| {
create_asset(ALICE, ASSET, 100);
create_asset(ALICE, ASSET);
assert_eq!(Assets::asset_exists(ASSET).encode(), Fungibles::read_state(AssetExists(ASSET)));
});
}
Expand All @@ -202,16 +226,16 @@ fn signed(account: AccountId) -> RuntimeOrigin {
RuntimeOrigin::signed(account)
}

fn create_asset(owner: AccountId, asset_id: AssetId, min_balance: Balance) {
assert_ok!(Assets::create(signed(owner), asset_id, owner, min_balance));
fn create_asset(owner: AccountId, asset_id: AssetId) {
assert_ok!(Assets::create(signed(owner), asset_id, owner, 1));
}

fn mint_asset(owner: AccountId, asset_id: AssetId, to: AccountId, value: Balance) {
assert_ok!(Assets::mint(signed(owner), asset_id, to, value));
}

fn create_asset_and_mint_to(owner: AccountId, asset_id: AssetId, to: AccountId, value: Balance) {
create_asset(owner, asset_id, 1);
create_asset(owner, asset_id);
mint_asset(owner, asset_id, to, value)
}

Expand Down
29 changes: 21 additions & 8 deletions pop-api/integration-tests/contracts/fungibles/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,14 @@ mod fungibles {
api::token_decimals(id)
}

// 3. Asset Management:
// - create
// - start_destroy
// - destroy_accounts
// - destroy_approvals
// - finish_destroy
// - set_metadata
// - clear_metadata
/// 3. Asset Management:
/// - create
/// - start_destroy
/// - destroy_accounts
/// - destroy_approvals
/// - finish_destroy
/// - set_metadata
/// - clear_metadata

#[ink(message)]
pub fn create(&self, id: AssetId, admin: AccountId, min_balance: Balance) -> Result<()> {
Expand Down Expand Up @@ -161,6 +161,19 @@ mod fungibles {
pub fn asset_exists(&self, id: AssetId) -> Result<bool> {
api::asset_exists(id)
}
/// 4. PSP-22 Mintable & Burnable Interface:
/// - mint
/// - burn

#[ink(message)]
pub fn mint(&self, id: AssetId, account: AccountId, amount: Balance) -> Result<()> {
api::mint(id, account, amount)
}

#[ink(message)]
pub fn burn(&self, id: AssetId, account: AccountId, amount: Balance) -> Result<()> {
api::burn(id, account, amount)
}
}

#[cfg(test)]
Expand Down
121 changes: 62 additions & 59 deletions pop-api/integration-tests/src/fungibles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,62 +483,65 @@ fn asset_exists_works() {
});
}

// #[test]
// #[ignore]
// fn mint_works() {
// new_test_ext().execute_with(|| {
// let _ = env_logger::try_init();
// let addr =
// instantiate(CONTRACT, INIT_VALUE, vec![]);
// let amount: Balance = 100 * UNIT;
//
// // Asset does not exist.
// assert_eq!(
// decoded::<Error>(transfer_from(addr.clone(), 1, None, Some(BOB), amount, &[0u8])),
// Token(UnknownAsset)
// );
// let asset = create_asset(ALICE, 1, 2);
// // Minting can only be done by the owner.
// assert_eq!(
// decoded::<Error>(transfer_from(addr.clone(), asset, None, Some(BOB), amount, &[0u8])),
// Ok(Module { index: 52, error: 2 }),
// );
// // Minimum balance of an asset can not be zero.
// assert_eq!(
// decoded::<Error>(transfer_from(addr.clone(), asset, None, Some(BOB), 1, &[0u8])),
// Token(BelowMinimum)
// );
// let asset = create_asset(addr.clone(), 2, 2);
// // Asset is not live, i.e. frozen or being destroyed.
// freeze_asset(addr.clone(), asset);
// assert_eq!(
// decoded::<Error>(transfer_from(addr.clone(), asset, None, Some(BOB), amount, &[0u8])),
// Ok(Module { index: 52, error: 16 }),
// );
// thaw_asset(addr.clone(), asset);
// // Successful mint.
// let balance_before_mint = Assets::balance(asset, &BOB);
// let result = transfer_from(addr.clone(), asset, None, Some(BOB), amount, &[0u8]);
// assert!(!result.did_revert(), "Contract reverted!");
// let balance_after_mint = Assets::balance(asset, &BOB);
// assert_eq!(balance_after_mint, balance_before_mint + amount);
// // Can not mint more tokens than Balance::MAX.
// assert_eq!(
// decoded::<Error>(transfer_from(
// addr.clone(),
// asset,
// None,
// Some(BOB),
// Balance::MAX,
// &[0u8]
// )),
// Arithmetic(Overflow)
// );
// // Asset is not live, i.e. frozen or being destroyed.
// start_destroy_asset(addr.clone(), asset);
// assert_eq!(
// decoded::<Error>(transfer_from(addr.clone(), asset, None, Some(BOB), amount, &[0u8])),
// Ok(Module { index: 52, error: 16 }),
// );
// });
// }
#[test]
fn mint_works() {
new_test_ext().execute_with(|| {
let _ = env_logger::try_init();
let addr = instantiate(CONTRACT, INIT_VALUE, vec![]);
let amount: Balance = 100 * UNIT;

// Asset does not exist.
assert_eq!(mint(addr.clone(), 1, BOB, amount), Err(Token(UnknownAsset)));
let asset = create_asset(ALICE, 1, 1);
// Minting can only be done by the owner.
assert_eq!(mint(addr.clone(), asset, BOB, 1), Err(Module { index: 52, error: 2 }));
let asset = create_asset(addr.clone(), 2, 2);
// Minimum balance of an asset can not be zero.
assert_eq!(mint(addr.clone(), asset, BOB, 1), Err(Token(BelowMinimum)));
// Asset is not live, i.e. frozen or being destroyed.
freeze_asset(addr.clone(), asset);
assert_eq!(mint(addr.clone(), asset, BOB, amount), Err(Module { index: 52, error: 16 }));
thaw_asset(addr.clone(), asset);
// Successful mint.
let balance_before_mint = Assets::balance(asset, &BOB);
assert_ok!(mint(addr.clone(), asset, BOB, amount));
let balance_after_mint = Assets::balance(asset, &BOB);
assert_eq!(balance_after_mint, balance_before_mint + amount);
// Account can not hold more tokens than Balance::MAX.
assert_eq!(mint(addr.clone(), asset, BOB, Balance::MAX,), Err(Arithmetic(Overflow)));
// Asset is not live, i.e. frozen or being destroyed.
start_destroy_asset(addr.clone(), asset);
assert_eq!(mint(addr.clone(), asset, BOB, amount), Err(Module { index: 52, error: 16 }));
});
}

#[test]
fn burn_works() {
new_test_ext().execute_with(|| {
let _ = env_logger::try_init();
let addr = instantiate(CONTRACT, INIT_VALUE, vec![]);
let amount: Balance = 100 * UNIT;

// Asset does not exist.
assert_eq!(burn(addr.clone(), 1, BOB, amount), Err(Module { index: 52, error: 3 }));
let asset = create_asset(ALICE, 1, 1);
// Bob has no tokens and thus pallet assets doesn't know the account.
assert_eq!(burn(addr.clone(), asset, BOB, 1), Err(Module { index: 52, error: 1 }));
// Burning can only be done by the manager.
mint_asset(ALICE, asset, BOB, amount);
assert_eq!(burn(addr.clone(), asset, BOB, 1), Err(Module { index: 52, error: 2 }));
let asset = create_asset_and_mint_to(addr.clone(), 2, BOB, amount);
// Asset is not live, i.e. frozen or being destroyed.
freeze_asset(addr.clone(), asset);
assert_eq!(burn(addr.clone(), asset, BOB, amount), Err(Module { index: 52, error: 16 }));
thaw_asset(addr.clone(), asset);
// Successful mint.
let balance_before_burn = Assets::balance(asset, &BOB);
assert_ok!(burn(addr.clone(), asset, BOB, amount));
let balance_after_burn = Assets::balance(asset, &BOB);
assert_eq!(balance_after_burn, balance_before_burn - amount);
// Asset is not live, i.e. frozen or being destroyed.
start_destroy_asset(addr.clone(), asset);
assert_eq!(burn(addr.clone(), asset, BOB, amount), Err(Module { index: 52, error: 16 }));
});
}
26 changes: 26 additions & 0 deletions pop-api/integration-tests/src/fungibles/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,32 @@ pub(super) fn clear_metadata(addr: AccountId32, asset_id: AssetId) -> Result<(),
.unwrap_or_else(|_| panic!("Contract reverted: {:?}", result))
}

pub(super) fn mint(
addr: AccountId32,
asset_id: AssetId,
account: AccountId32,
amount: Balance,
) -> Result<(), Error> {
let function = function_selector("mint");
let params = [function, asset_id.encode(), account.encode(), amount.encode()].concat();
let result = bare_call(addr, params, 0).expect("should work");
decoded::<Result<(), Error>>(result.clone())
.unwrap_or_else(|_| panic!("Contract reverted: {:?}", result))
}

pub(super) fn burn(
addr: AccountId32,
asset_id: AssetId,
account: AccountId32,
amount: Balance,
) -> Result<(), Error> {
let function = function_selector("burn");
let params = [function, asset_id.encode(), account.encode(), amount.encode()].concat();
let result = bare_call(addr, params, 0).expect("should work");
decoded::<Result<(), Error>>(result.clone())
.unwrap_or_else(|_| panic!("Contract reverted: {:?}", result))
}
Daanvdplas marked this conversation as resolved.
Show resolved Hide resolved

pub(super) fn create_asset(owner: AccountId32, asset_id: AssetId, min_balance: Balance) -> AssetId {
assert_ok!(Assets::create(
RuntimeOrigin::signed(owner.clone()),
Expand Down
41 changes: 41 additions & 0 deletions pop-api/src/v0/assets/fungibles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ fn build_read_state(state_query: u8) -> ChainExtensionMethod<(), (), (), false>
/// 1. PSP-22 Interface
/// 2. PSP-22 Metadata Interface
/// 3. Asset Management
/// 4. PSP-22 Mintable & Burnable Interface

mod constants {
/// 1. PSP-22 Interface:
Expand All @@ -51,6 +52,10 @@ mod constants {
pub(super) const SET_METADATA: u8 = 16;
pub(super) const CLEAR_METADATA: u8 = 17;
pub(super) const ASSET_EXISTS: u8 = 18;

/// 4. PSP-22 Mintable & Burnable interface:
pub(super) const MINT: u8 = 19;
pub(super) const BURN: u8 = 20;
}

/// Returns the total token supply for a given asset ID.
Expand Down Expand Up @@ -200,8 +205,43 @@ pub fn decrease_allowance(id: AssetId, spender: AccountId, value: Balance) -> Re
.call(&(id, spender, value))
}

/// Creates `amount` tokens and assigns them to `account`, increasing the total supply.
///
/// # Parameters
/// - `id` - The ID of the asset.
/// - `owner` - The account to be credited with the created tokens.
/// - `value` - The number of tokens to mint.
///
/// # Returns
/// Returns `Ok(())` if successful, or an error if the operation fails.
pub fn mint(id: AssetId, account: AccountId, amount: Balance) -> Result<()> {
build_dispatch(MINT)
.input::<(AssetId, AccountId, Balance)>()
.output::<Result<()>, true>()
.handle_error_code::<StatusCode>()
.call(&(id, account, amount))
}

/// Destroys `amount` tokens from `account`, reducing the total supply.
///
/// # Parameters
/// - `id` - The ID of the asset.
/// - `owner` - The account from which the tokens will be destroyed.
/// - `value` - The number of tokens to destroy.
///
/// # Returns
/// Returns `Ok(())` if successful, or an error if the operation fails.
pub fn burn(id: AssetId, account: AccountId, amount: Balance) -> Result<()> {
build_dispatch(BURN)
.input::<(AssetId, AccountId, Balance)>()
.output::<Result<()>, true>()
.handle_error_code::<StatusCode>()
.call(&(id, account, amount))
}

pub mod metadata {
use super::*;

/// Returns the token name for a given asset ID.
///
/// # Parameters
Expand Down Expand Up @@ -253,6 +293,7 @@ pub mod metadata {

pub mod asset_management {
use super::*;

/// Create a new token with a given asset ID.
///
/// # Parameters
Expand Down
Loading
Loading