From 4c98e03a01fb4259723580c64c1c8f78b3725bb2 Mon Sep 17 00:00:00 2001 From: girazoki Date: Fri, 8 Dec 2023 14:24:50 +0100 Subject: [PATCH 01/10] foreign asset creator pallet --- pallets/foreign-asset-creator/Cargo.toml | 43 ++++ pallets/foreign-asset-creator/src/lib.rs | 235 +++++++++++++++++++++ pallets/foreign-asset-creator/src/mock.rs | 182 ++++++++++++++++ pallets/foreign-asset-creator/src/tests.rs | 228 ++++++++++++++++++++ 4 files changed, 688 insertions(+) create mode 100644 pallets/foreign-asset-creator/Cargo.toml create mode 100644 pallets/foreign-asset-creator/src/lib.rs create mode 100644 pallets/foreign-asset-creator/src/mock.rs create mode 100644 pallets/foreign-asset-creator/src/tests.rs diff --git a/pallets/foreign-asset-creator/Cargo.toml b/pallets/foreign-asset-creator/Cargo.toml new file mode 100644 index 0000000..93e7309 --- /dev/null +++ b/pallets/foreign-asset-creator/Cargo.toml @@ -0,0 +1,43 @@ +[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-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = 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" ] } +staging-xcm = { workspace = true, features = [ "std" ] } + +[features] +default = [ "std" ] +std = [ + "frame-support/std", + "frame-system/std", + "parity-scale-codec/std", + "scale-info/std", + "serde", + "sp-io/std", + "sp-runtime/std", + "sp-std/std" +] + +runtime-benchmarks = [ "frame-benchmarking" ] +try-runtime = [ "frame-support/try-runtime" ] \ No newline at end of file diff --git a/pallets/foreign-asset-creator/src/lib.rs b/pallets/foreign-asset-creator/src/lib.rs new file mode 100644 index 0000000..f20054b --- /dev/null +++ b/pallets/foreign-asset-creator/src/lib.rs @@ -0,0 +1,235 @@ +// 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::*; +#[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::*; + + #[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; + } + + 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(0)] + 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(1)] + 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(1)] + 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(1)] + 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(()) + } + } +} diff --git a/pallets/foreign-asset-creator/src/mock.rs b/pallets/foreign-asset-creator/src/mock.rs new file mode 100644 index 0000000..c274ecf --- /dev/null +++ b/pallets/foreign-asset-creator/src/mock.rs @@ -0,0 +1,182 @@ +// 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; +} + +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..7776f1a --- /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(), + }, + ]) + }); +} From 16a621d2b6e5a37b3d7ad06ee6aeb95ce05600c1 Mon Sep 17 00:00:00 2001 From: girazoki Date: Fri, 8 Dec 2023 15:29:57 +0100 Subject: [PATCH 02/10] add maybe equivalence --- Cargo.toml | 1 + pallets/foreign-asset-creator/src/lib.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index b6adc92..10e6492 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "pallets/author-inherent", "pallets/author-mapping", "pallets/author-slot-filter", + "pallets/foreign-asset-creator", "pallets/migrations", "pallets/maintenance-mode", "pallets/randomness", diff --git a/pallets/foreign-asset-creator/src/lib.rs b/pallets/foreign-asset-creator/src/lib.rs index f20054b..7ff9b93 100644 --- a/pallets/foreign-asset-creator/src/lib.rs +++ b/pallets/foreign-asset-creator/src/lib.rs @@ -33,6 +33,7 @@ pub mod pallet { tokens::fungibles, }, }; + use sp_runtime::traits::MaybeEquivalence; use frame_system::pallet_prelude::*; #[pallet::pallet] @@ -232,4 +233,15 @@ pub mod pallet { 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()) + } + } } From bf1c7e7aeea90896f799db13d04a98c785f83df4 Mon Sep 17 00:00:00 2001 From: girazoki Date: Fri, 8 Dec 2023 15:30:28 +0100 Subject: [PATCH 03/10] lock --- Cargo.lock | 52 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d692e1..4dcb754 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1461,7 +1461,7 @@ dependencies = [ [[package]] name = "common" version = "0.1.0" -source = "git+https://github.com/w3f/ring-proof#d3dcccd76cfdb8e2f905e789f129618898026447" +source = "git+https://github.com/w3f/ring-proof#b273d33f9981e2bb3375ab45faeb537f7ee35224" dependencies = [ "ark-ec", "ark-ff", @@ -1469,6 +1469,7 @@ dependencies = [ "ark-serialize", "ark-std", "fflonk", + "getrandom_or_panic", "merlin 3.0.0", "rand_chacha 0.3.1", ] @@ -3147,7 +3148,7 @@ dependencies = [ [[package]] name = "fflonk" version = "0.1.0" -source = "git+https://github.com/w3f/fflonk#1beb0585e1c8488956fac7f05da061f9b41e8948" +source = "git+https://github.com/w3f/fflonk#1e854f35e9a65d08b11a86291405cdc95baa0a35" dependencies = [ "ark-ec", "ark-ff", @@ -3835,6 +3836,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom_or_panic" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "ghash" version = "0.4.4" @@ -6345,6 +6355,22 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-assets" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0#401f8a3e9448db854f5605b679fa085b8f445039" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-aura-style-filter" version = "0.9.0" @@ -6720,6 +6746,26 @@ 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-core", + "sp-io", + "sp-runtime", + "sp-std", + "staging-xcm", +] + [[package]] name = "pallet-grandpa" version = "4.0.0-dev" @@ -9541,7 +9587,7 @@ dependencies = [ [[package]] name = "ring" version = "0.1.0" -source = "git+https://github.com/w3f/ring-proof#d3dcccd76cfdb8e2f905e789f129618898026447" +source = "git+https://github.com/w3f/ring-proof#b273d33f9981e2bb3375ab45faeb537f7ee35224" dependencies = [ "ark-ec", "ark-ff", From 0963735b1df1618db8e45100882f9eb7a858566e Mon Sep 17 00:00:00 2001 From: girazoki Date: Tue, 9 Jan 2024 11:06:20 +0100 Subject: [PATCH 04/10] Add benchmarks --- pallets/foreign-asset-creator/Cargo.toml | 9 +- .../foreign-asset-creator/src/benchmarks.rs | 144 ++++++++++++++++++ pallets/foreign-asset-creator/src/lib.rs | 2 + 3 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 pallets/foreign-asset-creator/src/benchmarks.rs diff --git a/pallets/foreign-asset-creator/Cargo.toml b/pallets/foreign-asset-creator/Cargo.toml index 93e7309..d68c1e2 100644 --- a/pallets/foreign-asset-creator/Cargo.toml +++ b/pallets/foreign-asset-creator/Cargo.toml @@ -17,6 +17,9 @@ 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 } @@ -24,7 +27,6 @@ frame-benchmarking = { workspace = true, optional = true } pallet-balances = { workspace = true, features = [ "std" ] } pallet-assets = { workspace = true, features = [ "std" ] } sp-core = { workspace = true, features = [ "std" ] } -staging-xcm = { workspace = true, features = [ "std" ] } [features] default = [ "std" ] @@ -36,8 +38,9 @@ std = [ "serde", "sp-io/std", "sp-runtime/std", - "sp-std/std" + "sp-std/std", + "staging-xcm/std" ] -runtime-benchmarks = [ "frame-benchmarking" ] +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..ab0bb11 --- /dev/null +++ b/pallets/foreign-asset-creator/src/benchmarks.rs @@ -0,0 +1,144 @@ +// 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::{Call, Config, Pallet, AssetId}; +use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; +use frame_support::traits::{Currency, Get}; +use frame_system::RawOrigin; +use staging_xcm::latest::prelude::*; +use parity_scale_codec::HasCompact; +use sp_runtime::traits::AtLeast32BitUnsigned; +benchmarks! { + // This where clause allows us to create ForeignAssetTypes + where_clause { where T::ForeignAsset: From, AssetId: AtLeast32BitUnsigned } + 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 = 1u32.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 u32).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 u32).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 u32).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 u32).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 u32).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 u32).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 +); \ No newline at end of file diff --git a/pallets/foreign-asset-creator/src/lib.rs b/pallets/foreign-asset-creator/src/lib.rs index 7ff9b93..d95e0fa 100644 --- a/pallets/foreign-asset-creator/src/lib.rs +++ b/pallets/foreign-asset-creator/src/lib.rs @@ -22,6 +22,8 @@ pub use pallet::*; pub mod mock; #[cfg(test)] pub mod tests; +#[cfg(any(test, feature = "runtime-benchmarks"))] +mod benchmarks; #[pallet] pub mod pallet { From 02a1cb2ae3ec648e21c593d1b54eb25231648663 Mon Sep 17 00:00:00 2001 From: girazoki Date: Tue, 9 Jan 2024 11:26:13 +0100 Subject: [PATCH 05/10] benchmarks --- pallets/foreign-asset-creator/Cargo.toml | 1 + .../foreign-asset-creator/src/benchmarks.rs | 47 +- pallets/foreign-asset-creator/src/lib.rs | 440 +++++++++--------- pallets/foreign-asset-creator/src/mock.rs | 222 ++++----- pallets/foreign-asset-creator/src/tests.rs | 360 +++++++------- 5 files changed, 534 insertions(+), 536 deletions(-) diff --git a/pallets/foreign-asset-creator/Cargo.toml b/pallets/foreign-asset-creator/Cargo.toml index d68c1e2..37bad20 100644 --- a/pallets/foreign-asset-creator/Cargo.toml +++ b/pallets/foreign-asset-creator/Cargo.toml @@ -31,6 +31,7 @@ sp-core = { workspace = true, features = [ "std" ] } [features] default = [ "std" ] std = [ + "frame-benchmarking/std", "frame-support/std", "frame-system/std", "parity-scale-codec/std", diff --git a/pallets/foreign-asset-creator/src/benchmarks.rs b/pallets/foreign-asset-creator/src/benchmarks.rs index ab0bb11..4880bac 100644 --- a/pallets/foreign-asset-creator/src/benchmarks.rs +++ b/pallets/foreign-asset-creator/src/benchmarks.rs @@ -14,47 +14,46 @@ // You should have received a copy of the GNU General Public License // along with Moonkit. If not, see . - #![cfg(feature = "runtime-benchmarks")] -use crate::{Call, Config, Pallet, AssetId}; +use crate::{AssetId, Call, Config, Pallet}; use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; use frame_support::traits::{Currency, Get}; use frame_system::RawOrigin; -use staging_xcm::latest::prelude::*; use parity_scale_codec::HasCompact; use sp_runtime::traits::AtLeast32BitUnsigned; +use staging_xcm::latest::prelude::*; benchmarks! { // This where clause allows us to create ForeignAssetTypes where_clause { where T::ForeignAsset: From, AssetId: AtLeast32BitUnsigned } create_foreign_asset { - const USER_SEED: u32 = 1; - let manager = account("manager", 0, USER_SEED); + 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 = 1u32.into(); + let asset_id: AssetId = 1u32.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 { + 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); + 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 u32).into(); + let asset_id: AssetId = (i as u32).into(); let amount = 1u32.into(); Pallet::::create_foreign_asset( RawOrigin::Root.into(), foreign_asset.clone(), - asset_id.clone(), + asset_id.clone(), manager.clone(), - true, + true, amount, )?; } @@ -70,22 +69,22 @@ benchmarks! { assert_eq!(Pallet::::foreign_asset_for_id(asset_id_to_be_changed), Some(new_foreign_asset.clone())); } - remove_existing_asset_type { + 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); + 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 u32).into(); + let asset_id: AssetId = (i as u32).into(); let amount = 1u32.into(); Pallet::::create_foreign_asset( RawOrigin::Root.into(), foreign_asset.clone(), - asset_id.clone(), + asset_id.clone(), manager.clone(), - true, + true, amount, )?; } @@ -99,19 +98,19 @@ benchmarks! { 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); + 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 u32).into(); + let asset_id: AssetId = (i as u32).into(); let amount = 1u32.into(); Pallet::::create_foreign_asset( RawOrigin::Root.into(), foreign_asset.clone(), - asset_id.clone(), + asset_id.clone(), manager.clone(), - true, + true, amount, )?; } @@ -141,4 +140,4 @@ impl_benchmark_test_suite!( Pallet, crate::benchmarks::tests::new_test_ext(), crate::mock::Test -); \ No newline at end of file +); diff --git a/pallets/foreign-asset-creator/src/lib.rs b/pallets/foreign-asset-creator/src/lib.rs index d95e0fa..f2c2227 100644 --- a/pallets/foreign-asset-creator/src/lib.rs +++ b/pallets/foreign-asset-creator/src/lib.rs @@ -18,232 +18,230 @@ use frame_support::pallet; pub use pallet::*; +#[cfg(any(test, feature = "runtime-benchmarks"))] +mod benchmarks; #[cfg(test)] pub mod mock; #[cfg(test)] pub mod tests; -#[cfg(any(test, feature = "runtime-benchmarks"))] -mod benchmarks; #[pallet] pub mod pallet { - use super::*; - use frame_support::{ - pallet_prelude::*, - traits::{ - fungibles::{Create, Destroy}, - tokens::fungibles, - }, - }; - use sp_runtime::traits::MaybeEquivalence; - use frame_system::pallet_prelude::*; - - #[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; - } - - 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(0)] - 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(1)] - 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(1)] - 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(1)] - 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()) - } - } + 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; + } + + 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(0)] + 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(1)] + 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(1)] + 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(1)] + 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 index c274ecf..7b8041c 100644 --- a/pallets/foreign-asset-creator/src/mock.rs +++ b/pallets/foreign-asset-creator/src/mock.rs @@ -18,8 +18,8 @@ use super::*; use crate as pallet_foreign_asset_creator; use frame_support::{ - construct_runtime, parameter_types, - traits::{ConstU32, Everything}, + construct_runtime, parameter_types, + traits::{ConstU32, Everything}, }; use frame_system::EnsureRoot; use sp_core::H256; @@ -33,150 +33,150 @@ 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, - } + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + ForeignAssetCreator: pallet_foreign_asset_creator, + Assets: pallet_assets, + } ); parameter_types! { - pub const BlockHashCount: u32 = 250; + 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>; + 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; + 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 = (); + 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; + 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 = (); - } + 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 RuntimeEvent = RuntimeEvent; + type ForeignAsset = MultiLocation; + type ForeignAssetCreatorOrigin = EnsureRoot; + type ForeignAssetModifierOrigin = EnsureRoot; + type ForeignAssetDestroyerOrigin = EnsureRoot; + type Fungibles = Assets; } pub(crate) struct ExtBuilder { - // endowed accounts with balances - balances: Vec<(AccountId, Balance)>, + // endowed accounts with balances + balances: Vec<(AccountId, Balance)>, } impl Default for ExtBuilder { - fn default() -> ExtBuilder { - ExtBuilder { balances: vec![] } - } + 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 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::>() + 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); + assert_eq!(events(), e); } diff --git a/pallets/foreign-asset-creator/src/tests.rs b/pallets/foreign-asset-creator/src/tests.rs index 7776f1a..7bd35a6 100644 --- a/pallets/foreign-asset-creator/src/tests.rs +++ b/pallets/foreign-asset-creator/src/tests.rs @@ -21,208 +21,208 @@ 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(), - }]) - }); + 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 - ); - }); + 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 - ); - }); + 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(), - }, - ]) - }); + 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 - ); - }); + 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(), - }, - ]) - }); + 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(), - }, - ]) - }); + 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(), + }, + ]) + }); } From c80451bbc9912b8274c87fc4ff71ae7a3a102c9d Mon Sep 17 00:00:00 2001 From: girazoki Date: Tue, 9 Jan 2024 13:04:44 +0100 Subject: [PATCH 06/10] at least 16 bit unsigned --- pallets/foreign-asset-creator/src/benchmarks.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/foreign-asset-creator/src/benchmarks.rs b/pallets/foreign-asset-creator/src/benchmarks.rs index 4880bac..1d6e3ec 100644 --- a/pallets/foreign-asset-creator/src/benchmarks.rs +++ b/pallets/foreign-asset-creator/src/benchmarks.rs @@ -21,11 +21,11 @@ use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; use frame_support::traits::{Currency, Get}; use frame_system::RawOrigin; use parity_scale_codec::HasCompact; -use sp_runtime::traits::AtLeast32BitUnsigned; +use sp_runtime::traits::AtLeast16BitUnsigned; use staging_xcm::latest::prelude::*; benchmarks! { // This where clause allows us to create ForeignAssetTypes - where_clause { where T::ForeignAsset: From, AssetId: AtLeast32BitUnsigned } + where_clause { where T::ForeignAsset: From, AssetId: AtLeast16BitUnsigned } create_foreign_asset { const USER_SEED: u32 = 1; let manager = account("manager", 0, USER_SEED); From ea84a156d2c273179b0fad8ab687373e7a02575b Mon Sep 17 00:00:00 2001 From: girazoki Date: Tue, 9 Jan 2024 13:08:33 +0100 Subject: [PATCH 07/10] update --- Cargo.lock | 1 + Cargo.toml | 1 + pallets/foreign-asset-creator/Cargo.toml | 2 ++ pallets/foreign-asset-creator/src/benchmarks.rs | 16 ++++++++-------- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3aead43..f69b5ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6813,6 +6813,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", + "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", diff --git a/Cargo.toml b/Cargo.toml index 520efeb..b218b4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,7 @@ pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk", branch = pallet-whitelist = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.3.0", default-features = false } sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.3.0", default-features = false } sp-application-crypto = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.3.0", default-features = false } +sp-arithmetic = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.3.0", default-features = false } sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.3.0", default-features = false } sp-consensus-babe = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.3.0", default-features = false } sp-consensus-slots = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.3.0", default-features = false } diff --git a/pallets/foreign-asset-creator/Cargo.toml b/pallets/foreign-asset-creator/Cargo.toml index 37bad20..363f822 100644 --- a/pallets/foreign-asset-creator/Cargo.toml +++ b/pallets/foreign-asset-creator/Cargo.toml @@ -13,6 +13,7 @@ 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 } @@ -37,6 +38,7 @@ std = [ "parity-scale-codec/std", "scale-info/std", "serde", + "sp-arithmetic/std", "sp-io/std", "sp-runtime/std", "sp-std/std", diff --git a/pallets/foreign-asset-creator/src/benchmarks.rs b/pallets/foreign-asset-creator/src/benchmarks.rs index 1d6e3ec..3633f64 100644 --- a/pallets/foreign-asset-creator/src/benchmarks.rs +++ b/pallets/foreign-asset-creator/src/benchmarks.rs @@ -21,7 +21,7 @@ use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; use frame_support::traits::{Currency, Get}; use frame_system::RawOrigin; use parity_scale_codec::HasCompact; -use sp_runtime::traits::AtLeast16BitUnsigned; +use sp_arithmetic::traits::AtLeast16BitUnsigned; use staging_xcm::latest::prelude::*; benchmarks! { // This where clause allows us to create ForeignAssetTypes @@ -31,7 +31,7 @@ benchmarks! { let manager = account("manager", 0, USER_SEED); let foreign_asset = T::ForeignAsset::default(); let amount = 1u32.into(); - let asset_id: AssetId = 1u32.into(); + let asset_id: AssetId = 1u16.into(); }: _(RawOrigin::Root, foreign_asset.clone(), asset_id.clone(), manager, true, amount) verify { @@ -46,7 +46,7 @@ benchmarks! { 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 u32).into(); + let asset_id: AssetId = (i as u16).into(); let amount = 1u32.into(); Pallet::::create_foreign_asset( RawOrigin::Root.into(), @@ -63,7 +63,7 @@ benchmarks! { 0, X1(GeneralIndex((x-1) as u128)) ).into(); - let asset_id_to_be_changed: AssetId = ((x-1) as u32).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())); @@ -77,7 +77,7 @@ benchmarks! { 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 u32).into(); + let asset_id: AssetId = (i as u16).into(); let amount = 1u32.into(); Pallet::::create_foreign_asset( RawOrigin::Root.into(), @@ -89,7 +89,7 @@ benchmarks! { )?; } - let asset_id_to_be_removed: AssetId = ((x-1) as u32).into(); + 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()); @@ -103,7 +103,7 @@ benchmarks! { 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 u32).into(); + let asset_id: AssetId = (i as u16).into(); let amount = 1u32.into(); Pallet::::create_foreign_asset( RawOrigin::Root.into(), @@ -115,7 +115,7 @@ benchmarks! { )?; } - let asset_id_to_be_destroyed: AssetId = ((x-1) as u32).into(); + 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()); From 46ca74d13fa355b202afa3d9c11ed06bd3e6e36d Mon Sep 17 00:00:00 2001 From: girazoki Date: Tue, 9 Jan 2024 13:37:52 +0100 Subject: [PATCH 08/10] add benchmarks --- pallets/foreign-asset-creator/src/lib.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pallets/foreign-asset-creator/src/lib.rs b/pallets/foreign-asset-creator/src/lib.rs index f2c2227..0c702a8 100644 --- a/pallets/foreign-asset-creator/src/lib.rs +++ b/pallets/foreign-asset-creator/src/lib.rs @@ -18,6 +18,8 @@ use frame_support::pallet; pub use pallet::*; +pub mod weights; +pub use weights::WeightInfo; #[cfg(any(test, feature = "runtime-benchmarks"))] mod benchmarks; #[cfg(test)] @@ -61,6 +63,9 @@ pub mod pallet { 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< @@ -122,7 +127,7 @@ pub mod pallet { impl Pallet { /// Create new asset with the ForeignAssetCreator #[pallet::call_index(0)] - #[pallet::weight(0)] + #[pallet::weight(::WeightInfo::create_foreign_asset())] pub fn create_foreign_asset( origin: OriginFor, foreign_asset: T::ForeignAsset, @@ -158,7 +163,7 @@ pub mod pallet { /// We also change this if the previous units per second where pointing at the old /// assetType #[pallet::call_index(1)] - #[pallet::weight(1)] + #[pallet::weight(::WeightInfo::change_existing_asset_type())] pub fn change_existing_asset_type( origin: OriginFor, asset_id: AssetId, @@ -185,7 +190,7 @@ pub mod pallet { /// Remove a given assetId -> foreignAsset association #[pallet::call_index(2)] - #[pallet::weight(1)] + #[pallet::weight(::WeightInfo::remove_existing_asset_type())] pub fn remove_existing_asset_type( origin: OriginFor, asset_id: AssetId, @@ -212,7 +217,7 @@ pub mod pallet { /// plus the db writes and reads from removing all the associated /// data #[pallet::call_index(3)] - #[pallet::weight(1)] + #[pallet::weight(::WeightInfo::destroy_foreign_asset())] pub fn destroy_foreign_asset(origin: OriginFor, asset_id: AssetId) -> DispatchResult { T::ForeignAssetDestroyerOrigin::ensure_origin(origin)?; From da746f14c1280976288413af3d052352c40e09a0 Mon Sep 17 00:00:00 2001 From: girazoki Date: Tue, 9 Jan 2024 13:38:24 +0100 Subject: [PATCH 09/10] =?UTF-8?q?add=20weights=C2=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foreign-asset-creator/src/benchmarks.rs | 2 - pallets/foreign-asset-creator/src/weights.rs | 186 ++++++++++++++++++ 2 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 pallets/foreign-asset-creator/src/weights.rs diff --git a/pallets/foreign-asset-creator/src/benchmarks.rs b/pallets/foreign-asset-creator/src/benchmarks.rs index 3633f64..b0eca76 100644 --- a/pallets/foreign-asset-creator/src/benchmarks.rs +++ b/pallets/foreign-asset-creator/src/benchmarks.rs @@ -18,9 +18,7 @@ use crate::{AssetId, Call, Config, Pallet}; use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; -use frame_support::traits::{Currency, Get}; use frame_system::RawOrigin; -use parity_scale_codec::HasCompact; use sp_arithmetic::traits::AtLeast16BitUnsigned; use staging_xcm::latest::prelude::*; benchmarks! { 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)) + } +} From 95166933834db894dfa9f14f9b2436746277eb86 Mon Sep 17 00:00:00 2001 From: girazoki Date: Tue, 9 Jan 2024 14:22:04 +0100 Subject: [PATCH 10/10] fix tests --- pallets/foreign-asset-creator/src/mock.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/foreign-asset-creator/src/mock.rs b/pallets/foreign-asset-creator/src/mock.rs index 7b8041c..e944b27 100644 --- a/pallets/foreign-asset-creator/src/mock.rs +++ b/pallets/foreign-asset-creator/src/mock.rs @@ -133,6 +133,7 @@ impl Config for Test { type ForeignAssetModifierOrigin = EnsureRoot; type ForeignAssetDestroyerOrigin = EnsureRoot; type Fungibles = Assets; + type WeightInfo = (); } pub(crate) struct ExtBuilder {