diff --git a/pallets/api/src/fungibles/mod.rs b/pallets/api/src/fungibles/mod.rs index 9fb444f8..eefad0e1 100644 --- a/pallets/api/src/fungibles/mod.rs +++ b/pallets/api/src/fungibles/mod.rs @@ -28,6 +28,7 @@ type BalanceOf = > as Inspect< #[frame_support::pallet] pub mod pallet { use super::*; + use core::cmp::Ordering::*; use frame_support::{ dispatch::{DispatchResult, DispatchResultWithPostInfo, WithPostDispatchInfo}, pallet_prelude::*, @@ -75,6 +76,9 @@ pub mod pallet { /// Token decimals for a given asset ID. #[codec(index = 10)] TokenDecimals(AssetIdOf), + /// Check if token with a given asset ID exists. + #[codec(index = 18)] + AssetExists(AssetIdOf), } /// Configure the pallet by specifying the parameters and types on which it depends. @@ -95,49 +99,49 @@ pub mod pallet { /// `data` in unspecified format. /// /// # Parameters - /// * `id` - The ID of the asset. - /// * `to` - The recipient account. - /// * `value` - The number of tokens to transfer. + /// - `id` - The ID of the asset. + /// - `to` - The recipient account. + /// - `value` - The number of tokens to transfer. #[pallet::call_index(3)] #[pallet::weight(AssetsWeightInfoOf::::transfer_keep_alive())] pub fn transfer( origin: OriginFor, id: AssetIdOf, - target: AccountIdOf, - amount: BalanceOf, + to: AccountIdOf, + value: BalanceOf, ) -> DispatchResult { - let target = T::Lookup::unlookup(target); - AssetsOf::::transfer_keep_alive(origin, id.into(), target, amount) + let to = T::Lookup::unlookup(to); + AssetsOf::::transfer_keep_alive(origin, id.into(), to, value) } - /// Transfers `value` amount of tokens from the delegated account approved by the `owner` to - /// account `to`, with additional `data` in unspecified format. + /// Transfers `value` amount tokens on behalf of `from` to account `to` with additional `data` + /// in unspecified format. /// /// # Parameters - /// * `id` - The ID of the asset. - /// * `owner` - The account from which the asset balance will be withdrawn. - /// * `to` - The recipient account. - /// * `value` - The number of tokens to transfer. + /// - `id` - The ID of the asset. + /// - `owner` - 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)] #[pallet::weight(AssetsWeightInfoOf::::transfer_approved())] pub fn transfer_from( origin: OriginFor, id: AssetIdOf, - owner: AccountIdOf, - target: AccountIdOf, - amount: BalanceOf, + from: AccountIdOf, + to: AccountIdOf, + value: BalanceOf, ) -> DispatchResult { - let owner = T::Lookup::unlookup(owner); - let target = T::Lookup::unlookup(target); - AssetsOf::::transfer_approved(origin, id.into(), owner, target, amount) + let from = T::Lookup::unlookup(from); + let to = T::Lookup::unlookup(to); + AssetsOf::::transfer_approved(origin, id.into(), from, to, value) } /// Approves an account to spend a specified number of tokens on behalf of the caller. /// /// # Parameters - /// * `id` - The ID of the asset. - /// * `spender` - The account that is allowed to spend the tokens. - /// * `value` - The number of tokens to approve. + /// - `id` - The ID of the asset. + /// - `spender` - The account that is allowed to spend the tokens. + /// - `value` - The number of tokens to approve. #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::approve(1, 1))] pub fn approve( @@ -152,30 +156,32 @@ pub mod pallet { let spender = T::Lookup::unlookup(spender); let id: AssetIdParameterOf = 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::::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::::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::::approve_transfer(origin, id, spender, value)?; - Self::weight_approve(1, 1) + let return_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 + // because `approve_transfer` works additively (see `pallet-assets`). + Greater => { + AssetsOf::::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) + }, + // 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)))?; + if value.is_zero() { + return Ok(Some(Self::weight_approve(0, 1)).into()); + } + AssetsOf::::approve_transfer(origin, id, spender, value)?; + Self::weight_approve(1, 1) + }, }; Ok(Some(return_weight).into()) } @@ -183,9 +189,9 @@ pub mod pallet { /// Increases the allowance of a spender. /// /// # Parameters - /// * `id` - The ID of the asset. - /// * `spender` - The account that is allowed to spend the tokens. - /// * `value` - The number of tokens to increase the allowance by. + /// - `id` - The ID of the asset. + /// - `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())] pub fn increase_allowance( @@ -201,9 +207,9 @@ pub mod pallet { /// Decreases the allowance of a spender. /// /// # Parameters - /// * `id` - The ID of the asset. - /// * `spender` - The account that is allowed to spend the tokens. - /// * `value` - The number of tokens to decrease the allowance by. + /// - `id` - The ID of the asset. + /// - `spender` - The account that is allowed to spend the tokens. + /// - `value` - The number of tokens to decrease the allowance by. #[pallet::call_index(7)] #[pallet::weight(::WeightInfo::approve(1, 1))] pub fn decrease_allowance( @@ -214,25 +220,118 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin.clone()) .map_err(|e| e.with_weight(Self::weight_approve(0, 0)))?; - let mut current_allowance = AssetsOf::::allowance(id.clone(), &who, &spender); + 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()); } - - current_allowance.saturating_reduce(value); - // Cancel the aproval and set the new value if `current_allowance` is more than zero. + // 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)))?; - - if current_allowance.is_zero() { + 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, current_allowance)?; + AssetsOf::::approve_transfer(origin, id, spender, new_allowance)?; Ok(().into()) } + + /// Create a new token with a given asset ID. + /// + /// # Parameters + /// - `id` - The ID of the asset. + /// - `admin` - The account that will administer the asset. + /// - `min_balance` - The minimum balance required for accounts holding this asset. + #[pallet::call_index(11)] + #[pallet::weight(AssetsWeightInfoOf::::create())] + pub fn create( + origin: OriginFor, + id: AssetIdOf, + admin: AccountIdOf, + min_balance: BalanceOf, + ) -> DispatchResult { + let admin = T::Lookup::unlookup(admin); + AssetsOf::::create(origin, id.into(), admin, min_balance) + } + + /// Start the process of destroying a token with a given asset ID. + /// + /// # Parameters + /// - `id` - The ID of the asset. + #[pallet::call_index(12)] + #[pallet::weight(AssetsWeightInfoOf::::start_destroy())] + pub fn start_destroy(origin: OriginFor, id: AssetIdOf) -> DispatchResult { + AssetsOf::::start_destroy(origin, id.into()) + } + + /// Set the metadata for a token with a given asset ID. + /// + /// # Parameters + /// - `id`: The identifier of the asset to update. + /// - `name`: The user friendly name of this asset. Limited in length by + /// `pallet_assets::Config::StringLimit`. + /// - `symbol`: The exchange symbol for this asset. Limited in length by + /// `pallet_assets::Config::StringLimit`. + /// - `decimals`: The number of decimals this asset uses to represent one unit. + #[pallet::call_index(16)] + #[pallet::weight(AssetsWeightInfoOf::::set_metadata(name.len() as u32, symbol.len() as u32))] + pub fn set_metadata( + origin: OriginFor, + id: AssetIdOf, + name: Vec, + symbol: Vec, + decimals: u8, + ) -> DispatchResult { + AssetsOf::::set_metadata(origin, id.into(), name, symbol, decimals) + } + + /// Clear the metadata for a token with a given asset ID. + /// + /// # Parameters + /// - `id` - The ID of the asset. + #[pallet::call_index(17)] + #[pallet::weight(AssetsWeightInfoOf::::clear_metadata())] + pub fn clear_metadata(origin: OriginFor, id: AssetIdOf) -> DispatchResult { + AssetsOf::::clear_metadata(origin, id.into()) + } + + /// Creates `value` amount tokens and assigns them to `account`, increasing the total supply. + /// + /// # Parameters + /// - `id` - The ID of the asset. + /// - `account` - The account to be credited with the created tokens. + /// - `value` - The number of tokens to mint. + #[pallet::call_index(19)] + #[pallet::weight(AssetsWeightInfoOf::::mint())] + pub fn mint( + origin: OriginFor, + id: AssetIdOf, + account: AccountIdOf, + value: BalanceOf, + ) -> DispatchResult { + let account = T::Lookup::unlookup(account); + AssetsOf::::mint(origin, id.into(), account, value) + } + + /// Destroys `value` amount tokens from `account`, reducing the total supply. + /// + /// # Parameters + /// - `id` - The ID of the asset. + /// - `account` - The account from which the tokens will be destroyed. + /// - `value` - The number of tokens to destroy. + #[pallet::call_index(20)] + #[pallet::weight(AssetsWeightInfoOf::::burn())] + pub fn burn( + origin: OriginFor, + id: AssetIdOf, + account: AccountIdOf, + value: BalanceOf, + ) -> DispatchResult { + let account = T::Lookup::unlookup(account); + AssetsOf::::burn(origin, id.into(), account, value) + } } impl Pallet { @@ -242,8 +341,8 @@ pub mod pallet { /// encoded result. /// /// # Parameter - /// * `value` - An instance of `Read`, which specifies the type of state query and - /// the associated parameters. + /// - `value` - An instance of `Read`, which specifies the type of state query and + /// the associated parameters. pub fn read_state(value: Read) -> Vec { use Read::*; @@ -262,6 +361,7 @@ pub mod pallet { TokenDecimals(id) => { as MetadataInspect>>::decimals(id).encode() }, + AssetExists(id) => AssetsOf::::asset_exists(id).encode(), } } diff --git a/pallets/api/src/fungibles/tests.rs b/pallets/api/src/fungibles/tests.rs index 4377fc83..d6cc87e0 100644 --- a/pallets/api/src/fungibles/tests.rs +++ b/pallets/api/src/fungibles/tests.rs @@ -2,7 +2,9 @@ use crate::{fungibles::Read::*, mock::*}; use codec::Encode; use frame_support::{ assert_ok, - traits::fungibles::{approvals::Inspect, metadata::Inspect as MetadataInspect}, + traits::fungibles::{ + approvals::Inspect as ApprovalInspect, metadata::Inspect as MetadataInspect, Inspect, + }, }; const ASSET: u32 = 42; @@ -38,24 +40,6 @@ fn transfer_from_works() { }); } -#[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); - // 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); - // 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); - // 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); - }); -} - // Non-additive, sets new value. #[test] fn approve_works() { @@ -94,6 +78,99 @@ fn increase_allowance_works() { }); } +#[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); + // 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); + // 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); + // 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); + }); +} + +#[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)); + }); +} + +#[test] +fn start_destroy_works() { + new_test_ext().execute_with(|| { + create_asset(ALICE, ASSET); + assert_ok!(Fungibles::start_destroy(signed(ALICE), ASSET)); + }); +} + +#[test] +fn set_metadata_works() { + new_test_ext().execute_with(|| { + let name = vec![42]; + let symbol = vec![42]; + let decimals = 42; + create_asset(ALICE, ASSET); + assert_ok!(Fungibles::set_metadata( + signed(ALICE), + ASSET, + name.clone(), + symbol.clone(), + decimals + )); + assert_eq!(Assets::name(ASSET), name); + assert_eq!(Assets::symbol(ASSET), symbol); + assert_eq!(Assets::decimals(ASSET), 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); + }); +} + +#[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); + }); +} + +#[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); + }); +} + #[test] fn total_supply_works() { new_test_ext().execute_with(|| { @@ -137,12 +214,20 @@ fn token_metadata_works() { }); } +#[test] +fn asset_exists_works() { + new_test_ext().execute_with(|| { + create_asset(ALICE, ASSET); + assert_eq!(Assets::asset_exists(ASSET).encode(), Fungibles::read_state(AssetExists(ASSET))); + }); +} + 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) { @@ -150,7 +235,7 @@ fn mint_asset(owner: AccountId, asset_id: AssetId, to: AccountId, value: Balance } 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) } diff --git a/pop-api/examples/balance-transfer/lib.rs b/pop-api/examples/balance-transfer/lib.rs index 328a818b..d36dfa53 100755 --- a/pop-api/examples/balance-transfer/lib.rs +++ b/pop-api/examples/balance-transfer/lib.rs @@ -1,3 +1,4 @@ +// DEPRECATED #![cfg_attr(not(feature = "std"), no_std, no_main)] use pop_api::balances::*; @@ -131,4 +132,4 @@ mod balance_transfer { Ok(()) } } -} \ No newline at end of file +} diff --git a/pop-api/examples/nfts/lib.rs b/pop-api/examples/nfts/lib.rs index d47140e7..7920c179 100755 --- a/pop-api/examples/nfts/lib.rs +++ b/pop-api/examples/nfts/lib.rs @@ -1,3 +1,4 @@ +// DEPRECATED #![cfg_attr(not(feature = "std"), no_std, no_main)] use pop_api::nfts::*; @@ -33,27 +34,29 @@ mod nfts { } #[ink(message)] - pub fn create_nft_collection( &self ) -> Result<(), ContractError>{ + pub fn create_nft_collection(&self) -> Result<(), ContractError> { ink::env::debug_println!("Nfts::create_nft_collection: collection creation started."); - let admin = Self::env().caller(); - let item_settings = ItemSettings(BitFlags::from(ItemSetting::Transferable)); - - let mint_settings = MintSettings { - mint_type: MintType::Issuer, - price: Some(0), - start_block: Some(0), - end_block: Some(0), - default_item_settings: item_settings, - }; - - let config = CollectionConfig { - settings: CollectionSettings(BitFlags::from(CollectionSetting::TransferableItems)), - max_supply: None, - mint_settings, - }; - pop_api::nfts::create(admin, config)?; - ink::env::debug_println!("Nfts::create_nft_collection: collection created successfully."); - Ok(()) + let admin = Self::env().caller(); + let item_settings = ItemSettings(BitFlags::from(ItemSetting::Transferable)); + + let mint_settings = MintSettings { + mint_type: MintType::Issuer, + price: Some(0), + start_block: Some(0), + end_block: Some(0), + default_item_settings: item_settings, + }; + + let config = CollectionConfig { + settings: CollectionSettings(BitFlags::from(CollectionSetting::TransferableItems)), + max_supply: None, + mint_settings, + }; + pop_api::nfts::create(admin, config)?; + ink::env::debug_println!( + "Nfts::create_nft_collection: collection created successfully." + ); + Ok(()) } #[ink(message)] @@ -82,9 +85,7 @@ mod nfts { // check owner match owner(collection_id, item_id)? { Some(owner) if owner == receiver => { - ink::env::debug_println!( - "Nfts::mint success: minted item belongs to receiver" - ); + ink::env::debug_println!("Nfts::mint success: minted item belongs to receiver"); }, _ => { return Err(ContractError::NotOwner); @@ -113,4 +114,4 @@ mod nfts { Nfts::new(); } } -} \ No newline at end of file +} diff --git a/pop-api/examples/place-spot-order/lib.rs b/pop-api/examples/place-spot-order/lib.rs index f5e34f7f..669b9190 100755 --- a/pop-api/examples/place-spot-order/lib.rs +++ b/pop-api/examples/place-spot-order/lib.rs @@ -1,3 +1,4 @@ +// DEPRECATED #![cfg_attr(not(feature = "std"), no_std, no_main)] #[ink::contract(env = pop_api::Environment)] @@ -15,11 +16,7 @@ mod spot_order { } #[ink(message)] - pub fn place_spot_order( - &mut self, - max_amount: Balance, - para_id: u32, - ) { + pub fn place_spot_order(&mut self, max_amount: Balance, para_id: u32) { ink::env::debug_println!( "SpotOrder::place_spot_order: max_amount {:?} para_id: {:?} ", max_amount, @@ -28,10 +25,7 @@ mod spot_order { #[allow(unused_variables)] let res = pop_api::cross_chain::coretime::place_spot_order(max_amount, para_id); - ink::env::debug_println!( - "SpotOrder::place_spot_order: res {:?} ", - res, - ); + ink::env::debug_println!("SpotOrder::place_spot_order: res {:?} ", res,); ink::env::debug_println!("SpotOrder::place_spot_order end"); } @@ -46,4 +40,4 @@ mod spot_order { SpotOrder::new(); } } -} \ No newline at end of file +} diff --git a/pop-api/examples/read-runtime-state/lib.rs b/pop-api/examples/read-runtime-state/lib.rs index 05a44108..60ef70f0 100755 --- a/pop-api/examples/read-runtime-state/lib.rs +++ b/pop-api/examples/read-runtime-state/lib.rs @@ -1,35 +1,36 @@ +// DEPRECATED #![cfg_attr(not(feature = "std"), no_std, no_main)] #[ink::contract(env = pop_api::Environment)] mod read_relay_blocknumber { - use pop_api::primitives::storage_keys::{ - ParachainSystemKeys::LastRelayChainBlockNumber, RuntimeStateKeys::ParachainSystem, - }; + use pop_api::primitives::storage_keys::{ + ParachainSystemKeys::LastRelayChainBlockNumber, RuntimeStateKeys::ParachainSystem, + }; - #[ink(event)] - pub struct RelayBlockNumberRead { - value: BlockNumber, - } + #[ink(event)] + pub struct RelayBlockNumberRead { + value: BlockNumber, + } - #[ink(storage)] - #[derive(Default)] - pub struct ReadRelayBlockNumber; + #[ink(storage)] + #[derive(Default)] + pub struct ReadRelayBlockNumber; - impl ReadRelayBlockNumber { - #[ink(constructor, payable)] - pub fn new() -> Self { - ink::env::debug_println!("ReadRelayBlockNumber::new"); - Default::default() - } + impl ReadRelayBlockNumber { + #[ink(constructor, payable)] + pub fn new() -> Self { + ink::env::debug_println!("ReadRelayBlockNumber::new"); + Default::default() + } - #[ink(message)] - pub fn read_relay_block_number(&self) { - let result = - pop_api::state::read::(ParachainSystem(LastRelayChainBlockNumber)); - ink::env::debug_println!("Last relay block number read by contract: {:?}", result); - self.env().emit_event(RelayBlockNumberRead { - value: result.expect("Failed to read relay block number."), - }); - } - } -} \ No newline at end of file + #[ink(message)] + pub fn read_relay_block_number(&self) { + let result = + pop_api::state::read::(ParachainSystem(LastRelayChainBlockNumber)); + ink::env::debug_println!("Last relay block number read by contract: {:?}", result); + self.env().emit_event(RelayBlockNumberRead { + value: result.expect("Failed to read relay block number."), + }); + } + } +} diff --git a/pop-api/integration-tests/contracts/.gitignore b/pop-api/integration-tests/contracts/.gitignore new file mode 100755 index 00000000..d60800c8 --- /dev/null +++ b/pop-api/integration-tests/contracts/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +**/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +**/Cargo.lock diff --git a/pop-api/integration-tests/contracts/create_token_in_constructor/Cargo.toml b/pop-api/integration-tests/contracts/create_token_in_constructor/Cargo.toml new file mode 100755 index 00000000..2c202715 --- /dev/null +++ b/pop-api/integration-tests/contracts/create_token_in_constructor/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "create_token_in_constructor" +version = "0.1.0" +authors = ["[your_name] <[your_email]>"] +edition = "2021" + +[dependencies] +ink = { version = "5.0.0", default-features = false } +pop-api = { path = "../../..", default-features = false, features = ["fungibles"] } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + "pop-api/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/pop-api/integration-tests/contracts/create_token_in_constructor/lib.rs b/pop-api/integration-tests/contracts/create_token_in_constructor/lib.rs new file mode 100755 index 00000000..e9e5d127 --- /dev/null +++ b/pop-api/integration-tests/contracts/create_token_in_constructor/lib.rs @@ -0,0 +1,46 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +use ink::prelude::vec::Vec; +use pop_api::{ + assets::fungibles::{self as api}, + primitives::AssetId, + StatusCode, +}; + +pub type Result = core::result::Result; + +#[ink::contract] +mod create_token_in_constructor { + use super::*; + + #[ink(storage)] + pub struct Fungible { + id: AssetId, + } + + impl Fungible { + #[ink(constructor, payable)] + pub fn new(id: AssetId, min_balance: Balance) -> Result { + let contract = Self { id }; + // AccountId of the contract which will be set to the owner of the fungible token. + let owner = contract.env().account_id(); + api::create(id, owner, min_balance)?; + Ok(contract) + } + + #[ink(message)] + pub fn asset_exists(&self) -> Result { + api::asset_exists(self.id) + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn default_works() { + PopApiFungiblesExample::new(); + } + } +} diff --git a/pop-api/integration-tests/contracts/fungibles/lib.rs b/pop-api/integration-tests/contracts/fungibles/lib.rs index 1b42fec4..239d3a2d 100755 --- a/pop-api/integration-tests/contracts/fungibles/lib.rs +++ b/pop-api/integration-tests/contracts/fungibles/lib.rs @@ -122,55 +122,57 @@ mod fungibles { api::token_decimals(id) } - // 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<()> { - // ink::env::debug_println!( - // "PopApiFungiblesExample::create: id: {:?} admin: {:?} min_balance: {:?}", - // id, - // admin, - // min_balance, - // ); - // let result = api::create(id, admin, min_balance); - // ink::env::debug_println!("Result: {:?}", result); - // result.map_err(|e| e.into()) - // result - // } - - // #[ink(message)] - // pub fn set_metadata( - // &self, - // id: AssetId, - // name: Vec, - // symbol: Vec, - // decimals: u8, - // ) -> Result<()> { - // ink::env::debug_println!( - // "PopApiFungiblesExample::set_metadata: id: {:?} name: {:?} symbol: {:?}, decimals: {:?}", - // id, - // name, - // symbol, - // decimals, - // ); - // let result = api::set_metadata(id, name, symbol, decimals); - // ink::env::debug_println!("Result: {:?}", result); - // // result.map_err(|e| e.into()) - // result - // } - // - // #[ink(message)] - // pub fn asset_exists(&self, id: AssetId) -> Result { - // // api::asset_exists(id).map_err(|e| e.into()) - // api::asset_exists(id) - // } + /// 3. Asset Management: + /// - create + /// - start_destroy + /// - set_metadata + /// - clear_metadata + /// - asset_exists + + #[ink(message)] + pub fn create(&self, id: AssetId, admin: AccountId, min_balance: Balance) -> Result<()> { + api::create(id, admin, min_balance) + } + + #[ink(message)] + pub fn start_destroy(&self, id: AssetId) -> Result<()> { + api::start_destroy(id) + } + + #[ink(message)] + pub fn set_metadata( + &self, + id: AssetId, + name: Vec, + symbol: Vec, + decimals: u8, + ) -> Result<()> { + api::set_metadata(id, name, symbol, decimals) + } + + #[ink(message)] + pub fn clear_metadata(&self, id: AssetId) -> Result<()> { + api::clear_metadata(id) + } + + #[ink(message)] + pub fn asset_exists(&self, id: AssetId) -> Result { + 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)] diff --git a/pop-api/integration-tests/src/fungibles/mod.rs b/pop-api/integration-tests/src/fungibles/mod.rs new file mode 100644 index 00000000..0ca7d4f9 --- /dev/null +++ b/pop-api/integration-tests/src/fungibles/mod.rs @@ -0,0 +1,546 @@ +use super::*; +use pop_primitives::error::{ + ArithmeticError::*, + Error::{self, *}, + TokenError::*, +}; +use utils::*; + +mod utils; + +const ASSET_ID: AssetId = 1; +const CONTRACT: &str = "contracts/fungibles/target/ink/fungibles.wasm"; + +/// 1. PSP-22 Interface: +/// - total_supply +/// - balance_of +/// - allowance +/// - transfer +/// - transfer_from +/// - approve +/// - increase_allowance +/// - decrease_allowance + +#[test] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + // No tokens in circulation. + assert_eq!(total_supply(addr.clone(), ASSET_ID), Ok(Assets::total_supply(ASSET_ID))); + assert_eq!(total_supply(addr.clone(), ASSET_ID), Ok(0)); + + // Tokens in circulation. + create_asset_and_mint_to(addr.clone(), ASSET_ID, BOB, 100); + assert_eq!(total_supply(addr.clone(), ASSET_ID), Ok(Assets::total_supply(ASSET_ID))); + assert_eq!(total_supply(addr, ASSET_ID), Ok(100)); + }); +} + +#[test] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + // No tokens in circulation. + assert_eq!(balance_of(addr.clone(), ASSET_ID, BOB), Ok(Assets::balance(ASSET_ID, BOB))); + assert_eq!(balance_of(addr.clone(), ASSET_ID, BOB), Ok(0)); + + // Tokens in circulation. + create_asset_and_mint_to(addr.clone(), ASSET_ID, BOB, 100); + assert_eq!(balance_of(addr.clone(), ASSET_ID, BOB), Ok(Assets::balance(ASSET_ID, BOB))); + assert_eq!(balance_of(addr, ASSET_ID, BOB), Ok(100)); + }); +} + +#[test] +fn allowance_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + // No tokens in circulation. + assert_eq!( + allowance(addr.clone(), ASSET_ID, BOB, ALICE), + Ok(Assets::allowance(ASSET_ID, &BOB, &ALICE)) + ); + assert_eq!(allowance(addr.clone(), ASSET_ID, BOB, ALICE), Ok(0)); + + // Tokens in circulation. + create_asset_mint_and_approve(addr.clone(), ASSET_ID, BOB, 100, ALICE, 50); + assert_eq!( + allowance(addr.clone(), ASSET_ID, BOB, ALICE), + Ok(Assets::allowance(ASSET_ID, &BOB, &ALICE)) + ); + assert_eq!(allowance(addr, ASSET_ID, BOB, ALICE), Ok(50)); + }); +} + +#[test] +fn transfer_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!(transfer(addr.clone(), 1, BOB, amount), Err(Module { index: 52, error: 3 })); + // Create asset with Alice as owner and mint `amount` to contract address. + let asset = create_asset_and_mint_to(ALICE, 1, addr.clone(), amount); + // Asset is not live, i.e. frozen or being destroyed. + freeze_asset(ALICE, asset); + assert_eq!( + transfer(addr.clone(), asset, BOB, amount), + Err(Module { index: 52, error: 16 }) + ); + thaw_asset(ALICE, asset); + // Not enough balance. + assert_eq!( + transfer(addr.clone(), asset, BOB, amount + 1 * UNIT), + Err(Module { index: 52, error: 0 }) + ); + // Not enough balance due to ED. + assert_eq!(transfer(addr.clone(), asset, BOB, amount), Err(Module { index: 52, error: 0 })); + // Successful transfer. + let balance_before_transfer = Assets::balance(asset, &BOB); + assert_ok!(transfer(addr.clone(), asset, BOB, amount / 2)); + let balance_after_transfer = Assets::balance(asset, &BOB); + assert_eq!(balance_after_transfer, balance_before_transfer + amount / 2); + // Transfer asset to account that does not exist. + assert_eq!(transfer(addr.clone(), asset, FERDIE, amount / 4), Err(Token(CannotCreate))); + // Asset is not live, i.e. frozen or being destroyed. + start_destroy_asset(ALICE, asset); + assert_eq!( + transfer(addr.clone(), asset, BOB, amount / 4), + Err(Module { index: 52, error: 16 }) + ); + }); +} + +#[test] +fn transfer_from_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!( + transfer_from(addr.clone(), 1, ALICE, BOB, amount / 2), + Err(Module { index: 52, error: 3 }), + ); + // Create asset with Alice as owner and mint `amount` to contract address. + let asset = create_asset_and_mint_to(ALICE, 1, ALICE, amount); + // Unapproved transfer. + assert_eq!( + transfer_from(addr.clone(), asset, ALICE, BOB, amount / 2), + Err(Module { index: 52, error: 10 }) + ); + assert_ok!(Assets::approve_transfer( + RuntimeOrigin::signed(ALICE.into()), + asset.into(), + addr.clone().into(), + amount + 1 * UNIT, + )); + // Asset is not live, i.e. frozen or being destroyed. + freeze_asset(ALICE, asset); + assert_eq!( + transfer_from(addr.clone(), asset, ALICE, BOB, amount), + Err(Module { index: 52, error: 16 }), + ); + thaw_asset(ALICE, asset); + // Not enough balance. + assert_eq!( + transfer_from(addr.clone(), asset, ALICE, BOB, amount + 1 * UNIT), + Err(Module { index: 52, error: 0 }), + ); + // Successful transfer. + let balance_before_transfer = Assets::balance(asset, &BOB); + assert_ok!(transfer_from(addr.clone(), asset, ALICE, BOB, amount / 2)); + let balance_after_transfer = Assets::balance(asset, &BOB); + assert_eq!(balance_after_transfer, balance_before_transfer + amount / 2); + }); +} + +#[test] +fn approve_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let addr = instantiate(CONTRACT, 0, vec![]); + let amount: Balance = 100 * UNIT; + + // Asset does not exist. + assert_eq!(approve(addr.clone(), 0, BOB, amount), Err(Module { index: 52, error: 3 })); + let asset = create_asset_and_mint_to(ALICE, 0, addr.clone(), amount); + assert_eq!(approve(addr.clone(), asset, BOB, amount), Err(ConsumerRemaining)); + let addr = instantiate(CONTRACT, INIT_VALUE, vec![1]); + // Create asset with Alice as owner and mint `amount` to contract address. + let asset = create_asset_and_mint_to(ALICE, 1, addr.clone(), amount); + // Asset is not live, i.e. frozen or being destroyed. + freeze_asset(ALICE, asset); + assert_eq!(approve(addr.clone(), asset, BOB, amount), Err(Module { index: 52, error: 16 })); + thaw_asset(ALICE, asset); + // Successful approvals: + assert_eq!(0, Assets::allowance(asset, &addr, &BOB)); + assert_ok!(approve(addr.clone(), asset, BOB, amount)); + assert_eq!(Assets::allowance(asset, &addr, &BOB), amount); + // Non-additive, sets new value. + assert_ok!(approve(addr.clone(), asset, BOB, amount / 2)); + assert_eq!(Assets::allowance(asset, &addr, &BOB), amount / 2); + // Asset is not live, i.e. frozen or being destroyed. + start_destroy_asset(ALICE, asset); + assert_eq!(approve(addr.clone(), asset, BOB, amount), Err(Module { index: 52, error: 16 })); + }); +} + +#[test] +fn increase_allowance_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let amount: Balance = 100 * UNIT; + // Instantiate a contract without balance - test `ConsumerRemaining. + let addr = instantiate(CONTRACT, 0, vec![]); + // Asset does not exist. + assert_eq!( + increase_allowance(addr.clone(), 0, BOB, amount), + Err(Module { index: 52, error: 3 }) + ); + let asset = create_asset_and_mint_to(ALICE, 0, addr.clone(), amount); + assert_eq!(increase_allowance(addr.clone(), asset, BOB, amount), Err(ConsumerRemaining)); + + // Instantiate a contract with balance. + let addr = instantiate(CONTRACT, INIT_VALUE, vec![1]); + // Create asset with Alice as owner and mint `amount` to contract address. + let asset = create_asset_and_mint_to(ALICE, 1, addr.clone(), amount); + // Asset is not live, i.e. frozen or being destroyed. + freeze_asset(ALICE, asset); + assert_eq!( + increase_allowance(addr.clone(), asset, BOB, amount), + Err(Module { index: 52, error: 16 }) + ); + thaw_asset(ALICE, asset); + // Successful approvals: + assert_eq!(0, Assets::allowance(asset, &addr, &BOB)); + assert_ok!(increase_allowance(addr.clone(), asset, BOB, amount)); + assert_eq!(Assets::allowance(asset, &addr, &BOB), amount); + // Additive. + assert_ok!(increase_allowance(addr.clone(), asset, BOB, amount)); + assert_eq!(Assets::allowance(asset, &addr, &BOB), amount * 2); + // Asset is not live, i.e. frozen or being destroyed. + start_destroy_asset(ALICE, asset); + assert_eq!( + increase_allowance(addr.clone(), asset, BOB, amount), + Err(Module { index: 52, error: 16 }) + ); + }); +} + +#[test] +fn decrease_allowance_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!( + decrease_allowance(addr.clone(), 0, BOB, amount), + Err(Module { index: 52, error: 3 }), + ); + // Create asset and mint `amount` to contract address, then approve Bob to spend `amount`. + let asset = + create_asset_mint_and_approve(addr.clone(), 0, addr.clone(), amount, BOB, amount); + // Asset is not live, i.e. frozen or being destroyed. + freeze_asset(addr.clone(), asset); + assert_eq!( + decrease_allowance(addr.clone(), asset, BOB, amount), + Err(Module { index: 52, error: 16 }), + ); + thaw_asset(addr.clone(), asset); + // Successfully decrease allowance. + let allowance_before = Assets::allowance(asset, &addr, &BOB); + assert_ok!(decrease_allowance(addr.clone(), 0, BOB, amount / 2 - 1 * UNIT)); + let allowance_after = Assets::allowance(asset, &addr, &BOB); + assert_eq!(allowance_before - allowance_after, amount / 2 - 1 * UNIT); + // Asset is not live, i.e. frozen or being destroyed. + start_destroy_asset(addr.clone(), asset); + assert_eq!( + decrease_allowance(addr.clone(), asset, BOB, amount), + Err(Module { index: 52, error: 16 }), + ); + }); +} + +/// 2. PSP-22 Metadata Interface: +/// - token_name +/// - token_symbol +/// - token_decimals + +#[test] +fn token_metadata_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + let name: Vec = vec![11, 12, 13]; + let symbol: Vec = vec![21, 22, 23]; + let decimals: u8 = 69; + + // Token does not exist. + assert_eq!(token_name(addr.clone(), ASSET_ID), Ok(token_name_asset(ASSET_ID))); + assert_eq!(token_name(addr.clone(), ASSET_ID), Ok(Vec::::new())); + assert_eq!(token_symbol(addr.clone(), ASSET_ID), Ok(token_symbol_asset(ASSET_ID))); + assert_eq!(token_symbol(addr.clone(), ASSET_ID), Ok(Vec::::new())); + assert_eq!(token_decimals(addr.clone(), ASSET_ID), Ok(token_decimals_asset(ASSET_ID))); + assert_eq!(token_decimals(addr.clone(), ASSET_ID), Ok(0)); + // Create Token. + create_asset_and_set_metadata( + addr.clone(), + ASSET_ID, + name.clone(), + symbol.clone(), + decimals, + ); + assert_eq!(token_name(addr.clone(), ASSET_ID), Ok(token_name_asset(ASSET_ID))); + assert_eq!(token_name(addr.clone(), ASSET_ID), Ok(name)); + assert_eq!(token_symbol(addr.clone(), ASSET_ID), Ok(token_symbol_asset(ASSET_ID))); + assert_eq!(token_symbol(addr.clone(), ASSET_ID), Ok(symbol)); + assert_eq!(token_decimals(addr.clone(), ASSET_ID), Ok(token_decimals_asset(ASSET_ID))); + assert_eq!(token_decimals(addr.clone(), ASSET_ID), Ok(decimals)); + }); +} + +/// 3. Asset Management: +/// - create +/// - start_destroy +/// - set_metadata +/// - clear_metadata +/// - asset_exists + +#[test] +fn create_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + // Instantiate a contract without balance for fees. + let addr = instantiate(CONTRACT, 0, vec![0]); + // No balance to pay for fees. + assert_eq!( + create(addr.clone(), ASSET_ID, addr.clone(), 1), + Err(Module { index: 10, error: 2 }), + ); + + // Instantiate a contract without balance for deposit. + let addr = instantiate(CONTRACT, 100, vec![1]); + // No balance to pay the deposit. + assert_eq!( + create(addr.clone(), ASSET_ID, addr.clone(), 1), + Err(Module { index: 10, error: 2 }), + ); + + // Instantiate a contract with enough balance. + let addr = instantiate(CONTRACT, INIT_VALUE, vec![2]); + assert_eq!(create(addr.clone(), ASSET_ID, BOB, 0), Err(Module { index: 52, error: 7 }),); + // The minimal balance for an asset must be non zero. + assert_eq!(create(addr.clone(), ASSET_ID, BOB, 0), Err(Module { index: 52, error: 7 }),); + // Create asset successfully. + assert_ok!(create(addr.clone(), ASSET_ID, BOB, 1)); + // Asset ID is already taken. + assert_eq!(create(addr.clone(), ASSET_ID, BOB, 1), Err(Module { index: 52, error: 5 }),); + }); +} + +// Testing a contract that creates an asset in the constructor. +#[test] +fn instantiate_and_create_fungible_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let contract = + "contracts/create_token_in_constructor/target/ink/create_token_in_constructor.wasm"; + // Asset already exists. + create_asset(ALICE, 0, 1); + assert_eq!( + instantiate_and_create_fungible(contract, 0, 1), + Err(Module { index: 52, error: 5 }) + ); + // Successfully create an asset when instantiating the contract. + assert_ok!(instantiate_and_create_fungible(contract, ASSET_ID, 1)); + assert!(Assets::asset_exists(ASSET_ID)); + }); +} + +#[test] +fn start_destroy_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let addr = instantiate(CONTRACT, INIT_VALUE, vec![2]); + + // Asset does not exist. + assert_eq!(start_destroy(addr.clone(), ASSET_ID), Err(Module { index: 52, error: 3 }),); + // Create assets where contract is not the owner. + let asset = create_asset(ALICE, 0, 1); + // No Permission. + assert_eq!(start_destroy(addr.clone(), asset), Err(Module { index: 52, error: 2 }),); + let asset = create_asset(addr.clone(), ASSET_ID, 1); + assert_ok!(start_destroy(addr.clone(), asset)); + }); +} + +#[test] +fn set_metadata_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let name = vec![42]; + let symbol = vec![42]; + let decimals = 42u8; + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + // Asset does not exist. + assert_eq!( + set_metadata(addr.clone(), ASSET_ID, vec![0], vec![0], 0u8), + Err(Module { index: 52, error: 3 }), + ); + // Create assets where contract is not the owner. + let asset = create_asset(ALICE, 0, 1); + // No Permission. + assert_eq!( + set_metadata(addr.clone(), asset, vec![0], vec![0], 0u8), + Err(Module { index: 52, error: 2 }), + ); + let asset = create_asset(addr.clone(), ASSET_ID, 1); + // Asset is not live, i.e. frozen or being destroyed. + freeze_asset(addr.clone(), asset); + assert_eq!( + set_metadata(addr.clone(), ASSET_ID, vec![0], vec![0], 0u8), + Err(Module { index: 52, error: 16 }), + ); + thaw_asset(addr.clone(), asset); + // TODO: calling the below with a vector of length `100_000` errors in pallet contracts + // `OutputBufferTooSmall. Added to security analysis issue #131 to revisit. + // Set bad metadata - too large values. + assert_eq!( + set_metadata(addr.clone(), ASSET_ID, vec![0; 1000], vec![0; 1000], 0u8), + Err(Module { index: 52, error: 9 }), + ); + // Set metadata successfully. + assert_ok!(set_metadata(addr.clone(), ASSET_ID, name, symbol, decimals)); + // Asset is not live, i.e. frozen or being destroyed. + start_destroy_asset(addr.clone(), asset); + assert_eq!( + set_metadata(addr.clone(), ASSET_ID, vec![0], vec![0], 0), + Err(Module { index: 52, error: 16 }), + ); + }); +} + +#[test] +fn clear_metadata_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let name = vec![42]; + let symbol = vec![42]; + let decimals = 42u8; + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + // Asset does not exist. + assert_eq!(clear_metadata(addr.clone(), 0), Err(Module { index: 52, error: 3 }),); + // Create assets where contract is not the owner. + let asset = create_asset_and_set_metadata(ALICE, 0, vec![0], vec![0], 0); + // No Permission. + assert_eq!(clear_metadata(addr.clone(), asset), Err(Module { index: 52, error: 2 }),); + let asset = create_asset(addr.clone(), ASSET_ID, 1); + // Asset is not live, i.e. frozen or being destroyed. + freeze_asset(addr.clone(), asset); + assert_eq!(clear_metadata(addr.clone(), asset), Err(Module { index: 52, error: 16 }),); + thaw_asset(addr.clone(), asset); + // No metadata set. + assert_eq!(clear_metadata(addr.clone(), asset), Err(Module { index: 52, error: 3 }),); + set_metadata_asset(addr.clone(), asset, name, symbol, decimals); + // Clear metadata successfully. + assert_ok!(clear_metadata(addr.clone(), ASSET_ID)); + // Asset is not live, i.e. frozen or being destroyed. + start_destroy_asset(addr.clone(), asset); + assert_eq!( + set_metadata(addr.clone(), ASSET_ID, vec![0], vec![0], decimals), + Err(Module { index: 52, error: 16 }), + ); + }); +} + +#[test] +fn asset_exists_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); + + // No tokens in circulation. + assert_eq!(asset_exists(addr.clone(), ASSET_ID), Ok(Assets::asset_exists(ASSET_ID))); + + // Tokens in circulation. + create_asset(addr.clone(), ASSET_ID, 1); + assert_eq!(asset_exists(addr.clone(), ASSET_ID), Ok(Assets::asset_exists(ASSET_ID))); + }); +} + +#[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 })); + }); +} diff --git a/pop-api/integration-tests/src/fungibles/utils.rs b/pop-api/integration-tests/src/fungibles/utils.rs new file mode 100644 index 00000000..2f9be65c --- /dev/null +++ b/pop-api/integration-tests/src/fungibles/utils.rs @@ -0,0 +1,360 @@ +use super::*; + +pub(super) fn decoded(result: ExecReturnValue) -> Result { + ::decode(&mut &result.data[1..]).map_err(|_| result) +} + +pub(super) fn total_supply(addr: AccountId32, asset_id: AssetId) -> Result { + let function = function_selector("total_supply"); + let params = [function, asset_id.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn balance_of( + addr: AccountId32, + asset_id: AssetId, + owner: AccountId32, +) -> Result { + let function = function_selector("balance_of"); + let params = [function, asset_id.encode(), owner.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn allowance( + addr: AccountId32, + asset_id: AssetId, + owner: AccountId32, + spender: AccountId32, +) -> Result { + let function = function_selector("allowance"); + let params = [function, asset_id.encode(), owner.encode(), spender.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn token_name(addr: AccountId32, asset_id: AssetId) -> Result, Error> { + let function = function_selector("token_name"); + let params = [function, asset_id.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::, Error>>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn token_symbol(addr: AccountId32, asset_id: AssetId) -> Result, Error> { + let function = function_selector("token_symbol"); + let params = [function, asset_id.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::, Error>>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn token_decimals(addr: AccountId32, asset_id: AssetId) -> Result { + let function = function_selector("token_decimals"); + let params = [function, asset_id.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn asset_exists(addr: AccountId32, asset_id: AssetId) -> Result { + let function = function_selector("asset_exists"); + let params = [function, asset_id.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn transfer( + addr: AccountId32, + asset_id: AssetId, + to: AccountId32, + value: Balance, +) -> Result<(), Error> { + let function = function_selector("transfer"); + let params = [function, asset_id.encode(), to.encode(), value.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn transfer_from( + addr: AccountId32, + asset_id: AssetId, + from: AccountId32, + to: AccountId32, + value: Balance, +) -> Result<(), Error> { + let function = function_selector("transfer_from"); + let data: Vec = vec![]; + let params = + [function, asset_id.encode(), from.encode(), to.encode(), value.encode(), data.encode()] + .concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn approve( + addr: AccountId32, + asset_id: AssetId, + spender: AccountId32, + value: Balance, +) -> Result<(), Error> { + let function = function_selector("approve"); + let params = [function, asset_id.encode(), spender.encode(), value.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn increase_allowance( + addr: AccountId32, + asset_id: AssetId, + spender: AccountId32, + value: Balance, +) -> Result<(), Error> { + let function = function_selector("increase_allowance"); + let params = [function, asset_id.encode(), spender.encode(), value.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn decrease_allowance( + addr: AccountId32, + asset_id: AssetId, + spender: AccountId32, + value: Balance, +) -> Result<(), Error> { + let function = function_selector("decrease_allowance"); + let params = [function, asset_id.encode(), spender.encode(), value.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn create( + addr: AccountId32, + asset_id: AssetId, + admin: AccountId32, + min_balance: Balance, +) -> Result<(), Error> { + let function = function_selector("create"); + let params = [function, asset_id.encode(), admin.encode(), min_balance.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn start_destroy(addr: AccountId32, asset_id: AssetId) -> Result<(), Error> { + let function = function_selector("start_destroy"); + let params = [function, asset_id.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + match decoded::>(result) { + Ok(x) => x, + Err(result) => panic!("Contract reverted: {:?}", result), + } +} + +pub(super) fn set_metadata( + addr: AccountId32, + asset_id: AssetId, + name: Vec, + symbol: Vec, + decimals: u8, +) -> Result<(), Error> { + let function = function_selector("set_metadata"); + let params = + [function, asset_id.encode(), name.encode(), symbol.encode(), decimals.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn clear_metadata(addr: AccountId32, asset_id: AssetId) -> Result<(), Error> { + let function = function_selector("clear_metadata"); + let params = [function, asset_id.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.clone()) + .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.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.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} + +pub(super) fn create_asset(owner: AccountId32, asset_id: AssetId, min_balance: Balance) -> AssetId { + assert_ok!(Assets::create( + RuntimeOrigin::signed(owner.clone()), + asset_id.into(), + owner.into(), + min_balance + )); + asset_id +} + +pub(super) fn mint_asset( + owner: AccountId32, + asset_id: AssetId, + to: AccountId32, + value: Balance, +) -> AssetId { + assert_ok!(Assets::mint( + RuntimeOrigin::signed(owner.clone()), + asset_id.into(), + to.into(), + value + )); + asset_id +} + +pub(super) fn create_asset_and_mint_to( + owner: AccountId32, + asset_id: AssetId, + to: AccountId32, + value: Balance, +) -> AssetId { + create_asset(owner.clone(), asset_id, 1); + mint_asset(owner, asset_id, to, value) +} + +// Create an asset, mints to, and approves spender. +pub(super) fn create_asset_mint_and_approve( + owner: AccountId32, + asset_id: AssetId, + to: AccountId32, + mint: Balance, + spender: AccountId32, + approve: Balance, +) -> AssetId { + create_asset_and_mint_to(owner.clone(), asset_id, to.clone(), mint); + assert_ok!(Assets::approve_transfer( + RuntimeOrigin::signed(to.into()), + asset_id.into(), + spender.into(), + approve, + )); + asset_id +} + +// Freeze an asset. +pub(super) fn freeze_asset(owner: AccountId32, asset_id: AssetId) { + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(owner.into()), asset_id.into())); +} + +// Thaw an asset. +pub(super) fn thaw_asset(owner: AccountId32, asset_id: AssetId) { + assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(owner.into()), asset_id.into())); +} + +// Start destroying an asset. +pub(super) fn start_destroy_asset(owner: AccountId32, asset_id: AssetId) { + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(owner.into()), asset_id.into())); +} + +// Create an asset and set metadata. +pub(super) fn create_asset_and_set_metadata( + owner: AccountId32, + asset_id: AssetId, + name: Vec, + symbol: Vec, + decimals: u8, +) -> AssetId { + assert_ok!(Assets::create( + RuntimeOrigin::signed(owner.clone()), + asset_id.into(), + owner.clone().into(), + 100 + )); + set_metadata_asset(owner, asset_id, name, symbol, decimals); + asset_id +} + +// Set metadata of an asset. +pub(super) fn set_metadata_asset( + owner: AccountId32, + asset_id: AssetId, + name: Vec, + symbol: Vec, + decimals: u8, +) { + assert_ok!(Assets::set_metadata( + RuntimeOrigin::signed(owner.into()), + asset_id.into(), + name, + symbol, + decimals + )); +} + +pub(super) fn token_name_asset(asset_id: AssetId) -> Vec { + as MetadataInspect>::name( + asset_id, + ) +} + +pub(super) fn token_symbol_asset(asset_id: AssetId) -> Vec { + as MetadataInspect>::symbol( + asset_id, + ) +} + +pub(super) fn token_decimals_asset(asset_id: AssetId) -> u8 { + as MetadataInspect>::decimals( + asset_id, + ) +} + +pub(super) fn instantiate_and_create_fungible( + contract: &str, + asset_id: AssetId, + min_balance: Balance, +) -> Result<(), Error> { + let function = function_selector("new"); + let input = [function, asset_id.encode(), min_balance.encode()].concat(); + let (wasm_binary, _) = + load_wasm_module::(contract).expect("could not read .wasm file"); + let result = Contracts::bare_instantiate( + ALICE, + INIT_VALUE, + GAS_LIMIT, + None, + Code::Upload(wasm_binary), + input, + vec![], + DEBUG_OUTPUT, + CollectEvents::Skip, + ) + .result + .expect("should work") + .result; + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) +} diff --git a/pop-api/integration-tests/src/lib.rs b/pop-api/integration-tests/src/lib.rs index ea5ccb05..b728fca6 100644 --- a/pop-api/integration-tests/src/lib.rs +++ b/pop-api/integration-tests/src/lib.rs @@ -16,7 +16,7 @@ use pop_runtime_devnet::{ UNIT, }; -mod local_fungibles; +mod fungibles; type AssetId = u32; type Balance = u128; diff --git a/pop-api/integration-tests/src/local_fungibles.rs b/pop-api/integration-tests/src/local_fungibles.rs deleted file mode 100644 index 15a644ad..00000000 --- a/pop-api/integration-tests/src/local_fungibles.rs +++ /dev/null @@ -1,738 +0,0 @@ -use super::*; -use pop_primitives::error::{ - ArithmeticError::*, - Error::{self, *}, - TokenError::*, -}; - -const ASSET_ID: AssetId = 1; -const CONTRACT: &str = "contracts/fungibles/target/ink/fungibles.wasm"; - -fn decoded(result: ExecReturnValue) -> Result { - ::decode(&mut &result.data[2..]) - .map_err(|_| format!("\nTest failed by trying to decode `{:?}` into `T`\n", result)) -} - -// Call total_supply contract message. -fn total_supply(addr: AccountId32, asset_id: AssetId) -> Balance { - let function = function_selector("total_supply"); - let params = [function, asset_id.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - decoded::(result).unwrap() -} - -// Call balance_of contract message. -fn balance_of(addr: AccountId32, asset_id: AssetId, owner: AccountId32) -> Balance { - let function = function_selector("balance_of"); - let params = [function, asset_id.encode(), owner.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - decoded::(result).unwrap() -} - -// Call allowance contract message. -fn allowance( - addr: AccountId32, - asset_id: AssetId, - owner: AccountId32, - spender: AccountId32, -) -> Balance { - let function = function_selector("allowance"); - let params = [function, asset_id.encode(), owner.encode(), spender.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - decoded::(result).unwrap() -} - -// Call token_name contract message. -fn token_name(addr: AccountId32, asset_id: AssetId) -> Vec { - let function = function_selector("token_name"); - let params = [function, asset_id.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - decoded::>(result).unwrap() -} - -// Call token_symbol contract message. -fn token_symbol(addr: AccountId32, asset_id: AssetId) -> Vec { - let function = function_selector("token_symbol"); - let params = [function, asset_id.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - decoded::>(result).unwrap() -} - -// Call token_decimals contract message. -fn token_decimals(addr: AccountId32, asset_id: AssetId) -> u8 { - let function = function_selector("token_decimals"); - let params = [function, asset_id.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - decoded::(result).unwrap() -} - -fn transfer( - addr: AccountId32, - asset_id: AssetId, - to: AccountId32, - value: Balance, -) -> ExecReturnValue { - let function = function_selector("transfer"); - let params = [function, asset_id.encode(), to.encode(), value.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - result -} - -fn transfer_from( - addr: AccountId32, - asset_id: AssetId, - from: AccountId32, - to: AccountId32, - value: Balance, -) -> ExecReturnValue { - let function = function_selector("transfer_from"); - let data: Vec = vec![]; - let params = - [function, asset_id.encode(), from.encode(), to.encode(), value.encode(), data.encode()] - .concat(); - let result = bare_call(addr, params, 0).expect("should work"); - result -} - -fn approve( - addr: AccountId32, - asset_id: AssetId, - spender: AccountId32, - value: Balance, -) -> ExecReturnValue { - let function = function_selector("approve"); - let params = [function, asset_id.encode(), spender.encode(), value.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - result -} - -fn increase_allowance( - addr: AccountId32, - asset_id: AssetId, - spender: AccountId32, - value: Balance, -) -> ExecReturnValue { - let function = function_selector("increase_allowance"); - let params = [function, asset_id.encode(), spender.encode(), value.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - result -} - -fn decrease_allowance( - addr: AccountId32, - asset_id: AssetId, - spender: AccountId32, - value: Balance, -) -> ExecReturnValue { - let function = function_selector("decrease_allowance"); - let params = [function, asset_id.encode(), spender.encode(), value.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - result -} - -// fn asset_exists(addr: AccountId32, asset_id: AssetId) -> bool { -// let function = function_selector("asset_exists"); -// let params = [function, asset_id.encode()].concat(); -// let result = bare_call(addr, params, 0).expect("should work"); -// decoded::(result) -// } -// -// fn create( -// addr: AccountId32, -// asset_id: AssetId, -// admin: AccountId32, -// min_balance: Balance, -// ) -> ExecReturnValue { -// let function = function_selector("create"); -// let params = [function, asset_id.encode(), admin.encode(), min_balance.encode()].concat(); -// bare_call(addr, params, 0).expect("should work") -// } -// -// fn set_metadata( -// addr: AccountId32, -// asset_id: AssetId, -// name: Vec, -// symbol: Vec, -// decimals: u8, -// ) -> ExecReturnValue { -// let function = function_selector("set_metadata"); -// let params = -// [function, asset_id.encode(), name.encode(), symbol.encode(), decimals.encode()].concat(); -// bare_call(addr, params, 0).expect("should work") -// } - -fn create_asset(owner: AccountId32, asset_id: AssetId, min_balance: Balance) -> AssetId { - assert_ok!(Assets::create( - RuntimeOrigin::signed(owner.clone()), - asset_id.into(), - owner.into(), - min_balance - )); - asset_id -} - -fn mint_asset(owner: AccountId32, asset_id: AssetId, to: AccountId32, value: Balance) -> AssetId { - assert_ok!(Assets::mint( - RuntimeOrigin::signed(owner.clone()), - asset_id.into(), - to.into(), - value - )); - asset_id -} - -fn create_asset_and_mint_to( - owner: AccountId32, - asset_id: AssetId, - to: AccountId32, - value: Balance, -) -> AssetId { - create_asset(owner.clone(), asset_id, 1); - mint_asset(owner, asset_id, to, value) -} - -// Create an asset, mints to, and approves spender. -fn create_asset_mint_and_approve( - owner: AccountId32, - asset_id: AssetId, - to: AccountId32, - mint: Balance, - spender: AccountId32, - approve: Balance, -) -> AssetId { - let asset = create_asset_and_mint_to(owner.clone(), asset_id, to.clone(), mint); - assert_ok!(Assets::approve_transfer( - RuntimeOrigin::signed(to.into()), - asset_id.into(), - spender.into(), - approve, - )); - asset -} - -// Freeze an asset. -fn freeze_asset(owner: AccountId32, asset_id: AssetId) { - assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(owner.into()), asset_id.into())); -} - -// Thaw an asset. -fn thaw_asset(owner: AccountId32, asset_id: AssetId) { - assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(owner.into()), asset_id.into())); -} - -// Start destroying an asset. -fn start_destroy_asset(owner: AccountId32, asset_id: AssetId) { - assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(owner.into()), asset_id.into())); -} - -// Create an asset and set metadata. -fn create_asset_and_set_metadata( - owner: AccountId32, - asset_id: AssetId, - name: Vec, - symbol: Vec, - decimals: u8, -) { - assert_ok!(Assets::create( - RuntimeOrigin::signed(owner.clone()), - asset_id.into(), - owner.clone().into(), - 100 - )); - set_metadata_asset(owner, asset_id, name, symbol, decimals); -} - -// Set metadata of an asset. -fn set_metadata_asset( - owner: AccountId32, - asset_id: AssetId, - name: Vec, - symbol: Vec, - decimals: u8, -) { - assert_ok!(Assets::set_metadata( - RuntimeOrigin::signed(owner.into()), - asset_id.into(), - name, - symbol, - decimals - )); -} - -fn token_name_asset(asset_id: AssetId) -> Vec { - as MetadataInspect>::name( - asset_id, - ) -} - -fn token_symbol_asset(asset_id: AssetId) -> Vec { - as MetadataInspect>::symbol( - asset_id, - ) -} - -fn token_decimals_asset(asset_id: AssetId) -> u8 { - as MetadataInspect>::decimals( - asset_id, - ) -} - -/// 1. PSP-22 Interface: -/// - total_supply -/// - balance_of -/// - allowance -/// - transfer -/// - transfer_from -/// - approve -/// - increase_allowance -/// - decrease_allowance - -#[test] -fn total_supply_works() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - - // No tokens in circulation. - assert_eq!(Assets::total_supply(ASSET_ID), total_supply(addr.clone(), ASSET_ID)); - assert_eq!(0, total_supply(addr.clone(), ASSET_ID)); - - // Tokens in circulation. - create_asset_and_mint_to(addr.clone(), ASSET_ID, BOB, 100); - assert_eq!(Assets::total_supply(ASSET_ID), total_supply(addr.clone(), ASSET_ID)); - assert_eq!(100, total_supply(addr, ASSET_ID)); - }); -} - -#[test] -fn balance_of_works() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - - // No tokens in circulation. - assert_eq!(Assets::balance(ASSET_ID, BOB), balance_of(addr.clone(), ASSET_ID, BOB)); - assert_eq!(0, balance_of(addr.clone(), ASSET_ID, BOB)); - - // Tokens in circulation. - create_asset_and_mint_to(addr.clone(), ASSET_ID, BOB, 100); - assert_eq!(Assets::balance(ASSET_ID, BOB), balance_of(addr.clone(), ASSET_ID, BOB)); - assert_eq!(100, balance_of(addr, ASSET_ID, BOB)); - }); -} - -#[test] -fn allowance_works() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - - // No tokens in circulation. - assert_eq!( - Assets::allowance(ASSET_ID, &BOB, &ALICE), - allowance(addr.clone(), ASSET_ID, BOB, ALICE) - ); - assert_eq!(0, allowance(addr.clone(), ASSET_ID, BOB, ALICE)); - - // Tokens in circulation. - create_asset_mint_and_approve(addr.clone(), ASSET_ID, BOB, 100, ALICE, 50); - assert_eq!( - Assets::allowance(ASSET_ID, &BOB, &ALICE), - allowance(addr.clone(), ASSET_ID, BOB, ALICE) - ); - assert_eq!(50, allowance(addr, ASSET_ID, BOB, ALICE)); - }); -} - -#[test] -fn transfer_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::(transfer(addr.clone(), 1, BOB, amount,)), - Ok(Module { index: 52, error: 3 }), - ); - // Create asset with Alice as owner and mint `amount` to contract address. - let asset = create_asset_and_mint_to(ALICE, 1, addr.clone(), amount); - // Asset is not live, i.e. frozen or being destroyed. - freeze_asset(ALICE, asset); - assert_eq!( - decoded::(transfer(addr.clone(), asset, BOB, amount,)), - Ok(Module { index: 52, error: 16 }), - ); - thaw_asset(ALICE, asset); - // Not enough balance. - assert_eq!( - decoded::(transfer(addr.clone(), asset, BOB, amount + 1 * UNIT)), - Ok(Module { index: 52, error: 0 }), - ); - // Not enough balance due to ED. - assert_eq!( - decoded::(transfer(addr.clone(), asset, BOB, amount)), - Ok(Module { index: 52, error: 0 }), - ); - // Successful transfer. - let balance_before_transfer = Assets::balance(asset, &BOB); - let result = transfer(addr.clone(), asset, BOB, amount / 2); - assert!(!result.did_revert(), "Contract reverted!"); - let balance_after_transfer = Assets::balance(asset, &BOB); - assert_eq!(balance_after_transfer, balance_before_transfer + amount / 2); - // Transfer asset to account that does not exist. - assert_eq!( - decoded::(transfer(addr.clone(), asset, FERDIE, amount / 4)), - Ok(Token(CannotCreate)) - ); - // Asset is not live, i.e. frozen or being destroyed. - start_destroy_asset(ALICE, asset); - assert_eq!( - decoded::(transfer(addr.clone(), asset, BOB, amount / 4)), - Ok(Module { index: 52, error: 16 }), - ); - }); -} - -#[test] -fn transfer_from_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::(transfer_from(addr.clone(), 1, ALICE, BOB, amount / 2)), - Ok(Module { index: 52, error: 3 }), - ); - // Create asset with Alice as owner and mint `amount` to contract address. - let asset = create_asset_and_mint_to(ALICE, 1, ALICE, amount); - // Unapproved transfer. - assert_eq!( - decoded::(transfer_from(addr.clone(), asset, ALICE, BOB, amount / 2)), - Ok(Module { index: 52, error: 10 }) - ); - assert_ok!(Assets::approve_transfer( - RuntimeOrigin::signed(ALICE.into()), - asset.into(), - addr.clone().into(), - amount + 1 * UNIT, - )); - // Asset is not live, i.e. frozen or being destroyed. - freeze_asset(ALICE, asset); - assert_eq!( - decoded::(transfer_from(addr.clone(), asset, ALICE, BOB, amount)), - Ok(Module { index: 52, error: 16 }), - ); - thaw_asset(ALICE, asset); - // Not enough balance. - assert_eq!( - decoded::(transfer_from(addr.clone(), asset, ALICE, BOB, amount + 1 * UNIT,)), - Ok(Module { index: 52, error: 0 }), - ); - // Successful transfer. - let balance_before_transfer = Assets::balance(asset, &BOB); - let result = transfer_from(addr.clone(), asset, ALICE, BOB, amount / 2); - assert!(!result.did_revert(), "Contract reverted!"); - let balance_after_transfer = Assets::balance(asset, &BOB); - assert_eq!(balance_after_transfer, balance_before_transfer + amount / 2); - }); -} - -#[test] -fn approve_works() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - let addr = instantiate(CONTRACT, 0, vec![]); - let amount: Balance = 100 * UNIT; - // Asset does not exist. - assert_eq!( - decoded::(approve(addr.clone(), 0, BOB, amount)), - Ok(Module { index: 52, error: 3 }), - ); - let asset = create_asset_and_mint_to(ALICE, 0, addr.clone(), amount); - assert_eq!( - decoded::(approve(addr.clone(), asset, BOB, amount)), - Ok(ConsumerRemaining) - ); - - let addr = instantiate(CONTRACT, INIT_VALUE, vec![1]); - // Create asset with Alice as owner and mint `amount` to contract address. - let asset = create_asset_and_mint_to(ALICE, 1, addr.clone(), amount); - // Asset is not live, i.e. frozen or being destroyed. - freeze_asset(ALICE, asset); - assert_eq!( - decoded::(approve(addr.clone(), asset, BOB, amount)), - Ok(Module { index: 52, error: 16 }), - ); - thaw_asset(ALICE, asset); - // Successful approvals: - assert_eq!(0, Assets::allowance(asset, &addr, &BOB)); - assert!(!approve(addr.clone(), asset, BOB, amount).did_revert(), "Contract reverted!"); - assert_eq!(Assets::allowance(asset, &addr, &BOB), amount); - // Non-additive, sets new value. - assert!(!approve(addr.clone(), asset, BOB, amount / 2).did_revert(), "Contract reverted!"); - assert_eq!(Assets::allowance(asset, &addr, &BOB), amount / 2); - // Asset is not live, i.e. frozen or being destroyed. - start_destroy_asset(ALICE, asset); - assert_eq!( - decoded::(approve(addr.clone(), asset, BOB, amount)), - Ok(Module { index: 52, error: 16 }), - ); - }); -} - -#[test] -fn increase_allowance_works() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - let addr = instantiate(CONTRACT, 0, vec![]); - let amount: Balance = 100 * UNIT; - // Asset does not exist. - assert_eq!( - decoded::(increase_allowance(addr.clone(), 0, BOB, amount)), - Ok(Module { index: 52, error: 3 }), - ); - let asset = create_asset_and_mint_to(ALICE, 0, addr.clone(), amount); - assert_eq!( - decoded::(increase_allowance(addr.clone(), asset, BOB, amount)), - Ok(ConsumerRemaining) - ); - - let addr = instantiate(CONTRACT, INIT_VALUE, vec![1]); - // Create asset with Alice as owner and mint `amount` to contract address. - let asset = create_asset_and_mint_to(ALICE, 1, addr.clone(), amount); - // Asset is not live, i.e. frozen or being destroyed. - freeze_asset(ALICE, asset); - assert_eq!( - decoded::(increase_allowance(addr.clone(), asset, BOB, amount)), - Ok(Module { index: 52, error: 16 }), - ); - thaw_asset(ALICE, asset); - // Successful approvals: - assert_eq!(0, Assets::allowance(asset, &addr, &BOB)); - assert!( - !increase_allowance(addr.clone(), asset, BOB, amount).did_revert(), - "Contract reverted!" - ); - assert_eq!(Assets::allowance(asset, &addr, &BOB), amount); - // Additive. - assert!( - !increase_allowance(addr.clone(), asset, BOB, amount).did_revert(), - "Contract reverted!" - ); - assert_eq!(Assets::allowance(asset, &addr, &BOB), amount * 2); - // Asset is not live, i.e. frozen or being destroyed. - start_destroy_asset(ALICE, asset); - assert_eq!( - decoded::(increase_allowance(addr.clone(), asset, BOB, amount)), - Ok(Module { index: 52, error: 16 }), - ); - }); -} - -#[test] -fn decrease_allowance_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::(decrease_allowance(addr.clone(), 0, BOB, amount)), - Ok(Module { index: 52, error: 3 }), - ); - // Create asset and mint to the address contract, delegate Bob to spend the `amount`. - let asset = - create_asset_mint_and_approve(addr.clone(), 0, addr.clone(), amount, BOB, amount); - // Asset is not live, i.e. frozen or being destroyed. - freeze_asset(addr.clone(), asset); - assert_eq!( - decoded::(decrease_allowance(addr.clone(), asset, BOB, amount)), - Ok(Module { index: 52, error: 16 }), - ); - thaw_asset(addr.clone(), asset); - // Successfully decrease allowance. - let bob_allowance_before = Assets::allowance(asset, &addr, &BOB); - let result = decrease_allowance(addr.clone(), 0, BOB, amount / 2 - 1 * UNIT); - assert!(!result.did_revert(), "Contract reverted!"); - let bob_allowance_after = Assets::allowance(asset, &addr, &BOB); - assert_eq!(bob_allowance_before - bob_allowance_after, amount / 2 - 1 * UNIT); - // Asset is not live, i.e. frozen or being destroyed. - start_destroy_asset(addr.clone(), asset); - assert_eq!( - decoded::(decrease_allowance(addr.clone(), asset, BOB, amount)), - Ok(Module { index: 52, error: 16 }), - ); - }); -} - -/// 2. PSP-22 Metadata Interface: -/// - token_name -/// - token_symbol -/// - token_decimals - -#[test] -fn token_metadata_works() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - let addr = instantiate(CONTRACT, INIT_VALUE, vec![]); - - let name: Vec = vec![11, 12, 13]; - let symbol: Vec = vec![21, 22, 23]; - let decimals: u8 = 69; - // Token does not exist. - assert_eq!(token_name_asset(ASSET_ID), token_name(addr.clone(), ASSET_ID)); - assert_eq!(Vec::::new(), token_name(addr.clone(), ASSET_ID)); - assert_eq!(token_symbol_asset(ASSET_ID), token_symbol(addr.clone(), ASSET_ID)); - assert_eq!(Vec::::new(), token_symbol(addr.clone(), ASSET_ID)); - assert_eq!(token_decimals_asset(ASSET_ID), token_decimals(addr.clone(), ASSET_ID)); - assert_eq!(0, token_decimals(addr.clone(), ASSET_ID)); - - create_asset_and_set_metadata( - addr.clone(), - ASSET_ID, - name.clone(), - symbol.clone(), - decimals, - ); - assert_eq!(token_name_asset(ASSET_ID), token_name(addr.clone(), ASSET_ID)); - assert_eq!(name, token_name(addr.clone(), ASSET_ID)); - assert_eq!(token_symbol_asset(ASSET_ID), token_symbol(addr.clone(), ASSET_ID)); - assert_eq!(symbol, token_symbol(addr.clone(), ASSET_ID)); - assert_eq!(token_decimals_asset(ASSET_ID), token_decimals(addr.clone(), ASSET_ID)); - assert_eq!(decimals, token_decimals(addr.clone(), ASSET_ID)); - }); -} - -// #[test] -// #[ignore] -// fn asset_exists_works() { -// new_test_ext().execute_with(|| { -// let _ = env_logger::try_init(); -// let addr = -// instantiate(CONTRACT, INIT_VALUE, vec![]); -// -// // No tokens in circulation. -// assert_eq!(Assets::asset_exists(ASSET_ID), asset_exists(addr.clone(), ASSET_ID)); -// -// // Tokens in circulation. -// create_asset(addr.clone(), ASSET_ID, 1); -// assert_eq!(Assets::asset_exists(ASSET_ID), asset_exists(addr, ASSET_ID)); -// }); -// } - -// #[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::(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::(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::(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::(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::(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::(transfer_from(addr.clone(), asset, None, Some(BOB), amount, &[0u8])), -// Ok(Module { index: 52, error: 16 }), -// ); -// }); -// } - -// #[test] -// #[ignore] -// fn create_works() { -// new_test_ext().execute_with(|| { -// let _ = env_logger::try_init(); -// // Instantiate a contract without balance (relay token). -// let addr = instantiate(CONTRACT, 0, vec![0]); -// // No balance to pay for fees. -// assert_eq!( -// decoded::(create(addr.clone(), ASSET_ID, addr.clone(), 1)), -// Ok(Module { index: 10, error: 2 }), -// ); -// // Instantiate a contract without balance (relay token). -// let addr = instantiate(CONTRACT, 100, vec![2]); -// // No balance to pay the deposit. -// assert_eq!( -// decoded::(create(addr.clone(), ASSET_ID, addr.clone(), 1)), -// Ok(Module { index: 10, error: 2 }), -// ); -// // Instantiate a contract with balance. -// let addr = -// instantiate(CONTRACT, INIT_VALUE, vec![1]); -// assert_eq!( -// decoded::(create(addr.clone(), ASSET_ID, BOB, 0)), -// Ok(Module { index: 52, error: 7 }), -// ); -// create_asset(ALICE, ASSET_ID, 1); -// // Asset ID is already taken. -// assert_eq!( -// decoded::(create(addr.clone(), ASSET_ID, BOB, 1)), -// Ok(Module { index: 52, error: 5 }), -// ); -// // The minimal balance for an asset must be non zero. -// let new_asset = 2; -// let result = create(addr.clone(), new_asset, BOB, 1); -// assert!(!result.did_revert(), "Contract reverted!"); -// }); -// } - -// #[test] -// #[ignore] -// fn set_metadata_works() { -// new_test_ext().execute_with(|| { -// let _ = env_logger::try_init(); -// let addr = -// instantiate(CONTRACT, INIT_VALUE, vec![]); -// -// create_asset(addr.clone(), ASSET_ID, 1); -// let result = set_metadata(addr.clone(), ASSET_ID, vec![12], vec![12], 12); -// assert!(!result.did_revert(), "Contract reverted!"); -// }); -// } diff --git a/pop-api/src/lib.rs b/pop-api/src/lib.rs index 7fcfc85b..78a79f80 100644 --- a/pop-api/src/lib.rs +++ b/pop-api/src/lib.rs @@ -30,10 +30,10 @@ mod constants { /// Helper method to build `ChainExtensionMethod`. /// /// Parameters: -/// - 'version': The version of the chain extension -/// - 'function': The ID of the function -/// - 'module': The index of the runtime module -/// - 'dispatchable': The index of the module dispatchable functions +/// - 'version': The version of the chain extension. +/// - 'function': The ID of the function. +/// - 'module': The index of the runtime module. +/// - 'dispatchable': The index of the module dispatchable functions. fn build_extension_method( version: u8, function: u8, @@ -45,13 +45,13 @@ fn build_extension_method( /// Represents a status code returned by the runtime. /// -/// `StatusCode` encapsulates a `u32` value that indicates the status of an operation performed -/// by the runtime. It helps to communicate the success or failure of a Pop API call to the contract, +/// `StatusCode` encapsulates a `u32` value that indicates the status of an operation performed by +/// the runtime. It helps to communicate the success or failure of a Pop API call to the contract, /// providing a standardized way to handle errors. /// -/// This status code can be used to determine if an operation succeeded or if it encountered -/// an error. A `StatusCode` of `0` typically indicates success, while any other value represents -/// an error. +/// This status code can be used to determine if an operation succeeded or if it encountered an +/// error. A `StatusCode` of `0` typically indicates success, while any other value represents an +/// error. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[ink::scale_derive(Encode, Decode, TypeInfo)] pub struct StatusCode(pub u32); diff --git a/pop-api/src/v0/assets/fungibles.rs b/pop-api/src/v0/assets/fungibles.rs index 0712a80b..8600c439 100644 --- a/pop-api/src/v0/assets/fungibles.rs +++ b/pop-api/src/v0/assets/fungibles.rs @@ -3,22 +3,23 @@ use crate::{ primitives::{AccountId, AssetId, Balance}, Result, StatusCode, }; +pub use asset_management::*; use constants::*; use ink::{env::chain_extension::ChainExtensionMethod, prelude::vec::Vec}; pub use metadata::*; -/// Helper method to build a dispatch call `ChainExtensionMethod` for fungibles `v0` +/// Helper method to build a dispatch call `ChainExtensionMethod` for fungibles `v0`. /// /// Parameters: -/// - 'dispatchable': The index of the module dispatchable functions +/// - 'dispatchable': The index of the module dispatchable functions. fn build_dispatch(dispatchable: u8) -> ChainExtensionMethod<(), (), (), false> { crate::v0::build_dispatch(FUNGIBLES, dispatchable) } -/// Helper method to build a dispatch call `ChainExtensionMethod` for fungibles `v0`` +/// Helper method to build a dispatch call `ChainExtensionMethod` for fungibles `v0`. /// /// Parameters: -/// - 'state_query': The index of the runtime state query +/// - 'state_query': The index of the runtime state query. fn build_read_state(state_query: u8) -> ChainExtensionMethod<(), (), (), false> { crate::v0::build_read_state(FUNGIBLES, state_query) } @@ -27,48 +28,40 @@ 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: - /// - total_supply - pub const TOTAL_SUPPLY: u8 = 0; - /// - balance_of - pub const BALANCE_OF: u8 = 1; - /// - allowance - pub const ALLOWANCE: u8 = 2; - /// - transfer + pub(super) const TOTAL_SUPPLY: u8 = 0; + pub(super) const BALANCE_OF: u8 = 1; + pub(super) const ALLOWANCE: u8 = 2; pub(super) const TRANSFER: u8 = 3; - /// - transfer_from pub(super) const TRANSFER_FROM: u8 = 4; - /// - approve pub(super) const APPROVE: u8 = 5; - /// - increase_allowance pub(super) const INCREASE_ALLOWANCE: u8 = 6; - /// - decrease_allowance pub(super) const DECREASE_ALLOWANCE: u8 = 7; /// 2. PSP-22 Metadata Interface: - /// - token_name - pub const TOKEN_NAME: u8 = 8; - /// - token_symbol - pub const TOKEN_SYMBOL: u8 = 9; - /// - token_decimals - pub const TOKEN_DECIMALS: u8 = 10; - - // 3. Asset Management: - // - create - // - start_destroy - // - destroy_accounts - // - destroy_approvals - // - finish_destroy - // - set_metadata - // - clear_metadata + pub(super) const TOKEN_NAME: u8 = 8; + pub(super) const TOKEN_SYMBOL: u8 = 9; + pub(super) const TOKEN_DECIMALS: u8 = 10; + + /// 3. Asset Management: + pub(super) const CREATE: u8 = 11; + pub(super) const START_DESTROY: u8 = 12; + 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. /// -/// # Arguments -/// * `id` - The ID of the asset. +/// # Parameters +/// - `id` - The ID of the asset. /// /// # Returns /// The total supply of the token, or an error if the operation fails. @@ -84,9 +77,9 @@ pub fn total_supply(id: AssetId) -> Result { /// Returns the account balance for the specified `owner` for a given asset ID. Returns `0` if /// the account is non-existent. /// -/// # Arguments -/// * `id` - The ID of the asset. -/// * `owner` - The account whose balance is being queried. +/// # Parameters +/// - `id` - The ID of the asset. +/// - `owner` - The account whose balance is being queried. /// /// # Returns /// The balance of the specified account, or an error if the operation fails. @@ -102,10 +95,10 @@ pub fn balance_of(id: AssetId, owner: AccountId) -> Result { /// Returns the amount which `spender` is still allowed to withdraw from `owner` for a given /// asset ID. Returns `0` if no allowance has been set. /// -/// # Arguments -/// * `id` - The ID of the asset. -/// * `owner` - The account that owns the tokens. -/// * `spender` - The account that is allowed to spend the tokens. +/// # Parameters +/// - `id` - The ID of the asset. +/// - `owner` - The account that owns the tokens. +/// - `spender` - The account that is allowed to spend the tokens. /// /// # Returns /// The remaining allowance, or an error if the operation fails. @@ -121,67 +114,66 @@ pub fn allowance(id: AssetId, owner: AccountId, spender: AccountId) -> Result Result<()> { +pub fn transfer(id: AssetId, to: AccountId, value: Balance) -> Result<()> { build_dispatch(TRANSFER) .input::<(AssetId, AccountId, Balance)>() .output::, true>() .handle_error_code::() - .call(&(id, target, amount)) + .call(&(id, to, value)) } -/// Transfers `value` tokens on behalf of `from` to account `to` with additional `data` -/// in unspecified format. If `from` is equal to `None`, tokens will be minted to account `to`. If -/// `to` is equal to `None`, tokens will be burned from account `from`. +/// Transfers `value` amount tokens on behalf of `from` to account `to` with additional `data` +/// in unspecified format. /// -/// # Arguments -/// * `id` - The ID of the asset. -/// * `from` - The account from which the tokens are transferred. -/// * `to` - The recipient account. -/// * `value` - The number of tokens to transfer. +/// # Parameters +/// - `id` - The ID of the asset. +/// - `from` - The account from which the tokens are transferred. +/// - `to` - The recipient account. +/// - `value` - The number of tokens to transfer. /// /// # Returns /// Returns `Ok(())` if successful, or an error if the transfer fails. #[inline] -pub fn transfer_from(id: AssetId, from: AccountId, to: AccountId, amount: Balance) -> Result<()> { +pub fn transfer_from(id: AssetId, from: AccountId, to: AccountId, value: Balance) -> Result<()> { build_dispatch(TRANSFER_FROM) .input::<(AssetId, AccountId, AccountId, Balance)>() .output::, true>() .handle_error_code::() - .call(&(id, from, to, amount)) + .call(&(id, from, to, value)) } /// Approves an account to spend a specified number of tokens on behalf of the caller. /// -/// # Arguments -/// * `id` - The ID of the asset. -/// * `spender` - The account that is allowed to spend the tokens. -/// * `value` - The number of tokens to approve. +/// # Parameters +/// - `id` - The ID of the asset. +/// - `spender` - The account that is allowed to spend the tokens. +/// - `value` - The number of tokens to approve. /// /// # Returns /// Returns `Ok(())` if successful, or an error if the approval fails. #[inline] -pub fn approve(id: AssetId, spender: AccountId, amount: Balance) -> Result<()> { +pub fn approve(id: AssetId, spender: AccountId, value: Balance) -> Result<()> { build_dispatch(APPROVE) .input::<(AssetId, AccountId, Balance)>() .output::, true>() .handle_error_code::() - .call(&(id, spender, amount)) + .call(&(id, spender, value)) } /// Increases the allowance of a spender. /// -/// # Arguments -/// * `id` - The ID of the asset. -/// * `spender` - The account that is allowed to spend the tokens. -/// * `value` - The number of tokens to increase the allowance by. +/// # Parameters +/// - `id` - The ID of the asset. +/// - `spender` - The account that is allowed to spend the tokens. +/// - `value` - The number of tokens to increase the allowance by. /// /// # Returns /// Returns `Ok(())` if successful, or an error if the operation fails. @@ -196,10 +188,10 @@ pub fn increase_allowance(id: AssetId, spender: AccountId, value: Balance) -> Re /// Decreases the allowance of a spender. /// -/// # Arguments -/// * `id` - The ID of the asset. -/// * `spender` - The account that is allowed to spend the tokens. -/// * `value` - The number of tokens to decrease the allowance by. +/// # Parameters +/// - `id` - The ID of the asset. +/// - `spender` - The account that is allowed to spend the tokens. +/// - `value` - The number of tokens to decrease the allowance by. /// /// # Returns /// Returns `Ok(())` if successful, or an error if the operation fails. @@ -212,12 +204,47 @@ pub fn decrease_allowance(id: AssetId, spender: AccountId, value: Balance) -> Re .call(&(id, spender, value)) } +/// Creates `value` amount tokens and assigns them to `account`, increasing the total supply. +/// +/// # Parameters +/// - `id` - The ID of the asset. +/// - `account` - 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, value: Balance) -> Result<()> { + build_dispatch(MINT) + .input::<(AssetId, AccountId, Balance)>() + .output::, true>() + .handle_error_code::() + .call(&(id, account, value)) +} + +/// Destroys `value` amount tokens from `account`, reducing the total supply. +/// +/// # Parameters +/// - `id` - The ID of the asset. +/// - `account` - 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, value: Balance) -> Result<()> { + build_dispatch(BURN) + .input::<(AssetId, AccountId, Balance)>() + .output::, true>() + .handle_error_code::() + .call(&(id, account, value)) +} + pub mod metadata { use super::*; + /// Returns the token name for a given asset ID. /// - /// # Arguments - /// * `id` - The ID of the asset. + /// # Parameters + /// - `id` - The ID of the asset. /// /// # Returns /// The name of the token as a byte vector, or an error if the operation fails. @@ -232,8 +259,8 @@ pub mod metadata { /// Returns the token symbol for a given asset ID. /// - /// # Arguments - /// * `id` - The ID of the asset. + /// # Parameters + /// - `id` - The ID of the asset. /// /// # Returns /// The symbol of the token as a byte vector, or an error if the operation fails. @@ -248,11 +275,11 @@ pub mod metadata { /// Returns the token decimals for a given asset ID. /// - /// # Arguments - /// * `id` - The ID of the asset. + /// # Parameters + /// - `id` - The ID of the asset. /// /// # Returns - /// The number of decimals of the token as a byte vector, or an error if the operation fails. + /// The number of decimals of the token, or an error if the operation fails. #[inline] pub fn token_decimals(id: AssetId) -> Result { build_read_state(TOKEN_DECIMALS) @@ -263,90 +290,87 @@ pub mod metadata { } } -// pub asset_management { -// /// Create a new token with a given asset ID. -// /// -// /// # Arguments -// /// * `id` - The ID of the asset. -// /// * `admin` - The account that will administer the asset. -// /// * `min_balance` - The minimum balance required for accounts holding this asset. -// /// -// /// # Returns -// /// Returns `Ok(())` if successful, or an error if the creation fails. -// pub fn create(id: AssetId, admin: impl Into>, min_balance: Balance) -> Result<()> { -// Ok(()) -// } -// -// /// Start the process of destroying a token with a given asset ID. -// /// -// /// # Arguments -// /// * `id` - The ID of the asset. -// /// -// /// # Returns -// /// Returns `Ok(())` if successful, or an error if the operation fails. -// fn start_destroy(id: AssetId) -> Result<()> { -// Ok(()) -// } -// -// /// Destroy all accounts associated with a token with a given asset ID. -// /// -// /// # Arguments -// /// * `id` - The ID of the asset. -// /// -// /// # Returns -// /// Returns `Ok(())` if successful, or an error if the operation fails. -// fn destroy_accounts(id: AssetId) -> Result<()> { -// Ok(()) -// } -// -// /// Destroy all approvals associated with a token with a given asset ID. -// /// -// /// # Arguments -// /// * `id` - The ID of the asset. -// /// -// /// # Returns -// /// Returns `Ok(())` if successful, or an error if the operation fails. -// fn destroy_approvals(id: AssetId) -> Result<()> { -// Ok(()) -// } -// -// /// Complete the process of destroying a token with a given asset ID. -// /// -// /// # Arguments -// /// * `id` - The ID of the asset. -// /// -// /// # Returns -// /// Returns `Ok(())` if successful, or an error if the operation fails. -// fn finish_destroy(id: AssetId) -> Result<()> { -// Ok(()) -// } -// -// /// Set the metadata for a token with a given asset ID. -// /// -// /// # Arguments -// /// * `id` - The ID of the asset. -// /// -// /// # Returns -// /// Returns `Ok(())` if successful, or an error if the operation fails. -// pub fn set_metadata(id: AssetId, name: Vec, symbol: Vec, decimals: u8) -> Result<()> { -// Ok(()) -// } -// -// /// Clear the metadata for a token with a given asset ID. -// /// -// /// # Arguments -// /// * `id` - The ID of the asset. -// /// -// /// # Returns -// /// Returns `Ok(())` if successful, or an error if the operation fails. -// fn clear_metadata(id: AssetId) -> Result<()> { -// Ok(()) -// } -// } -// -// pub fn asset_exists(id: AssetId) -> Result { -// assets::asset_exists(id) -// } +pub mod asset_management { + use super::*; + + /// Create a new token with a given asset ID. + /// + /// # Parameters + /// - `id` - The ID of the asset. + /// - `admin` - The account that will administer the asset. + /// - `min_balance` - The minimum balance required for accounts holding this asset. + /// + /// # Returns + /// Returns `Ok(())` if successful, or an error if the creation fails. + pub fn create(id: AssetId, admin: AccountId, min_balance: Balance) -> Result<()> { + build_dispatch(CREATE) + .input::<(AssetId, AccountId, Balance)>() + .output::, true>() + .handle_error_code::() + .call(&(id, admin, min_balance)) + } + + /// Start the process of destroying a token with a given asset ID. + /// + /// # Parameters + /// - `id` - The ID of the asset. + /// + /// # Returns + /// Returns `Ok(())` if successful, or an error if the operation fails. + pub fn start_destroy(id: AssetId) -> Result<()> { + build_dispatch(START_DESTROY) + .input::() + .output::, true>() + .handle_error_code::() + .call(&(id)) + } + + /// Set the metadata for a token with a given asset ID. + /// + /// # Parameters + /// - `id`: The identifier of the asset to update. + /// - `name`: The user friendly name of this asset. Limited in length by `StringLimit`. + /// - `symbol`: The exchange symbol for this asset. Limited in length by `StringLimit`. + /// - `decimals`: The number of decimals this asset uses to represent one unit. + /// + /// # Returns + /// Returns `Ok(())` if successful, or an error if the operation fails. + pub fn set_metadata(id: AssetId, name: Vec, symbol: Vec, decimals: u8) -> Result<()> { + build_dispatch(SET_METADATA) + .input::<(AssetId, Vec, Vec, u8)>() + .output::, true>() + .handle_error_code::() + .call(&(id, name, symbol, decimals)) + } + + /// Clear the metadata for a token with a given asset ID. + /// + /// # Parameters + /// - `id` - The ID of the asset. + /// + /// # Returns + /// Returns `Ok(())` if successful, or an error if the operation fails. + pub fn clear_metadata(id: AssetId) -> Result<()> { + build_dispatch(CLEAR_METADATA) + .input::() + .output::, true>() + .handle_error_code::() + .call(&(id)) + } + + /// Checks if token with a given asset ID exists. + /// + /// # Parameters + /// - `id` - The ID of the asset. + #[inline] + pub fn asset_exists(id: AssetId) -> Result { + build_read_state(ASSET_EXISTS) + .input::() + .output::, true>() + .handle_error_code::() + .call(&(id)) + } +} /// Represents various errors related to local fungible assets in the Pop API. /// diff --git a/runtime/devnet/src/config/api.rs b/runtime/devnet/src/config/api.rs index b3932a79..bdff6bbb 100644 --- a/runtime/devnet/src/config/api.rs +++ b/runtime/devnet/src/config/api.rs @@ -25,6 +25,10 @@ impl Contains for AllowedApiCalls { | transfer_from { .. } | approve { .. } | increase_allowance { .. } | decrease_allowance { .. } + | create { .. } | set_metadata { .. } + | start_destroy { .. } + | clear_metadata { .. } + | mint { .. } | burn { .. } ) ) } @@ -40,7 +44,7 @@ impl Contains> for AllowedApiCalls { TotalSupply(..) | BalanceOf { .. } | Allowance { .. } | TokenName(..) | TokenSymbol(..) - | TokenDecimals(..) + | TokenDecimals(..) | AssetExists(..) ) ) }