From 92a8465ea0e0fad8a96a8134a7522cc90b248356 Mon Sep 17 00:00:00 2001 From: R0GUE Date: Mon, 22 Apr 2024 15:05:31 +0200 Subject: [PATCH] refactor: chain extension tests --- Cargo.lock | 8 + pop-api/Cargo.toml | 2 +- .../.gitignore | 0 .../Cargo.toml | 2 +- pop-api/examples/fungibles/lib.rs | 123 ++ pop-api/examples/trust_backed_assets/lib.rs | 74 -- pop-api/src/lib.rs | 11 +- pop-api/src/v0/assets/fungibles.rs | 535 +++++++++ pop-api/src/v0/assets/mod.rs | 2 +- pop-api/src/v0/assets/trust_backed.rs | 519 --------- pop-api/src/v0/mod.rs | 2 +- pop-api/wrappers/fungibles/.gitignore | 9 + pop-api/wrappers/fungibles/Cargo.toml | 28 + pop-api/wrappers/fungibles/lib.rs | 171 +++ primitives/Cargo.toml | 14 +- primitives/src/lib.rs | 12 + primitives/src/storage_keys.rs | 14 +- runtime/devnet/Cargo.toml | 2 +- runtime/devnet/src/config/assets.rs | 8 +- runtime/devnet/src/config/mod.rs | 2 +- runtime/devnet/src/config/proxy.rs | 40 +- runtime/devnet/src/extensions.rs | 1018 ----------------- runtime/devnet/src/extensions/mod.rs | 885 ++++++++++++++ .../src/extensions/tests/local_fungibles.rs | 232 ++++ runtime/devnet/src/extensions/tests/mod.rs | 86 ++ runtime/devnet/src/lib.rs | 62 +- runtime/testnet/Cargo.toml | 2 +- runtime/testnet/src/config/assets.rs | 8 +- runtime/testnet/src/config/proxy.rs | 40 +- runtime/testnet/src/extensions.rs | 138 +-- runtime/testnet/src/lib.rs | 99 +- 31 files changed, 2244 insertions(+), 1904 deletions(-) rename pop-api/examples/{trust_backed_assets => fungibles}/.gitignore (100%) rename pop-api/examples/{trust_backed_assets => fungibles}/Cargo.toml (93%) create mode 100755 pop-api/examples/fungibles/lib.rs delete mode 100755 pop-api/examples/trust_backed_assets/lib.rs create mode 100644 pop-api/src/v0/assets/fungibles.rs delete mode 100644 pop-api/src/v0/assets/trust_backed.rs create mode 100755 pop-api/wrappers/fungibles/.gitignore create mode 100755 pop-api/wrappers/fungibles/Cargo.toml create mode 100755 pop-api/wrappers/fungibles/lib.rs delete mode 100644 runtime/devnet/src/extensions.rs create mode 100644 runtime/devnet/src/extensions/mod.rs create mode 100644 runtime/devnet/src/extensions/tests/local_fungibles.rs create mode 100644 runtime/devnet/src/extensions/tests/mod.rs diff --git a/Cargo.lock b/Cargo.lock index e43d2b18..17cc70e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9673,6 +9673,8 @@ version = "0.0.0" dependencies = [ "bounded-collections 0.1.9", "parity-scale-codec", + "scale-decode", + "scale-encode", "scale-info", ] @@ -9720,16 +9722,19 @@ dependencies = [ "pallet-collator-selection", "pallet-contracts", "pallet-message-queue", + "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-preimage", + "pallet-proxy", "pallet-scheduler", "pallet-session", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", + "pallet-utility", "pallet-xcm", "parachains-common", "parity-scale-codec", @@ -9790,16 +9795,19 @@ dependencies = [ "pallet-collator-selection", "pallet-contracts", "pallet-message-queue", + "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-preimage", + "pallet-proxy", "pallet-scheduler", "pallet-session", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", + "pallet-utility", "pallet-xcm", "parachains-common", "parity-scale-codec", diff --git a/pop-api/Cargo.toml b/pop-api/Cargo.toml index 0fa1119c..80818235 100644 --- a/pop-api/Cargo.toml +++ b/pop-api/Cargo.toml @@ -13,7 +13,7 @@ scale-info = { version = "2.6", default-features = false, features = ["derive"] sp-io = { version = "23.0.0", default-features = false, features = ["disable_panic_handler", "disable_oom", "disable_allocator"] } sp-runtime = { version = "24.0", default-features = false } -pop-primitives = { path = "../primitives", default-features = false } +pop-primitives = { path = "../primitives", features = ["devnet"], default-features = false } [lib] name = "pop_api" diff --git a/pop-api/examples/trust_backed_assets/.gitignore b/pop-api/examples/fungibles/.gitignore similarity index 100% rename from pop-api/examples/trust_backed_assets/.gitignore rename to pop-api/examples/fungibles/.gitignore diff --git a/pop-api/examples/trust_backed_assets/Cargo.toml b/pop-api/examples/fungibles/Cargo.toml similarity index 93% rename from pop-api/examples/trust_backed_assets/Cargo.toml rename to pop-api/examples/fungibles/Cargo.toml index 3c3716fc..1fe32d6d 100755 --- a/pop-api/examples/trust_backed_assets/Cargo.toml +++ b/pop-api/examples/fungibles/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pop_api_trust_backed_assets_example" +name = "fungibles" version = "0.1.0" authors = ["[your_name] <[your_email]>"] edition = "2021" diff --git a/pop-api/examples/fungibles/lib.rs b/pop-api/examples/fungibles/lib.rs new file mode 100755 index 00000000..ff64298e --- /dev/null +++ b/pop-api/examples/fungibles/lib.rs @@ -0,0 +1,123 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +// Fungibles wrapper contract to allow contracts to interact with local fungibles without the pop api. +use pop_api::{primitives::{AccountId as AccountId32, AssetId}, assets::fungibles::*}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum FungiblesError { + // AssetsError(Error), + // /// The origin of the call doesn't have the right permission. + // BadOrigin, + // /// Custom error type for cases in which an implementation adds its own restrictions. + // Custom(String), + /// Not enough balance to fulfill a request is available. + InsufficientBalance, + /// Not enough allowance to fulfill a request is available. + InsufficientAllowance, + /// The asset status is not the expected status. + IncorrectStatus, + /// The asset ID is already taken. + InUse, + /// Minimum balance should be non-zero. + MinBalanceZero, + /// The signing account has no permission to do the operation. + NoPermission, + // /// Safe transfer check fails (e.g. if the receiving contract does not accept tokens). + // SafeTransferCheckFailed(String), + /// The given asset ID is unknown. + Unknown, + /// Recipient's address is zero. + ZeroRecipientAddress, + /// Sender's address is zero. + ZeroSenderAddress, +} + +impl From for FungiblesError { + fn from(error: Error) -> Self { + match error { + // Error::BalanceLow => Err(InsufficientBalance), + Error::InUse => FungiblesError::InUse, + Error::MinBalanceZero => FungiblesError::MinBalanceZero, + Error::Unknown => FungiblesError::Unknown, + _ => todo!() + } + } +} + +/// The fungibles result type. +pub type Result = core::result::Result; + +#[ink::contract(env = pop_api::Environment)] +mod fungibles { + use super::*; + + #[ink(storage)] + #[derive(Default)] + pub struct Fungibles; + + impl Fungibles { + #[ink(constructor, payable)] + pub fn new() -> Self { + ink::env::debug_println!("PopApiAssetsExample::new"); + Default::default() + } + + #[ink(message)] + pub fn total_supply(&self, id: AssetId) -> Result { + total_supply(id).map_err(From::from) + } + + #[ink(message)] + pub fn balance_of(&self, id: AssetId, owner: AccountId32) -> Result { + balance_of(id, owner).map_err(From::from) + } + + #[ink(message)] + pub fn allowance(&self, id: AssetId, owner: AccountId32, spender: AccountId32) -> Result { + allowance(id, owner, spender).map_err(From::from) + } + + // #[ink(message)] + // pub fn token_name(id: AssetId) -> Result>> { + // token_name(id) + // } + // + // #[ink(message)] + // pub fn mint_asset( + // &mut self, + // id: u32, + // beneficiary: AccountId, + // amount: Balance, + // ) -> Result<()> { + // ink::env::debug_println!( + // "PopApiAssetsExample::mint_asset_through_runtime: id: {:?} beneficiary: {:?} amount: {:?}", + // id, + // beneficiary, + // amount + // ); + // + // // Check if asset doesn't exist. + // if !asset_exists(id)? { + // return Err(FungiblesError::UnknownAsset); + // } + // + // // Mint asset via pop api. + // mint(id, beneficiary, amount)?; + // ink::env::debug_println!( + // "PopApiAssetsExample::mint_asset_through_runtime: asset(s) minted successfully" + // ); + // Ok(()) + // } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn default_works() { + PopApiAssetsExample::new(); + } + } +} diff --git a/pop-api/examples/trust_backed_assets/lib.rs b/pop-api/examples/trust_backed_assets/lib.rs deleted file mode 100755 index 3606f852..00000000 --- a/pop-api/examples/trust_backed_assets/lib.rs +++ /dev/null @@ -1,74 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -// Utilizing Trust Backed Assets with the Pop API. -// -// This example demonstrates interaction with trust backed assets via the assets pallet. Trust backed assets are originated -// and managed within Pop Network, harnessing the platform's inherent trust, security, and governance models. -use pop_api::assets::trust_backed as trust_backed_assets; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub enum ContractError { - TrustBackedAssetsError(trust_backed_assets::Error), - UnknownAsset, -} - -impl From for ContractError { - fn from(value: trust_backed_assets::Error) -> Self { - ContractError::TrustBackedAssetsError(value) - } -} - -#[ink::contract(env = pop_api::Environment)] -mod pop_api_tb_assets_example { - use super::*; - - #[ink(storage)] - #[derive(Default)] - pub struct PopApiTBAssetsExample; - - impl PopApiTBAssetsExample { - #[ink(constructor, payable)] - pub fn new() -> Self { - ink::env::debug_println!("Contract::new"); - Default::default() - } - - #[ink(message)] - pub fn mint_asset_through_runtime( - &mut self, - id: u32, - beneficiary: AccountId, - amount: Balance, - ) -> Result<(), ContractError> { - ink::env::debug_println!( - "Contract::mint_asset_through_runtime: id: {:?} beneficiary: {:?} amount: {:?}", - id, - beneficiary, - amount - ); - - // Check if asset doesn't exist. - if !trust_backed_assets::asset_exists(id)? { - return Err(ContractError::UnknownAsset); - } - - // Mint asset via pop api. - trust_backed_assets::mint(id, beneficiary, amount)?; - ink::env::debug_println!( - "Contract::mint_asset_through_runtime: asset(s) minted successfully" - ); - Ok(()) - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn default_works() { - PopApiTBAssetsExample::new(); - } - } -} diff --git a/pop-api/src/lib.rs b/pop-api/src/lib.rs index 68ce6906..8df381de 100644 --- a/pop-api/src/lib.rs +++ b/pop-api/src/lib.rs @@ -3,14 +3,15 @@ pub mod primitives; pub mod v0; -use crate::PopApiError::{Balances, Nfts, TrustBackedAssets, UnknownStatusCode}; +use crate::PopApiError::{Balances, Nfts, Assets, UnknownStatusCode}; use ink::{prelude::vec::Vec, ChainExtensionInstance}; -use primitives::{cross_chain::*, storage_keys::*}; +use primitives::{AccountId as AccountId32, cross_chain::*, storage_keys::*}; pub use sp_runtime::{BoundedVec, MultiAddress, MultiSignature}; use v0::RuntimeCall; pub use v0::{balances, cross_chain, nfts, relay_chain_block_number, state, assets}; -type AccountId = ::AccountId; +// type AccountId = ::AccountId; +type AccountId = AccountId32; type Balance = ::Balance; type BlockNumber = ::BlockNumber; type StringLimit = u32; @@ -26,7 +27,7 @@ pub enum PopApiError { SystemCallFiltered, Balances(balances::Error), Nfts(nfts::Error), - TrustBackedAssets(assets::trust_backed::Error), + Assets(assets::fungibles::Error), Xcm(cross_chain::Error), } @@ -38,7 +39,7 @@ impl ink::env::chain_extension::FromStatusCode for PopApiError { 5 => Err(PopApiError::SystemCallFiltered), 10_000..=10_999 => Err(Balances((status_code - 10_000).try_into()?)), 50_000..=50_999 => Err(Nfts((status_code - 50_000).try_into()?)), - 52_000..=52_999 => Err(TrustBackedAssets((status_code - 52_000).try_into()?)), + 52_000..=52_999 => Err(Assets((status_code - 52_000).try_into()?)), _ => Err(UnknownStatusCode(status_code)), } } diff --git a/pop-api/src/v0/assets/fungibles.rs b/pop-api/src/v0/assets/fungibles.rs new file mode 100644 index 00000000..b7cfef8f --- /dev/null +++ b/pop-api/src/v0/assets/fungibles.rs @@ -0,0 +1,535 @@ +#![allow(warnings, unused)] +use crate::{Balance, PopApiError::UnknownStatusCode, RuntimeCall, *}; +use ink::prelude::vec::Vec; +use primitives::{AssetId, MultiAddress}; +use scale::{Compact, Encode}; + +type Result = core::result::Result; + +/// Local Fungibles: +/// 1. PSP-22 Interface +/// 2. PSP-22 Metadata Interface +/// 3. Asset Management + +/// 1. PSP-22 Interface: +/// - total_supply +/// - balance_of +/// - allowance +/// - transfer +/// - transfer_from +/// - approve +/// - increase_allowance +/// - decrease_allowance + +/// Returns the total token supply for a given asset ID. +/// +/// # Arguments +/// * `id` - The ID of the asset. +/// +/// # Returns +/// The total supply of the token, or an error if the operation fails. +pub fn total_supply(id: AssetId) -> Result { + Ok(state::read(RuntimeStateKeys::Assets(AssetsKeys::TotalSupply(id)))?) +} + +/// 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. +/// +/// # Returns +/// The balance of the specified account, or an error if the operation fails. +pub fn balance_of(id: AssetId, owner: AccountId) -> Result { + Ok(state::read(RuntimeStateKeys::Assets(AssetsKeys::BalanceOf(id, owner)))?) +} + +/// 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. +/// +/// # Returns +/// The remaining allowance, or an error if the operation fails. +pub fn allowance(id: AssetId, owner: AccountId, spender: AccountId) -> Result { + Ok(state::read(RuntimeStateKeys::Assets(AssetsKeys::Allowance(id, owner, spender)))?) +} + +/// Transfers `value` amount of tokens from the caller's account to account `to`, with additional +/// `data` in unspecified format. +/// +/// # Arguments +/// * `id` - The ID of the asset. +/// * `to` - The recipient account. +/// * `value` - The number of tokens to transfer. +/// +/// # Returns +/// Returns `Ok(())` if successful, or an error if the transfer fails. +#[allow(unused_variables)] +pub fn transfer( + id: AssetId, + to: impl Into>, + value: Balance, +) -> Result<()> { + todo!() + // TODO: transfer or transfer_keep_alive + // Ok(dispatch(RuntimeCall::Assets(AssetsCall::Transfer { + // id: id.into(), + // target: target.into(), + // amount: Compact(amount), + // }))?) + // Ok(dispatch(RuntimeCall::Assets(AssetsCall::TransferKeepAlive { + // id: id.into(), + // target: target.into(), + // amount: Compact(amount), + // }))?) +} + +/// Transfers `value` tokens on the behalf of `from` to the account `to` with additional `data` +/// in unspecified format. This can be used to allow a contract to transfer tokens on ones behalf +/// and/or to charge fees in sub-currencies, for example. +/// +/// # 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. +/// +/// # Returns +/// Returns `Ok(())` if successful, or an error if the transfer fails. +#[allow(unused_variables)] +pub fn transfer_from( + id: AssetId, + from: impl Into>, + to: impl Into>, + value: Balance, +) -> Result<()> { + todo!() + // TODO: depending on `from` and `to`, decide whether to mint, burn or transfer_approved. + // Ok(dispatch(RuntimeCall::Assets(AssetsCall::Mint { + // id: id.into(), + // beneficiary: beneficiary.into(), + // amount: Compact(amount), + // }))?) + // Ok(dispatch(RuntimeCall::Assets(AssetsCall::Burn { + // id: id.into(), + // who: who.into(), + // amount: Compact(amount), + // }))?) + // Ok(dispatch(RuntimeCall::Assets(AssetsCall::TransferApproved { + // id: id.into(), + // owner: from.into(), + // destination: to.into(), + // amount: Compact(value), + // }))?) +} + +/// Approves an account to spend a specified number of tokens on behalf of the caller. +/// +/// # Arguments +/// * `id` - The ID of the asset. +/// * `spender` - The account that is allowed to spend the tokens. +/// * `value` - The number of tokens to approve. +/// +/// # Returns +/// Returns `Ok(())` if successful, or an error if the approval fails. +#[allow(unused_variables)] +fn approve(id: AssetId, spender: AccountId, value: Balance) -> Result<()> { + todo!() + // TODO: read allowance and increase or decrease. + // Ok(dispatch(RuntimeCall::Assets(AssetsCall::ApproveTransfer { + // id: id.into(), + // delegate: spender.into(), + // amount: Compact(value), + // }))?) +} + +/// Increases the allowance of a spender. +/// +/// # Arguments +/// * `id` - The ID of the asset. +/// * `spender` - The account that is allowed to spend the tokens. +/// * `value` - The number of tokens to increase the allowance by. +/// +/// # Returns +/// Returns `Ok(())` if successful, or an error if the operation fails. +fn increase_allowance(id: AssetId, spender: AccountId, value: Balance) -> Result<()> { + Ok(dispatch(RuntimeCall::Assets(AssetsCall::ApproveTransfer { + id: id.into(), + delegate: spender.into(), + amount: Compact(value), + }))?) +} + +/// 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. +/// +/// # Returns +/// Returns `Ok(())` if successful, or an error if the operation fails. +#[allow(unused_variables)] +fn decrease_allowance(id: AssetId, spender: AccountId, value: Balance) -> Result<()> { + todo!() + // TODO: cancel_approval + approve_transfer + // Ok(dispatch(RuntimeCall::Assets(AssetsCall::CancelApproval { + // id: id.into(), + // delegate: delegate.into(), + // }))?) + // Ok(dispatch(RuntimeCall::Assets(AssetsCall::ApproveTransfer { + // id: id.into(), + // delegate: spender.into(), + // amount: Compact(value), + // }))?) +} + +/// 2. PSP-22 Metadata Interface: +/// - token_name +/// - token_symbol +/// - token_decimals + +/// Returns the token name for a given asset ID. +/// +/// # Arguments +/// * `id` - The ID of the asset. +/// +/// # Returns +/// The name of the token as a byte vector, or an error if the operation fails. +#[allow(unused_variables)] +pub fn token_name(id: AssetId) -> Result>> { + todo!() + // Ok(state::read(RuntimeStateKeys::Assets(AssetsKeys::TokenName(id)))?) +} + +/// Returns the token symbol for a given asset ID. +/// +/// # Arguments +/// * `id` - The ID of the asset. +/// +/// # Returns +/// The symbol of the token as a byte vector, or an error if the operation fails. +#[allow(unused_variables)] +fn token_symbol(id: AssetId) -> Result>> { + todo!() +} + +/// Returns the token decimals for a given asset ID. +/// +/// # Arguments +/// * `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. +#[allow(unused_variables)] +fn token_decimals(id: AssetId) -> Result>> { + todo!() +} + +/// 3. Asset Management: +/// - create +/// - start_destroy +/// - destroy_accounts +/// - destroy_approvals +/// - finish_destroy +/// - set_metadata +/// - clear_metadata + +/// 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. +fn create(id: AssetId, admin: AccountId, min_balance: Balance) -> Result<()> { + Ok(dispatch(RuntimeCall::Assets(AssetsCall::Create { + id: id.into(), + admin: admin.into(), + min_balance: Compact(min_balance), + }))?) +} + +/// 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(dispatch(RuntimeCall::Assets(AssetsCall::StartDestroy { + id: id.into(), + }))?) +} + +/// 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(dispatch(RuntimeCall::Assets(AssetsCall::DestroyAccounts { + id: id.into(), + }))?) +} + +/// 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(dispatch(RuntimeCall::Assets(AssetsCall::DestroyApprovals { + id: id.into(), + }))?) +} + +/// 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(dispatch(RuntimeCall::Assets(AssetsCall::FinishDestroy { + id: id.into(), + }))?) +} + +/// 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. +fn set_metadata(id: AssetId, name: Vec, symbol: Vec, decimals: u8) -> Result<()> { + Ok(dispatch(RuntimeCall::Assets(AssetsCall::SetMetadata { + id: id.into(), + name, + symbol, + decimals, + }))?) +} + +/// 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(dispatch(RuntimeCall::Assets(AssetsCall::ClearMetadata { + id: id.into(), + }))?) +} + +pub fn asset_exists(id: AssetId) -> Result { + Ok(state::read(RuntimeStateKeys::Assets(AssetsKeys::AssetExists(id)))?) +} + +// Parameters to extrinsics representing an asset id (`AssetIdParameter`) and a balance amount (`Balance`) are expected +// to be compact encoded. The pop api handles that for the developer. +// +// reference: https://substrate.stackexchange.com/questions/1873/what-is-the-meaning-of-palletcompact-in-pallet-development +// +// Asset id that is compact encoded. +type AssetIdParameter = Compact; +// Balance amount that is compact encoded. +type BalanceParameter = Compact; + +#[derive(Encode)] +pub(crate) enum AssetsCall { + #[codec(index = 0)] + Create { + id: AssetIdParameter, + admin: MultiAddress, + min_balance: BalanceParameter, + }, + #[codec(index = 2)] + StartDestroy { id: AssetIdParameter }, + #[codec(index = 3)] + DestroyAccounts { id: AssetIdParameter }, + #[codec(index = 4)] + DestroyApprovals { id: AssetIdParameter }, + #[codec(index = 5)] + FinishDestroy { id: AssetIdParameter }, + #[codec(index = 6)] + Mint { + id: AssetIdParameter, + beneficiary: MultiAddress, + amount: BalanceParameter, + }, + #[codec(index = 7)] + Burn { id: AssetIdParameter, who: MultiAddress, amount: BalanceParameter }, + #[codec(index = 8)] + Transfer { id: AssetIdParameter, target: MultiAddress, amount: BalanceParameter }, + #[codec(index = 9)] + TransferKeepAlive { + id: AssetIdParameter, + target: MultiAddress, + amount: BalanceParameter, + }, + #[codec(index = 10)] + ForceTransfer { + id: AssetIdParameter, + source: MultiAddress, + dest: MultiAddress, + amount: BalanceParameter, + }, + #[codec(index = 11)] + Freeze { id: AssetIdParameter, who: MultiAddress }, + #[codec(index = 12)] + Thaw { id: AssetIdParameter, who: MultiAddress }, + #[codec(index = 13)] + FreezeAsset { id: AssetIdParameter }, + #[codec(index = 14)] + ThawAsset { id: AssetIdParameter }, + #[codec(index = 15)] + TransferOwnership { id: AssetIdParameter, owner: MultiAddress }, + #[codec(index = 16)] + SetTeam { + id: AssetIdParameter, + issuer: MultiAddress, + admin: MultiAddress, + freezer: MultiAddress, + }, + #[codec(index = 17)] + SetMetadata { id: AssetIdParameter, name: Vec, symbol: Vec, decimals: u8 }, + #[codec(index = 18)] + ClearMetadata { id: AssetIdParameter }, + #[codec(index = 22)] + ApproveTransfer { + id: AssetIdParameter, + delegate: MultiAddress, + amount: BalanceParameter, + }, + #[codec(index = 23)] + CancelApproval { id: AssetIdParameter, delegate: MultiAddress }, + #[codec(index = 24)] + ForceCancelApproval { + id: AssetIdParameter, + owner: MultiAddress, + delegate: MultiAddress, + }, + #[codec(index = 25)] + TransferApproved { + id: AssetIdParameter, + owner: MultiAddress, + destination: MultiAddress, + amount: BalanceParameter, + }, + #[codec(index = 26)] + Touch { id: AssetIdParameter }, + #[codec(index = 27)] + Refund { id: AssetIdParameter, allow_burn: bool }, + #[codec(index = 28)] + SetMinBalance { id: AssetIdParameter, min_balance: BalanceParameter }, + #[codec(index = 29)] + TouchOther { id: AssetIdParameter, who: MultiAddress }, + #[codec(index = 30)] + RefundOther { id: AssetIdParameter, who: MultiAddress }, + #[codec(index = 31)] + Block { id: AssetIdParameter, who: MultiAddress }, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum Error { + /// Account balance must be greater than or equal to the transfer amount. + BalanceLow, + /// The account to alter does not exist. + NoAccount, + /// The signing account has no permission to do the operation. + NoPermission, + /// The given asset ID is unknown. + Unknown, + /// The origin account is frozen. + Frozen, + /// The asset ID is already taken. + InUse, + /// Invalid witness data given. + BadWitness, + /// Minimum balance should be non-zero. + MinBalanceZero, + /// Unable to increment the consumer reference counters on the account. Either no provider + /// reference exists to allow a non-zero balance of a non-self-sufficient asset, or one + /// fewer then the maximum number of consumers has been reached. + UnavailableConsumer, + /// Invalid metadata given. + BadMetadata, + /// No approval exists that would allow the transfer. + Unapproved, + /// The source account would not survive the transfer and it needs to stay alive. + WouldDie, + /// The asset-account already exists. + AlreadyExists, + /// The asset-account doesn't have an associated deposit. + NoDeposit, + /// The operation would result in funds being burned. + WouldBurn, + /// The asset is a live asset and is actively being used. Usually emit for operations such + /// as `start_destroy` which require the asset to be in a destroying state. + LiveAsset, + /// The asset is not live, and likely being destroyed. + AssetNotLive, + /// The asset status is not the expected status. + IncorrectStatus, + /// The asset should be frozen before the given operation. + NotFrozen, + /// Callback action resulted in error + CallbackFailed, +} + +impl TryFrom for Error { + type Error = PopApiError; + + fn try_from(status_code: u32) -> core::result::Result { + use Error::*; + match status_code { + 0 => Ok(BalanceLow), + 1 => Ok(NoAccount), + 2 => Ok(NoPermission), + 3 => Ok(Unknown), + 4 => Ok(Frozen), + 5 => Ok(InUse), + 6 => Ok(BadWitness), + 7 => Ok(MinBalanceZero), + 8 => Ok(UnavailableConsumer), + 9 => Ok(BadMetadata), + 10 => Ok(Unapproved), + 11 => Ok(WouldDie), + 12 => Ok(AlreadyExists), + 13 => Ok(NoDeposit), + 14 => Ok(WouldBurn), + 15 => Ok(LiveAsset), + 16 => Ok(AssetNotLive), + 17 => Ok(IncorrectStatus), + 18 => Ok(NotFrozen), + _ => Err(UnknownStatusCode(status_code)), + } + } +} + +impl From for Error { + fn from(error: PopApiError) -> Self { + match error { + PopApiError::Assets(e) => e, + _ => panic!("Unexpected pallet assets error. This error is unknown to pallet assets"), + } + } +} diff --git a/pop-api/src/v0/assets/mod.rs b/pop-api/src/v0/assets/mod.rs index 7ad40f15..d6b0261c 100644 --- a/pop-api/src/v0/assets/mod.rs +++ b/pop-api/src/v0/assets/mod.rs @@ -1 +1 @@ -pub mod trust_backed; \ No newline at end of file +pub mod fungibles; \ No newline at end of file diff --git a/pop-api/src/v0/assets/trust_backed.rs b/pop-api/src/v0/assets/trust_backed.rs deleted file mode 100644 index fb413413..00000000 --- a/pop-api/src/v0/assets/trust_backed.rs +++ /dev/null @@ -1,519 +0,0 @@ -use crate::{Balance, PopApiError::UnknownStatusCode, RuntimeCall, *}; -use ink::prelude::vec::Vec; -use primitives::{AssetId, MultiAddress}; -use scale::{Compact, Encode}; - -type Result = core::result::Result; - -/// https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/assets/src/lib.rs -/// -/// Extrinsics within pallet assets (TrustBackedAssets Instance) that can be used via the pop api on Pop Network: -/// 1. create -/// 2. start_destroy -/// 3. destroy_accounts -/// 4. destroy_approvals -/// 5. finish_destroy -/// 6. mint -/// 7. burn -/// 8. transfer -/// 9. transfer_keep_alive -/// 10. force_transfer -/// 11. freeze -/// 12. thaw -/// 13. freeze_asset -/// 14. thaw_asset -/// 15. transfer_ownership -/// 16. set_team -/// 17. set_metadata -/// 18. clear_metadata -/// 19. approve_transfer -/// 20. cancel_approval -/// 21. force_cancel_approval -/// 22. transfer_approved -/// 23. touch -/// 24. refund -/// 25. set_min_balance -/// 26. touch_other -/// 27. refund_other -/// 28. block - - -/// Issue a new class of fungible assets from a public origin. -pub fn create( - id: AssetId, - admin: impl Into>, - min_balance: Balance, -) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::Create { - id: id.into(), - admin: admin.into(), - min_balance: Compact(min_balance), - }))?) -} - -/// Start the process of destroying a fungible asset class. -pub fn start_destroy(id: AssetId) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::StartDestroy { - id: id.into(), - }))?) -} - -/// Destroy all accounts associated with a given asset. -pub fn destroy_accounts(id: AssetId) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::DestroyAccounts { - id: id.into(), - }))?) -} - -/// Destroy all approvals associated with a given asset up to the max (see runtime configuration TrustBackedAssets `RemoveItemsLimit`). -pub fn destroy_approvals(id: AssetId) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::DestroyApprovals { - id: id.into(), - }))?) -} - -/// Complete destroying asset and unreserve currency. -pub fn finish_destroy(id: AssetId) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::FinishDestroy { - id: id.into(), - }))?) -} - -/// Mint assets of a particular class. -pub fn mint( - id: AssetId, - beneficiary: impl Into>, - amount: Balance, -) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::Mint { - id: id.into(), - beneficiary: beneficiary.into(), - amount: Compact(amount), - }))?) -} - -/// Reduce the balance of `who` by as much as possible up to `amount` assets of `id`. -pub fn burn( - id: AssetId, - who: impl Into>, - amount: Balance, -) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::Burn { - id: id.into(), - who: who.into(), - amount: Compact(amount), - }))?) -} - -/// Move some assets from the sender account to another. -pub fn transfer( - id: AssetId, - target: impl Into>, - amount: Balance, -) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::Transfer { - id: id.into(), - target: target.into(), - amount: Compact(amount), - }))?) -} - -/// Move some assets from the sender account to another, keeping the sender account alive. -pub fn transfer_keep_alive( - id: AssetId, - target: impl Into>, - amount: Balance, -) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::TransferKeepAlive { - id: id.into(), - target: target.into(), - amount: Compact(amount), - }))?) -} - -/// Move some assets from one account to another. Sender should be the Admin of the asset `id`. -pub fn force_transfer( - id: AssetId, - source: impl Into>, - dest: impl Into>, - amount: Balance, -) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::ForceTransfer { - id: id.into(), - source: source.into(), - dest: dest.into(), - amount: Compact(amount), - }))?) -} - -/// Disallow further unprivileged transfers of an asset `id` from an account `who`. `who` -/// must already exist as an entry in `Account`s of the asset. If you want to freeze an -/// account that does not have an entry, use `touch_other` first. -pub fn freeze(id: AssetId, who: impl Into>) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::Freeze { - id: id.into(), - who: who.into(), - }))?) -} - -/// Allow unprivileged transfers to and from an account again. -pub fn thaw(id: AssetId, who: impl Into>) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::Thaw { - id: id.into(), - who: who.into(), - }))?) -} - -/// Disallow further unprivileged transfers for the asset class. -pub fn freeze_asset(id: AssetId) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::FreezeAsset { - id: id.into(), - }))?) -} - -/// Allow unprivileged transfers for the asset again. -pub fn thaw_asset(id: AssetId) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::ThawAsset { - id: id.into(), - }))?) -} - -/// Change the Owner of an asset. -pub fn transfer_ownership( - id: AssetId, - owner: impl Into>, -) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::TransferOwnership { - id: id.into(), - owner: owner.into(), - }))?) -} - -/// Change the Issuer, Admin and Freezer of an asset. -pub fn set_team( - id: AssetId, - issuer: impl Into>, - admin: impl Into>, - freezer: impl Into>, -) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::SetTeam { - id: id.into(), - issuer: issuer.into(), - admin: admin.into(), - freezer: freezer.into(), - }))?) -} - -/// Set the metadata for an asset. -pub fn set_metadata(id: AssetId, name: Vec, symbol: Vec, decimals: u8) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::SetMetadata { - id: id.into(), - name, - symbol, - decimals, - }))?) -} - -/// Clear the metadata for an asset. -pub fn clear_metadata(id: AssetId) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::ClearMetadata { - id: id.into(), - }))?) -} - -/// Approve an amount of asset for transfer by a delegated third-party account. -pub fn approve_transfer( - id: AssetId, - delegate: impl Into>, - amount: Balance, -) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::ApproveTransfer { - id: id.into(), - delegate: delegate.into(), - amount: Compact(amount), - }))?) -} - -/// Cancel all of some asset approved for delegated transfer by a third-party account. -pub fn cancel_approval( - id: AssetId, - delegate: impl Into>, -) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::CancelApproval { - id: id.into(), - delegate: delegate.into(), - }))?) -} - -/// Cancel all of some asset approved for delegated transfer by a third-party account. -pub fn force_cancel_approval( - id: AssetId, - owner: impl Into>, - delegate: impl Into>, -) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::ForceCancelApproval { - id: id.into(), - owner: owner.into(), - delegate: delegate.into(), - }))?) -} - -/// Transfer some asset balance from a previously delegated account to some third-party -/// account. -pub fn transfer_approved( - id: AssetId, - owner: impl Into>, - destination: impl Into>, - amount: Balance, -) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::TransferApproved { - id: id.into(), - owner: owner.into(), - destination: destination.into(), - amount: Compact(amount), - }))?) -} - -/// Create an asset account for non-provider assets. -pub fn touch(id: AssetId) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::Touch { - id: id.into(), - }))?) -} - -/// Return the deposit (if any) of an asset account or a consumer reference (if any) of an -/// account. -pub fn refund(id: AssetId, allow_burn: bool) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::Refund { - id: id.into(), - allow_burn, - }))?) -} - -/// Sets the minimum balance of an asset. -pub fn set_min_balance(id: AssetId, min_balance: Balance) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::SetMinBalance { - id: id.into(), - min_balance: Compact(min_balance), - }))?) -} - -/// Create an asset account for `who`. -pub fn touch_other(id: AssetId, who: impl Into>) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::TouchOther { - id: id.into(), - who: who.into(), - }))?) -} - -/// Return the deposit (if any) of a target asset account. Useful if you are the depositor. -pub fn refund_other(id: AssetId, who: impl Into>) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::RefundOther { - id: id.into(), - who: who.into(), - }))?) -} - -/// Disallow further unprivileged transfers of an asset `id` to and from an account `who`. -pub fn block(id: AssetId, who: impl Into>) -> Result<()> { - Ok(dispatch(RuntimeCall::TrustBackedAssets(TrustBackedAssetsCalls::Block { - id: id.into(), - who: who.into(), - }))?) -} - -pub fn asset_exists(id: AssetId) -> Result { - Ok(state::read(RuntimeStateKeys::TrustBackedAssets(TrustBackedAssetsKeys::AssetExists(id)))?) -} - -// Parameters to extrinsics representing an asset id (`AssetIdParameter`) and a balance amount (`Balance`) are expected -// to be compact encoded. The pop api handles that for the developer. -// -// reference: https://substrate.stackexchange.com/questions/1873/what-is-the-meaning-of-palletcompact-in-pallet-development -// -// Asset id that is compact encoded. -type AssetIdParameter = Compact; -// Balance amount that is compact encoded. -type BalanceParameter = Compact; - -#[derive(Encode)] -pub(crate) enum TrustBackedAssetsCalls { - #[codec(index = 0)] - Create { - id: AssetIdParameter, - admin: MultiAddress, - min_balance: BalanceParameter, - }, - #[codec(index = 2)] - StartDestroy { id: AssetIdParameter }, - #[codec(index = 3)] - DestroyAccounts { id: AssetIdParameter }, - #[codec(index = 4)] - DestroyApprovals { id: AssetIdParameter }, - #[codec(index = 5)] - FinishDestroy { id: AssetIdParameter }, - #[codec(index = 6)] - Mint { - id: AssetIdParameter, - beneficiary: MultiAddress, - amount: BalanceParameter, - }, - #[codec(index = 7)] - Burn { id: AssetIdParameter, who: MultiAddress, amount: BalanceParameter }, - #[codec(index = 8)] - Transfer { id: AssetIdParameter, target: MultiAddress, amount: BalanceParameter }, - #[codec(index = 9)] - TransferKeepAlive { - id: AssetIdParameter, - target: MultiAddress, - amount: BalanceParameter, - }, - #[codec(index = 10)] - ForceTransfer { - id: AssetIdParameter, - source: MultiAddress, - dest: MultiAddress, - amount: BalanceParameter, - }, - #[codec(index = 11)] - Freeze { id: AssetIdParameter, who: MultiAddress }, - #[codec(index = 12)] - Thaw { id: AssetIdParameter, who: MultiAddress }, - #[codec(index = 13)] - FreezeAsset { id: AssetIdParameter }, - #[codec(index = 14)] - ThawAsset { id: AssetIdParameter }, - #[codec(index = 15)] - TransferOwnership { id: AssetIdParameter, owner: MultiAddress }, - #[codec(index = 16)] - SetTeam { - id: AssetIdParameter, - issuer: MultiAddress, - admin: MultiAddress, - freezer: MultiAddress, - }, - #[codec(index = 17)] - SetMetadata { id: AssetIdParameter, name: Vec, symbol: Vec, decimals: u8 }, - #[codec(index = 18)] - ClearMetadata { id: AssetIdParameter }, - #[codec(index = 22)] - ApproveTransfer { - id: AssetIdParameter, - delegate: MultiAddress, - amount: BalanceParameter, - }, - #[codec(index = 23)] - CancelApproval { id: AssetIdParameter, delegate: MultiAddress }, - #[codec(index = 24)] - ForceCancelApproval { - id: AssetIdParameter, - owner: MultiAddress, - delegate: MultiAddress, - }, - #[codec(index = 25)] - TransferApproved { - id: AssetIdParameter, - owner: MultiAddress, - destination: MultiAddress, - amount: BalanceParameter, - }, - #[codec(index = 26)] - Touch { id: AssetIdParameter }, - #[codec(index = 27)] - Refund { id: AssetIdParameter, allow_burn: bool }, - #[codec(index = 28)] - SetMinBalance { id: AssetIdParameter, min_balance: BalanceParameter }, - #[codec(index = 29)] - TouchOther { id: AssetIdParameter, who: MultiAddress }, - #[codec(index = 30)] - RefundOther { id: AssetIdParameter, who: MultiAddress }, - #[codec(index = 31)] - Block { id: AssetIdParameter, who: MultiAddress }, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Encode, scale::Decode)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub enum Error { - /// Account balance must be greater than or equal to the transfer amount. - BalanceLow, - /// The account to alter does not exist. - NoAccount, - /// The signing account has no permission to do the operation. - NoPermission, - /// The given asset ID is unknown. - Unknown, - /// The origin account is frozen. - Frozen, - /// The asset ID is already taken. - InUse, - /// Invalid witness data given. - BadWitness, - /// Minimum balance should be non-zero. - MinBalanceZero, - /// Unable to increment the consumer reference counters on the account. Either no provider - /// reference exists to allow a non-zero balance of a non-self-sufficient asset, or one - /// fewer then the maximum number of consumers has been reached. - UnavailableConsumer, - /// Invalid metadata given. - BadMetadata, - /// No approval exists that would allow the transfer. - Unapproved, - /// The source account would not survive the transfer and it needs to stay alive. - WouldDie, - /// The asset-account already exists. - AlreadyExists, - /// The asset-account doesn't have an associated deposit. - NoDeposit, - /// The operation would result in funds being burned. - WouldBurn, - /// The asset is a live asset and is actively being used. Usually emit for operations such - /// as `start_destroy` which require the asset to be in a destroying state. - LiveAsset, - /// The asset is not live, and likely being destroyed. - AssetNotLive, - /// The asset status is not the expected status. - IncorrectStatus, - /// The asset should be frozen before the given operation. - NotFrozen, - /// Callback action resulted in error - CallbackFailed, -} - -impl TryFrom for Error { - type Error = PopApiError; - - fn try_from(status_code: u32) -> core::result::Result { - use Error::*; - match status_code { - 0 => Ok(BalanceLow), - 1 => Ok(NoAccount), - 2 => Ok(NoPermission), - 3 => Ok(Unknown), - 4 => Ok(Frozen), - 5 => Ok(InUse), - 6 => Ok(BadWitness), - 7 => Ok(MinBalanceZero), - 8 => Ok(UnavailableConsumer), - 9 => Ok(BadMetadata), - 10 => Ok(Unapproved), - 11 => Ok(WouldDie), - 12 => Ok(AlreadyExists), - 13 => Ok(NoDeposit), - 14 => Ok(WouldBurn), - 15 => Ok(LiveAsset), - 16 => Ok(AssetNotLive), - 17 => Ok(IncorrectStatus), - 18 => Ok(NotFrozen), - _ => Err(UnknownStatusCode(status_code)), - } - } -} - -impl From for Error { - fn from(error: PopApiError) -> Self { - match error { - PopApiError::TrustBackedAssets(e) => e, - _ => panic!("Unexpected pallet assets error. This error is unknown to pallet assets"), - } - } -} diff --git a/pop-api/src/v0/mod.rs b/pop-api/src/v0/mod.rs index d914db24..2360c6d3 100644 --- a/pop-api/src/v0/mod.rs +++ b/pop-api/src/v0/mod.rs @@ -20,5 +20,5 @@ pub(crate) enum RuntimeCall { #[codec(index = 50)] Nfts(nfts::NftCalls), #[codec(index = 52)] - TrustBackedAssets(assets::trust_backed::TrustBackedAssetsCalls), + Assets(assets::fungibles::AssetsCall), } diff --git a/pop-api/wrappers/fungibles/.gitignore b/pop-api/wrappers/fungibles/.gitignore new file mode 100755 index 00000000..8de8f877 --- /dev/null +++ b/pop-api/wrappers/fungibles/.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/wrappers/fungibles/Cargo.toml b/pop-api/wrappers/fungibles/Cargo.toml new file mode 100755 index 00000000..f0712574 --- /dev/null +++ b/pop-api/wrappers/fungibles/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "fungibles" +version = "0.1.0" +authors = ["[your_name] <[your_email]>"] +edition = "2021" + +[dependencies] +ink = { version = "5.0.0", default-features = false } +pop-api = { path = "../../../pop-api", default-features = false } +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } + +[dev-dependencies] +ink_e2e = { version = "5.0.0" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + "pop-api/std", + "scale/std", + "scale-info/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/pop-api/wrappers/fungibles/lib.rs b/pop-api/wrappers/fungibles/lib.rs new file mode 100755 index 00000000..e641854c --- /dev/null +++ b/pop-api/wrappers/fungibles/lib.rs @@ -0,0 +1,171 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +// Fungibles wrapper contract to allow contracts to interact with local fungibles without the pop api. +use pop_api::assets::fungibles::*; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum FungiblesError { + // AssetsError(Error), + // /// The origin of the call doesn't have the right permission. + // BadOrigin, + // /// Custom error type for cases in which an implementation adds its own restrictions. + // Custom(String), + /// Not enough balance to fulfill a request is available. + InsufficientBalance, + /// Not enough allowance to fulfill a request is available. + InsufficientAllowance, + /// The asset status is not the expected status. + IncorrectStatus, + /// The asset ID is already taken. + InUse, + /// Minimum balance should be non-zero. + MinBalanceZero, + /// The signing account has no permission to do the operation. + NoPermission, + // /// Safe transfer check fails (e.g. if the receiving contract does not accept tokens). + // SafeTransferCheckFailed(String), + /// The given asset ID is unknown. + Unknown, + /// Recipient's address is zero. + ZeroRecipientAddress, + /// Sender's address is zero. + ZeroSenderAddress, +} + +impl From for FungiblesError { + fn from(error: Error) -> Self { + match error { + // Error::BalanceLow => Err(InsufficientBalance), + Error::InUse => FungiblesError::InUse, + Error::MinBalanceZero => FungiblesError::MinBalanceZero, + Error::Unknown = FungiblesError::Unknown, + _ => todo!() + } + } +} + +/// The fungibles result type. +pub type Result = core::result::Result; + +#[ink::contract] +mod fungibles { + use super::*; + + #[ink(storage)] + pub struct Fungibles { + id: u32, + } + + impl Fungibles { + /// Create a wrapper contract that fully adheres to the psp22 standard for a given asset ID. + /// + /// # Arguments + /// * `id` - The ID of the asset. + /// + /// # Returns + /// Returns `Ok(Self { id })` if successful, or an error if the asset does not exist. + #[ink(constructor)] + pub fn new(id: u32) -> Result { + // Check if asset exists. + if !asset_exists(id)? { + return Err(FungiblesError::Unknown); + } + Ok(Self { id }) + } + + #[ink(message)] + pub fn total_supply(&mut self) -> Result { + total_supply(self.id).map_err(From::from) + } + + // #[ink(message)] + // pub fn get(&self) -> bool { + // self.value + // } + } + + #[cfg(test)] + mod tests { + /// Imports all the definitions from the outer scope so we can use them here. + use super::*; + + /// We test if the default constructor does its job. + #[ink::test] + fn default_works() { + let fungibles = Fungibles::default(); + assert_eq!(fungibles.get(), false); + } + + /// We test a simple use case of our contract. + #[ink::test] + fn it_works() { + let mut fungibles = Fungibles::new(false); + assert_eq!(fungibles.get(), false); + fungibles.flip(); + assert_eq!(fungibles.get(), true); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + + /// A helper function used for calling contract messages. + use ink_e2e::ContractsBackend; + + /// The End-to-End test `Result` type. + type E2EResult = std::result::Result>; + + /// We test that we can upload and instantiate the contract using its default constructor. + #[ink_e2e::test] + async fn default_works(mut client: ink_e2e::Client) -> E2EResult<()> { + // Given + let mut constructor = FungiblesRef::default(); + + // When + let contract = client + .instantiate("fungibles", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + // Then + let get = call_builder.get(); + let get_result = client.call(&ink_e2e::alice(), &get).dry_run().await?; + assert!(matches!(get_result.return_value(), false)); + + Ok(()) + } + + /// We test that we can read and write a value from the on-chain contract contract. + #[ink_e2e::test] + async fn it_works(mut client: ink_e2e::Client) -> E2EResult<()> { + // Given + let mut constructor = FungiblesRef::new(false); + let contract = client + .instantiate("fungibles", &ink_e2e::bob(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call_builder = contract.call_builder::(); + + let get = call_builder.get(); + let get_result = client.call(&ink_e2e::bob(), &get).dry_run().await?; + assert!(matches!(get_result.return_value(), false)); + + // When + let flip = call_builder.flip(); + let _flip_result = + client.call(&ink_e2e::bob(), &flip).submit().await.expect("flip failed"); + + // Then + let get = call_builder.get(); + let get_result = client.call(&ink_e2e::bob(), &get).dry_run().await?; + assert!(matches!(get_result.return_value(), true)); + + Ok(()) + } + } +} diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index b6f9adaa..1098b557 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -6,13 +6,25 @@ edition = "2021" [dependencies] bounded-collections = { version = "0.1", default-features = false } -scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } + +scale = { package = "parity-scale-codec", version = "3.6.9", default-features = false, features = ["derive"] } +scale-decode = { version = "0.10.0", default-features = false, features = ["derive"], optional = true } +scale-encode = { version = "0.5.0", default-features = false, features = ["derive"], optional = true } scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } +#scale = { workspace = true, features = ["max-encoded-len"] } +#scale-decode = { workspace = true, features = ["derive"], optional = true } +#scale-encode = { workspace = true, features = ["derive"], optional = true } +#scale-info = { workspace = true, features = ["derive"], optional = true } + [features] default = ["std"] std = [ "bounded-collections/std", "scale/std", + "scale-decode/std", + "scale-encode/std", "scale-info/std", ] +devnet = [] +testnet = [] \ No newline at end of file diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index ebad36d3..7c1672b8 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -1,10 +1,22 @@ #![cfg_attr(not(feature = "std"), no_std, no_main)] pub use bounded_collections::{BoundedBTreeMap, BoundedBTreeSet, BoundedVec, ConstU32}; +use scale::{Decode, Encode, MaxEncodedLen}; +#[cfg(feature = "std")] +use { + scale_decode::DecodeAsType, + scale_encode::EncodeAsType, + scale_info::TypeInfo, +}; + pub mod cross_chain; pub mod storage_keys; +#[derive(Encode, Decode, Debug, MaxEncodedLen, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "std", derive(TypeInfo, DecodeAsType, EncodeAsType))] +pub struct AccountId(pub [u8; 32]); + // Identifier for the class of asset. pub type AssetId = u32; // Id used for identifying non-fungible collections. diff --git a/primitives/src/storage_keys.rs b/primitives/src/storage_keys.rs index a03b3a09..67448842 100644 --- a/primitives/src/storage_keys.rs +++ b/primitives/src/storage_keys.rs @@ -1,11 +1,12 @@ use super::*; -use scale::{Decode, Encode, MaxEncodedLen}; +// use scale::{Decode, Encode, MaxEncodedLen}; #[derive(Encode, Decode, Debug, MaxEncodedLen)] pub enum RuntimeStateKeys { Nfts(NftsKeys), ParachainSystem(ParachainSystemKeys), - TrustBackedAssets(TrustBackedAssetsKeys), + #[cfg(feature = "devnet")] + Assets(AssetsKeys), } #[derive(Encode, Decode, Debug, MaxEncodedLen)] @@ -35,8 +36,15 @@ pub enum NftsKeys { CollectionAttribute(CollectionId, BoundedVec), } +/// The required input for state queries in pallet assets. +#[cfg(feature = "devnet")] #[derive(Encode, Decode, Debug, MaxEncodedLen)] -pub enum TrustBackedAssetsKeys { +pub enum AssetsKeys { + Allowance(AssetId, AccountId, AccountId), /// Check if the asset exists. AssetExists(AssetId), + /// Check balance. + BalanceOf(AssetId, AccountId), + /// Returns the total token supply for a given asset ID. + TotalSupply(AssetId), } diff --git a/runtime/devnet/Cargo.toml b/runtime/devnet/Cargo.toml index 55d9942d..d4fe2923 100644 --- a/runtime/devnet/Cargo.toml +++ b/runtime/devnet/Cargo.toml @@ -22,7 +22,7 @@ scale-info.workspace = true smallvec.workspace = true # Local -pop-primitives.workspace = true +pop-primitives = { workspace = true, default-features = false, features = ["devnet"] } pop-runtime-common = { workspace = true, default-features = false } # Substrate diff --git a/runtime/devnet/src/config/assets.rs b/runtime/devnet/src/config/assets.rs index f51f8875..2c8ea952 100644 --- a/runtime/devnet/src/config/assets.rs +++ b/runtime/devnet/src/config/assets.rs @@ -1,6 +1,6 @@ use crate::{ - deposit, AccountId, Balance, Balances, BlockNumber, Nfts, Runtime, RuntimeEvent, - RuntimeHoldReason, TrustBackedAssets, DAYS, EXISTENTIAL_DEPOSIT, UNIT, + deposit, AccountId, Assets, Balance, Balances, BlockNumber, Nfts, Runtime, RuntimeEvent, + RuntimeHoldReason, DAYS, EXISTENTIAL_DEPOSIT, UNIT, }; use frame_support::{ parameter_types, @@ -86,7 +86,7 @@ impl pallet_nft_fractionalization::Config for Runtime { type NftId = ::ItemId; type AssetBalance = >::Balance; type AssetId = >::AssetId; - type Assets = TrustBackedAssets; + type Assets = Assets; type Nfts = Nfts; type PalletId = NftFractionalizationPalletId; type WeightInfo = pallet_nft_fractionalization::weights::SubstrateWeight; @@ -96,7 +96,7 @@ impl pallet_nft_fractionalization::Config for Runtime { } pub type TrustBackedAssetsInstance = pallet_assets::Instance1; -pub(crate) type TrustBackedAssetsCall = pallet_assets::Call; +pub(crate) type AssetsCall = pallet_assets::Call; impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; diff --git a/runtime/devnet/src/config/mod.rs b/runtime/devnet/src/config/mod.rs index a3a64c92..c370bb1d 100644 --- a/runtime/devnet/src/config/mod.rs +++ b/runtime/devnet/src/config/mod.rs @@ -1,4 +1,4 @@ -mod assets; +pub(crate) mod assets; mod contracts; mod proxy; // Public due to integration tests crate. diff --git a/runtime/devnet/src/config/proxy.rs b/runtime/devnet/src/config/proxy.rs index a4fd479a..07d5f0f8 100644 --- a/runtime/devnet/src/config/proxy.rs +++ b/runtime/devnet/src/config/proxy.rs @@ -1,4 +1,4 @@ -use super::assets::TrustBackedAssetsCall; +use super::assets::AssetsCall; use crate::{Balances, Runtime, RuntimeCall, RuntimeEvent}; use frame_support::traits::InstanceFilter; use pop_runtime_common::proxy::{ @@ -34,16 +34,16 @@ impl InstanceFilter for ProxyType { }, ProxyType::AssetOwner => matches!( c, - RuntimeCall::Assets(TrustBackedAssetsCall::create { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::start_destroy { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::destroy_accounts { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::destroy_approvals { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::finish_destroy { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::transfer_ownership { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::set_team { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::set_metadata { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::clear_metadata { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::set_min_balance { .. }) + RuntimeCall::Assets(AssetsCall::create { .. }) + | RuntimeCall::Assets(AssetsCall::start_destroy { .. }) + | RuntimeCall::Assets(AssetsCall::destroy_accounts { .. }) + | RuntimeCall::Assets(AssetsCall::destroy_approvals { .. }) + | RuntimeCall::Assets(AssetsCall::finish_destroy { .. }) + | RuntimeCall::Assets(AssetsCall::transfer_ownership { .. }) + | RuntimeCall::Assets(AssetsCall::set_team { .. }) + | RuntimeCall::Assets(AssetsCall::set_metadata { .. }) + | RuntimeCall::Assets(AssetsCall::clear_metadata { .. }) + | RuntimeCall::Assets(AssetsCall::set_min_balance { .. }) | RuntimeCall::Nfts(pallet_nfts::Call::create { .. }) | RuntimeCall::Nfts(pallet_nfts::Call::destroy { .. }) | RuntimeCall::Nfts(pallet_nfts::Call::redeposit { .. }) @@ -56,15 +56,15 @@ impl InstanceFilter for ProxyType { ), ProxyType::AssetManager => matches!( c, - RuntimeCall::Assets(TrustBackedAssetsCall::mint { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::burn { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::freeze { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::block { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::thaw { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::freeze_asset { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::thaw_asset { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::touch_other { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::refund_other { .. }) + RuntimeCall::Assets(AssetsCall::mint { .. }) + | RuntimeCall::Assets(AssetsCall::burn { .. }) + | RuntimeCall::Assets(AssetsCall::freeze { .. }) + | RuntimeCall::Assets(AssetsCall::block { .. }) + | RuntimeCall::Assets(AssetsCall::thaw { .. }) + | RuntimeCall::Assets(AssetsCall::freeze_asset { .. }) + | RuntimeCall::Assets(AssetsCall::thaw_asset { .. }) + | RuntimeCall::Assets(AssetsCall::touch_other { .. }) + | RuntimeCall::Assets(AssetsCall::refund_other { .. }) | RuntimeCall::Nfts(pallet_nfts::Call::force_mint { .. }) | RuntimeCall::Nfts(pallet_nfts::Call::update_mint_settings { .. }) | RuntimeCall::Nfts(pallet_nfts::Call::mint_pre_signed { .. }) diff --git a/runtime/devnet/src/extensions.rs b/runtime/devnet/src/extensions.rs deleted file mode 100644 index c8e88eb2..00000000 --- a/runtime/devnet/src/extensions.rs +++ /dev/null @@ -1,1018 +0,0 @@ -use cumulus_pallet_parachain_system::RelaychainDataProvider; -use frame_support::traits::{Contains, OriginTrait}; -use frame_support::{ - dispatch::{GetDispatchInfo, RawOrigin}, - pallet_prelude::*, - traits::{fungibles::Inspect, nonfungibles_v2::Inspect as NonFungiblesInspect}, -}; -use pallet_contracts::chain_extension::{ - BufInBufOutState, ChainExtension, ChargedAmount, Environment, Ext, InitState, RetVal, -}; -use pop_primitives::{ - cross_chain::CrossChainMessage, - storage_keys::{NftsKeys, ParachainSystemKeys, RuntimeStateKeys, TrustBackedAssetsKeys}, - AssetId, CollectionId, ItemId, -}; -use sp_core::crypto::UncheckedFrom; -use sp_runtime::{ - traits::{BlockNumberProvider, Dispatchable}, - DispatchError, -}; -use sp_std::{boxed::Box, vec::Vec}; -use xcm::{ - latest::{prelude::*, OriginKind::SovereignAccount}, - VersionedXcm, -}; - -use crate::{ - assets_config::TrustBackedAssetsInstance, AccountId, AllowedPopApiCalls, RuntimeCall, - RuntimeOrigin, UNIT, -}; - -const LOG_TARGET: &str = "pop-api::extension"; - -type ContractSchedule = ::Schedule; - -#[derive(Default)] -pub struct PopApiExtension; - -impl ChainExtension for PopApiExtension -where - T: pallet_contracts::Config - + pallet_xcm::Config - + pallet_assets::Config - + pallet_nfts::Config - + cumulus_pallet_parachain_system::Config - + frame_system::Config< - RuntimeOrigin = RuntimeOrigin, - AccountId = AccountId, - RuntimeCall = RuntimeCall, - >, - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ - fn call(&mut self, env: Environment) -> Result - where - E: Ext, - T::AccountId: UncheckedFrom + AsRef<[u8]>, - { - log::debug!(target:LOG_TARGET, " extension called "); - match v0::FuncId::try_from(env.func_id())? { - v0::FuncId::Dispatch => { - match dispatch::(env) { - Ok(()) => Ok(RetVal::Converging(0)), - Err(DispatchError::Module(error)) => { - // encode status code = pallet index in runtime + error index, allowing for - // 999 errors - Ok(RetVal::Converging( - (error.index as u32 * 1_000) + u32::from_le_bytes(error.error), - )) - }, - Err(e) => Err(e), - } - }, - v0::FuncId::ReadState => { - read_state::(env)?; - Ok(RetVal::Converging(0)) - }, - v0::FuncId::SendXcm => { - send_xcm::(env)?; - Ok(RetVal::Converging(0)) - }, - } - } -} - -pub mod v0 { - #[derive(Debug)] - pub enum FuncId { - Dispatch, - ReadState, - SendXcm, - } -} - -impl TryFrom for v0::FuncId { - type Error = DispatchError; - - fn try_from(func_id: u16) -> Result { - let id = match func_id { - 0x0 => Self::Dispatch, - 0x1 => Self::ReadState, - 0x2 => Self::SendXcm, - _ => { - log::error!("called an unregistered `func_id`: {:}", func_id); - return Err(DispatchError::Other("unimplemented func_id")); - }, - }; - - Ok(id) - } -} - -fn dispatch_call( - env: &mut Environment, - call: RuntimeCall, - mut origin: RuntimeOrigin, - log_prefix: &str, -) -> Result<(), DispatchError> -where - T: frame_system::Config, - RuntimeOrigin: From>, - E: Ext, -{ - let charged_dispatch_weight = env.charge_weight(call.get_dispatch_info().weight)?; - - log::debug!(target:LOG_TARGET, "{} inputted RuntimeCall: {:?}", log_prefix, call); - - origin.add_filter(AllowedPopApiCalls::contains); - - match call.dispatch(origin) { - Ok(info) => { - log::debug!(target:LOG_TARGET, "{} success, actual weight: {:?}", log_prefix, info.actual_weight); - - // refund weight if the actual weight is less than the charged weight - if let Some(actual_weight) = info.actual_weight { - env.adjust_weight(charged_dispatch_weight, actual_weight); - } - - Ok(()) - }, - Err(err) => { - log::debug!(target:LOG_TARGET, "{} failed: error: {:?}", log_prefix, err.error); - Err(err.error) - }, - } -} - -fn charge_overhead_weight( - env: &mut Environment, - len: u32, - log_prefix: &str, -) -> Result -where - T: pallet_contracts::Config, - E: Ext, -{ - let contract_host_weight = ContractSchedule::::get().host_fn_weights; - - // calculate weight for reading bytes of `len` - // reference: https://github.com/paritytech/polkadot-sdk/blob/117a9433dac88d5ac00c058c9b39c511d47749d2/substrate/frame/contracts/src/wasm/runtime.rs#L267 - let base_weight: Weight = contract_host_weight.return_per_byte.saturating_mul(len.into()); - - // debug_message weight is a good approximation of the additional overhead of going - // from contract layer to substrate layer. - // reference: https://github.com/paritytech/ink-examples/blob/b8d2caa52cf4691e0ddd7c919e4462311deb5ad0/psp22-extension/runtime/psp22-extension-example.rs#L236 - let overhead = contract_host_weight.debug_message; - - let charged_weight = env.charge_weight(base_weight.saturating_add(overhead))?; - log::debug!(target: LOG_TARGET, "{} charged weight: {:?}", log_prefix, charged_weight); - - Ok(charged_weight) -} - -fn dispatch(env: Environment) -> Result<(), DispatchError> -where - T: pallet_contracts::Config - + frame_system::Config, - RuntimeOrigin: From>, - E: Ext, -{ - const LOG_PREFIX: &str = " dispatch |"; - - let mut env = env.buf_in_buf_out(); - let len = env.in_len(); - - charge_overhead_weight::(&mut env, len, LOG_PREFIX)?; - - // read the input as RuntimeCall - let call: RuntimeCall = env.read_as_unbounded(len)?; - - // contract is the origin by default - let origin: RuntimeOrigin = RawOrigin::Signed(env.ext().address().clone()).into(); - - dispatch_call::(&mut env, call, origin, LOG_PREFIX) -} - -fn read_state(env: Environment) -> Result<(), DispatchError> -where - T: pallet_contracts::Config - + pallet_assets::Config - + pallet_nfts::Config - + cumulus_pallet_parachain_system::Config - + frame_system::Config, - E: Ext, -{ - const LOG_PREFIX: &str = " read_state |"; - - let mut env = env.buf_in_buf_out(); - - // To be conservative, we charge the weight for reading the input bytes of a fixed-size type. - let base_weight: Weight = ContractSchedule::::get() - .host_fn_weights - .return_per_byte - .saturating_mul(env.in_len().into()); - let charged_weight = env.charge_weight(base_weight)?; - - log::debug!(target:LOG_TARGET, "{} charged weight: {:?}", LOG_PREFIX, charged_weight); - - let key: RuntimeStateKeys = env.read_as()?; - - let result = match key { - RuntimeStateKeys::Nfts(key) => read_nfts_state::(key, &mut env), - RuntimeStateKeys::ParachainSystem(key) => { - read_parachain_system_state::(key, &mut env) - }, - RuntimeStateKeys::TrustBackedAssets(key) => { - read_trust_backed_assets_state::(key, &mut env) - }, - }? - .encode(); - - log::trace!( - target:LOG_TARGET, - "{} result: {:?}.", LOG_PREFIX, result - ); - env.write(&result, false, None).map_err(|e| { - log::trace!(target: LOG_TARGET, "{:?}", e); - DispatchError::Other("unable to write results to contract memory") - }) -} - -fn read_parachain_system_state( - key: ParachainSystemKeys, - env: &mut Environment, -) -> Result, DispatchError> -where - T: pallet_contracts::Config + cumulus_pallet_parachain_system::Config, - E: Ext, -{ - match key { - ParachainSystemKeys::LastRelayChainBlockNumber => { - env.charge_weight(T::DbWeight::get().reads(1_u64))?; - Ok(RelaychainDataProvider::::current_block_number().encode()) - }, - } -} - -fn read_nfts_state( - key: NftsKeys, - env: &mut Environment, -) -> Result, DispatchError> -where - T: pallet_contracts::Config + pallet_nfts::Config, - E: Ext, -{ - match key { - NftsKeys::Collection(collection) => { - env.charge_weight(T::DbWeight::get().reads(1_u64))?; - Ok(pallet_nfts::Collection::::get(collection).encode()) - }, - NftsKeys::CollectionOwner(collection) => { - env.charge_weight(T::DbWeight::get().reads(1_u64))?; - Ok(pallet_nfts::Pallet::::collection_owner(collection).encode()) - }, - NftsKeys::Item(collection, item) => { - env.charge_weight(T::DbWeight::get().reads(1_u64))?; - Ok(pallet_nfts::Item::::get(collection, item).encode()) - }, - NftsKeys::Owner(collection, item) => { - env.charge_weight(T::DbWeight::get().reads(1_u64))?; - Ok(pallet_nfts::Pallet::::owner(collection, item).encode()) - }, - NftsKeys::Attribute(collection, item, key) => { - env.charge_weight(T::DbWeight::get().reads(1_u64))?; - Ok(pallet_nfts::Pallet::::attribute(&collection, &item, &key).encode()) - }, - // NftsKeys::CustomAttribute(account, collection, item, key) => { - // env.charge_weight(T::DbWeight::get().reads(1_u64))?; - // Ok(pallet_nfts::Pallet::::custom_attribute(&account, &collection, &item, &key) - // .encode()) - // }, - NftsKeys::SystemAttribute(collection, item, key) => { - env.charge_weight(T::DbWeight::get().reads(1_u64))?; - Ok(pallet_nfts::Pallet::::system_attribute(&collection, item.as_ref(), &key) - .encode()) - }, - NftsKeys::CollectionAttribute(collection, key) => { - env.charge_weight(T::DbWeight::get().reads(1_u64))?; - Ok(pallet_nfts::Pallet::::collection_attribute(&collection, &key).encode()) - }, - } -} - -fn read_trust_backed_assets_state( - key: TrustBackedAssetsKeys, - env: &mut Environment, -) -> Result, DispatchError> -where - T: pallet_contracts::Config - + pallet_assets::Config, - E: Ext, -{ - match key { - TrustBackedAssetsKeys::AssetExists(id) => { - env.charge_weight(T::DbWeight::get().reads(1_u64))?; - Ok(pallet_assets::Pallet::::asset_exists(id).encode()) - }, - } -} - -fn send_xcm(env: Environment) -> Result<(), DispatchError> -where - T: pallet_contracts::Config - + frame_system::Config< - RuntimeOrigin = RuntimeOrigin, - AccountId = AccountId, - RuntimeCall = RuntimeCall, - >, - E: Ext, -{ - const LOG_PREFIX: &str = " send_xcm |"; - - let mut env = env.buf_in_buf_out(); - let len = env.in_len(); - - let _ = charge_overhead_weight::(&mut env, len, LOG_PREFIX)?; - - // read the input as CrossChainMessage - let xc_call: CrossChainMessage = env.read_as::()?; - - // Determine the call to dispatch - let (dest, message) = match xc_call { - CrossChainMessage::Relay(message) => { - let dest = Location::parent().into_versioned(); - let assets: Asset = (Here, 10 * UNIT).into(); - let beneficiary: Location = - AccountId32 { id: (env.ext().address().clone()).into(), network: None }.into(); - let message = Xcm::builder() - .withdraw_asset(assets.clone().into()) - .buy_execution(assets.clone(), Unlimited) - .transact( - SovereignAccount, - Weight::from_parts(250_000_000, 10_000), - message.encode().into(), - ) - .refund_surplus() - .deposit_asset(assets.into(), beneficiary) - .build(); - (dest, message) - }, - }; - - // TODO: revisit to replace with signed contract origin - let origin: RuntimeOrigin = RawOrigin::Root.into(); - - // Generate runtime call to dispatch - let call = RuntimeCall::PolkadotXcm(pallet_xcm::Call::send { - dest: Box::new(dest), - message: Box::new(VersionedXcm::V4(message)), - }); - - dispatch_call::(&mut env, call, origin, LOG_PREFIX) -} - -#[cfg(test)] -mod tests { - pub use super::*; - pub use crate::*; - use enumflags2::BitFlags; - pub use pallet_contracts::Code; - use pallet_nfts::{CollectionConfig, CollectionSetting, CollectionSettings, MintSettings}; - use parachains_common::CollectionId; - pub use sp_runtime::{traits::Hash, AccountId32}; - - const DEBUG_OUTPUT: pallet_contracts::DebugInfo = pallet_contracts::DebugInfo::UnsafeDebug; - - const ALICE: AccountId32 = AccountId32::new([1_u8; 32]); - const BOB: AccountId32 = AccountId32::new([2_u8; 32]); - const INITIAL_AMOUNT: u128 = 100_000 * UNIT; - const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); - - fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::::default() - .build_storage() - .expect("Frame system builds valid default genesis config"); - - pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, INITIAL_AMOUNT), (BOB, INITIAL_AMOUNT)], - } - .assimilate_storage(&mut t) - .expect("Pallet balances storage can be assimilated"); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } - - fn load_wasm_module(path: &str) -> std::io::Result<(Vec, ::Output)> - where - T: frame_system::Config, - { - let wasm_binary = std::fs::read(path)?; - let code_hash = T::Hashing::hash(&wasm_binary); - Ok((wasm_binary, code_hash)) - } - - fn function_selector(name: &str) -> Vec { - let hash = sp_io::hashing::blake2_256(name.as_bytes()); - [hash[0..4].to_vec()].concat() - } - - // NFT helper functions - fn collection_config_from_disabled_settings( - settings: BitFlags, - ) -> CollectionConfig { - CollectionConfig { - settings: CollectionSettings::from_disabled(settings), - max_supply: None, - mint_settings: MintSettings::default(), - } - } - - fn default_collection_config() -> CollectionConfig { - collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()) - } - - #[test] - #[ignore] - fn dispatch_balance_transfer_from_contract_works() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - - let (wasm_binary, _) = load_wasm_module::( - "../../pop-api/examples/balance-transfer/target/ink/balance_transfer.wasm", - ) - .unwrap(); - - let init_value = 100 * UNIT; - - let result = Contracts::bare_instantiate( - ALICE, - init_value, - GAS_LIMIT, - None, - Code::Upload(wasm_binary), - function_selector("new"), - vec![], - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - ) - .result - .unwrap(); - - assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); - - let addr = result.account_id; - - let function = function_selector("transfer_through_runtime"); - let value_to_send: u128 = 10 * UNIT; - let params = [function, BOB.encode(), value_to_send.encode()].concat(); - - let bob_balance_before = Balances::free_balance(&BOB); - assert_eq!(bob_balance_before, INITIAL_AMOUNT); - - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), - None, - params, - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - pallet_contracts::Determinism::Enforced, - ); - - if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { - log::debug!( - "Contract debug buffer - {:?}", - String::from_utf8(result.debug_message.clone()) - ); - log::debug!("result: {:?}", result); - } - - // check for revert - assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); - - let bob_balance_after = Balances::free_balance(&BOB); - assert_eq!(bob_balance_before + value_to_send, bob_balance_after); - }); - } - - // Create a test for tesing create_nft_collection - #[test] - #[ignore] - fn dispatch_nfts_create_nft_collection() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - - let (wasm_binary, _) = load_wasm_module::( - "../../pop-api/examples/nfts/target/ink/pop_api_nft_example.wasm", - ) - .unwrap(); - - let init_value = 100 * UNIT; - - let result = Contracts::bare_instantiate( - ALICE, - init_value, - GAS_LIMIT, - None, - Code::Upload(wasm_binary), - function_selector("new"), - vec![], - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - ) - .result - .unwrap(); - - assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); - - let addr = result.account_id; - - let function = function_selector("create_nft_collection"); - - let params = [function].concat(); - - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), - None, - params, - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - pallet_contracts::Determinism::Enforced, - ); - - if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { - log::debug!( - "Contract debug buffer - {:?}", - String::from_utf8(result.debug_message.clone()) - ); - log::debug!("result: {:?}", result); - } - - // check that the nft collection was created - assert_eq!(Nfts::collection_owner(0), Some(addr.clone().into())); - - // test reading the collection - let function = function_selector("read_collection"); - - let params = [function, 0.encode()].concat(); - - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), - None, - params, - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - pallet_contracts::Determinism::Enforced, - ); - - if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { - log::debug!( - "Contract debug buffer - {:?}", - String::from_utf8(result.debug_message.clone()) - ); - log::debug!("result: {:?}", result); - } - - // assert that the collection was read successfully - assert_eq!(result.result.clone().unwrap().data, vec![1, 1]); - }); - } - - #[test] - #[ignore] - fn dispatch_nfts_mint_from_contract_works() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - - let (wasm_binary, _) = - load_wasm_module::("../../pop-api/examples/nfts/target/ink/nfts.wasm") - .unwrap(); - - let init_value = 100; - - let result = Contracts::bare_instantiate( - ALICE, - init_value, - GAS_LIMIT, - None, - Code::Upload(wasm_binary), - function_selector("new"), - vec![], - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - ) - .result - .unwrap(); - - assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); - - let addr = result.account_id; - - let collection_id: u32 = 0; - let item_id: u32 = 1; - - // create nft collection with contract as owner - assert_eq!( - Nfts::force_create( - RuntimeOrigin::root(), - addr.clone().into(), - default_collection_config() - ), - Ok(()) - ); - - assert_eq!(Nfts::collection_owner(collection_id), Some(addr.clone().into())); - // assert that the item does not exist yet - assert_eq!(Nfts::owner(collection_id, item_id), None); - - let function = function_selector("mint_through_runtime"); - - let params = - [function, collection_id.encode(), item_id.encode(), BOB.encode()].concat(); - - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), - None, - params, - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - pallet_contracts::Determinism::Enforced, - ); - - if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { - log::debug!( - "Contract debug buffer - {:?}", - String::from_utf8(result.debug_message.clone()) - ); - log::debug!("result: {:?}", result); - } - - // check for revert - assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); - - assert_eq!(Nfts::owner(collection_id, item_id), Some(BOB.into())); - }); - } - - #[test] - #[ignore] - fn nfts_mint_surfaces_error() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - - let (wasm_binary, _) = - load_wasm_module::("../../pop-api/examples/nfts/target/ink/nfts.wasm") - .unwrap(); - - let init_value = 100; - - let result = Contracts::bare_instantiate( - ALICE, - init_value, - GAS_LIMIT, - None, - Code::Upload(wasm_binary), - function_selector("new"), - vec![], - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - ) - .result - .unwrap(); - - assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); - - let addr = result.account_id; - - let collection_id: u32 = 0; - let item_id: u32 = 1; - - let function = function_selector("mint_through_runtime"); - - let params = - [function, collection_id.encode(), item_id.encode(), BOB.encode()].concat(); - - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), - None, - params, - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - pallet_contracts::Determinism::Enforced, - ); - - if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { - log::debug!( - "Contract debug buffer - {:?}", - String::from_utf8(result.debug_message.clone()) - ); - log::debug!("result: {:?}", result); - } - - // check for revert with expected error - let result = result.result.unwrap(); - assert!(result.did_revert()); - }); - } - - #[test] - #[ignore] - fn reading_last_relay_chain_block_number_works() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - - let (wasm_binary, _) = load_wasm_module::( - "../../pop-api/examples/read-runtime-state/target/ink/read_relay_blocknumber.wasm", - ) - .unwrap(); - - let init_value = 100; - - let contract = Contracts::bare_instantiate( - ALICE, - init_value, - GAS_LIMIT, - None, - Code::Upload(wasm_binary), - function_selector("new"), - vec![], - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - ) - .result - .unwrap(); - - assert!(!contract.result.did_revert(), "deploying contract reverted {:?}", contract); - - let addr = contract.account_id; - - let function = function_selector("read_relay_block_number"); - let params = [function].concat(); - - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), - None, - params, - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::UnsafeCollect, - pallet_contracts::Determinism::Relaxed, - ); - - if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { - log::debug!( - "Contract debug buffer - {:?}", - String::from_utf8(result.debug_message.clone()) - ); - log::debug!("result: {:?}", result); - } - - // check for revert - assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); - }); - } - - #[test] - #[ignore] - fn place_spot_order_from_contract_works() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - - let (wasm_binary, _) = load_wasm_module::( - "../../pop-api/examples/place-spot-order/target/ink/spot_order.wasm", - ) - .unwrap(); - - let init_value = 100 * UNIT; - - let result = Contracts::bare_instantiate( - ALICE, - init_value, - GAS_LIMIT, - None, - Code::Upload(wasm_binary), - function_selector("new"), - vec![], - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - ) - .result - .unwrap(); - - assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); - - let addr = result.account_id; - - let function = function_selector("place_spot_order"); - - let max_amount = 1 * UNIT; - let para_id = 2000; - - let params = [function, max_amount.encode(), para_id.encode()].concat(); - - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), - None, - params, - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - pallet_contracts::Determinism::Enforced, - ); - - if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { - log::debug!( - "Contract debug buffer - {:?}", - String::from_utf8(result.debug_message.clone()) - ); - log::debug!("result: {:?}", result); - } - - // check for revert - assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); - }); - } - - #[test] - #[ignore] - fn dispatch_trust_backed_assets_mint_from_contract_works() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - - let (wasm_binary, _) = load_wasm_module::( - "../../pop-api/examples/trust_backed_assets/target/ink/pop_api_trust_backed_assets_example.wasm", - ) - .unwrap(); - - let init_value = 100; - - let result = Contracts::bare_instantiate( - ALICE, - init_value, - GAS_LIMIT, - None, - Code::Upload(wasm_binary), - function_selector("new"), - vec![], - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - ) - .result - .unwrap(); - - assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); - let addr = result.account_id; - - let asset_id: u32 = 1; - let min_balance = 1; - let amount: u128 = 100 * UNIT; - let function = function_selector("mint_asset_through_runtime"); - let params = [function, asset_id.encode(), BOB.encode(), amount.encode()].concat(); - - // Mint asset which does not exist. - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - GAS_LIMIT, - None, - params.clone(), - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - pallet_contracts::Determinism::Enforced, - ); - - if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { - log::debug!( - "Contract debug buffer - {:?}", - String::from_utf8(result.debug_message.clone()) - ); - log::debug!("result: {:?}", result); - } - - // Check for revert. - assert!(result.result.unwrap().did_revert(), "Contract should have been reverted!"); - - // Create asset with contract as owner. - assert_eq!( - TrustBackedAssets::force_create( - RuntimeOrigin::root(), - asset_id.into(), - addr.clone().into(), - true, - min_balance, - ), - Ok(()) - ); - - // Check Bob's asset balance before minting through contract. - let bob_balance_before = TrustBackedAssets::balance(asset_id, &BOB); - assert_eq!(bob_balance_before, 0); - - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - GAS_LIMIT, - None, - params, - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - pallet_contracts::Determinism::Enforced, - ); - - if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { - log::debug!( - "Contract debug buffer - {:?}", - String::from_utf8(result.debug_message.clone()) - ); - log::debug!("result: {:?}", result); - } - - // Check for revert - assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); - - let bob_balance_after = TrustBackedAssets::balance(asset_id, &BOB); - assert_eq!(bob_balance_after, bob_balance_before + amount); - }); - } - - #[test] - #[ignore] - fn allow_call_filter_blocks_call() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - - let (wasm_binary, _) = load_wasm_module::( - "../../tests/contracts/filtered-call/target/ink/pop_api_filtered_call.wasm", - ) - .unwrap(); - - let init_value = 100 * UNIT; - - let result = Contracts::bare_instantiate( - ALICE, - init_value, - GAS_LIMIT, - None, - Code::Upload(wasm_binary), - function_selector("new"), - vec![], - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - ) - .result - .unwrap(); - - assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); - - let addr = result.account_id; - - let function = function_selector("get_filtered"); - let params = [function].concat(); - - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), - None, - params, - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - pallet_contracts::Determinism::Enforced, - ); - - if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { - log::debug!( - "Contract debug buffer - {:?}", - String::from_utf8(result.debug_message.clone()) - ); - log::debug!("filtered result: {:?}", result); - } - - // check for revert - assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); - }); - } -} diff --git a/runtime/devnet/src/extensions/mod.rs b/runtime/devnet/src/extensions/mod.rs new file mode 100644 index 00000000..4bd95e9b --- /dev/null +++ b/runtime/devnet/src/extensions/mod.rs @@ -0,0 +1,885 @@ +use cumulus_pallet_parachain_system::RelaychainDataProvider; +use frame_support::traits::{Contains, OriginTrait}; +use frame_support::{ + dispatch::{GetDispatchInfo, RawOrigin}, + pallet_prelude::*, + traits::{fungibles::{Inspect, approvals::Inspect as ApprovalInspect}, nonfungibles_v2::Inspect as NonFungiblesInspect}, +}; +use pallet_contracts::chain_extension::{ + BufInBufOutState, ChainExtension, ChargedAmount, Environment, Ext, InitState, RetVal, +}; +use pop_primitives::{ + cross_chain::CrossChainMessage, + storage_keys::{AssetsKeys, NftsKeys, ParachainSystemKeys, RuntimeStateKeys}, + AssetId, CollectionId, ItemId, +}; +use sp_core::crypto::UncheckedFrom; +use sp_runtime::{ + traits::{BlockNumberProvider, Dispatchable}, + DispatchError, +}; +use sp_std::{boxed::Box, vec::Vec}; +use xcm::{ + latest::{prelude::*, OriginKind::SovereignAccount}, + VersionedXcm, +}; + +use crate::{ + config::assets::TrustBackedAssetsInstance, AccountId, AllowedPopApiCalls, RuntimeCall, + RuntimeOrigin, UNIT, +}; + +#[cfg(test)] +mod tests; + +const LOG_TARGET: &str = "pop-api::extension"; + +type ContractSchedule = ::Schedule; + +#[derive(Default)] +pub struct PopApiExtension; + +impl ChainExtension for PopApiExtension +where + T: pallet_contracts::Config + + pallet_xcm::Config + + pallet_assets::Config + + pallet_nfts::Config + + cumulus_pallet_parachain_system::Config + + frame_system::Config< + RuntimeOrigin = RuntimeOrigin, + AccountId = AccountId, + RuntimeCall = RuntimeCall, + >, + T::AccountId: UncheckedFrom + AsRef<[u8]>, +{ + fn call(&mut self, env: Environment) -> Result + where + E: Ext, + // T::AccountId: UncheckedFrom + AsRef<[u8]>, + { + log::debug!(target:LOG_TARGET, " extension called "); + match v0::FuncId::try_from(env.func_id())? { + v0::FuncId::Dispatch => { + match dispatch::(env) { + Ok(()) => Ok(RetVal::Converging(0)), + Err(DispatchError::Module(error)) => { + // encode status code = pallet index in runtime + error index, allowing for + // 999 errors + Ok(RetVal::Converging( + (error.index as u32 * 1_000) + u32::from_le_bytes(error.error), + )) + }, + Err(e) => Err(e), + } + }, + v0::FuncId::ReadState => { + read_state::(env)?; + Ok(RetVal::Converging(0)) + }, + v0::FuncId::SendXcm => { + send_xcm::(env)?; + Ok(RetVal::Converging(0)) + }, + } + } +} + +pub mod v0 { + #[derive(Debug)] + pub enum FuncId { + Dispatch, + ReadState, + SendXcm, + } +} + +impl TryFrom for v0::FuncId { + type Error = DispatchError; + + fn try_from(func_id: u16) -> Result { + let id = match func_id { + 0x0 => Self::Dispatch, + 0x1 => Self::ReadState, + 0x2 => Self::SendXcm, + _ => { + log::error!("called an unregistered `func_id`: {:}", func_id); + return Err(DispatchError::Other("unimplemented func_id")); + }, + }; + + Ok(id) + } +} + +fn dispatch_call( + env: &mut Environment, + call: RuntimeCall, + mut origin: RuntimeOrigin, + log_prefix: &str, +) -> Result<(), DispatchError> +where + T: frame_system::Config, + RuntimeOrigin: From>, + E: Ext, +{ + let charged_dispatch_weight = env.charge_weight(call.get_dispatch_info().weight)?; + + log::debug!(target:LOG_TARGET, "{} inputted RuntimeCall: {:?}", log_prefix, call); + + origin.add_filter(AllowedPopApiCalls::contains); + + match call.dispatch(origin) { + Ok(info) => { + log::debug!(target:LOG_TARGET, "{} success, actual weight: {:?}", log_prefix, info.actual_weight); + + // refund weight if the actual weight is less than the charged weight + if let Some(actual_weight) = info.actual_weight { + env.adjust_weight(charged_dispatch_weight, actual_weight); + } + + Ok(()) + }, + Err(err) => { + log::debug!(target:LOG_TARGET, "{} failed: error: {:?}", log_prefix, err.error); + Err(err.error) + }, + } +} + +fn charge_overhead_weight( + env: &mut Environment, + len: u32, + log_prefix: &str, +) -> Result +where + T: pallet_contracts::Config, + E: Ext, +{ + let contract_host_weight = ContractSchedule::::get().host_fn_weights; + + // calculate weight for reading bytes of `len` + // reference: https://github.com/paritytech/polkadot-sdk/blob/117a9433dac88d5ac00c058c9b39c511d47749d2/substrate/frame/contracts/src/wasm/runtime.rs#L267 + let base_weight: Weight = contract_host_weight.return_per_byte.saturating_mul(len.into()); + + // debug_message weight is a good approximation of the additional overhead of going + // from contract layer to substrate layer. + // reference: https://github.com/paritytech/ink-examples/blob/b8d2caa52cf4691e0ddd7c919e4462311deb5ad0/psp22-extension/runtime/psp22-extension-example.rs#L236 + let overhead = contract_host_weight.debug_message; + + let charged_weight = env.charge_weight(base_weight.saturating_add(overhead))?; + log::debug!(target: LOG_TARGET, "{} charged weight: {:?}", log_prefix, charged_weight); + + Ok(charged_weight) +} + +fn dispatch(env: Environment) -> Result<(), DispatchError> +where + T: pallet_contracts::Config + + frame_system::Config, + RuntimeOrigin: From>, + E: Ext, +{ + const LOG_PREFIX: &str = " dispatch |"; + + let mut env = env.buf_in_buf_out(); + let len = env.in_len(); + + charge_overhead_weight::(&mut env, len, LOG_PREFIX)?; + + // read the input as RuntimeCall + let call: RuntimeCall = env.read_as_unbounded(len)?; + + // contract is the origin by default + let origin: RuntimeOrigin = RawOrigin::Signed(env.ext().address().clone()).into(); + + dispatch_call::(&mut env, call, origin, LOG_PREFIX) +} + +fn read_state(env: Environment) -> Result<(), DispatchError> +where + T: pallet_contracts::Config + + pallet_assets::Config + + pallet_nfts::Config + + cumulus_pallet_parachain_system::Config + + frame_system::Config, + E: Ext, +{ + const LOG_PREFIX: &str = " read_state |"; + + let mut env = env.buf_in_buf_out(); + + // To be conservative, we charge the weight for reading the input bytes of a fixed-size type. + let base_weight: Weight = ContractSchedule::::get() + .host_fn_weights + .return_per_byte + .saturating_mul(env.in_len().into()); + let charged_weight = env.charge_weight(base_weight)?; + + log::debug!(target:LOG_TARGET, "{} charged weight: {:?}", LOG_PREFIX, charged_weight); + + let key: RuntimeStateKeys = env.read_as()?; + + let result = match key { + RuntimeStateKeys::Nfts(key) => read_nfts_state::(key, &mut env), + RuntimeStateKeys::ParachainSystem(key) => { + read_parachain_system_state::(key, &mut env) + }, + RuntimeStateKeys::Assets(key) => read_trust_backed_assets_state::(key, &mut env), + }? + .encode(); + + log::trace!( + target:LOG_TARGET, + "{} result: {:?}.", LOG_PREFIX, result + ); + env.write(&result, false, None).map_err(|e| { + log::trace!(target: LOG_TARGET, "{:?}", e); + DispatchError::Other("unable to write results to contract memory") + }) +} + +fn read_parachain_system_state( + key: ParachainSystemKeys, + env: &mut Environment, +) -> Result, DispatchError> +where + T: pallet_contracts::Config + cumulus_pallet_parachain_system::Config, + E: Ext, +{ + match key { + ParachainSystemKeys::LastRelayChainBlockNumber => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(RelaychainDataProvider::::current_block_number().encode()) + }, + } +} + +fn read_nfts_state( + key: NftsKeys, + env: &mut Environment, +) -> Result, DispatchError> +where + T: pallet_contracts::Config + pallet_nfts::Config, + E: Ext, +{ + match key { + NftsKeys::Collection(collection) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Collection::::get(collection).encode()) + }, + NftsKeys::CollectionOwner(collection) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Pallet::::collection_owner(collection).encode()) + }, + NftsKeys::Item(collection, item) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Item::::get(collection, item).encode()) + }, + NftsKeys::Owner(collection, item) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Pallet::::owner(collection, item).encode()) + }, + NftsKeys::Attribute(collection, item, key) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Pallet::::attribute(&collection, &item, &key).encode()) + }, + // NftsKeys::CustomAttribute(account, collection, item, key) => { + // env.charge_weight(T::DbWeight::get().reads(1_u64))?; + // Ok(pallet_nfts::Pallet::::custom_attribute(&account, &collection, &item, &key) + // .encode()) + // }, + NftsKeys::SystemAttribute(collection, item, key) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Pallet::::system_attribute(&collection, item.as_ref(), &key) + .encode()) + }, + NftsKeys::CollectionAttribute(collection, key) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Pallet::::collection_attribute(&collection, &key).encode()) + }, + } +} + +fn read_trust_backed_assets_state( + key: AssetsKeys, + env: &mut Environment, +) -> Result, DispatchError> +where + T: pallet_contracts::Config + + pallet_assets::Config, + E: Ext, + T: frame_system::Config +{ + match key { + AssetsKeys::Allowance(id, owner, spender) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_assets::Pallet::::allowance(id, &owner.0.into(), &spender.0.into()).encode()) + }, + AssetsKeys::AssetExists(id) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_assets::Pallet::::asset_exists(id).encode()) + }, + AssetsKeys::BalanceOf(id, owner) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_assets::Pallet::::balance(id, &owner.0.into()).encode()) + }, + AssetsKeys::TotalSupply(id) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_assets::Pallet::::total_supply(id).encode()) + }, + } +} + +fn send_xcm(env: Environment) -> Result<(), DispatchError> +where + T: pallet_contracts::Config + + frame_system::Config< + RuntimeOrigin = RuntimeOrigin, + AccountId = AccountId, + RuntimeCall = RuntimeCall, + >, + E: Ext, +{ + const LOG_PREFIX: &str = " send_xcm |"; + + let mut env = env.buf_in_buf_out(); + let len = env.in_len(); + + let _ = charge_overhead_weight::(&mut env, len, LOG_PREFIX)?; + + // read the input as CrossChainMessage + let xc_call: CrossChainMessage = env.read_as::()?; + + // Determine the call to dispatch + let (dest, message) = match xc_call { + CrossChainMessage::Relay(message) => { + let dest = Location::parent().into_versioned(); + let assets: Asset = (Here, 10 * UNIT).into(); + let beneficiary: Location = + AccountId32 { id: (env.ext().address().clone()).into(), network: None }.into(); + let message = Xcm::builder() + .withdraw_asset(assets.clone().into()) + .buy_execution(assets.clone(), Unlimited) + .transact( + SovereignAccount, + Weight::from_parts(250_000_000, 10_000), + message.encode().into(), + ) + .refund_surplus() + .deposit_asset(assets.into(), beneficiary) + .build(); + (dest, message) + }, + }; + + // TODO: revisit to replace with signed contract origin + let origin: RuntimeOrigin = RawOrigin::Root.into(); + + // Generate runtime call to dispatch + let call = RuntimeCall::PolkadotXcm(pallet_xcm::Call::send { + dest: Box::new(dest), + message: Box::new(VersionedXcm::V4(message)), + }); + + dispatch_call::(&mut env, call, origin, LOG_PREFIX) +} + +// use enumflags2::BitFlags; +// use pallet_nfts::{CollectionConfig, CollectionSetting, CollectionSettings, MintSettings}; +// use parachains_common::CollectionId; +// { +// // NFT helper functions +// fn collection_config_from_disabled_settings( +// settings: BitFlags, +// ) -> CollectionConfig { +// CollectionConfig { +// settings: CollectionSettings::from_disabled(settings), +// max_supply: None, +// mint_settings: MintSettings::default(), +// } +// } +// +// fn default_collection_config() -> CollectionConfig { +// collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()) +// } +// +// #[test] +// #[ignore] +// fn dispatch_balance_transfer_from_contract_works() { +// new_test_ext().execute_with(|| { +// let _ = env_logger::try_init(); +// +// let (wasm_binary, _) = load_wasm_module::( +// "../../pop-api/examples/balance-transfer/target/ink/balance_transfer.wasm", +// ) +// .unwrap(); +// +// let init_value = 100 * UNIT; +// +// let result = Contracts::bare_instantiate( +// ALICE, +// init_value, +// GAS_LIMIT, +// None, +// Code::Upload(wasm_binary), +// function_selector("new"), +// vec![], +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// ) +// .result +// .unwrap(); +// +// assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); +// +// let addr = result.account_id; +// +// let function = function_selector("transfer_through_runtime"); +// let value_to_send: u128 = 10 * UNIT; +// let params = [function, BOB.encode(), value_to_send.encode()].concat(); +// +// let bob_balance_before = Balances::free_balance(&BOB); +// assert_eq!(bob_balance_before, INITIAL_AMOUNT); +// +// let result = Contracts::bare_call( +// ALICE, +// addr.clone(), +// 0, +// Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), +// None, +// params, +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// pallet_contracts::Determinism::Enforced, +// ); +// +// if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { +// log::debug!( +// "Contract debug buffer - {:?}", +// String::from_utf8(result.debug_message.clone()) +// ); +// log::debug!("result: {:?}", result); +// } +// +// // check for revert +// assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); +// +// let bob_balance_after = Balances::free_balance(&BOB); +// assert_eq!(bob_balance_before + value_to_send, bob_balance_after); +// }); +// } +// +// // Create a test for tesing create_nft_collection +// #[test] +// #[ignore] +// fn dispatch_nfts_create_nft_collection() { +// new_test_ext().execute_with(|| { +// let _ = env_logger::try_init(); +// +// let (wasm_binary, _) = load_wasm_module::( +// "../../pop-api/examples/nfts/target/ink/pop_api_nft_example.wasm", +// ) +// .unwrap(); +// +// let init_value = 100 * UNIT; +// +// let result = Contracts::bare_instantiate( +// ALICE, +// init_value, +// GAS_LIMIT, +// None, +// Code::Upload(wasm_binary), +// function_selector("new"), +// vec![], +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// ) +// .result +// .unwrap(); +// +// assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); +// +// let addr = result.account_id; +// +// let function = function_selector("create_nft_collection"); +// +// let params = [function].concat(); +// +// let result = Contracts::bare_call( +// ALICE, +// addr.clone(), +// 0, +// Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), +// None, +// params, +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// pallet_contracts::Determinism::Enforced, +// ); +// +// if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { +// log::debug!( +// "Contract debug buffer - {:?}", +// String::from_utf8(result.debug_message.clone()) +// ); +// log::debug!("result: {:?}", result); +// } +// +// // check that the nft collection was created +// assert_eq!(Nfts::collection_owner(0), Some(addr.clone().into())); +// +// // test reading the collection +// let function = function_selector("read_collection"); +// +// let params = [function, 0.encode()].concat(); +// +// let result = Contracts::bare_call( +// ALICE, +// addr.clone(), +// 0, +// Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), +// None, +// params, +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// pallet_contracts::Determinism::Enforced, +// ); +// +// if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { +// log::debug!( +// "Contract debug buffer - {:?}", +// String::from_utf8(result.debug_message.clone()) +// ); +// log::debug!("result: {:?}", result); +// } +// +// // assert that the collection was read successfully +// assert_eq!(result.result.clone().unwrap().data, vec![1, 1]); +// }); +// } +// +// #[test] +// #[ignore] +// fn dispatch_nfts_mint_from_contract_works() { +// new_test_ext().execute_with(|| { +// let _ = env_logger::try_init(); +// +// let (wasm_binary, _) = +// load_wasm_module::("../../pop-api/examples/nfts/target/ink/nfts.wasm") +// .unwrap(); +// +// let init_value = 100; +// +// let result = Contracts::bare_instantiate( +// ALICE, +// init_value, +// GAS_LIMIT, +// None, +// Code::Upload(wasm_binary), +// function_selector("new"), +// vec![], +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// ) +// .result +// .unwrap(); +// +// assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); +// +// let addr = result.account_id; +// +// let collection_id: u32 = 0; +// let item_id: u32 = 1; +// +// // create nft collection with contract as owner +// assert_eq!( +// Nfts::force_create( +// RuntimeOrigin::root(), +// addr.clone().into(), +// default_collection_config() +// ), +// Ok(()) +// ); +// +// assert_eq!(Nfts::collection_owner(collection_id), Some(addr.clone().into())); +// // assert that the item does not exist yet +// assert_eq!(Nfts::owner(collection_id, item_id), None); +// +// let function = function_selector("mint_through_runtime"); +// +// let params = +// [function, collection_id.encode(), item_id.encode(), BOB.encode()].concat(); +// +// let result = Contracts::bare_call( +// ALICE, +// addr.clone(), +// 0, +// Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), +// None, +// params, +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// pallet_contracts::Determinism::Enforced, +// ); +// +// if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { +// log::debug!( +// "Contract debug buffer - {:?}", +// String::from_utf8(result.debug_message.clone()) +// ); +// log::debug!("result: {:?}", result); +// } +// +// // check for revert +// assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); +// +// assert_eq!(Nfts::owner(collection_id, item_id), Some(BOB.into())); +// }); +// } +// +// #[test] +// #[ignore] +// fn nfts_mint_surfaces_error() { +// new_test_ext().execute_with(|| { +// let _ = env_logger::try_init(); +// +// let (wasm_binary, _) = +// load_wasm_module::("../../pop-api/examples/nfts/target/ink/nfts.wasm") +// .unwrap(); +// +// let init_value = 100; +// +// let result = Contracts::bare_instantiate( +// ALICE, +// init_value, +// GAS_LIMIT, +// None, +// Code::Upload(wasm_binary), +// function_selector("new"), +// vec![], +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// ) +// .result +// .unwrap(); +// +// assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); +// +// let addr = result.account_id; +// +// let collection_id: u32 = 0; +// let item_id: u32 = 1; +// +// let function = function_selector("mint_through_runtime"); +// +// let params = +// [function, collection_id.encode(), item_id.encode(), BOB.encode()].concat(); +// +// let result = Contracts::bare_call( +// ALICE, +// addr.clone(), +// 0, +// Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), +// None, +// params, +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// pallet_contracts::Determinism::Enforced, +// ); +// +// if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { +// log::debug!( +// "Contract debug buffer - {:?}", +// String::from_utf8(result.debug_message.clone()) +// ); +// log::debug!("result: {:?}", result); +// } +// +// // check for revert with expected error +// let result = result.result.unwrap(); +// assert!(result.did_revert()); +// }); +// } +// +// #[test] +// #[ignore] +// fn reading_last_relay_chain_block_number_works() { +// new_test_ext().execute_with(|| { +// let _ = env_logger::try_init(); +// +// let (wasm_binary, _) = load_wasm_module::( +// "../../pop-api/examples/read-runtime-state/target/ink/read_relay_blocknumber.wasm", +// ) +// .unwrap(); +// +// let init_value = 100; +// +// let contract = Contracts::bare_instantiate( +// ALICE, +// init_value, +// GAS_LIMIT, +// None, +// Code::Upload(wasm_binary), +// function_selector("new"), +// vec![], +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// ) +// .result +// .unwrap(); +// +// assert!(!contract.result.did_revert(), "deploying contract reverted {:?}", contract); +// +// let addr = contract.account_id; +// +// let function = function_selector("read_relay_block_number"); +// let params = [function].concat(); +// +// let result = Contracts::bare_call( +// ALICE, +// addr.clone(), +// 0, +// Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), +// None, +// params, +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::UnsafeCollect, +// pallet_contracts::Determinism::Relaxed, +// ); +// +// if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { +// log::debug!( +// "Contract debug buffer - {:?}", +// String::from_utf8(result.debug_message.clone()) +// ); +// log::debug!("result: {:?}", result); +// } +// +// // check for revert +// assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); +// }); +// } +// +// #[test] +// #[ignore] +// fn place_spot_order_from_contract_works() { +// new_test_ext().execute_with(|| { +// let _ = env_logger::try_init(); +// +// let (wasm_binary, _) = load_wasm_module::( +// "../../pop-api/examples/place-spot-order/target/ink/spot_order.wasm", +// ) +// .unwrap(); +// +// let init_value = 100 * UNIT; +// +// let result = Contracts::bare_instantiate( +// ALICE, +// init_value, +// GAS_LIMIT, +// None, +// Code::Upload(wasm_binary), +// function_selector("new"), +// vec![], +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// ) +// .result +// .unwrap(); +// +// assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); +// +// let addr = result.account_id; +// +// let function = function_selector("place_spot_order"); +// +// let max_amount = 1 * UNIT; +// let para_id = 2000; +// +// let params = [function, max_amount.encode(), para_id.encode()].concat(); +// +// let result = Contracts::bare_call( +// ALICE, +// addr.clone(), +// 0, +// Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), +// None, +// params, +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// pallet_contracts::Determinism::Enforced, +// ); +// +// if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { +// log::debug!( +// "Contract debug buffer - {:?}", +// String::from_utf8(result.debug_message.clone()) +// ); +// log::debug!("result: {:?}", result); +// } +// +// // check for revert +// assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); +// }); +// } +// +// #[test] +// #[ignore] +// fn allow_call_filter_blocks_call() { +// new_test_ext().execute_with(|| { +// let _ = env_logger::try_init(); +// +// let (wasm_binary, _) = load_wasm_module::( +// "../../tests/contracts/filtered-call/target/ink/pop_api_filtered_call.wasm", +// ) +// .unwrap(); +// +// let init_value = 100 * UNIT; +// +// let result = Contracts::bare_instantiate( +// ALICE, +// init_value, +// GAS_LIMIT, +// None, +// Code::Upload(wasm_binary), +// function_selector("new"), +// vec![], +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// ) +// .result +// .unwrap(); +// +// assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); +// +// let addr = result.account_id; +// +// let function = function_selector("get_filtered"); +// let params = [function].concat(); +// +// let result = Contracts::bare_call( +// ALICE, +// addr.clone(), +// 0, +// Weight::from_parts(100_000_000_000, 3 * 1024 * 1024), +// None, +// params, +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// pallet_contracts::Determinism::Enforced, +// ); +// +// if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { +// log::debug!( +// "Contract debug buffer - {:?}", +// String::from_utf8(result.debug_message.clone()) +// ); +// log::debug!("filtered result: {:?}", result); +// } +// +// // check for revert +// assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); +// }); +// } +// } diff --git a/runtime/devnet/src/extensions/tests/local_fungibles.rs b/runtime/devnet/src/extensions/tests/local_fungibles.rs new file mode 100644 index 00000000..8bb5c6ab --- /dev/null +++ b/runtime/devnet/src/extensions/tests/local_fungibles.rs @@ -0,0 +1,232 @@ +#![cfg(test)] +use super::*; + +const ASSET_ID: u32 = 1; + +fn allowance(addr: AccountId32, asset_id: u32, owner: AccountId32, spender: AccountId32) -> ExecReturnValue { + let function = function_selector("allowance"); + let params = [function, asset_id.encode(), owner.encode(), spender.encode()].concat(); + do_bare_call(addr, params, 0).expect("should work") +} + +// Call balance_of contract message. +fn balance_of(addr: AccountId32, asset_id: u32, owner: AccountId32) -> ExecReturnValue { + let function = function_selector("balance_of"); + let params = [function, asset_id.encode(), owner.encode()].concat(); + do_bare_call(addr, params, 0).expect("should work") +} + +// Call total_supply contract message. +fn total_supply(addr: AccountId32, asset_id: u32) -> ExecReturnValue { + let function = function_selector("total_supply"); + let params = [function, asset_id.encode()].concat(); + do_bare_call(addr, params, 0).expect("should work") +} + +// Create an asset and mint to owner. +fn create_asset_and_mint_to(asset_id: u32, owner: AccountId32, value: u128) { + assert_eq!( + Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + owner.clone().into(), + true, + 1, + ), + Ok(()) + ); + assert_eq!( + Assets::mint( + RuntimeOrigin::signed(owner.clone().into()), + asset_id.into(), + owner.into(), + value, + ), + Ok(()) + ); +} + +// Create an asset, mints to, and approves spender. +fn create_asset_mint_and_approve(asset_id: u32, owner: AccountId32, mint: u128, spender: AccountId32, approve: u128) { + create_asset_and_mint_to(asset_id, owner.clone(), mint); + assert_eq!( + Assets::approve_transfer( + RuntimeOrigin::signed(owner.into()), + asset_id.into(), + spender.into(), + approve, + ), + Ok(()) + ); +} + + +#[test] +#[ignore] +fn total_supply_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let addr = instantiate("../../pop-api/examples/fungibles/target/ink/fungibles.wasm"); + + // No tokens in circulation. + assert_eq!( + Assets::total_supply(ASSET_ID).encode(), + total_supply(addr.clone(), ASSET_ID).data[2..] + ); + + // Tokens in circulation. + create_asset_and_mint_to(ASSET_ID, BOB, 100); + assert_eq!( + Assets::total_supply(ASSET_ID).encode(), + total_supply(addr, ASSET_ID).data[2..] + ); + }); +} + +#[test] +#[ignore] +fn balance_of_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let addr = instantiate("../../pop-api/examples/fungibles/target/ink/fungibles.wasm"); + + // No tokens in circulation. + assert_eq!( + Assets::balance(ASSET_ID, BOB).encode(), + balance_of(addr.clone(), ASSET_ID, BOB).data[2..] + ); + + // Tokens in circulation. + create_asset_and_mint_to(ASSET_ID, BOB, 100); + assert_eq!( + Assets::balance(ASSET_ID, BOB).encode(), + balance_of(addr, ASSET_ID, BOB).data[2..] + ); + }); +} + +#[test] +#[ignore] +fn allowance_works() { + new_test_ext().execute_with(|| { + let _ = env_logger::try_init(); + let addr = instantiate("../../pop-api/examples/fungibles/target/ink/fungibles.wasm"); + + // No tokens in circulation. + assert_eq!( + Assets::allowance(ASSET_ID, &BOB, &ALICE).encode(), + allowance(addr.clone(), ASSET_ID, BOB, ALICE).data[2..] + ); + + // Tokens in circulation. + create_asset_mint_and_approve(ASSET_ID, BOB, 100, ALICE, 50); + assert_eq!( + Assets::allowance(ASSET_ID, &BOB, &ALICE).encode(), + allowance(addr, ASSET_ID, BOB, ALICE).data[2..] + ); + }); +} + +// #[test] +// #[ignore] +// fn dispatch_mint_asset_from_contract_works() { +// new_test_ext().execute_with(|| { +// let _ = env_logger::try_init(); +// +// let (wasm_binary, _) = load_wasm_module::( +// "../../pop-api/examples/assets/target/ink/fungibles.wasm", +// ) +// .unwrap(); +// +// let init_value = 100; +// +// let result = Contracts::bare_instantiate( +// ALICE, +// init_value, +// GAS_LIMIT, +// None, +// Code::Upload(wasm_binary), +// function_selector("new"), +// vec![], +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// ) +// .result +// .unwrap(); +// +// assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); +// let addr = result.account_id; +// +// let asset_id: u32 = 1; +// let min_balance = 1; +// let amount: u128 = 100 * UNIT; +// let function = function_selector("mint_asset_through_runtime"); +// let params = [function, asset_id.encode(), BOB.encode(), amount.encode()].concat(); +// +// // Mint asset which does not exist. +// let result = Contracts::bare_call( +// ALICE, +// addr.clone(), +// 0, +// GAS_LIMIT, +// None, +// params.clone(), +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// pallet_contracts::Determinism::Enforced, +// ); +// +// if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { +// log::debug!( +// "Contract debug buffer - {:?}", +// String::from_utf8(result.debug_message.clone()) +// ); +// log::debug!("result: {:?}", result); +// } +// +// // Check for revert. +// assert!(result.result.unwrap().did_revert(), "Contract should have been reverted!"); +// +// // Create asset with contract as owner. +// assert_eq!( +// Assets::force_create( +// RuntimeOrigin::root(), +// asset_id.into(), +// addr.clone().into(), +// true, +// min_balance, +// ), +// Ok(()) +// ); +// +// // Check Bob's asset balance before minting through contract. +// let bob_balance_before = Assets::balance(asset_id, &BOB); +// assert_eq!(bob_balance_before, 0); +// +// let result = Contracts::bare_call( +// ALICE, +// addr.clone(), +// 0, +// GAS_LIMIT, +// None, +// params, +// DEBUG_OUTPUT, +// pallet_contracts::CollectEvents::Skip, +// pallet_contracts::Determinism::Enforced, +// ); +// +// if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { +// log::debug!( +// "Contract debug buffer - {:?}", +// String::from_utf8(result.debug_message.clone()) +// ); +// log::debug!("result: {:?}", result); +// } +// +// // Check for revert +// assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); +// +// let bob_balance_after = Assets::balance(asset_id, &BOB); +// assert_eq!(bob_balance_after, bob_balance_before + amount); +// }); +// } diff --git a/runtime/devnet/src/extensions/tests/mod.rs b/runtime/devnet/src/extensions/tests/mod.rs new file mode 100644 index 00000000..3607bc90 --- /dev/null +++ b/runtime/devnet/src/extensions/tests/mod.rs @@ -0,0 +1,86 @@ +#![cfg(test)] +use super::*; +use crate::{Assets, Contracts, Runtime, System}; +use pallet_contracts::{ExecReturnValue, CollectEvents, Code, Determinism}; +use sp_runtime::{traits::Hash, AccountId32, BuildStorage}; + +mod local_fungibles; + +const DEBUG_OUTPUT: pallet_contracts::DebugInfo = pallet_contracts::DebugInfo::UnsafeDebug; + +const ALICE: AccountId32 = AccountId32::new([1_u8; 32]); +const BOB: AccountId32 = AccountId32::new([2_u8; 32]); +const INITIAL_AMOUNT: u128 = 100_000 * UNIT; +const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); + +fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + pallet_balances::GenesisConfig:: { + balances: vec![(ALICE, INITIAL_AMOUNT), (BOB, INITIAL_AMOUNT)], + } + .assimilate_storage(&mut t) + .expect("Pallet balances storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +fn load_wasm_module(path: &str) -> std::io::Result<(Vec, ::Output)> +where + T: frame_system::Config, +{ + let wasm_binary = std::fs::read(path)?; + let code_hash = T::Hashing::hash(&wasm_binary); + Ok((wasm_binary, code_hash)) +} + +fn function_selector(name: &str) -> Vec { + let hash = sp_io::hashing::blake2_256(name.as_bytes()); + [hash[0..4].to_vec()].concat() +} + +fn do_bare_call( + addr: AccountId32, + input: Vec, + value: u128, +) -> Result { + let result = Contracts::bare_call( + ALICE, + addr.into(), + value.into(), + GAS_LIMIT, + None, + input, + DEBUG_OUTPUT, + CollectEvents::Skip, + Determinism::Enforced, + ); + log::debug!( + "Contract debug buffer - {:?}", + String::from_utf8(result.debug_message.clone()) + ); + log::debug!("result: {:?}", result); + result.result +} + +// Deploy, instantiate and return contract address. +fn instantiate(contract: &str) -> AccountId32 { + let (wasm_binary, _) = load_wasm_module::(contract).expect("could not read .wasm file"); + let result = Contracts::bare_instantiate( + ALICE, + 1000, + GAS_LIMIT, + None, + Code::Upload(wasm_binary), + function_selector("new"), + vec![], + DEBUG_OUTPUT, + CollectEvents::Skip, + ).result.unwrap(); + assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); + result.account_id +} \ No newline at end of file diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index 52a0938f..a82be804 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -254,40 +254,40 @@ impl Contains for FilteredCalls { pub struct AllowedPopApiCalls; impl Contains for crate::AllowedPopApiCalls { fn contains(c: &RuntimeCall) -> bool { - use assets_config::TrustBackedAssetsCall; + use config::assets::AssetsCall; use pallet_nfts::Call as NftsCall; matches!( c, RuntimeCall::Balances(BalancesCall::transfer_keep_alive { .. }) - | RuntimeCall::TrustBackedAssets( - TrustBackedAssetsCall::create { .. } - | TrustBackedAssetsCall::start_destroy { .. } - | TrustBackedAssetsCall::destroy_accounts { .. } - | TrustBackedAssetsCall::destroy_approvals { .. } - | TrustBackedAssetsCall::finish_destroy { .. } - | TrustBackedAssetsCall::mint { .. } - | TrustBackedAssetsCall::burn { .. } - | TrustBackedAssetsCall::transfer { .. } - | TrustBackedAssetsCall::transfer_keep_alive { .. } - | TrustBackedAssetsCall::force_transfer { .. } - | TrustBackedAssetsCall::freeze { .. } - | TrustBackedAssetsCall::thaw { .. } - | TrustBackedAssetsCall::freeze_asset { .. } - | TrustBackedAssetsCall::thaw_asset { .. } - | TrustBackedAssetsCall::transfer_ownership { .. } - | TrustBackedAssetsCall::set_team { .. } - | TrustBackedAssetsCall::set_metadata { .. } - | TrustBackedAssetsCall::clear_metadata { .. } - | TrustBackedAssetsCall::approve_transfer { .. } - | TrustBackedAssetsCall::cancel_approval { .. } - | TrustBackedAssetsCall::force_cancel_approval { .. } - | TrustBackedAssetsCall::transfer_approved { .. } - | TrustBackedAssetsCall::touch { .. } - | TrustBackedAssetsCall::refund { .. } - | TrustBackedAssetsCall::set_min_balance { .. } - | TrustBackedAssetsCall::touch_other { .. } - | TrustBackedAssetsCall::refund_other { .. } - | TrustBackedAssetsCall::block { .. } + | RuntimeCall::Assets( + AssetsCall::create { .. } + | AssetsCall::start_destroy { .. } + | AssetsCall::destroy_accounts { .. } + | AssetsCall::destroy_approvals { .. } + | AssetsCall::finish_destroy { .. } + | AssetsCall::mint { .. } + | AssetsCall::burn { .. } + | AssetsCall::transfer { .. } + | AssetsCall::transfer_keep_alive { .. } + | AssetsCall::force_transfer { .. } + | AssetsCall::freeze { .. } + | AssetsCall::thaw { .. } + | AssetsCall::freeze_asset { .. } + | AssetsCall::thaw_asset { .. } + | AssetsCall::transfer_ownership { .. } + | AssetsCall::set_team { .. } + | AssetsCall::set_metadata { .. } + | AssetsCall::clear_metadata { .. } + | AssetsCall::approve_transfer { .. } + | AssetsCall::cancel_approval { .. } + | AssetsCall::force_cancel_approval { .. } + | AssetsCall::transfer_approved { .. } + | AssetsCall::touch { .. } + | AssetsCall::refund { .. } + | AssetsCall::set_min_balance { .. } + | AssetsCall::touch_other { .. } + | AssetsCall::refund_other { .. } + | AssetsCall::block { .. } ) | RuntimeCall::Nfts( NftsCall::create { .. } | NftsCall::destroy { .. } @@ -662,7 +662,7 @@ construct_runtime!( // Assets Nfts: pallet_nfts = 50, NftFractionalization: pallet_nft_fractionalization = 51, - TrustBackedAssets: pallet_assets:: = 52, + Assets: pallet_assets:: = 52, } ); diff --git a/runtime/testnet/Cargo.toml b/runtime/testnet/Cargo.toml index d68bfd15..43b1e310 100644 --- a/runtime/testnet/Cargo.toml +++ b/runtime/testnet/Cargo.toml @@ -22,7 +22,7 @@ scale-info.workspace = true smallvec.workspace = true # Local -pop-primitives.workspace = true +pop-primitives = { workspace = true, features = ["testnet"] } pop-runtime-common = { workspace = true, default-features = false } # Substrate diff --git a/runtime/testnet/src/config/assets.rs b/runtime/testnet/src/config/assets.rs index f51f8875..2c8ea952 100644 --- a/runtime/testnet/src/config/assets.rs +++ b/runtime/testnet/src/config/assets.rs @@ -1,6 +1,6 @@ use crate::{ - deposit, AccountId, Balance, Balances, BlockNumber, Nfts, Runtime, RuntimeEvent, - RuntimeHoldReason, TrustBackedAssets, DAYS, EXISTENTIAL_DEPOSIT, UNIT, + deposit, AccountId, Assets, Balance, Balances, BlockNumber, Nfts, Runtime, RuntimeEvent, + RuntimeHoldReason, DAYS, EXISTENTIAL_DEPOSIT, UNIT, }; use frame_support::{ parameter_types, @@ -86,7 +86,7 @@ impl pallet_nft_fractionalization::Config for Runtime { type NftId = ::ItemId; type AssetBalance = >::Balance; type AssetId = >::AssetId; - type Assets = TrustBackedAssets; + type Assets = Assets; type Nfts = Nfts; type PalletId = NftFractionalizationPalletId; type WeightInfo = pallet_nft_fractionalization::weights::SubstrateWeight; @@ -96,7 +96,7 @@ impl pallet_nft_fractionalization::Config for Runtime { } pub type TrustBackedAssetsInstance = pallet_assets::Instance1; -pub(crate) type TrustBackedAssetsCall = pallet_assets::Call; +pub(crate) type AssetsCall = pallet_assets::Call; impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; diff --git a/runtime/testnet/src/config/proxy.rs b/runtime/testnet/src/config/proxy.rs index a4fd479a..07d5f0f8 100644 --- a/runtime/testnet/src/config/proxy.rs +++ b/runtime/testnet/src/config/proxy.rs @@ -1,4 +1,4 @@ -use super::assets::TrustBackedAssetsCall; +use super::assets::AssetsCall; use crate::{Balances, Runtime, RuntimeCall, RuntimeEvent}; use frame_support::traits::InstanceFilter; use pop_runtime_common::proxy::{ @@ -34,16 +34,16 @@ impl InstanceFilter for ProxyType { }, ProxyType::AssetOwner => matches!( c, - RuntimeCall::Assets(TrustBackedAssetsCall::create { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::start_destroy { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::destroy_accounts { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::destroy_approvals { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::finish_destroy { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::transfer_ownership { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::set_team { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::set_metadata { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::clear_metadata { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::set_min_balance { .. }) + RuntimeCall::Assets(AssetsCall::create { .. }) + | RuntimeCall::Assets(AssetsCall::start_destroy { .. }) + | RuntimeCall::Assets(AssetsCall::destroy_accounts { .. }) + | RuntimeCall::Assets(AssetsCall::destroy_approvals { .. }) + | RuntimeCall::Assets(AssetsCall::finish_destroy { .. }) + | RuntimeCall::Assets(AssetsCall::transfer_ownership { .. }) + | RuntimeCall::Assets(AssetsCall::set_team { .. }) + | RuntimeCall::Assets(AssetsCall::set_metadata { .. }) + | RuntimeCall::Assets(AssetsCall::clear_metadata { .. }) + | RuntimeCall::Assets(AssetsCall::set_min_balance { .. }) | RuntimeCall::Nfts(pallet_nfts::Call::create { .. }) | RuntimeCall::Nfts(pallet_nfts::Call::destroy { .. }) | RuntimeCall::Nfts(pallet_nfts::Call::redeposit { .. }) @@ -56,15 +56,15 @@ impl InstanceFilter for ProxyType { ), ProxyType::AssetManager => matches!( c, - RuntimeCall::Assets(TrustBackedAssetsCall::mint { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::burn { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::freeze { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::block { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::thaw { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::freeze_asset { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::thaw_asset { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::touch_other { .. }) - | RuntimeCall::Assets(TrustBackedAssetsCall::refund_other { .. }) + RuntimeCall::Assets(AssetsCall::mint { .. }) + | RuntimeCall::Assets(AssetsCall::burn { .. }) + | RuntimeCall::Assets(AssetsCall::freeze { .. }) + | RuntimeCall::Assets(AssetsCall::block { .. }) + | RuntimeCall::Assets(AssetsCall::thaw { .. }) + | RuntimeCall::Assets(AssetsCall::freeze_asset { .. }) + | RuntimeCall::Assets(AssetsCall::thaw_asset { .. }) + | RuntimeCall::Assets(AssetsCall::touch_other { .. }) + | RuntimeCall::Assets(AssetsCall::refund_other { .. }) | RuntimeCall::Nfts(pallet_nfts::Call::force_mint { .. }) | RuntimeCall::Nfts(pallet_nfts::Call::update_mint_settings { .. }) | RuntimeCall::Nfts(pallet_nfts::Call::mint_pre_signed { .. }) diff --git a/runtime/testnet/src/extensions.rs b/runtime/testnet/src/extensions.rs index 0d552090..6bbfaa36 100644 --- a/runtime/testnet/src/extensions.rs +++ b/runtime/testnet/src/extensions.rs @@ -3,25 +3,23 @@ use frame_support::traits::{Contains, OriginTrait}; use frame_support::{ dispatch::{GetDispatchInfo, RawOrigin}, pallet_prelude::*, - traits::{fungibles::Inspect, nonfungibles_v2::Inspect as NonFungiblesInspect}, + traits::nonfungibles_v2::Inspect as NonFungiblesInspect, }; use pallet_contracts::chain_extension::{ BufInBufOutState, ChainExtension, ChargedAmount, Environment, Ext, InitState, RetVal, }; use pop_primitives::{ - storage_keys::{NftsKeys, ParachainSystemKeys, RuntimeStateKeys, TrustBackedAssetsKeys}, - AssetId, CollectionId, ItemId, + storage_keys::{NftsKeys, ParachainSystemKeys, RuntimeStateKeys}, + CollectionId, ItemId, }; use sp_core::crypto::UncheckedFrom; use sp_runtime::{ traits::{BlockNumberProvider, Dispatchable}, DispatchError, }; +use sp_std::vec::Vec; -use crate::{ - assets_config::TrustBackedAssetsInstance, AccountId, AllowedPopApiCalls, RuntimeCall, - RuntimeOrigin, -}; +use crate::{AccountId, AllowedPopApiCalls, RuntimeCall, RuntimeOrigin}; const LOG_TARGET: &str = "pop-api::extension"; @@ -34,7 +32,6 @@ impl ChainExtension for PopApiExtension where T: pallet_contracts::Config + pallet_xcm::Config - + pallet_assets::Config + pallet_nfts::Config + cumulus_pallet_parachain_system::Config + frame_system::Config< @@ -184,7 +181,6 @@ where fn read_state(env: Environment) -> Result<(), DispatchError> where T: pallet_contracts::Config - + pallet_assets::Config + pallet_nfts::Config + cumulus_pallet_parachain_system::Config + frame_system::Config, @@ -210,9 +206,6 @@ where RuntimeStateKeys::ParachainSystem(key) => { read_parachain_system_state::(key, &mut env) }, - RuntimeStateKeys::TrustBackedAssets(key) => { - read_trust_backed_assets_state::(key, &mut env) - }, }? .encode(); @@ -288,23 +281,6 @@ where } } -fn read_trust_backed_assets_state( - key: TrustBackedAssetsKeys, - env: &mut Environment, -) -> Result, DispatchError> -where - T: pallet_contracts::Config - + pallet_assets::Config, - E: Ext, -{ - match key { - TrustBackedAssetsKeys::AssetExists(id) => { - env.charge_weight(T::DbWeight::get().reads(1_u64))?; - Ok(pallet_assets::Pallet::::asset_exists(id).encode()) - }, - } -} - #[cfg(test)] mod tests { pub use super::*; @@ -701,110 +677,6 @@ mod tests { }); } - #[test] - #[ignore] - fn dispatch_trust_backed_assets_mint_from_contract_works() { - new_test_ext().execute_with(|| { - let _ = env_logger::try_init(); - - let (wasm_binary, _) = load_wasm_module::( - "../../pop-api/examples/trust_backed_assets/target/ink/pop_api_trust_backed_assets_example.wasm", - ) - .unwrap(); - - let init_value = 100; - - let result = Contracts::bare_instantiate( - ALICE, - init_value, - GAS_LIMIT, - None, - Code::Upload(wasm_binary), - function_selector("new"), - vec![], - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - ) - .result - .unwrap(); - - assert!(!result.result.did_revert(), "deploying contract reverted {:?}", result); - let addr = result.account_id; - - let asset_id: u32 = 1; - let min_balance = 1; - let amount: u128 = 100 * UNIT; - let function = function_selector("mint_asset_through_runtime"); - let params = [function, asset_id.encode(), BOB.encode(), amount.encode()].concat(); - - // Mint asset which does not exist. - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - GAS_LIMIT, - None, - params.clone(), - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - pallet_contracts::Determinism::Enforced, - ); - - if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { - log::debug!( - "Contract debug buffer - {:?}", - String::from_utf8(result.debug_message.clone()) - ); - log::debug!("result: {:?}", result); - } - - // Check for revert. - assert!(result.result.unwrap().did_revert(), "Contract should have been reverted!"); - - // Create asset with contract as owner. - assert_eq!( - TrustBackedAssets::force_create( - RuntimeOrigin::root(), - asset_id.into(), - addr.clone().into(), - true, - min_balance, - ), - Ok(()) - ); - - // Check Bob's asset balance before minting through contract. - let bob_balance_before = TrustBackedAssets::balance(asset_id, &BOB); - assert_eq!(bob_balance_before, 0); - - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - GAS_LIMIT, - None, - params, - DEBUG_OUTPUT, - pallet_contracts::CollectEvents::Skip, - pallet_contracts::Determinism::Enforced, - ); - - if DEBUG_OUTPUT == pallet_contracts::DebugInfo::UnsafeDebug { - log::debug!( - "Contract debug buffer - {:?}", - String::from_utf8(result.debug_message.clone()) - ); - log::debug!("result: {:?}", result); - } - - // Check for revert - assert!(!result.result.unwrap().did_revert(), "Contract reverted!"); - - let bob_balance_after = TrustBackedAssets::balance(asset_id, &BOB); - assert_eq!(bob_balance_after, bob_balance_before + amount); - }); - } - #[test] #[ignore] fn allow_call_filter_blocks_call() { diff --git a/runtime/testnet/src/lib.rs b/runtime/testnet/src/lib.rs index 312d9b50..66a5092c 100644 --- a/runtime/testnet/src/lib.rs +++ b/runtime/testnet/src/lib.rs @@ -253,74 +253,43 @@ impl Contains for FilteredCalls { pub struct AllowedPopApiCalls; impl Contains for crate::AllowedPopApiCalls { fn contains(c: &RuntimeCall) -> bool { - use assets_config::TrustBackedAssetsCall; use pallet_nfts::Call as NftsCall; matches!( c, RuntimeCall::Balances(BalancesCall::transfer_keep_alive { .. }) - | RuntimeCall::TrustBackedAssets( - TrustBackedAssetsCall::create { .. } - | TrustBackedAssetsCall::start_destroy { .. } - | TrustBackedAssetsCall::destroy_accounts { .. } - | TrustBackedAssetsCall::destroy_approvals { .. } - | TrustBackedAssetsCall::finish_destroy { .. } - | TrustBackedAssetsCall::mint { .. } - | TrustBackedAssetsCall::burn { .. } - | TrustBackedAssetsCall::transfer { .. } - | TrustBackedAssetsCall::transfer_keep_alive { .. } - | TrustBackedAssetsCall::force_transfer { .. } - | TrustBackedAssetsCall::freeze { .. } - | TrustBackedAssetsCall::thaw { .. } - | TrustBackedAssetsCall::freeze_asset { .. } - | TrustBackedAssetsCall::thaw_asset { .. } - | TrustBackedAssetsCall::transfer_ownership { .. } - | TrustBackedAssetsCall::set_team { .. } - | TrustBackedAssetsCall::set_metadata { .. } - | TrustBackedAssetsCall::clear_metadata { .. } - | TrustBackedAssetsCall::approve_transfer { .. } - | TrustBackedAssetsCall::cancel_approval { .. } - | TrustBackedAssetsCall::force_cancel_approval { .. } - | TrustBackedAssetsCall::transfer_approved { .. } - | TrustBackedAssetsCall::touch { .. } - | TrustBackedAssetsCall::refund { .. } - | TrustBackedAssetsCall::set_min_balance { .. } - | TrustBackedAssetsCall::touch_other { .. } - | TrustBackedAssetsCall::refund_other { .. } - | TrustBackedAssetsCall::block { .. } - ) | RuntimeCall::Nfts( - NftsCall::create { .. } - | NftsCall::destroy { .. } - | NftsCall::mint { .. } - | NftsCall::burn { .. } - | NftsCall::transfer { .. } - | NftsCall::redeposit { .. } - | NftsCall::lock_item_transfer { .. } - | NftsCall::unlock_item_transfer { .. } - | NftsCall::lock_collection { .. } - | NftsCall::transfer_ownership { .. } - | NftsCall::set_team { .. } - | NftsCall::approve_transfer { .. } - | NftsCall::cancel_approval { .. } - | NftsCall::clear_all_transfer_approvals { .. } - | NftsCall::lock_item_properties { .. } - | NftsCall::set_attribute { .. } - | NftsCall::clear_attribute { .. } - | NftsCall::approve_item_attributes { .. } - | NftsCall::cancel_item_attributes_approval { .. } - | NftsCall::set_metadata { .. } - | NftsCall::clear_metadata { .. } - | NftsCall::set_collection_metadata { .. } - | NftsCall::clear_collection_metadata { .. } - | NftsCall::set_accept_ownership { .. } - | NftsCall::set_collection_max_supply { .. } - | NftsCall::update_mint_settings { .. } - | NftsCall::set_price { .. } - | NftsCall::buy_item { .. } - | NftsCall::pay_tips { .. } - | NftsCall::create_swap { .. } - | NftsCall::cancel_swap { .. } - | NftsCall::claim_swap { .. } - ) + | RuntimeCall::Nfts( + NftsCall::create { .. } + | NftsCall::destroy { .. } + | NftsCall::mint { .. } | NftsCall::burn { .. } + | NftsCall::transfer { .. } + | NftsCall::redeposit { .. } + | NftsCall::lock_item_transfer { .. } + | NftsCall::unlock_item_transfer { .. } + | NftsCall::lock_collection { .. } + | NftsCall::transfer_ownership { .. } + | NftsCall::set_team { .. } + | NftsCall::approve_transfer { .. } + | NftsCall::cancel_approval { .. } + | NftsCall::clear_all_transfer_approvals { .. } + | NftsCall::lock_item_properties { .. } + | NftsCall::set_attribute { .. } + | NftsCall::clear_attribute { .. } + | NftsCall::approve_item_attributes { .. } + | NftsCall::cancel_item_attributes_approval { .. } + | NftsCall::set_metadata { .. } + | NftsCall::clear_metadata { .. } + | NftsCall::set_collection_metadata { .. } + | NftsCall::clear_collection_metadata { .. } + | NftsCall::set_accept_ownership { .. } + | NftsCall::set_collection_max_supply { .. } + | NftsCall::update_mint_settings { .. } + | NftsCall::set_price { .. } + | NftsCall::buy_item { .. } + | NftsCall::pay_tips { .. } + | NftsCall::create_swap { .. } + | NftsCall::cancel_swap { .. } + | NftsCall::claim_swap { .. } + ) ) } } @@ -661,7 +630,7 @@ construct_runtime!( // Assets Nfts: pallet_nfts = 50, NftFractionalization: pallet_nft_fractionalization = 51, - TrustBackedAssets: pallet_assets:: = 52, + Assets: pallet_assets:: = 52, } );