diff --git a/pallets/api/src/fungibles/mod.rs b/pallets/api/src/fungibles/mod.rs index eefad0e1..7aa9ac00 100644 --- a/pallets/api/src/fungibles/mod.rs +++ b/pallets/api/src/fungibles/mod.rs @@ -84,6 +84,8 @@ pub mod pallet { /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config + pallet_assets::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The instance of pallet assets it is tightly coupled to. type AssetsInstance; /// Weight information for dispatchables in this pallet. @@ -93,6 +95,43 @@ pub mod pallet { #[pallet::pallet] pub struct Pallet(_); + /// The events that can be emitted. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Event emitted when allowance by `owner` to `spender` changes. + Approval { + /// The ID of the asset. + id: AssetIdOf, + /// Account providing allowance. + owner: AccountIdOf, + /// Allowance beneficiary. + spender: AccountIdOf, + /// New allowance amount. + value: BalanceOf, + }, + /// Event emitted when transfer of tokens occurs. + Transfer { + /// The ID of the asset. + id: AssetIdOf, + /// Transfer sender. `None` in case of minting new tokens. + from: Option>, + /// Transfer recipient. `None` in case of burning tokens. + to: Option>, + /// Amount of tokens transferred (or minted/burned). + value: BalanceOf, + }, + /// Event emitted when a token class is created. + Create { + /// The ID of the asset. + id: AssetIdOf, + /// Creator of the asset. + creator: AccountIdOf, + /// Admin of the asset. + admin: AccountIdOf, + }, + } + #[pallet::call] impl Pallet { /// Transfers `value` amount of tokens from the caller's account to account `to`, with additional @@ -110,8 +149,15 @@ pub mod pallet { to: AccountIdOf, value: BalanceOf, ) -> DispatchResult { - let to = T::Lookup::unlookup(to); - AssetsOf::::transfer_keep_alive(origin, id.into(), to, value) + AssetsOf::::transfer_keep_alive( + origin.clone(), + id.clone().into(), + T::Lookup::unlookup(to.clone()), + value, + )?; + let from = ensure_signed(origin)?; + Self::deposit_event(Event::Transfer { id, from: Some(from), to: Some(to), value }); + Ok(()) } /// Transfers `value` amount tokens on behalf of `from` to account `to` with additional `data` @@ -119,7 +165,7 @@ pub mod pallet { /// /// # Parameters /// - `id` - The ID of the asset. - /// - `owner` - The account from which the asset balance will be withdrawn. + /// - `from` - The account from which the asset balance will be withdrawn. /// - `to` - The recipient account. /// - `value` - The number of tokens to transfer. #[pallet::call_index(4)] @@ -131,9 +177,15 @@ pub mod pallet { to: AccountIdOf, value: BalanceOf, ) -> DispatchResult { - let from = T::Lookup::unlookup(from); - let to = T::Lookup::unlookup(to); - AssetsOf::::transfer_approved(origin, id.into(), from, to, value) + AssetsOf::::transfer_approved( + origin, + id.clone().into(), + T::Lookup::unlookup(from.clone()), + T::Lookup::unlookup(to.clone()), + value, + )?; + Self::deposit_event(Event::Transfer { id, from: Some(from), to: Some(to), value }); + Ok(()) } /// Approves an account to spend a specified number of tokens on behalf of the caller. @@ -150,13 +202,11 @@ pub mod pallet { spender: AccountIdOf, value: BalanceOf, ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin.clone()) + let owner = ensure_signed(origin.clone()) .map_err(|e| e.with_weight(Self::weight_approve(0, 0)))?; - let current_allowance = AssetsOf::::allowance(id.clone(), &who, &spender); - let spender = T::Lookup::unlookup(spender); - let id: AssetIdParameterOf = id.into(); + let current_allowance = AssetsOf::::allowance(id.clone(), &owner, &spender); - let return_weight = match value.cmp(¤t_allowance) { + let weight = match value.cmp(¤t_allowance) { // If the new value is equal to the current allowance, do nothing. Equal => Self::weight_approve(0, 0), // If the new value is greater than the current allowance, approve the difference @@ -164,8 +214,8 @@ pub mod pallet { Greater => { AssetsOf::::approve_transfer( origin, - id, - spender, + id.clone().into(), + T::Lookup::unlookup(spender.clone()), value.saturating_sub(current_allowance), ) .map_err(|e| e.with_weight(Self::weight_approve(1, 0)))?; @@ -174,16 +224,24 @@ pub mod pallet { // If the new value is less than the current allowance, cancel the approval and // set the new value. Less => { - AssetsOf::::cancel_approval(origin.clone(), id.clone(), spender.clone()) - .map_err(|e| e.with_weight(Self::weight_approve(0, 1)))?; + let id_param: AssetIdParameterOf = id.clone().into(); + let spender_source = T::Lookup::unlookup(spender.clone()); + AssetsOf::::cancel_approval( + origin.clone(), + id_param.clone(), + spender_source.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()); + Self::weight_approve(0, 1) + } else { + AssetsOf::::approve_transfer(origin, id_param, spender_source, value)?; + Self::weight_approve(1, 1) } - AssetsOf::::approve_transfer(origin, id, spender, value)?; - Self::weight_approve(1, 1) }, }; - Ok(Some(return_weight).into()) + Self::deposit_event(Event::Approval { id, owner, spender, value }); + Ok(Some(weight).into()) } /// Increases the allowance of a spender. @@ -193,15 +251,25 @@ pub mod pallet { /// - `spender` - The account that is allowed to spend the tokens. /// - `value` - The number of tokens to increase the allowance by. #[pallet::call_index(6)] - #[pallet::weight(AssetsWeightInfoOf::::approve_transfer())] + #[pallet::weight(::WeightInfo::approve(1, 0))] pub fn increase_allowance( origin: OriginFor, id: AssetIdOf, spender: AccountIdOf, value: BalanceOf, - ) -> DispatchResult { - let spender = T::Lookup::unlookup(spender); - AssetsOf::::approve_transfer(origin, id.into(), spender, value) + ) -> DispatchResultWithPostInfo { + let owner = ensure_signed(origin.clone()) + .map_err(|e| e.with_weight(Self::weight_approve(0, 0)))?; + AssetsOf::::approve_transfer( + origin, + id.clone().into(), + T::Lookup::unlookup(spender.clone()), + value, + ) + .map_err(|e| e.with_weight(AssetsWeightInfoOf::::approve_transfer()))?; + let value = AssetsOf::::allowance(id.clone(), &owner, &spender); + Self::deposit_event(Event::Approval { id, owner, spender, value }); + Ok(().into()) } /// Decreases the allowance of a spender. @@ -218,24 +286,31 @@ pub mod pallet { spender: AccountIdOf, value: BalanceOf, ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin.clone()) + let owner = ensure_signed(origin.clone()) .map_err(|e| e.with_weight(Self::weight_approve(0, 0)))?; - let current_allowance = AssetsOf::::allowance(id.clone(), &who, &spender); - let spender = T::Lookup::unlookup(spender); - let id: AssetIdParameterOf = id.into(); - if value.is_zero() { return Ok(Some(Self::weight_approve(0, 0)).into()); } - // Cancel the aproval and set the new value if `new_allowance` is more than zero. - AssetsOf::::cancel_approval(origin.clone(), id.clone(), spender.clone()) - .map_err(|e| e.with_weight(Self::weight_approve(0, 1)))?; + let current_allowance = AssetsOf::::allowance(id.clone(), &owner, &spender); + let spender_source = T::Lookup::unlookup(spender.clone()); + let id_param: AssetIdParameterOf = id.clone().into(); + + // Cancel the approval and set the new value if `new_allowance` is more than zero. + AssetsOf::::cancel_approval( + origin.clone(), + id_param.clone(), + spender_source.clone(), + ) + .map_err(|e| e.with_weight(Self::weight_approve(0, 1)))?; let new_allowance = current_allowance.saturating_sub(value); - if new_allowance.is_zero() { - return Ok(Some(Self::weight_approve(0, 1)).into()); - } - AssetsOf::::approve_transfer(origin, id, spender, new_allowance)?; - Ok(().into()) + let weight = if new_allowance.is_zero() { + Self::weight_approve(0, 1) + } else { + AssetsOf::::approve_transfer(origin, id_param, spender_source, new_allowance)?; + Self::weight_approve(1, 1) + }; + Self::deposit_event(Event::Approval { id, owner, spender, value: new_allowance }); + Ok(Some(weight).into()) } /// Create a new token with a given asset ID. @@ -252,8 +327,15 @@ pub mod pallet { admin: AccountIdOf, min_balance: BalanceOf, ) -> DispatchResult { - let admin = T::Lookup::unlookup(admin); - AssetsOf::::create(origin, id.into(), admin, min_balance) + let creator = ensure_signed(origin.clone())?; + AssetsOf::::create( + origin, + id.clone().into(), + T::Lookup::unlookup(admin.clone()), + min_balance, + )?; + Self::deposit_event(Event::Create { id, creator, admin }); + Ok(()) } /// Start the process of destroying a token with a given asset ID. @@ -297,7 +379,7 @@ pub mod pallet { AssetsOf::::clear_metadata(origin, id.into()) } - /// Creates `value` amount tokens and assigns them to `account`, increasing the total supply. + /// Creates `value` amount of tokens and assigns them to `account`, increasing the total supply. /// /// # Parameters /// - `id` - The ID of the asset. @@ -311,11 +393,17 @@ pub mod pallet { account: AccountIdOf, value: BalanceOf, ) -> DispatchResult { - let account = T::Lookup::unlookup(account); - AssetsOf::::mint(origin, id.into(), account, value) + AssetsOf::::mint( + origin, + id.clone().into(), + T::Lookup::unlookup(account.clone()), + value, + )?; + Self::deposit_event(Event::Transfer { id, from: None, to: Some(account), value }); + Ok(()) } - /// Destroys `value` amount tokens from `account`, reducing the total supply. + /// Destroys `value` amount of tokens from `account`, reducing the total supply. /// /// # Parameters /// - `id` - The ID of the asset. @@ -329,8 +417,14 @@ pub mod pallet { account: AccountIdOf, value: BalanceOf, ) -> DispatchResult { - let account = T::Lookup::unlookup(account); - AssetsOf::::burn(origin, id.into(), account, value) + AssetsOf::::burn( + origin, + id.clone().into(), + T::Lookup::unlookup(account.clone()), + value, + )?; + Self::deposit_event(Event::Transfer { id, from: Some(account), to: None, value }); + Ok(()) } } diff --git a/pallets/api/src/fungibles/tests.rs b/pallets/api/src/fungibles/tests.rs index d6cc87e0..ca2a85c9 100644 --- a/pallets/api/src/fungibles/tests.rs +++ b/pallets/api/src/fungibles/tests.rs @@ -2,6 +2,7 @@ use crate::{fungibles::Read::*, mock::*}; use codec::Encode; use frame_support::{ assert_ok, + sp_runtime::traits::Zero, traits::fungibles::{ approvals::Inspect as ApprovalInspect, metadata::Inspect as MetadataInspect, Inspect, }, @@ -9,34 +10,45 @@ use frame_support::{ const ASSET: u32 = 42; +type Event = crate::fungibles::Event; + #[test] fn transfer_works() { new_test_ext().execute_with(|| { - let amount: Balance = 100 * UNIT; - create_asset_and_mint_to(ALICE, ASSET, ALICE, amount); - let balance_before_transfer = Assets::balance(ASSET, &BOB); - assert_ok!(Fungibles::transfer(signed(ALICE), ASSET, BOB, amount / 2)); - let balance_after_transfer = Assets::balance(ASSET, &BOB); - assert_eq!(balance_after_transfer, balance_before_transfer + amount / 2); + let value: Balance = 100 * UNIT; + let id = ASSET; + let from = Some(ALICE); + let to = Some(BOB); + + create_asset_and_mint_to(ALICE, id, ALICE, value * 2); + let balance_before_transfer = Assets::balance(id, &BOB); + assert_ok!(Fungibles::transfer(signed(ALICE), id, BOB, value)); + let balance_after_transfer = Assets::balance(id, &BOB); + assert_eq!(balance_after_transfer, balance_before_transfer + value); + System::assert_last_event(Event::Transfer { id, from, to, value }.into()); }); } #[test] fn transfer_from_works() { new_test_ext().execute_with(|| { - let amount: Balance = 100 * UNIT; - // Approve CHARLIE to transfer up to `amount` to BOB. - create_asset_mint_and_approve(ALICE, ASSET, ALICE, amount * 2, CHARLIE, amount / 2); - let transferred = amount / 2; + let value: Balance = 100 * UNIT; + let id = ASSET; + let from = Some(ALICE); + let to = Some(BOB); + + // Approve CHARLIE to transfer up to `value` to BOB. + create_asset_mint_and_approve(ALICE, id, ALICE, value * 2, CHARLIE, value); // Successfully call transfer from. - let alice_balance_before_transfer = Assets::balance(ASSET, &ALICE); - let balance_before_transfer = Assets::balance(ASSET, &BOB); - assert_ok!(Fungibles::transfer_from(signed(CHARLIE), ASSET, ALICE, BOB, transferred)); - let alice_balance_after_transfer = Assets::balance(ASSET, &ALICE); - let balance_after_transfer = Assets::balance(ASSET, &BOB); - // Check that BOB receives the `amount` and ALICE `amount` is spent successfully by CHARLIE. - assert_eq!(balance_after_transfer, balance_before_transfer + transferred); - assert_eq!(alice_balance_after_transfer, alice_balance_before_transfer - transferred); + let alice_balance_before_transfer = Assets::balance(id, &ALICE); + let bob_balance_before_transfer = Assets::balance(id, &BOB); + assert_ok!(Fungibles::transfer_from(signed(CHARLIE), id, ALICE, BOB, value)); + let alice_balance_after_transfer = Assets::balance(id, &ALICE); + let bob_balance_after_transfer = Assets::balance(id, &BOB); + // Check that BOB receives the `value` and ALICE `amount` is spent successfully by CHARLIE. + assert_eq!(bob_balance_after_transfer, bob_balance_before_transfer + value); + assert_eq!(alice_balance_after_transfer, alice_balance_before_transfer - value); + System::assert_last_event(Event::Transfer { id, from, to, value }.into()); }); } @@ -44,130 +56,169 @@ fn transfer_from_works() { #[test] fn approve_works() { new_test_ext().execute_with(|| { - let amount: Balance = 100 * UNIT; - create_asset_and_mint_to(ALICE, ASSET, ALICE, amount); - assert_eq!(0, Assets::allowance(ASSET, &ALICE, &BOB)); - assert_ok!(Fungibles::approve(signed(ALICE), ASSET, BOB, amount)); - assert_eq!(Assets::allowance(ASSET, &ALICE, &BOB), amount); - // Approves an amount to spend that is lower than the current allowance. - assert_ok!(Fungibles::approve(signed(ALICE), ASSET, BOB, amount / 2)); - assert_eq!(Assets::allowance(ASSET, &ALICE, &BOB), amount / 2); - // Approves an amount to spend that is higher than the current allowance. - assert_ok!(Fungibles::approve(signed(ALICE), ASSET, BOB, amount * 2)); - assert_eq!(Assets::allowance(ASSET, &ALICE, &BOB), amount * 2); - // Approves an amount to spend that is equal to the current allowance. - assert_ok!(Fungibles::approve(signed(ALICE), ASSET, BOB, amount * 2)); - assert_eq!(Assets::allowance(ASSET, &ALICE, &BOB), amount * 2); + let value: Balance = 100 * UNIT; + let id = ASSET; + let owner = ALICE; + let spender = BOB; + + create_asset_and_mint_to(ALICE, id, ALICE, value); + assert_eq!(0, Assets::allowance(id, &ALICE, &BOB)); + assert_ok!(Fungibles::approve(signed(ALICE), id, BOB, value)); + assert_eq!(Assets::allowance(id, &ALICE, &BOB), value); + System::assert_last_event(Event::Approval { id, owner, spender, value }.into()); + // Approves an value to spend that is lower than the current allowance. + assert_ok!(Fungibles::approve(signed(ALICE), id, BOB, value / 2)); + assert_eq!(Assets::allowance(id, &ALICE, &BOB), value / 2); + System::assert_last_event(Event::Approval { id, owner, spender, value: value / 2 }.into()); + // Approves an value to spend that is higher than the current allowance. + assert_ok!(Fungibles::approve(signed(ALICE), id, BOB, value * 2)); + assert_eq!(Assets::allowance(id, &ALICE, &BOB), value * 2); + System::assert_last_event(Event::Approval { id, owner, spender, value: value * 2 }.into()); + // Approves an value to spend that is equal to the current allowance. + assert_ok!(Fungibles::approve(signed(ALICE), id, BOB, value * 2)); + assert_eq!(Assets::allowance(id, &ALICE, &BOB), value * 2); + System::assert_last_event(Event::Approval { id, owner, spender, value: value * 2 }.into()); // Sets allowance to zero. - assert_ok!(Fungibles::approve(signed(ALICE), ASSET, BOB, 0)); - assert_eq!(Assets::allowance(ASSET, &ALICE, &BOB), 0); + assert_ok!(Fungibles::approve(signed(ALICE), id, BOB, 0)); + assert_eq!(Assets::allowance(id, &ALICE, &BOB), 0); + System::assert_last_event(Event::Approval { id, owner, spender, value: 0 }.into()); }); } #[test] fn increase_allowance_works() { new_test_ext().execute_with(|| { - let amount: Balance = 100 * UNIT; - create_asset_and_mint_to(ALICE, ASSET, ALICE, amount); - assert_eq!(0, Assets::allowance(ASSET, &ALICE, &BOB)); - assert_ok!(Fungibles::increase_allowance(signed(ALICE), ASSET, BOB, amount)); - assert_eq!(Assets::allowance(ASSET, &ALICE, &BOB), amount); + let value: Balance = 100 * UNIT; + let id = ASSET; + let owner = ALICE; + let spender = BOB; + + create_asset_and_mint_to(ALICE, id, ALICE, value); + assert_eq!(0, Assets::allowance(id, &ALICE, &BOB)); + assert_ok!(Fungibles::increase_allowance(signed(ALICE), id, BOB, value)); + assert_eq!(Assets::allowance(id, &ALICE, &BOB), value); + System::assert_last_event(Event::Approval { id, owner, spender, value }.into()); // Additive. - assert_ok!(Fungibles::increase_allowance(signed(ALICE), ASSET, BOB, amount)); - assert_eq!(Assets::allowance(ASSET, &ALICE, &BOB), amount * 2); + assert_ok!(Fungibles::increase_allowance(signed(ALICE), id, BOB, value)); + assert_eq!(Assets::allowance(id, &ALICE, &BOB), value * 2); + System::assert_last_event(Event::Approval { id, owner, spender, value: value * 2 }.into()); }); } #[test] fn decrease_allowance_works() { new_test_ext().execute_with(|| { - let amount: Balance = 100 * UNIT; - create_asset_mint_and_approve(ALICE, ASSET, ALICE, amount, BOB, amount); - assert_eq!(Assets::allowance(ASSET, &ALICE, &BOB), amount); + let value: Balance = 100 * UNIT; + let id = ASSET; + let owner = ALICE; + let spender = BOB; + + create_asset_mint_and_approve(ALICE, id, ALICE, value, BOB, value); + assert_eq!(Assets::allowance(id, &ALICE, &BOB), value); // Owner balance is not changed if decreased by zero. - assert_ok!(Fungibles::decrease_allowance(signed(ALICE), ASSET, BOB, 0)); - assert_eq!(Assets::allowance(ASSET, &ALICE, &BOB), amount); + assert_ok!(Fungibles::decrease_allowance(signed(ALICE), id, BOB, 0)); + assert_eq!(Assets::allowance(id, &ALICE, &BOB), value); // Decrease allowance successfully. - assert_ok!(Fungibles::decrease_allowance(signed(ALICE), ASSET, BOB, amount / 2 - 1 * UNIT)); - assert_eq!(Assets::allowance(ASSET, &ALICE, &BOB), amount / 2 + 1 * UNIT); + assert_ok!(Fungibles::decrease_allowance(signed(ALICE), id, BOB, value / 2)); + assert_eq!(Assets::allowance(id, &ALICE, &BOB), value / 2); + System::assert_last_event(Event::Approval { id, owner, spender, value: value / 2 }.into()); // Saturating if current allowance is decreased more than the owner balance. - assert_ok!(Fungibles::decrease_allowance(signed(ALICE), ASSET, BOB, amount)); - assert_eq!(Assets::allowance(ASSET, &ALICE, &BOB), 0); + assert_ok!(Fungibles::decrease_allowance(signed(ALICE), id, BOB, value)); + assert_eq!(Assets::allowance(id, &ALICE, &BOB), 0); + System::assert_last_event(Event::Approval { id, owner, spender, value: 0 }.into()); }); } #[test] fn create_works() { new_test_ext().execute_with(|| { - assert!(!Assets::asset_exists(ASSET)); - assert_ok!(Fungibles::create(signed(ALICE), ASSET, ALICE, 100)); - assert!(Assets::asset_exists(ASSET)); + let id = ASSET; + let creator = ALICE; + let admin = ALICE; + + assert!(!Assets::asset_exists(id)); + assert_ok!(Fungibles::create(signed(creator), id, admin, 100)); + assert!(Assets::asset_exists(id)); + System::assert_last_event(Event::Create { id, creator, admin }.into()); }); } #[test] fn start_destroy_works() { new_test_ext().execute_with(|| { - create_asset(ALICE, ASSET); - assert_ok!(Fungibles::start_destroy(signed(ALICE), ASSET)); + let id = ASSET; + + create_asset(ALICE, id); + assert_ok!(Fungibles::start_destroy(signed(ALICE), id)); }); } #[test] fn set_metadata_works() { new_test_ext().execute_with(|| { + let id = ASSET; let name = vec![42]; let symbol = vec![42]; let decimals = 42; - create_asset(ALICE, ASSET); + + create_asset(ALICE, id); assert_ok!(Fungibles::set_metadata( signed(ALICE), - ASSET, + id, name.clone(), symbol.clone(), decimals )); - assert_eq!(Assets::name(ASSET), name); - assert_eq!(Assets::symbol(ASSET), symbol); - assert_eq!(Assets::decimals(ASSET), decimals); + assert_eq!(Assets::name(id), name); + assert_eq!(Assets::symbol(id), symbol); + assert_eq!(Assets::decimals(id), decimals); }); } #[test] fn clear_metadata_works() { new_test_ext().execute_with(|| { - let name = vec![42]; - let symbol = vec![42]; - let decimals = 42; - create_asset_and_set_metadata(ALICE, ASSET, name, symbol, decimals); - assert_ok!(Fungibles::clear_metadata(signed(ALICE), ASSET)); - assert_eq!(Assets::name(ASSET), Vec::::new()); - assert_eq!(Assets::symbol(ASSET), Vec::::new()); - assert_eq!(Assets::decimals(ASSET), 0u8); + let id = ASSET; + + create_asset_and_set_metadata(ALICE, id, vec![42], vec![42], 42); + assert_ok!(Fungibles::clear_metadata(signed(ALICE), id)); + assert!(Assets::name(id).is_empty()); + assert!(Assets::symbol(id).is_empty()); + assert!(Assets::decimals(id).is_zero()); }); } #[test] fn mint_works() { 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); + let value: Balance = 100 * UNIT; + let id = ASSET; + let from = None; + let to = Some(BOB); + + create_asset(ALICE, id); + let balance_before_mint = Assets::balance(id, &BOB); + assert_ok!(Fungibles::mint(signed(ALICE), id, BOB, value)); + let balance_after_mint = Assets::balance(id, &BOB); + assert_eq!(balance_after_mint, balance_before_mint + value); + System::assert_last_event(Event::Transfer { id, from, to, value }.into()); }); } #[test] fn burn_works() { 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); + let value: Balance = 100 * UNIT; + let id = ASSET; + let from = Some(BOB); + let to = None; + + create_asset_and_mint_to(ALICE, id, BOB, value); + let balance_before_burn = Assets::balance(id, &BOB); + assert_ok!(Fungibles::burn(signed(ALICE), id, BOB, value)); + let balance_after_burn = Assets::balance(id, &BOB); + assert_eq!(balance_after_burn, balance_before_burn - value); + System::assert_last_event(Event::Transfer { id, from, to, value }.into()); }); } diff --git a/pallets/api/src/mock.rs b/pallets/api/src/mock.rs index b20e2635..77e17394 100644 --- a/pallets/api/src/mock.rs +++ b/pallets/api/src/mock.rs @@ -97,6 +97,7 @@ impl pallet_assets::Config for Test { type BenchmarkHelper = (); } impl crate::fungibles::Config for Test { + type RuntimeEvent = RuntimeEvent; type AssetsInstance = AssetsInstance; type WeightInfo = (); } diff --git a/pop-api/src/v0/assets/fungibles.rs b/pop-api/src/v0/assets/fungibles.rs index 8600c439..323db2b6 100644 --- a/pop-api/src/v0/assets/fungibles.rs +++ b/pop-api/src/v0/assets/fungibles.rs @@ -58,6 +58,91 @@ mod constants { pub(super) const BURN: u8 = 20; } +/// A set of events for use in smart contracts interacting with the fungibles API. +/// +/// The `Transfer` and `Approval` events conform to the PSP-22 standard. The other events +/// (`Create`, `StartDestroy`, `SetMetadata`, `ClearMetadata`) are provided for convenience. +/// +/// These events are not emitted by the API itself but can be used in your contracts to +/// track asset operations. Be mindful of the costs associated with emitting events. +/// +/// For more details, refer to [ink! events](https://use.ink/basics/events). +pub mod events { + use super::*; + + /// Event emitted when allowance by `owner` to `spender` changes. + #[ink::event] + pub struct Approval { + /// Account providing allowance. + #[ink(topic)] + pub owner: AccountId, + /// Allowance beneficiary. + #[ink(topic)] + pub spender: AccountId, + /// New allowance amount. + pub value: u128, + } + + /// Event emitted when transfer of tokens occurs. + #[ink::event] + pub struct Transfer { + /// Transfer sender. `None` in case of minting new tokens. + #[ink(topic)] + pub from: Option, + /// Transfer recipient. `None` in case of burning tokens. + #[ink(topic)] + pub to: Option, + /// Amount of tokens transferred (or minted/burned). + pub value: u128, + } + + /// Event emitted when a token class is created. + #[ink::event] + pub struct Create { + /// The ID of the asset. + #[ink(topic)] + pub id: AssetId, + /// Creator of the asset. + #[ink(topic)] + pub creator: AccountId, + /// Admin of the asset. + #[ink(topic)] + pub admin: AccountId, + } + + /// Event emitted when a asset is in the process of being destroyed. + #[ink::event] + pub struct StartDestroy { + /// The ID of the asset. + #[ink(topic)] + pub id: AssetId, + } + + /// Event emitted when new metadata is set for an asset. + #[ink::event] + pub struct SetMetadata { + /// The ID of the asset created. + #[ink(topic)] + pub id: AssetId, + /// The name of the asset. + #[ink(topic)] + pub name: Vec, + /// The symbol of the asset. + #[ink(topic)] + pub symbol: Vec, + /// The decimals of the asset. + pub decimals: u8, + } + + /// Event emitted when metadata is cleared for a token. + #[ink::event] + pub struct ClearMetadata { + /// The ID of the asset. + #[ink(topic)] + pub id: AssetId, + } +} + /// Returns the total token supply for a given asset ID. /// /// # Parameters diff --git a/runtime/devnet/src/config/api.rs b/runtime/devnet/src/config/api.rs index 5f234cd4..4a404c99 100644 --- a/runtime/devnet/src/config/api.rs +++ b/runtime/devnet/src/config/api.rs @@ -1,4 +1,6 @@ -use crate::{config::assets::TrustBackedAssetsInstance, fungibles, Runtime, RuntimeCall}; +use crate::{ + config::assets::TrustBackedAssetsInstance, fungibles, Runtime, RuntimeCall, RuntimeEvent, +}; use codec::{Decode, Encode, MaxEncodedLen}; use pop_chain_extension::{CallFilter, ReadState}; use sp_std::vec::Vec; @@ -60,6 +62,7 @@ impl CallFilter for Extension { } impl fungibles::Config for Runtime { + type RuntimeEvent = RuntimeEvent; type AssetsInstance = TrustBackedAssetsInstance; type WeightInfo = fungibles::weights::SubstrateWeight; }