diff --git a/Cargo.lock b/Cargo.lock index 3cfa1dc..c4a19b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6219,6 +6219,22 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-assets" +version = "4.0.0-dev" +source = "git+https://github.com/moondance-labs/polkadot-sdk?branch=tanssi-polkadot-v1.3.0#11369f41346d66f2ad227565aacb38d734993f94" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-async-backing" version = "0.9.0" @@ -6756,6 +6772,27 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-foreign-asset-creator" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-assets", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "staging-xcm", +] + [[package]] name = "pallet-grandpa" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 3cc0edf..18d33fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ pallet-utility = { git = "https://github.com/moondance-labs/polkadot-sdk", branc pallet-whitelist = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false } sp-api = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false } sp-application-crypto = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false } +sp-arithmetic = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false } sp-block-builder = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false } sp-consensus-babe = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false } sp-consensus-slots = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false } diff --git a/pallets/foreign-asset-creator/Cargo.toml b/pallets/foreign-asset-creator/Cargo.toml new file mode 100644 index 0000000..363f822 --- /dev/null +++ b/pallets/foreign-asset-creator/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "pallet-foreign-asset-creator" +authors = { workspace = true } +edition = "2021" +version = "0.1.0" + +[dependencies] +log = { workspace = true } +serde = { workspace = true, optional = true } + +# Substrate +frame-support = { workspace = true } +frame-system = { workspace = true } +parity-scale-codec = { workspace = true, features = [ "derive" ] } +scale-info = { workspace = true, features = [ "derive" ] } +sp-arithmetic = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +# Polkadot +staging-xcm = { workspace = true, optional = true } + +# Benchmarks +frame-benchmarking = { workspace = true, optional = true } + +[dev-dependencies] +pallet-balances = { workspace = true, features = [ "std" ] } +pallet-assets = { workspace = true, features = [ "std" ] } +sp-core = { workspace = true, features = [ "std" ] } + +[features] +default = [ "std" ] +std = [ + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "parity-scale-codec/std", + "scale-info/std", + "serde", + "sp-arithmetic/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "staging-xcm/std" +] + +runtime-benchmarks = [ "frame-benchmarking", "staging-xcm"] +try-runtime = [ "frame-support/try-runtime" ] \ No newline at end of file diff --git a/pallets/foreign-asset-creator/src/benchmarks.rs b/pallets/foreign-asset-creator/src/benchmarks.rs new file mode 100644 index 0000000..b0eca76 --- /dev/null +++ b/pallets/foreign-asset-creator/src/benchmarks.rs @@ -0,0 +1,141 @@ +// Copyright Moonsong Labs +// This file is part of Moonkit. + +// Moonkit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonkit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonkit. If not, see . + +#![cfg(feature = "runtime-benchmarks")] + +use crate::{AssetId, Call, Config, Pallet}; +use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; +use frame_system::RawOrigin; +use sp_arithmetic::traits::AtLeast16BitUnsigned; +use staging_xcm::latest::prelude::*; +benchmarks! { + // This where clause allows us to create ForeignAssetTypes + where_clause { where T::ForeignAsset: From, AssetId: AtLeast16BitUnsigned } + create_foreign_asset { + const USER_SEED: u32 = 1; + let manager = account("manager", 0, USER_SEED); + let foreign_asset = T::ForeignAsset::default(); + let amount = 1u32.into(); + let asset_id: AssetId = 1u16.into(); + + }: _(RawOrigin::Root, foreign_asset.clone(), asset_id.clone(), manager, true, amount) + verify { + assert_eq!(Pallet::::foreign_asset_for_id(asset_id), Some(foreign_asset)); + } + + change_existing_asset_type { + // We make it dependent on the number of existing assets already + let x in 5..100; + const USER_SEED: u32 = 1; + let manager: T::AccountId = account("manager", 0, USER_SEED); + + for i in 0..x { + let foreign_asset: T::ForeignAsset = MultiLocation::new(0, X1(GeneralIndex(i as u128))).into(); + let asset_id: AssetId = (i as u16).into(); + let amount = 1u32.into(); + Pallet::::create_foreign_asset( + RawOrigin::Root.into(), + foreign_asset.clone(), + asset_id.clone(), + manager.clone(), + true, + amount, + )?; + } + + let new_foreign_asset = T::ForeignAsset::default(); + let asset_type_to_be_changed: T::ForeignAsset = MultiLocation::new( + 0, + X1(GeneralIndex((x-1) as u128)) + ).into(); + let asset_id_to_be_changed: AssetId = ((x-1) as u16).into(); + }: _(RawOrigin::Root, asset_id_to_be_changed.clone(), new_foreign_asset.clone()) + verify { + assert_eq!(Pallet::::foreign_asset_for_id(asset_id_to_be_changed), Some(new_foreign_asset.clone())); + } + + remove_existing_asset_type { + // We make it dependent on the number of existing assets already + let x in 5..100; + const USER_SEED: u32 = 1; + let manager: T::AccountId = account("manager", 0, USER_SEED); + + for i in 0..x { + let foreign_asset: T::ForeignAsset = MultiLocation::new(0, X1(GeneralIndex(i as u128))).into(); + let asset_id: AssetId = (i as u16).into(); + let amount = 1u32.into(); + Pallet::::create_foreign_asset( + RawOrigin::Root.into(), + foreign_asset.clone(), + asset_id.clone(), + manager.clone(), + true, + amount, + )?; + } + + let asset_id_to_be_removed: AssetId = ((x-1) as u16).into(); + }: _(RawOrigin::Root, asset_id_to_be_removed.clone()) + verify { + assert!(Pallet::::foreign_asset_for_id(asset_id_to_be_removed).is_none()); + } + + destroy_foreign_asset { + // We make it dependent on the number of existing assets already + let x in 5..100; + const USER_SEED: u32 = 1; + let manager: T::AccountId = account("manager", 0, USER_SEED); + + for i in 0..x { + let foreign_asset: T::ForeignAsset = MultiLocation::new(0, X1(GeneralIndex(i as u128))).into(); + let asset_id: AssetId = (i as u16).into(); + let amount = 1u32.into(); + Pallet::::create_foreign_asset( + RawOrigin::Root.into(), + foreign_asset.clone(), + asset_id.clone(), + manager.clone(), + true, + amount, + )?; + } + + let asset_id_to_be_destroyed: AssetId = ((x-1) as u16).into(); + }: _(RawOrigin::Root, asset_id_to_be_destroyed.clone()) + verify { + assert!(Pallet::::foreign_asset_for_id(asset_id_to_be_destroyed).is_none()); + } +} + +#[cfg(test)] +mod tests { + use crate::mock::Test; + use sp_io::TestExternalities; + use sp_runtime::BuildStorage; + + pub fn new_test_ext() -> TestExternalities { + let t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + TestExternalities::new(t) + } +} + +impl_benchmark_test_suite!( + Pallet, + crate::benchmarks::tests::new_test_ext(), + crate::mock::Test +); diff --git a/pallets/foreign-asset-creator/src/lib.rs b/pallets/foreign-asset-creator/src/lib.rs new file mode 100644 index 0000000..0c702a8 --- /dev/null +++ b/pallets/foreign-asset-creator/src/lib.rs @@ -0,0 +1,252 @@ +// Copyright Moonsong Labs +// This file is part of Moonkit. + +// Moonkit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonkit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonkit. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet; +pub use pallet::*; +pub mod weights; +pub use weights::WeightInfo; +#[cfg(any(test, feature = "runtime-benchmarks"))] +mod benchmarks; +#[cfg(test)] +pub mod mock; +#[cfg(test)] +pub mod tests; + +#[pallet] +pub mod pallet { + use super::*; + use frame_support::{ + pallet_prelude::*, + traits::{ + fungibles::{Create, Destroy}, + tokens::fungibles, + }, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::traits::MaybeEquivalence; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The Foreign Asset Kind. + type ForeignAsset: Parameter + Member + Ord + PartialOrd + Default; + + /// Origin that is allowed to create and modify asset information for foreign assets + type ForeignAssetCreatorOrigin: EnsureOrigin; + + /// Origin that is allowed to create and modify asset information for foreign assets + type ForeignAssetModifierOrigin: EnsureOrigin; + + /// Origin that is allowed to create and modify asset information for foreign assets + type ForeignAssetDestroyerOrigin: EnsureOrigin; + + type Fungibles: fungibles::Create + + fungibles::Destroy + + fungibles::Inspect; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + pub type AssetBalance = <::Fungibles as fungibles::Inspect< + ::AccountId, + >>::Balance; + pub type AssetId = <::Fungibles as fungibles::Inspect< + ::AccountId, + >>::AssetId; + + /// An error that can occur while executing the mapping pallet's logic. + #[pallet::error] + pub enum Error { + AssetAlreadyExists, + AssetDoesNotExist, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// New asset with the asset manager is registered + ForeignAssetCreated { + asset_id: AssetId, + foreign_asset: T::ForeignAsset, + }, + /// Changed the xcm type mapping for a given asset id + ForeignAssetTypeChanged { + asset_id: AssetId, + new_foreign_asset: T::ForeignAsset, + }, + /// Removed all information related to an assetId + ForeignAssetRemoved { + asset_id: AssetId, + foreign_asset: T::ForeignAsset, + }, + /// Removed all information related to an assetId and destroyed asset + ForeignAssetDestroyed { + asset_id: AssetId, + foreign_asset: T::ForeignAsset, + }, + } + + /// Mapping from an asset id to a Foreign asset type. + /// This is mostly used when receiving transaction specifying an asset directly, + /// like transferring an asset from this chain to another. + #[pallet::storage] + #[pallet::getter(fn foreign_asset_for_id)] + pub type AssetIdToForeignAsset = + StorageMap<_, Blake2_128Concat, AssetId, T::ForeignAsset>; + + /// Reverse mapping of AssetIdToForeignAsset. Mapping from a foreign asset to an asset id. + /// This is mostly used when receiving a multilocation XCM message to retrieve + /// the corresponding asset in which tokens should me minted. + #[pallet::storage] + #[pallet::getter(fn asset_id_for_foreign)] + pub type ForeignAssetToAssetId = + StorageMap<_, Blake2_128Concat, T::ForeignAsset, AssetId>; + + #[pallet::call] + impl Pallet { + /// Create new asset with the ForeignAssetCreator + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::create_foreign_asset())] + pub fn create_foreign_asset( + origin: OriginFor, + foreign_asset: T::ForeignAsset, + asset_id: AssetId, + admin: T::AccountId, + is_sufficient: bool, + min_balance: AssetBalance, + ) -> DispatchResult { + T::ForeignAssetCreatorOrigin::ensure_origin(origin)?; + + // Ensure such an assetId does not exist + ensure!( + AssetIdToForeignAsset::::get(&asset_id).is_none(), + Error::::AssetAlreadyExists + ); + + // Important: this creates the asset without taking deposits, so the origin able to do this should be priviledged + T::Fungibles::create(asset_id.clone(), admin, is_sufficient, min_balance)?; + + // Insert the association assetId->foreigAsset + // Insert the association foreigAsset->assetId + AssetIdToForeignAsset::::insert(&asset_id, &foreign_asset); + ForeignAssetToAssetId::::insert(&foreign_asset, &asset_id); + + Self::deposit_event(Event::ForeignAssetCreated { + asset_id, + foreign_asset, + }); + Ok(()) + } + + /// Change the xcm type mapping for a given assetId + /// We also change this if the previous units per second where pointing at the old + /// assetType + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::change_existing_asset_type())] + pub fn change_existing_asset_type( + origin: OriginFor, + asset_id: AssetId, + new_foreign_asset: T::ForeignAsset, + ) -> DispatchResult { + T::ForeignAssetModifierOrigin::ensure_origin(origin)?; + + let previous_foreign_asset = + AssetIdToForeignAsset::::get(&asset_id).ok_or(Error::::AssetDoesNotExist)?; + + // Insert new foreign asset info + AssetIdToForeignAsset::::insert(&asset_id, &new_foreign_asset); + ForeignAssetToAssetId::::insert(&new_foreign_asset, &asset_id); + + // Remove previous foreign asset info + ForeignAssetToAssetId::::remove(&previous_foreign_asset); + + Self::deposit_event(Event::ForeignAssetTypeChanged { + asset_id, + new_foreign_asset, + }); + Ok(()) + } + + /// Remove a given assetId -> foreignAsset association + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::remove_existing_asset_type())] + pub fn remove_existing_asset_type( + origin: OriginFor, + asset_id: AssetId, + ) -> DispatchResult { + T::ForeignAssetDestroyerOrigin::ensure_origin(origin)?; + + let foreign_asset = + AssetIdToForeignAsset::::get(&asset_id).ok_or(Error::::AssetDoesNotExist)?; + + // Remove from AssetIdToForeignAsset + AssetIdToForeignAsset::::remove(&asset_id); + // Remove from ForeignAssetToAssetId + ForeignAssetToAssetId::::remove(&foreign_asset); + + Self::deposit_event(Event::ForeignAssetRemoved { + asset_id, + foreign_asset, + }); + Ok(()) + } + + /// Destroy a given foreign assetId + /// The weight in this case is the one returned by the trait + /// plus the db writes and reads from removing all the associated + /// data + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::destroy_foreign_asset())] + pub fn destroy_foreign_asset(origin: OriginFor, asset_id: AssetId) -> DispatchResult { + T::ForeignAssetDestroyerOrigin::ensure_origin(origin)?; + + let foreign_asset = + AssetIdToForeignAsset::::get(&asset_id).ok_or(Error::::AssetDoesNotExist)?; + + // Important: this starts the destruction process, making sure the assets are non-transferable anymore + // make sure the destruction process is completable by other means + T::Fungibles::start_destroy(asset_id.clone(), None)?; + + // Remove from AssetIdToForeignAsset + AssetIdToForeignAsset::::remove(&asset_id); + // Remove from ForeignAssetToAssetId + ForeignAssetToAssetId::::remove(&foreign_asset); + + Self::deposit_event(Event::ForeignAssetDestroyed { + asset_id, + foreign_asset, + }); + Ok(()) + } + } + + impl MaybeEquivalence> for Pallet { + fn convert(foreign_asset: &T::ForeignAsset) -> Option> { + Pallet::::asset_id_for_foreign(foreign_asset.clone()) + } + fn convert_back(id: &AssetId) -> Option { + Pallet::::foreign_asset_for_id(id.clone()) + } + } +} diff --git a/pallets/foreign-asset-creator/src/mock.rs b/pallets/foreign-asset-creator/src/mock.rs new file mode 100644 index 0000000..e944b27 --- /dev/null +++ b/pallets/foreign-asset-creator/src/mock.rs @@ -0,0 +1,183 @@ +// Copyright Moonsong Labs +// This file is part of Moonkit. + +// Moonkit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonkit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonkit. If not, see . + +use super::*; +use crate as pallet_foreign_asset_creator; + +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU32, Everything}, +}; +use frame_system::EnsureRoot; +use sp_core::H256; +use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; +use sp_runtime::BuildStorage; +use staging_xcm::latest::prelude::*; + +type Block = frame_system::mocking::MockBlock; + +pub type AccountId = u64; +pub type Balance = u64; + +construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + ForeignAssetCreator: pallet_foreign_asset_creator, + Assets: pallet_assets, + } +); + +parameter_types! { + pub const BlockHashCount: u32 = 250; +} +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Block = Block; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; +} + +impl pallet_balances::Config for Test { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} + +parameter_types! { + pub const AssetDeposit: u64 = 0; + pub const ApprovalDeposit: u64 = 0; + pub const StringLimit: u32 = 50; + pub const MetadataDepositBase: u64 = 0; + pub const MetadataDepositPerByte: u64 = 0; +} + +type AssetId = u32; + +impl pallet_assets::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetId; + type AssetIdParameter = parity_scale_codec::Compact; + type Currency = Balances; + type CreateOrigin = frame_support::traits::NeverEnsureOrigin; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type AssetAccountDeposit = AssetDeposit; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = StringLimit; + type Freezer = (); + type Extra = (); + type CallbackHandle = (); + type WeightInfo = (); + type RemoveItemsLimit = ConstU32<1000>; + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type ForeignAsset = MultiLocation; + type ForeignAssetCreatorOrigin = EnsureRoot; + type ForeignAssetModifierOrigin = EnsureRoot; + type ForeignAssetDestroyerOrigin = EnsureRoot; + type Fungibles = Assets; + type WeightInfo = (); +} + +pub(crate) struct ExtBuilder { + // endowed accounts with balances + balances: Vec<(AccountId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> ExtBuilder { + ExtBuilder { balances: vec![] } + } +} + +impl ExtBuilder { + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + pallet_balances::GenesisConfig:: { + balances: self.balances, + } + .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 + } +} + +pub(crate) fn events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let RuntimeEvent::ForeignAssetCreator(inner) = e { + Some(inner) + } else { + None + } + }) + .collect::>() +} + +pub fn expect_events(e: Vec>) { + assert_eq!(events(), e); +} diff --git a/pallets/foreign-asset-creator/src/tests.rs b/pallets/foreign-asset-creator/src/tests.rs new file mode 100644 index 0000000..7bd35a6 --- /dev/null +++ b/pallets/foreign-asset-creator/src/tests.rs @@ -0,0 +1,228 @@ +// Copyright Moonsong Labs +// This file is part of Moonkit. + +// Moonkit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonkit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonkit. If not, see . +use crate::*; +use mock::*; + +use frame_support::{assert_noop, assert_ok}; +use staging_xcm::latest::prelude::*; + +#[test] +fn creating_foreign_works() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(ForeignAssetCreator::create_foreign_asset( + RuntimeOrigin::root(), + MultiLocation::parent(), + 1u32.into(), + 1u32.into(), + true, + 1u64, + )); + + assert_eq!( + ForeignAssetCreator::foreign_asset_for_id(1).unwrap(), + MultiLocation::parent() + ); + assert_eq!( + ForeignAssetCreator::asset_id_for_foreign(MultiLocation::parent()).unwrap(), + 1 + ); + expect_events(vec![crate::Event::ForeignAssetCreated { + asset_id: 1, + foreign_asset: MultiLocation::parent(), + }]) + }); +} + +#[test] +fn test_asset_exists_error() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(ForeignAssetCreator::create_foreign_asset( + RuntimeOrigin::root(), + MultiLocation::parent(), + 1u32.into(), + 1u32.into(), + true, + 1u64, + )); + assert_eq!( + ForeignAssetCreator::foreign_asset_for_id(1).unwrap(), + MultiLocation::parent() + ); + assert_noop!( + ForeignAssetCreator::create_foreign_asset( + RuntimeOrigin::root(), + MultiLocation::parent(), + 1u32.into(), + 1u32.into(), + true, + 1u64, + ), + Error::::AssetAlreadyExists + ); + }); +} + +#[test] +fn test_regular_user_cannot_call_extrinsics() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + ForeignAssetCreator::create_foreign_asset( + RuntimeOrigin::signed(1), + MultiLocation::parent(), + 1u32.into(), + 1u32.into(), + true, + 1u64, + ), + sp_runtime::DispatchError::BadOrigin + ); + + assert_noop!( + ForeignAssetCreator::change_existing_asset_type( + RuntimeOrigin::signed(1), + 1, + MultiLocation::parent() + ), + sp_runtime::DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_root_can_change_foreign_asset_for_asset_id() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(ForeignAssetCreator::create_foreign_asset( + RuntimeOrigin::root(), + MultiLocation::parent(), + 1u32.into(), + 1u32.into(), + true, + 1u64, + )); + + assert_ok!(ForeignAssetCreator::change_existing_asset_type( + RuntimeOrigin::root(), + 1, + MultiLocation::here() + )); + + // New associations are stablished + assert_eq!( + ForeignAssetCreator::foreign_asset_for_id(1).unwrap(), + MultiLocation::here() + ); + assert_eq!( + ForeignAssetCreator::asset_id_for_foreign(MultiLocation::here()).unwrap(), + 1 + ); + + // Old ones are deleted + assert!(ForeignAssetCreator::asset_id_for_foreign(MultiLocation::parent()).is_none()); + + expect_events(vec![ + crate::Event::ForeignAssetCreated { + asset_id: 1, + foreign_asset: MultiLocation::parent(), + }, + crate::Event::ForeignAssetTypeChanged { + asset_id: 1, + new_foreign_asset: MultiLocation::here(), + }, + ]) + }); +} + +#[test] +fn test_asset_id_non_existent_error() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + ForeignAssetCreator::change_existing_asset_type( + RuntimeOrigin::root(), + 1, + MultiLocation::parent() + ), + Error::::AssetDoesNotExist + ); + }); +} + +#[test] +fn test_root_can_remove_asset_association() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(ForeignAssetCreator::create_foreign_asset( + RuntimeOrigin::root(), + MultiLocation::parent(), + 1u32.into(), + 1u32.into(), + true, + 1u64, + )); + + assert_ok!(ForeignAssetCreator::remove_existing_asset_type( + RuntimeOrigin::root(), + 1 + )); + + // Mappings are deleted + assert!(ForeignAssetCreator::foreign_asset_for_id(1).is_none()); + assert!(ForeignAssetCreator::asset_id_for_foreign(MultiLocation::parent()).is_none()); + + expect_events(vec![ + crate::Event::ForeignAssetCreated { + asset_id: 1, + foreign_asset: MultiLocation::parent(), + }, + crate::Event::ForeignAssetRemoved { + asset_id: 1, + foreign_asset: MultiLocation::parent(), + }, + ]) + }); +} + +#[test] +fn test_destroy_foreign_asset_also_removes_everything() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(ForeignAssetCreator::create_foreign_asset( + RuntimeOrigin::root(), + MultiLocation::parent(), + 1u32.into(), + 1u32.into(), + true, + 1u64, + )); + + assert_ok!(ForeignAssetCreator::destroy_foreign_asset( + RuntimeOrigin::root(), + 1 + )); + + // Mappings are deleted + assert!(ForeignAssetCreator::asset_id_for_foreign(MultiLocation::parent()).is_none()); + assert!(ForeignAssetCreator::foreign_asset_for_id(1).is_none()); + + expect_events(vec![ + crate::Event::ForeignAssetCreated { + asset_id: 1, + foreign_asset: MultiLocation::parent(), + }, + crate::Event::ForeignAssetDestroyed { + asset_id: 1, + foreign_asset: MultiLocation::parent(), + }, + ]) + }); +} diff --git a/pallets/foreign-asset-creator/src/weights.rs b/pallets/foreign-asset-creator/src/weights.rs new file mode 100644 index 0000000..f9c784f --- /dev/null +++ b/pallets/foreign-asset-creator/src/weights.rs @@ -0,0 +1,186 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Tanssi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Tanssi. If not, see + + +//! Autogenerated weights for pallet_foreign_asset_creator +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2024-01-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `girazoki-XPS-15-9530`, CPU: `13th Gen Intel(R) Core(TM) i9-13900H` +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 + +// Executed Command: +// ./target/release/tanssi-node +// benchmark +// pallet +// --execution=wasm +// --wasm-execution=compiled +// --pallet +// pallet_foreign_asset_creator +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --template=./benchmarking/frame-weight-template.hbs +// --json-file +// raw.json +// --output +// weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_foreign_asset_creator. +pub trait WeightInfo { + fn create_foreign_asset() -> Weight; + fn change_existing_asset_type() -> Weight; + fn remove_existing_asset_type() -> Weight; + fn destroy_foreign_asset() -> Weight; +} + +/// Weights for pallet_foreign_asset_creator using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `ForeignAssetsCreator::AssetIdToForeignAsset` (r:1 w:1) + /// Proof: `ForeignAssetsCreator::AssetIdToForeignAsset` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ForeignAssets::Asset` (r:1 w:1) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(208), added: 2683, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssetsCreator::ForeignAssetToAssetId` (r:0 w:1) + /// Proof: `ForeignAssetsCreator::ForeignAssetToAssetId` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn create_foreign_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `3673` + // Minimum execution time: 17_654_000 picoseconds. + Weight::from_parts(18_621_000, 3673) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `ForeignAssetsCreator::AssetIdToForeignAsset` (r:1 w:1) + /// Proof: `ForeignAssetsCreator::AssetIdToForeignAsset` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ForeignAssetsCreator::ForeignAssetToAssetId` (r:0 w:2) + /// Proof: `ForeignAssetsCreator::ForeignAssetToAssetId` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn change_existing_asset_type() -> Weight { + // Proof Size summary in bytes: + // Measured: `427` + // Estimated: `3880` + // Minimum execution time: 17_469_000 picoseconds. + Weight::from_parts(20_276_697, 3880) + // Standard Error: 1_876 + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `ForeignAssetsCreator::AssetIdToForeignAsset` (r:1 w:1) + /// Proof: `ForeignAssetsCreator::AssetIdToForeignAsset` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ForeignAssetsCreator::ForeignAssetToAssetId` (r:0 w:1) + /// Proof: `ForeignAssetsCreator::ForeignAssetToAssetId` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn remove_existing_asset_type() -> Weight { + // Proof Size summary in bytes: + // Measured: `427` + // Estimated: `3880` + // Minimum execution time: 15_165_000 picoseconds. + Weight::from_parts(18_041_533, 3880) + // Standard Error: 1_836 + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `ForeignAssetsCreator::AssetIdToForeignAsset` (r:1 w:1) + /// Proof: `ForeignAssetsCreator::AssetIdToForeignAsset` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ForeignAssets::Asset` (r:1 w:1) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(208), added: 2683, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssetsCreator::ForeignAssetToAssetId` (r:0 w:1) + /// Proof: `ForeignAssetsCreator::ForeignAssetToAssetId` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn destroy_foreign_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `981` + // Estimated: `4441` + // Minimum execution time: 22_589_000 picoseconds. + Weight::from_parts(26_897_574, 4441) + // Standard Error: 3_872 + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: `ForeignAssetsCreator::AssetIdToForeignAsset` (r:1 w:1) + /// Proof: `ForeignAssetsCreator::AssetIdToForeignAsset` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ForeignAssets::Asset` (r:1 w:1) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(208), added: 2683, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssetsCreator::ForeignAssetToAssetId` (r:0 w:1) + /// Proof: `ForeignAssetsCreator::ForeignAssetToAssetId` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn create_foreign_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `3673` + // Minimum execution time: 17_654_000 picoseconds. + Weight::from_parts(18_621_000, 3673) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: `ForeignAssetsCreator::AssetIdToForeignAsset` (r:1 w:1) + /// Proof: `ForeignAssetsCreator::AssetIdToForeignAsset` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ForeignAssetsCreator::ForeignAssetToAssetId` (r:0 w:2) + /// Proof: `ForeignAssetsCreator::ForeignAssetToAssetId` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn change_existing_asset_type() -> Weight { + // Proof Size summary in bytes: + // Measured: `427` + // Estimated: `3880` + // Minimum execution time: 17_469_000 picoseconds. + Weight::from_parts(20_276_697, 3880) + // Standard Error: 1_876 + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: `ForeignAssetsCreator::AssetIdToForeignAsset` (r:1 w:1) + /// Proof: `ForeignAssetsCreator::AssetIdToForeignAsset` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ForeignAssetsCreator::ForeignAssetToAssetId` (r:0 w:1) + /// Proof: `ForeignAssetsCreator::ForeignAssetToAssetId` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn remove_existing_asset_type() -> Weight { + // Proof Size summary in bytes: + // Measured: `427` + // Estimated: `3880` + // Minimum execution time: 15_165_000 picoseconds. + Weight::from_parts(18_041_533, 3880) + // Standard Error: 1_836 + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `ForeignAssetsCreator::AssetIdToForeignAsset` (r:1 w:1) + /// Proof: `ForeignAssetsCreator::AssetIdToForeignAsset` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ForeignAssets::Asset` (r:1 w:1) + /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(208), added: 2683, mode: `MaxEncodedLen`) + /// Storage: `ForeignAssetsCreator::ForeignAssetToAssetId` (r:0 w:1) + /// Proof: `ForeignAssetsCreator::ForeignAssetToAssetId` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn destroy_foreign_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `981` + // Estimated: `4441` + // Minimum execution time: 22_589_000 picoseconds. + Weight::from_parts(26_897_574, 4441) + // Standard Error: 3_872 + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } +}