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))
+ }
+}