From f72b15abbe11f031c1eb56e3668bcfffbce0019e Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Tue, 30 Jul 2024 12:25:23 +0200 Subject: [PATCH 1/7] feat: create & set_metadata --- pallets/api/src/fungibles/mod.rs | 37 +++ pallets/api/src/fungibles/tests.rs | 33 +- pop-api/examples/balance-transfer/lib.rs | 3 +- pop-api/examples/nfts/lib.rs | 49 +-- pop-api/examples/place-spot-order/lib.rs | 14 +- pop-api/examples/read-runtime-state/lib.rs | 55 +-- .../integration-tests/contracts/.gitignore | 9 + .../contracts/fungibles/lib.rs | 47 +-- .../integration-tests/src/local_fungibles.rs | 203 ++++++----- pop-api/src/v0/assets/fungibles.rs | 314 ++++++++++-------- runtime/devnet/src/config/api.rs | 6 +- 11 files changed, 451 insertions(+), 319 deletions(-) create mode 100755 pop-api/integration-tests/contracts/.gitignore diff --git a/pallets/api/src/fungibles/mod.rs b/pallets/api/src/fungibles/mod.rs index 32f9af67..6cca9e90 100644 --- a/pallets/api/src/fungibles/mod.rs +++ b/pallets/api/src/fungibles/mod.rs @@ -177,6 +177,43 @@ pub mod pallet { let spender = T::Lookup::unlookup(spender); AssetsOf::::approve_transfer(origin, id.into(), spender, value) } + + /// 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. + #[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) + } + + /// 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. + #[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) + } } impl Pallet { diff --git a/pallets/api/src/fungibles/tests.rs b/pallets/api/src/fungibles/tests.rs index c8de1020..3c548bb3 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; @@ -57,6 +59,35 @@ fn increase_allowance_works() { }); } +#[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 set_metadata_works() { + new_test_ext().execute_with(|| { + let name = vec![42]; + let symbol = vec![42]; + let decimals = 42; + create_asset(ALICE, ASSET, 100); + 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 total_supply_works() { new_test_ext().execute_with(|| { 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/fungibles/lib.rs b/pop-api/integration-tests/contracts/fungibles/lib.rs index 1b42fec4..234dca2f 100755 --- a/pop-api/integration-tests/contracts/fungibles/lib.rs +++ b/pop-api/integration-tests/contracts/fungibles/lib.rs @@ -131,40 +131,21 @@ mod fungibles { // - 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 create(&self, id: AssetId, admin: AccountId, min_balance: Balance) -> Result<()> { + api::create(id, admin, min_balance) + } - // #[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 set_metadata( + &self, + id: AssetId, + name: Vec, + symbol: Vec, + decimals: u8, + ) -> Result<()> { + api::set_metadata(id, name, symbol, decimals) + } // // #[ink(message)] // pub fn asset_exists(&self, id: AssetId) -> Result { diff --git a/pop-api/integration-tests/src/local_fungibles.rs b/pop-api/integration-tests/src/local_fungibles.rs index 6de57ca4..6ef2b378 100644 --- a/pop-api/integration-tests/src/local_fungibles.rs +++ b/pop-api/integration-tests/src/local_fungibles.rs @@ -109,29 +109,31 @@ fn increase_allowance( // 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( + 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(); + let result = bare_call(addr, params, 0).expect("should work"); + result +} + +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(); + let result = bare_call(addr, params, 0).expect("should work"); + result +} fn create_asset(owner: AccountId32, asset_id: AssetId, min_balance: Balance) -> AssetId { assert_ok!(Assets::create( @@ -496,6 +498,108 @@ fn token_metadata_works() { }); } +/// 3. Asset Management: +/// - create +/// - start_destroy +/// - destroy_accounts +/// - destroy_approvals +/// - finish_destroy +/// - set_metadata + +#[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!( + decoded::(create(addr.clone(), ASSET_ID, addr.clone(), 1)), + Ok(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!( + decoded::(create(addr.clone(), ASSET_ID, addr.clone(), 1)), + Ok(Module { index: 10, error: 2 }), + ); + // Instantiate a contract with enough balance. + let addr = instantiate(CONTRACT, INIT_VALUE, vec![2]); + assert_eq!( + decoded::(create(addr.clone(), ASSET_ID, BOB, 0)), + Ok(Module { index: 52, error: 7 }), + ); + // The minimal balance for an asset must be non zero. + assert_eq!( + decoded::(create(addr.clone(), ASSET_ID, BOB, 0)), + Ok(Module { index: 52, error: 7 }), + ); + // Create asset successfully. + let result = create(addr.clone(), ASSET_ID, BOB, 1); + assert!(!result.did_revert(), "Contract reverted!"); + // Asset ID is already taken. + assert_eq!( + decoded::(create(addr.clone(), ASSET_ID, BOB, 1)), + Ok(Module { index: 52, error: 5 }), + ); + }); +} + +#[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!( + decoded::(set_metadata(addr.clone(), ASSET_ID, vec![0], vec![0], 0u8)), + Ok(Module { index: 52, error: 3 }), + ); + // Create assets where contract is not the owner. + let asset = create_asset(ALICE, 0, 1); + // No Permission. + assert_eq!( + decoded::(set_metadata(addr.clone(), asset, vec![0], vec![0], 0u8)), + Ok(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!( + decoded::(set_metadata(addr.clone(), ASSET_ID, vec![0], vec![0], 0u8)), + Ok(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!( + decoded::(set_metadata( + addr.clone(), + ASSET_ID, + vec![0; 1000], + vec![0; 1000], + 0u8 + )), + Ok(Module { index: 52, error: 9 }), + ); + // Set metadata successfully. + let result = set_metadata(addr.clone(), ASSET_ID, name, symbol, decimals); + assert!(!result.did_revert(), "Contract reverted!"); + // Asset is not live, i.e. frozen or being destroyed. + start_destroy_asset(addr.clone(), asset); + assert_eq!( + decoded::(set_metadata(addr.clone(), ASSET_ID, vec![0], vec![0], decimals)), + Ok(Module { index: 52, error: 16 }), + ); + }); +} + // #[test] // #[ignore] // fn asset_exists_works() { @@ -572,56 +676,3 @@ fn token_metadata_works() { // ); // }); // } - -// #[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/v0/assets/fungibles.rs b/pop-api/src/v0/assets/fungibles.rs index 0712a80b..23eff2b6 100644 --- a/pop-api/src/v0/assets/fungibles.rs +++ b/pop-api/src/v0/assets/fungibles.rs @@ -3,6 +3,7 @@ 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::*; @@ -30,45 +31,34 @@ fn build_read_state(state_query: u8) -> ChainExtensionMethod<(), (), (), false> 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 DESTROY_ACCOUNTS: u8 = 13; + pub(super) const DESTROY_APPROVALS: u8 = 14; + pub(super) const FINISH_DESTROY: u8 = 15; + pub(super) const SET_METADATA: u8 = 16; + pub(super) const CLEAR_METADATA: u8 = 17; } /// 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 +74,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 +92,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,10 +111,10 @@ pub fn allowance(id: AssetId, owner: AccountId, spender: AccountId) -> Result Result<()> { /// 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`. /// -/// # 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. @@ -160,10 +150,10 @@ pub fn transfer_from(id: AssetId, from: AccountId, to: AccountId, amount: Balanc /// 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. @@ -178,10 +168,10 @@ pub fn approve(id: AssetId, spender: AccountId, amount: Balance) -> Result<()> { /// 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 +186,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. @@ -216,8 +206,8 @@ 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 +222,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,8 +238,8 @@ 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. @@ -263,90 +253,122 @@ 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. + fn start_destroy(id: AssetId) -> Result<()> { + build_dispatch(START_DESTROY) + .input::() + .output::, true>() + .handle_error_code::() + .call(&(id)) + } + + /// Destroy all accounts associated with 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. + fn destroy_accounts(id: AssetId) -> Result<()> { + build_dispatch(DESTROY_ACCOUNTS) + .input::() + .output::, true>() + .handle_error_code::() + .call(&(id)) + } + + /// Destroy all approvals associated with 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. + fn destroy_approvals(id: AssetId) -> Result<()> { + build_dispatch(DESTROY_APPROVALS) + .input::() + .output::, true>() + .handle_error_code::() + .call(&(id)) + } + + /// Complete 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. + fn finish_destroy(id: AssetId) -> Result<()> { + build_dispatch(FINISH_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. + fn clear_metadata(id: AssetId) -> Result<()> { + build_dispatch(CLEAR_METADATA) + .input::() + .output::, true>() + .handle_error_code::() + .call(&(id)) + } + // + // pub fn asset_exists(id: AssetId) -> Result { + // assets::asset_exists(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 884128f2..72204a8c 100644 --- a/runtime/devnet/src/config/api.rs +++ b/runtime/devnet/src/config/api.rs @@ -20,7 +20,11 @@ impl Contains for AllowedApiCalls { use fungibles::Call::*; matches!( c, - RuntimeCall::Fungibles(transfer { .. } | approve { .. } | increase_allowance { .. }) + RuntimeCall::Fungibles( + transfer { .. } + | approve { .. } | increase_allowance { .. } + | create { .. } | set_metadata { .. } + ) ) } } From 7739392b42730187e91743bf7bb1f7db8b877202 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Tue, 30 Jul 2024 17:04:09 +0200 Subject: [PATCH 2/7] feat: rest of asset management --- pallets/api/src/fungibles/mod.rs | 63 ++++++- pallets/api/src/fungibles/tests.rs | 25 +++ .../contracts/fungibles/lib.rs | 27 ++- .../integration-tests/src/local_fungibles.rs | 174 +++++++++++++++++- pop-api/src/v0/assets/fungibles.rs | 10 +- runtime/devnet/src/config/api.rs | 5 + 6 files changed, 295 insertions(+), 9 deletions(-) diff --git a/pallets/api/src/fungibles/mod.rs b/pallets/api/src/fungibles/mod.rs index 6cca9e90..c0561c4b 100644 --- a/pallets/api/src/fungibles/mod.rs +++ b/pallets/api/src/fungibles/mod.rs @@ -21,6 +21,7 @@ type AssetIdParameterOf = >>:: type AssetsOf = pallet_assets::Pallet>; type AssetsInstanceOf = ::AssetsInstance; type AssetsWeightInfoOf = >>::WeightInfo; +type RemoveItemsLimitOf = >>::RemoveItemsLimit; type BalanceOf = > as Inspect< ::AccountId, >>::Balance; @@ -196,12 +197,64 @@ pub mod pallet { AssetsOf::::create(origin, id.into(), admin, min_balance) } + /// 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. + #[pallet::call_index(12)] + #[pallet::weight(AssetsWeightInfoOf::::start_destroy())] + pub fn start_destroy(origin: OriginFor, id: AssetIdOf) -> DispatchResult { + AssetsOf::::start_destroy(origin, id.into()) + } + + /// Destroy all accounts associated with a token with a given asset ID. + /// + /// # Parameters + /// - `id` - The ID of the asset. + // TODO: weight function + #[pallet::call_index(13)] + #[pallet::weight(AssetsWeightInfoOf::::destroy_accounts(RemoveItemsLimitOf::::get() / 6))] + pub fn destroy_accounts( + origin: OriginFor, + id: AssetIdOf, + ) -> DispatchResultWithPostInfo { + AssetsOf::::destroy_accounts(origin, id.into()) + } + + /// Destroy all approvals associated with a token with a given asset ID. + /// + /// # Parameters + /// - `id` - The ID of the asset. + // TODO: weight function + #[pallet::call_index(14)] + #[pallet::weight(AssetsWeightInfoOf::::destroy_approvals(RemoveItemsLimitOf::::get() / 4))] + pub fn destroy_approvals( + origin: OriginFor, + id: AssetIdOf, + ) -> DispatchResultWithPostInfo { + AssetsOf::::destroy_approvals(origin, id.into()) + } + + /// Complete the process of destroying a token with a given asset ID. + /// + /// # Parameters + /// - `id` - The ID of the asset. + #[pallet::call_index(15)] + #[pallet::weight(AssetsWeightInfoOf::::finish_destroy())] + pub fn finish_destroy(origin: OriginFor, id: AssetIdOf) -> DispatchResult { + AssetsOf::::finish_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 `StringLimit`. - /// - `symbol`: The exchange symbol for this asset. Limited in length by `StringLimit`. + /// - `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))] @@ -214,6 +267,12 @@ pub mod pallet { ) -> DispatchResult { AssetsOf::::set_metadata(origin, id.into(), name, symbol, decimals) } + + #[pallet::call_index(17)] + #[pallet::weight(AssetsWeightInfoOf::::clear_metadata())] + pub fn clear_metadata(origin: OriginFor, id: AssetIdOf) -> DispatchResult { + AssetsOf::::clear_metadata(origin, id.into()) + } } impl Pallet { diff --git a/pallets/api/src/fungibles/tests.rs b/pallets/api/src/fungibles/tests.rs index 3c548bb3..735aadf1 100644 --- a/pallets/api/src/fungibles/tests.rs +++ b/pallets/api/src/fungibles/tests.rs @@ -68,6 +68,17 @@ fn create_works() { }); } +#[test] +fn destroy_asset_works() { + new_test_ext().execute_with(|| { + create_asset(ALICE, ASSET, 100); + assert_ok!(Fungibles::start_destroy(signed(ALICE), ASSET)); + assert_ok!(Fungibles::destroy_accounts(signed(ALICE), ASSET)); + assert_ok!(Fungibles::destroy_approvals(signed(ALICE), ASSET)); + assert_ok!(Fungibles::finish_destroy(signed(ALICE), ASSET)); + }); +} + #[test] fn set_metadata_works() { new_test_ext().execute_with(|| { @@ -88,6 +99,20 @@ fn set_metadata_works() { }); } +#[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 total_supply_works() { new_test_ext().execute_with(|| { diff --git a/pop-api/integration-tests/contracts/fungibles/lib.rs b/pop-api/integration-tests/contracts/fungibles/lib.rs index 234dca2f..105d5564 100755 --- a/pop-api/integration-tests/contracts/fungibles/lib.rs +++ b/pop-api/integration-tests/contracts/fungibles/lib.rs @@ -136,6 +136,26 @@ mod fungibles { api::create(id, admin, min_balance) } + #[ink(message)] + pub fn start_destroy(&self, id: AssetId) -> Result<()> { + api::start_destroy(id) + } + + #[ink(message)] + pub fn destroy_accounts(&self, id: AssetId) -> Result<()> { + api::destroy_accounts(id) + } + + #[ink(message)] + pub fn destroy_approvals(&self, id: AssetId) -> Result<()> { + api::destroy_approvals(id) + } + + #[ink(message)] + pub fn finish_destroy(&self, id: AssetId) -> Result<()> { + api::finish_destroy(id) + } + #[ink(message)] pub fn set_metadata( &self, @@ -146,7 +166,12 @@ mod fungibles { ) -> 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).map_err(|e| e.into()) diff --git a/pop-api/integration-tests/src/local_fungibles.rs b/pop-api/integration-tests/src/local_fungibles.rs index 6ef2b378..33d43c40 100644 --- a/pop-api/integration-tests/src/local_fungibles.rs +++ b/pop-api/integration-tests/src/local_fungibles.rs @@ -121,6 +121,34 @@ fn create( result } +fn start_destroy(addr: AccountId32, asset_id: AssetId) -> ExecReturnValue { + let function = function_selector("start_destroy"); + let params = [function, asset_id.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + result +} + +fn destroy_accounts(addr: AccountId32, asset_id: AssetId) -> ExecReturnValue { + let function = function_selector("destroy_accounts"); + let params = [function, asset_id.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + result +} + +fn destroy_approvals(addr: AccountId32, asset_id: AssetId) -> ExecReturnValue { + let function = function_selector("destroy_approvals"); + let params = [function, asset_id.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + result +} + +fn finish_destroy(addr: AccountId32, asset_id: AssetId) -> ExecReturnValue { + let function = function_selector("finish_destroy"); + let params = [function, asset_id.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + result +} + fn set_metadata( addr: AccountId32, asset_id: AssetId, @@ -135,6 +163,13 @@ fn set_metadata( result } +fn clear_metadata(addr: AccountId32, asset_id: AssetId) -> ExecReturnValue { + let function = function_selector("clear_metadata"); + let params = [function, asset_id.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + result +} + fn create_asset(owner: AccountId32, asset_id: AssetId, min_balance: Balance) -> AssetId { assert_ok!(Assets::create( RuntimeOrigin::signed(owner.clone()), @@ -205,7 +240,7 @@ fn create_asset_and_set_metadata( name: Vec, symbol: Vec, decimals: u8, -) { +) -> AssetId { assert_ok!(Assets::create( RuntimeOrigin::signed(owner.clone()), asset_id.into(), @@ -213,6 +248,7 @@ fn create_asset_and_set_metadata( 100 )); set_metadata_asset(owner, asset_id, name, symbol, decimals); + asset_id } // Set metadata of an asset. @@ -546,6 +582,95 @@ fn create_works() { }); } +#[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!( + decoded::(start_destroy(addr.clone(), ASSET_ID)), + Ok(Module { index: 52, error: 3 }), + ); + // Create assets where contract is not the owner. + let asset = create_asset(ALICE, 0, 1); + // No Permission. + assert_eq!( + decoded::(start_destroy(addr.clone(), asset)), + Ok(Module { index: 52, error: 2 }), + ); + let asset = create_asset(addr.clone(), ASSET_ID, 1); + let result = start_destroy(addr.clone(), asset); + assert!(!result.did_revert(), "Contract reverted!"); + }); +} + +#[test] +fn destroy_accounts_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!( + decoded::(destroy_accounts(addr.clone(), ASSET_ID)), + Ok(Module { index: 52, error: 3 }), + ); + let asset = create_asset(addr.clone(), ASSET_ID, 1); + // The destroy process hasn't been started. + assert_eq!( + decoded::(destroy_accounts(addr.clone(), asset)), + Ok(Module { index: 52, error: 17 }), + ); + start_destroy_asset(addr.clone(), asset); + let result = destroy_accounts(addr.clone(), asset); + assert!(!result.did_revert(), "Contract reverted!"); + }); +} + +#[test] +fn destroy_approvals_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!( + decoded::(destroy_approvals(addr.clone(), ASSET_ID)), + Ok(Module { index: 52, error: 3 }), + ); + let asset = create_asset(addr.clone(), ASSET_ID, 1); + // The destroy process hasn't been started. + assert_eq!( + decoded::(destroy_approvals(addr.clone(), asset)), + Ok(Module { index: 52, error: 17 }), + ); + start_destroy_asset(addr.clone(), asset); + let result = destroy_approvals(addr.clone(), asset); + assert!(!result.did_revert(), "Contract reverted!"); + }); +} + +#[test] +fn finish_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!( + decoded::(finish_destroy(addr.clone(), ASSET_ID)), + Ok(Module { index: 52, error: 3 }), + ); + let asset = create_asset(addr.clone(), ASSET_ID, 1); + // The destroy process hasn't been started. + assert_eq!( + decoded::(finish_destroy(addr.clone(), asset)), + Ok(Module { index: 52, error: 17 }), + ); + start_destroy_asset(addr.clone(), asset); + let result = finish_destroy(addr.clone(), asset); + assert!(!result.did_revert(), "Contract reverted!"); + }); +} + #[test] fn set_metadata_works() { new_test_ext().execute_with(|| { @@ -593,6 +718,53 @@ fn set_metadata_works() { assert!(!result.did_revert(), "Contract reverted!"); // Asset is not live, i.e. frozen or being destroyed. start_destroy_asset(addr.clone(), asset); + assert_eq!( + decoded::(set_metadata(addr.clone(), ASSET_ID, vec![0], vec![0], 0)), + Ok(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!( + decoded::(clear_metadata(addr.clone(), 0)), + Ok(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!( + decoded::(clear_metadata(addr.clone(), asset)), + Ok(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!( + decoded::(clear_metadata(addr.clone(), asset)), + Ok(Module { index: 52, error: 16 }), + ); + thaw_asset(addr.clone(), asset); + // No metadata set. + assert_eq!( + decoded::(clear_metadata(addr.clone(), asset)), + Ok(Module { index: 52, error: 3 }), + ); + set_metadata_asset(addr.clone(), asset, name, symbol, decimals); + // Clear metadata successfully. + let result = clear_metadata(addr.clone(), ASSET_ID); + assert!(!result.did_revert(), "Contract reverted!"); + // Asset is not live, i.e. frozen or being destroyed. + start_destroy_asset(addr.clone(), asset); assert_eq!( decoded::(set_metadata(addr.clone(), ASSET_ID, vec![0], vec![0], decimals)), Ok(Module { index: 52, error: 16 }), diff --git a/pop-api/src/v0/assets/fungibles.rs b/pop-api/src/v0/assets/fungibles.rs index 23eff2b6..ec2b9059 100644 --- a/pop-api/src/v0/assets/fungibles.rs +++ b/pop-api/src/v0/assets/fungibles.rs @@ -279,7 +279,7 @@ pub mod asset_management { /// /// # Returns /// Returns `Ok(())` if successful, or an error if the operation fails. - fn start_destroy(id: AssetId) -> Result<()> { + pub fn start_destroy(id: AssetId) -> Result<()> { build_dispatch(START_DESTROY) .input::() .output::, true>() @@ -294,7 +294,7 @@ pub mod asset_management { /// /// # Returns /// Returns `Ok(())` if successful, or an error if the operation fails. - fn destroy_accounts(id: AssetId) -> Result<()> { + pub fn destroy_accounts(id: AssetId) -> Result<()> { build_dispatch(DESTROY_ACCOUNTS) .input::() .output::, true>() @@ -309,7 +309,7 @@ pub mod asset_management { /// /// # Returns /// Returns `Ok(())` if successful, or an error if the operation fails. - fn destroy_approvals(id: AssetId) -> Result<()> { + pub fn destroy_approvals(id: AssetId) -> Result<()> { build_dispatch(DESTROY_APPROVALS) .input::() .output::, true>() @@ -324,7 +324,7 @@ pub mod asset_management { /// /// # Returns /// Returns `Ok(())` if successful, or an error if the operation fails. - fn finish_destroy(id: AssetId) -> Result<()> { + pub fn finish_destroy(id: AssetId) -> Result<()> { build_dispatch(FINISH_DESTROY) .input::() .output::, true>() @@ -357,7 +357,7 @@ pub mod asset_management { /// /// # Returns /// Returns `Ok(())` if successful, or an error if the operation fails. - fn clear_metadata(id: AssetId) -> Result<()> { + pub fn clear_metadata(id: AssetId) -> Result<()> { build_dispatch(CLEAR_METADATA) .input::() .output::, true>() diff --git a/runtime/devnet/src/config/api.rs b/runtime/devnet/src/config/api.rs index 72204a8c..bc38a3c3 100644 --- a/runtime/devnet/src/config/api.rs +++ b/runtime/devnet/src/config/api.rs @@ -24,6 +24,11 @@ impl Contains for AllowedApiCalls { transfer { .. } | approve { .. } | increase_allowance { .. } | create { .. } | set_metadata { .. } + | start_destroy { .. } + | destroy_accounts { .. } + | destroy_approvals { .. } + | finish_destroy { .. } + | clear_metadata { .. } ) ) } From d4f7f99445509f560f3cab304fab3be6300072e0 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Tue, 30 Jul 2024 17:15:32 +0200 Subject: [PATCH 3/7] style: pub function docs --- pallets/api/src/fungibles/mod.rs | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pallets/api/src/fungibles/mod.rs b/pallets/api/src/fungibles/mod.rs index c0561c4b..9579a66c 100644 --- a/pallets/api/src/fungibles/mod.rs +++ b/pallets/api/src/fungibles/mod.rs @@ -96,9 +96,9 @@ 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( @@ -114,9 +114,9 @@ pub mod pallet { /// 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( @@ -164,9 +164,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( @@ -181,10 +181,10 @@ pub mod pallet { /// 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. + /// # 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( @@ -199,10 +199,10 @@ pub mod pallet { /// 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. + /// # 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(12)] #[pallet::weight(AssetsWeightInfoOf::::start_destroy())] pub fn start_destroy(origin: OriginFor, id: AssetIdOf) -> DispatchResult { @@ -282,7 +282,7 @@ pub mod pallet { /// encoded result. /// /// # Parameter - /// * `value` - An instance of `Read`, which specifies the type of state query and + /// - `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::*; From 6f4da1852fd7923a1616cc2481ecb3c731051d25 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Tue, 30 Jul 2024 18:40:08 +0200 Subject: [PATCH 4/7] fix: remove unnecessary api functions --- pallets/api/src/fungibles/mod.rs | 39 --------- pallets/api/src/fungibles/tests.rs | 5 +- .../contracts/fungibles/lib.rs | 15 ---- .../integration-tests/src/local_fungibles.rs | 87 ------------------- pop-api/src/v0/assets/fungibles.rs | 48 ---------- runtime/devnet/src/config/api.rs | 3 - 6 files changed, 1 insertion(+), 196 deletions(-) diff --git a/pallets/api/src/fungibles/mod.rs b/pallets/api/src/fungibles/mod.rs index 9579a66c..00b71401 100644 --- a/pallets/api/src/fungibles/mod.rs +++ b/pallets/api/src/fungibles/mod.rs @@ -21,7 +21,6 @@ type AssetIdParameterOf = >>:: type AssetsOf = pallet_assets::Pallet>; type AssetsInstanceOf = ::AssetsInstance; type AssetsWeightInfoOf = >>::WeightInfo; -type RemoveItemsLimitOf = >>::RemoveItemsLimit; type BalanceOf = > as Inspect< ::AccountId, >>::Balance; @@ -209,44 +208,6 @@ pub mod pallet { AssetsOf::::start_destroy(origin, id.into()) } - /// Destroy all accounts associated with a token with a given asset ID. - /// - /// # Parameters - /// - `id` - The ID of the asset. - // TODO: weight function - #[pallet::call_index(13)] - #[pallet::weight(AssetsWeightInfoOf::::destroy_accounts(RemoveItemsLimitOf::::get() / 6))] - pub fn destroy_accounts( - origin: OriginFor, - id: AssetIdOf, - ) -> DispatchResultWithPostInfo { - AssetsOf::::destroy_accounts(origin, id.into()) - } - - /// Destroy all approvals associated with a token with a given asset ID. - /// - /// # Parameters - /// - `id` - The ID of the asset. - // TODO: weight function - #[pallet::call_index(14)] - #[pallet::weight(AssetsWeightInfoOf::::destroy_approvals(RemoveItemsLimitOf::::get() / 4))] - pub fn destroy_approvals( - origin: OriginFor, - id: AssetIdOf, - ) -> DispatchResultWithPostInfo { - AssetsOf::::destroy_approvals(origin, id.into()) - } - - /// Complete the process of destroying a token with a given asset ID. - /// - /// # Parameters - /// - `id` - The ID of the asset. - #[pallet::call_index(15)] - #[pallet::weight(AssetsWeightInfoOf::::finish_destroy())] - pub fn finish_destroy(origin: OriginFor, id: AssetIdOf) -> DispatchResult { - AssetsOf::::finish_destroy(origin, id.into()) - } - /// Set the metadata for a token with a given asset ID. /// /// # Parameters diff --git a/pallets/api/src/fungibles/tests.rs b/pallets/api/src/fungibles/tests.rs index 735aadf1..5ef08f63 100644 --- a/pallets/api/src/fungibles/tests.rs +++ b/pallets/api/src/fungibles/tests.rs @@ -69,13 +69,10 @@ fn create_works() { } #[test] -fn destroy_asset_works() { +fn start_destroy_works() { new_test_ext().execute_with(|| { create_asset(ALICE, ASSET, 100); assert_ok!(Fungibles::start_destroy(signed(ALICE), ASSET)); - assert_ok!(Fungibles::destroy_accounts(signed(ALICE), ASSET)); - assert_ok!(Fungibles::destroy_approvals(signed(ALICE), ASSET)); - assert_ok!(Fungibles::finish_destroy(signed(ALICE), ASSET)); }); } diff --git a/pop-api/integration-tests/contracts/fungibles/lib.rs b/pop-api/integration-tests/contracts/fungibles/lib.rs index 105d5564..b3728560 100755 --- a/pop-api/integration-tests/contracts/fungibles/lib.rs +++ b/pop-api/integration-tests/contracts/fungibles/lib.rs @@ -141,21 +141,6 @@ mod fungibles { api::start_destroy(id) } - #[ink(message)] - pub fn destroy_accounts(&self, id: AssetId) -> Result<()> { - api::destroy_accounts(id) - } - - #[ink(message)] - pub fn destroy_approvals(&self, id: AssetId) -> Result<()> { - api::destroy_approvals(id) - } - - #[ink(message)] - pub fn finish_destroy(&self, id: AssetId) -> Result<()> { - api::finish_destroy(id) - } - #[ink(message)] pub fn set_metadata( &self, diff --git a/pop-api/integration-tests/src/local_fungibles.rs b/pop-api/integration-tests/src/local_fungibles.rs index 33d43c40..096c16cf 100644 --- a/pop-api/integration-tests/src/local_fungibles.rs +++ b/pop-api/integration-tests/src/local_fungibles.rs @@ -128,27 +128,6 @@ fn start_destroy(addr: AccountId32, asset_id: AssetId) -> ExecReturnValue { result } -fn destroy_accounts(addr: AccountId32, asset_id: AssetId) -> ExecReturnValue { - let function = function_selector("destroy_accounts"); - let params = [function, asset_id.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - result -} - -fn destroy_approvals(addr: AccountId32, asset_id: AssetId) -> ExecReturnValue { - let function = function_selector("destroy_approvals"); - let params = [function, asset_id.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - result -} - -fn finish_destroy(addr: AccountId32, asset_id: AssetId) -> ExecReturnValue { - let function = function_selector("finish_destroy"); - let params = [function, asset_id.encode()].concat(); - let result = bare_call(addr, params, 0).expect("should work"); - result -} - fn set_metadata( addr: AccountId32, asset_id: AssetId, @@ -605,72 +584,6 @@ fn start_destroy_works() { }); } -#[test] -fn destroy_accounts_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!( - decoded::(destroy_accounts(addr.clone(), ASSET_ID)), - Ok(Module { index: 52, error: 3 }), - ); - let asset = create_asset(addr.clone(), ASSET_ID, 1); - // The destroy process hasn't been started. - assert_eq!( - decoded::(destroy_accounts(addr.clone(), asset)), - Ok(Module { index: 52, error: 17 }), - ); - start_destroy_asset(addr.clone(), asset); - let result = destroy_accounts(addr.clone(), asset); - assert!(!result.did_revert(), "Contract reverted!"); - }); -} - -#[test] -fn destroy_approvals_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!( - decoded::(destroy_approvals(addr.clone(), ASSET_ID)), - Ok(Module { index: 52, error: 3 }), - ); - let asset = create_asset(addr.clone(), ASSET_ID, 1); - // The destroy process hasn't been started. - assert_eq!( - decoded::(destroy_approvals(addr.clone(), asset)), - Ok(Module { index: 52, error: 17 }), - ); - start_destroy_asset(addr.clone(), asset); - let result = destroy_approvals(addr.clone(), asset); - assert!(!result.did_revert(), "Contract reverted!"); - }); -} - -#[test] -fn finish_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!( - decoded::(finish_destroy(addr.clone(), ASSET_ID)), - Ok(Module { index: 52, error: 3 }), - ); - let asset = create_asset(addr.clone(), ASSET_ID, 1); - // The destroy process hasn't been started. - assert_eq!( - decoded::(finish_destroy(addr.clone(), asset)), - Ok(Module { index: 52, error: 17 }), - ); - start_destroy_asset(addr.clone(), asset); - let result = finish_destroy(addr.clone(), asset); - assert!(!result.did_revert(), "Contract reverted!"); - }); -} - #[test] fn set_metadata_works() { new_test_ext().execute_with(|| { diff --git a/pop-api/src/v0/assets/fungibles.rs b/pop-api/src/v0/assets/fungibles.rs index ec2b9059..6f9774d3 100644 --- a/pop-api/src/v0/assets/fungibles.rs +++ b/pop-api/src/v0/assets/fungibles.rs @@ -48,9 +48,6 @@ mod constants { /// 3. Asset Management: pub(super) const CREATE: u8 = 11; pub(super) const START_DESTROY: u8 = 12; - pub(super) const DESTROY_ACCOUNTS: u8 = 13; - pub(super) const DESTROY_APPROVALS: u8 = 14; - pub(super) const FINISH_DESTROY: u8 = 15; pub(super) const SET_METADATA: u8 = 16; pub(super) const CLEAR_METADATA: u8 = 17; } @@ -287,51 +284,6 @@ pub mod asset_management { .call(&(id)) } - /// Destroy all accounts associated with 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 destroy_accounts(id: AssetId) -> Result<()> { - build_dispatch(DESTROY_ACCOUNTS) - .input::() - .output::, true>() - .handle_error_code::() - .call(&(id)) - } - - /// Destroy all approvals associated with 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 destroy_approvals(id: AssetId) -> Result<()> { - build_dispatch(DESTROY_APPROVALS) - .input::() - .output::, true>() - .handle_error_code::() - .call(&(id)) - } - - /// Complete 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 finish_destroy(id: AssetId) -> Result<()> { - build_dispatch(FINISH_DESTROY) - .input::() - .output::, true>() - .handle_error_code::() - .call(&(id)) - } - /// Set the metadata for a token with a given asset ID. /// /// # Parameters diff --git a/runtime/devnet/src/config/api.rs b/runtime/devnet/src/config/api.rs index bc38a3c3..1f6bdfe8 100644 --- a/runtime/devnet/src/config/api.rs +++ b/runtime/devnet/src/config/api.rs @@ -25,9 +25,6 @@ impl Contains for AllowedApiCalls { | approve { .. } | increase_allowance { .. } | create { .. } | set_metadata { .. } | start_destroy { .. } - | destroy_accounts { .. } - | destroy_approvals { .. } - | finish_destroy { .. } | clear_metadata { .. } ) ) From 584b9c6db5e3db4dbf42358bea97a28d29acf4ee Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Wed, 31 Jul 2024 16:55:21 +0200 Subject: [PATCH 5/7] feat: create asset in constructor --- pallets/api/src/fungibles/mod.rs | 4 + pallets/api/src/fungibles/tests.rs | 8 + .../create_token_in_constructor/Cargo.toml | 21 ++ .../create_token_in_constructor/lib.rs | 45 +++ .../contracts/fungibles/lib.rs | 9 +- .../{local_fungibles.rs => fungibles/mod.rs} | 340 ++---------------- .../integration-tests/src/fungibles/utils.rs | 333 +++++++++++++++++ pop-api/integration-tests/src/lib.rs | 2 +- pop-api/src/v0/assets/fungibles.rs | 18 +- runtime/devnet/src/config/api.rs | 2 +- 10 files changed, 460 insertions(+), 322 deletions(-) create mode 100755 pop-api/integration-tests/contracts/create_token_in_constructor/Cargo.toml create mode 100755 pop-api/integration-tests/contracts/create_token_in_constructor/lib.rs rename pop-api/integration-tests/src/{local_fungibles.rs => fungibles/mod.rs} (64%) create mode 100644 pop-api/integration-tests/src/fungibles/utils.rs diff --git a/pallets/api/src/fungibles/mod.rs b/pallets/api/src/fungibles/mod.rs index 1e0fcbe9..73835b17 100644 --- a/pallets/api/src/fungibles/mod.rs +++ b/pallets/api/src/fungibles/mod.rs @@ -75,6 +75,9 @@ pub mod pallet { /// Token decimals for a given asset ID. #[codec(index = 10)] TokenDecimals(AssetIdOf), + /// Check if token exists for a given asset ID. + #[codec(index = 18)] + AssetExists(AssetIdOf), } /// Configure the pallet by specifying the parameters and types on which it depends. @@ -319,6 +322,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 558c8073..b464a13a 100644 --- a/pallets/api/src/fungibles/tests.rs +++ b/pallets/api/src/fungibles/tests.rs @@ -190,6 +190,14 @@ fn token_metadata_works() { }); } +#[test] +fn asset_exists_works() { + new_test_ext().execute_with(|| { + create_asset(ALICE, ASSET, 100); + assert_eq!(Assets::asset_exists(ASSET).encode(), Fungibles::read_state(AssetExists(ASSET))); + }); +} + fn signed(account: AccountId) -> RuntimeOrigin { RuntimeOrigin::signed(account) } 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..3a800382 --- /dev/null +++ b/pop-api/integration-tests/contracts/create_token_in_constructor/lib.rs @@ -0,0 +1,45 @@ +#![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 }; + let account = contract.env().account_id(); + api::create(id, account, 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 b3728560..888dd86d 100755 --- a/pop-api/integration-tests/contracts/fungibles/lib.rs +++ b/pop-api/integration-tests/contracts/fungibles/lib.rs @@ -157,11 +157,10 @@ mod fungibles { api::clear_metadata(id) } - // #[ink(message)] - // pub fn asset_exists(&self, id: AssetId) -> Result { - // // api::asset_exists(id).map_err(|e| e.into()) - // api::asset_exists(id) - // } + #[ink(message)] + pub fn asset_exists(&self, id: AssetId) -> Result { + api::asset_exists(id) + } } #[cfg(test)] diff --git a/pop-api/integration-tests/src/local_fungibles.rs b/pop-api/integration-tests/src/fungibles/mod.rs similarity index 64% rename from pop-api/integration-tests/src/local_fungibles.rs rename to pop-api/integration-tests/src/fungibles/mod.rs index 1a1ea3bf..ca3922b3 100644 --- a/pop-api/integration-tests/src/local_fungibles.rs +++ b/pop-api/integration-tests/src/fungibles/mod.rs @@ -4,6 +4,9 @@ use pop_primitives::error::{ Error::{self, *}, TokenError::*, }; +use utils::*; + +mod utils; const ASSET_ID: AssetId = 1; const CONTRACT: &str = "contracts/fungibles/target/ink/fungibles.wasm"; @@ -315,6 +318,7 @@ fn token_metadata_works() { /// - destroy_approvals /// - finish_destroy /// - set_metadata +/// - asset_exists #[test] fn create_works() { @@ -348,6 +352,17 @@ fn create_works() { }); } +#[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"; + let addr = instantiate_and_create_fungible(contract, ASSET_ID, 1); + assert_eq!(asset_exists(addr, ASSET_ID), Ok(Assets::asset_exists(ASSET_ID))); + }); +} + #[test] fn start_destroy_works() { new_test_ext().execute_with(|| { @@ -446,22 +461,20 @@ fn clear_metadata_works() { }); } -// #[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] +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] // #[ignore] @@ -522,298 +535,3 @@ fn clear_metadata_works() { // ); // }); // } - -fn decoded(result: ExecReturnValue) -> Result { - ::decode(&mut &result.data[1..]).map_err(|_| result) -} - -// Call total_supply contract message. -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)) -} - -// Call balance_of contract message. -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)) -} - -// Call allowance contract message. -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)) -} - -// Call token_name contract message. -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)) -} - -// Call token_symbol contract message. -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)) -} - -// Call token_decimals contract message. -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)) -} - -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)) -} - -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)) -} - -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)) -} - -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)) -} - -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)) -} - -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)) -} - -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), - } -} - -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)) -} - -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)) -} - -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 { - 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. -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, -) -> 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. -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, - ) -} 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..555bfd50 --- /dev/null +++ b/pop-api/integration-tests/src/fungibles/utils.rs @@ -0,0 +1,333 @@ +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 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, +) -> AccountId32 { + 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 + .unwrap(); + assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); + result.account_id +} 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/src/v0/assets/fungibles.rs b/pop-api/src/v0/assets/fungibles.rs index 844b08b1..be8d44af 100644 --- a/pop-api/src/v0/assets/fungibles.rs +++ b/pop-api/src/v0/assets/fungibles.rs @@ -50,6 +50,7 @@ mod constants { 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; } /// Returns the total token supply for a given asset ID. @@ -316,10 +317,19 @@ pub mod asset_management { .handle_error_code::() .call(&(id)) } - // - // pub fn asset_exists(id: AssetId) -> Result { - // assets::asset_exists(id) - // } + + /// Checks if token exists for a given asset ID. + /// + /// # 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 14574ffc..66c6c58b 100644 --- a/runtime/devnet/src/config/api.rs +++ b/runtime/devnet/src/config/api.rs @@ -43,7 +43,7 @@ impl Contains> for AllowedApiCalls { TotalSupply(..) | BalanceOf { .. } | Allowance { .. } | TokenName(..) | TokenSymbol(..) - | TokenDecimals(..) + | TokenDecimals(..) | AssetExists(..) ) ) } From 772514c10bde521545e6adfa18870deb2550ec7b Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Wed, 31 Jul 2024 17:33:08 +0200 Subject: [PATCH 6/7] refactor: IT create token in constructor --- pallets/api/src/fungibles/mod.rs | 2 +- .../contracts/create_token_in_constructor/lib.rs | 5 +++-- pop-api/integration-tests/src/fungibles/mod.rs | 11 +++++++++-- pop-api/integration-tests/src/fungibles/utils.rs | 9 +++++---- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/pallets/api/src/fungibles/mod.rs b/pallets/api/src/fungibles/mod.rs index 73835b17..8c2ae26b 100644 --- a/pallets/api/src/fungibles/mod.rs +++ b/pallets/api/src/fungibles/mod.rs @@ -303,7 +303,7 @@ pub mod pallet { /// /// # Parameter /// - `value` - An instance of `Read`, which specifies the type of state query and - /// the associated parameters. + /// the associated parameters. pub fn read_state(value: Read) -> Vec { use Read::*; 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 index 3a800382..e9e5d127 100755 --- a/pop-api/integration-tests/contracts/create_token_in_constructor/lib.rs +++ b/pop-api/integration-tests/contracts/create_token_in_constructor/lib.rs @@ -22,8 +22,9 @@ mod create_token_in_constructor { #[ink(constructor, payable)] pub fn new(id: AssetId, min_balance: Balance) -> Result { let contract = Self { id }; - let account = contract.env().account_id(); - api::create(id, account, min_balance)?; + // 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) } diff --git a/pop-api/integration-tests/src/fungibles/mod.rs b/pop-api/integration-tests/src/fungibles/mod.rs index ca3922b3..81a276a9 100644 --- a/pop-api/integration-tests/src/fungibles/mod.rs +++ b/pop-api/integration-tests/src/fungibles/mod.rs @@ -358,8 +358,15 @@ fn instantiate_and_create_fungible_works() { let _ = env_logger::try_init(); let contract = "contracts/create_token_in_constructor/target/ink/create_token_in_constructor.wasm"; - let addr = instantiate_and_create_fungible(contract, ASSET_ID, 1); - assert_eq!(asset_exists(addr, ASSET_ID), Ok(Assets::asset_exists(ASSET_ID))); + // 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)); }); } diff --git a/pop-api/integration-tests/src/fungibles/utils.rs b/pop-api/integration-tests/src/fungibles/utils.rs index 555bfd50..c4571aec 100644 --- a/pop-api/integration-tests/src/fungibles/utils.rs +++ b/pop-api/integration-tests/src/fungibles/utils.rs @@ -310,7 +310,7 @@ pub(super) fn instantiate_and_create_fungible( contract: &str, asset_id: AssetId, min_balance: Balance, -) -> AccountId32 { +) -> Result<(), Error> { let function = function_selector("new"); let input = [function, asset_id.encode(), min_balance.encode()].concat(); let (wasm_binary, _) = @@ -327,7 +327,8 @@ pub(super) fn instantiate_and_create_fungible( CollectEvents::Skip, ) .result - .unwrap(); - assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); - result.account_id + .expect("should work") + .result; + decoded::>(result.clone()) + .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) } From 8816c3c64ad8ad3a4dcfed1a6b76e13c46046b29 Mon Sep 17 00:00:00 2001 From: Daan van der Plas <93204684+Daanvdplas@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:21:32 +0200 Subject: [PATCH 7/7] feat: burn and mint (#150) --- pallets/api/src/fungibles/mod.rs | 36 ++++++ pallets/api/src/fungibles/tests.rs | 36 +++++- .../contracts/fungibles/lib.rs | 29 +++-- .../integration-tests/src/fungibles/mod.rs | 121 +++++++++--------- .../integration-tests/src/fungibles/utils.rs | 26 ++++ pop-api/src/v0/assets/fungibles.rs | 41 ++++++ runtime/devnet/src/config/api.rs | 1 + 7 files changed, 217 insertions(+), 73 deletions(-) diff --git a/pallets/api/src/fungibles/mod.rs b/pallets/api/src/fungibles/mod.rs index 8c2ae26b..1001663b 100644 --- a/pallets/api/src/fungibles/mod.rs +++ b/pallets/api/src/fungibles/mod.rs @@ -293,6 +293,42 @@ pub mod pallet { pub fn clear_metadata(origin: OriginFor, id: AssetIdOf) -> DispatchResult { AssetsOf::::clear_metadata(origin, id.into()) } + + /// Creates `amount` tokens and assigns them to `account`, increasing the total supply. + /// + /// # Parameters + /// - `id` - The ID of the asset. + /// - `owner` - The account to be credited with the created tokens. + /// - `value` - The number of tokens to mint. + #[pallet::call_index(19)] + #[pallet::weight(AssetsWeightInfoOf::::mint())] + pub fn mint( + origin: OriginFor, + id: AssetIdOf, + account: AccountIdOf, + amount: BalanceOf, + ) -> DispatchResult { + let account = T::Lookup::unlookup(account); + AssetsOf::::mint(origin, id.into(), account, amount) + } + + /// Destroys `amount` tokens from `account`, reducing the total supply. + /// + /// # Parameters + /// - `id` - The ID of the asset. + /// - `owner` - The account from which the tokens will be destroyed. + /// - `value` - The number of tokens to destroy. + #[pallet::call_index(20)] + #[pallet::weight(AssetsWeightInfoOf::::burn())] + pub fn burn( + origin: OriginFor, + id: AssetIdOf, + account: AccountIdOf, + amount: BalanceOf, + ) -> DispatchResult { + let account = T::Lookup::unlookup(account); + AssetsOf::::burn(origin, id.into(), account, amount) + } } impl Pallet { diff --git a/pallets/api/src/fungibles/tests.rs b/pallets/api/src/fungibles/tests.rs index b464a13a..d6cc87e0 100644 --- a/pallets/api/src/fungibles/tests.rs +++ b/pallets/api/src/fungibles/tests.rs @@ -108,7 +108,7 @@ fn create_works() { #[test] fn start_destroy_works() { new_test_ext().execute_with(|| { - create_asset(ALICE, ASSET, 100); + create_asset(ALICE, ASSET); assert_ok!(Fungibles::start_destroy(signed(ALICE), ASSET)); }); } @@ -119,7 +119,7 @@ fn set_metadata_works() { let name = vec![42]; let symbol = vec![42]; let decimals = 42; - create_asset(ALICE, ASSET, 100); + create_asset(ALICE, ASSET); assert_ok!(Fungibles::set_metadata( signed(ALICE), ASSET, @@ -147,6 +147,30 @@ fn clear_metadata_works() { }); } +#[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(|| { @@ -193,7 +217,7 @@ fn token_metadata_works() { #[test] fn asset_exists_works() { new_test_ext().execute_with(|| { - create_asset(ALICE, ASSET, 100); + create_asset(ALICE, ASSET); assert_eq!(Assets::asset_exists(ASSET).encode(), Fungibles::read_state(AssetExists(ASSET))); }); } @@ -202,8 +226,8 @@ 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) { @@ -211,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/integration-tests/contracts/fungibles/lib.rs b/pop-api/integration-tests/contracts/fungibles/lib.rs index 888dd86d..5e74eee9 100755 --- a/pop-api/integration-tests/contracts/fungibles/lib.rs +++ b/pop-api/integration-tests/contracts/fungibles/lib.rs @@ -122,14 +122,14 @@ mod fungibles { api::token_decimals(id) } - // 3. Asset Management: - // - create - // - start_destroy - // - destroy_accounts - // - destroy_approvals - // - finish_destroy - // - set_metadata - // - clear_metadata + /// 3. Asset Management: + /// - create + /// - start_destroy + /// - destroy_accounts + /// - destroy_approvals + /// - finish_destroy + /// - set_metadata + /// - clear_metadata #[ink(message)] pub fn create(&self, id: AssetId, admin: AccountId, min_balance: Balance) -> Result<()> { @@ -161,6 +161,19 @@ mod fungibles { 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 index 81a276a9..31d1c443 100644 --- a/pop-api/integration-tests/src/fungibles/mod.rs +++ b/pop-api/integration-tests/src/fungibles/mod.rs @@ -483,62 +483,65 @@ fn asset_exists_works() { }); } -// #[test] -// #[ignore] -// fn mint_works() { -// new_test_ext().execute_with(|| { -// let _ = env_logger::try_init(); -// let addr = -// instantiate(CONTRACT, INIT_VALUE, vec![]); -// let amount: Balance = 100 * UNIT; -// -// // Asset does not exist. -// assert_eq!( -// decoded::(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] +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 index c4571aec..2f9be65c 100644 --- a/pop-api/integration-tests/src/fungibles/utils.rs +++ b/pop-api/integration-tests/src/fungibles/utils.rs @@ -184,6 +184,32 @@ pub(super) fn clear_metadata(addr: AccountId32, asset_id: AssetId) -> Result<(), .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) } +pub(super) fn mint( + addr: AccountId32, + asset_id: AssetId, + account: AccountId32, + amount: Balance, +) -> Result<(), Error> { + let function = function_selector("mint"); + let params = [function, asset_id.encode(), account.encode(), amount.encode()].concat(); + let result = bare_call(addr, params, 0).expect("should work"); + decoded::>(result.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()), diff --git a/pop-api/src/v0/assets/fungibles.rs b/pop-api/src/v0/assets/fungibles.rs index be8d44af..27b96ae9 100644 --- a/pop-api/src/v0/assets/fungibles.rs +++ b/pop-api/src/v0/assets/fungibles.rs @@ -28,6 +28,7 @@ fn build_read_state(state_query: u8) -> ChainExtensionMethod<(), (), (), false> /// 1. PSP-22 Interface /// 2. PSP-22 Metadata Interface /// 3. Asset Management +/// 4. PSP-22 Mintable & Burnable Interface mod constants { /// 1. PSP-22 Interface: @@ -51,6 +52,10 @@ mod constants { pub(super) const SET_METADATA: u8 = 16; pub(super) const CLEAR_METADATA: u8 = 17; pub(super) const ASSET_EXISTS: u8 = 18; + + /// 4. PSP-22 Mintable & Burnable interface: + pub(super) const MINT: u8 = 19; + pub(super) const BURN: u8 = 20; } /// Returns the total token supply for a given asset ID. @@ -200,8 +205,43 @@ pub fn decrease_allowance(id: AssetId, spender: AccountId, value: Balance) -> Re .call(&(id, spender, value)) } +/// Creates `amount` tokens and assigns them to `account`, increasing the total supply. +/// +/// # Parameters +/// - `id` - The ID of the asset. +/// - `owner` - The account to be credited with the created tokens. +/// - `value` - The number of tokens to mint. +/// +/// # Returns +/// Returns `Ok(())` if successful, or an error if the operation fails. +pub fn mint(id: AssetId, account: AccountId, amount: Balance) -> Result<()> { + build_dispatch(MINT) + .input::<(AssetId, AccountId, Balance)>() + .output::, true>() + .handle_error_code::() + .call(&(id, account, amount)) +} + +/// Destroys `amount` tokens from `account`, reducing the total supply. +/// +/// # Parameters +/// - `id` - The ID of the asset. +/// - `owner` - The account from which the tokens will be destroyed. +/// - `value` - The number of tokens to destroy. +/// +/// # Returns +/// Returns `Ok(())` if successful, or an error if the operation fails. +pub fn burn(id: AssetId, account: AccountId, amount: Balance) -> Result<()> { + build_dispatch(BURN) + .input::<(AssetId, AccountId, Balance)>() + .output::, true>() + .handle_error_code::() + .call(&(id, account, amount)) +} + pub mod metadata { use super::*; + /// Returns the token name for a given asset ID. /// /// # Parameters @@ -253,6 +293,7 @@ pub mod metadata { pub mod asset_management { use super::*; + /// Create a new token with a given asset ID. /// /// # Parameters diff --git a/runtime/devnet/src/config/api.rs b/runtime/devnet/src/config/api.rs index 66c6c58b..bdff6bbb 100644 --- a/runtime/devnet/src/config/api.rs +++ b/runtime/devnet/src/config/api.rs @@ -28,6 +28,7 @@ impl Contains for AllowedApiCalls { | create { .. } | set_metadata { .. } | start_destroy { .. } | clear_metadata { .. } + | mint { .. } | burn { .. } ) ) }