diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs index 35a660ed3c40..12306e633466 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs @@ -14,18 +14,17 @@ // limitations under the License. use crate::*; -use frame_support::BoundedVec; use parachains_common::rococo::currency::EXISTENTIAL_DEPOSIT; use rococo_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub; use sp_runtime::ModuleError; #[test] fn swap_locally_on_chain_using_local_assets() { - let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocation::get()); - let asset_one = Box::new(MultiLocation { + let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocation::get(); + let asset_one = MultiLocation { parents: 0, interior: X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), - }); + }; AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; @@ -47,8 +46,8 @@ fn swap_locally_on_chain_using_local_assets() { assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - asset_native.clone(), - asset_one.clone(), + Box::new(asset_native), + Box::new(asset_one), )); assert_expected_events!( @@ -60,8 +59,8 @@ fn swap_locally_on_chain_using_local_assets() { assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - asset_native.clone(), - asset_one.clone(), + Box::new(asset_native), + Box::new(asset_one), 1_000_000_000_000, 2_000_000_000_000, 0, @@ -76,7 +75,7 @@ fn swap_locally_on_chain_using_local_assets() { ] ); - let path = BoundedVec::<_, _>::truncate_from(vec![asset_native.clone(), asset_one.clone()]); + let path = vec![Box::new(asset_native), Box::new(asset_one)]; assert_ok!( ::AssetConversion::swap_exact_tokens_for_tokens( @@ -101,8 +100,8 @@ fn swap_locally_on_chain_using_local_assets() { assert_ok!(::AssetConversion::remove_liquidity( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - asset_native, - asset_one, + Box::new(asset_native), + Box::new(asset_one), 1414213562273 - EXISTENTIAL_DEPOSIT * 2, // all but the 2 EDs can't be retrieved. 0, 0, @@ -113,7 +112,7 @@ fn swap_locally_on_chain_using_local_assets() { #[test] fn swap_locally_on_chain_using_foreign_assets() { - let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocation::get()); + let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocation::get(); let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubRococo::para_id()); let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); let asset_id_on_penpal = match asset_location_on_penpal.last() { @@ -165,12 +164,11 @@ fn swap_locally_on_chain_using_foreign_assets() { ] ); - let foreign_asset_at_asset_hub_rococo = Box::new(foreign_asset_at_asset_hub_rococo); // 4. Create pool: assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - asset_native.clone(), - foreign_asset_at_asset_hub_rococo.clone(), + Box::new(asset_native), + Box::new(foreign_asset_at_asset_hub_rococo), )); assert_expected_events!( @@ -183,8 +181,8 @@ fn swap_locally_on_chain_using_foreign_assets() { // 5. Add liquidity: assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), - asset_native.clone(), - foreign_asset_at_asset_hub_rococo.clone(), + Box::new(asset_native), + Box::new(foreign_asset_at_asset_hub_rococo), 1_000_000_000_000, 2_000_000_000_000, 0, @@ -202,10 +200,7 @@ fn swap_locally_on_chain_using_foreign_assets() { ); // 6. Swap! - let path = BoundedVec::<_, _>::truncate_from(vec![ - asset_native.clone(), - foreign_asset_at_asset_hub_rococo.clone(), - ]); + let path = vec![Box::new(asset_native), Box::new(foreign_asset_at_asset_hub_rococo)]; assert_ok!( ::AssetConversion::swap_exact_tokens_for_tokens( @@ -231,8 +226,8 @@ fn swap_locally_on_chain_using_foreign_assets() { // 7. Remove liquidity assert_ok!(::AssetConversion::remove_liquidity( ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), - asset_native, - foreign_asset_at_asset_hub_rococo, + Box::new(asset_native), + Box::new(foreign_asset_at_asset_hub_rococo), 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. 0, 0, @@ -243,7 +238,7 @@ fn swap_locally_on_chain_using_foreign_assets() { #[test] fn cannot_create_pool_from_pool_assets() { - let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocation::get()); + let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocation::get(); let mut asset_one = asset_hub_rococo_runtime::xcm_config::PoolAssetsPalletLocation::get(); asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets"); @@ -268,7 +263,7 @@ fn cannot_create_pool_from_pool_assets() { assert_matches::assert_matches!( ::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - asset_native.clone(), + Box::new(asset_native), Box::new(asset_one), ), Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("UnsupportedAsset")) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs index 1fa77bd45654..46a9b4252e13 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs @@ -18,11 +18,11 @@ use westend_system_emulated_network::penpal_emulated_chain::LocalTeleportableToA #[test] fn swap_locally_on_chain_using_local_assets() { - let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocation::get()); - let asset_one = Box::new(MultiLocation { + let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocation::get(); + let asset_one = MultiLocation { parents: 0, interior: X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), - }); + }; AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; @@ -44,8 +44,8 @@ fn swap_locally_on_chain_using_local_assets() { assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native.clone(), - asset_one.clone(), + Box::new(asset_native), + Box::new(asset_one), )); assert_expected_events!( @@ -57,8 +57,8 @@ fn swap_locally_on_chain_using_local_assets() { assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native.clone(), - asset_one.clone(), + Box::new(asset_native), + Box::new(asset_one), 1_000_000_000_000, 2_000_000_000_000, 0, @@ -73,7 +73,7 @@ fn swap_locally_on_chain_using_local_assets() { ] ); - let path = BoundedVec::<_, _>::truncate_from(vec![asset_native.clone(), asset_one.clone()]); + let path = vec![Box::new(asset_native), Box::new(asset_one)]; assert_ok!(::AssetConversion::swap_exact_tokens_for_tokens( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), @@ -96,8 +96,8 @@ fn swap_locally_on_chain_using_local_assets() { assert_ok!(::AssetConversion::remove_liquidity( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native, - asset_one, + Box::new(asset_native), + Box::new(asset_one), 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. 0, 0, @@ -108,7 +108,7 @@ fn swap_locally_on_chain_using_local_assets() { #[test] fn swap_locally_on_chain_using_foreign_assets() { - let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocation::get()); + let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocation::get(); let ah_as_seen_by_penpal = PenpalB::sibling_location_of(AssetHubWestend::para_id()); let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); let asset_id_on_penpal = match asset_location_on_penpal.last() { @@ -160,12 +160,11 @@ fn swap_locally_on_chain_using_foreign_assets() { ] ); - let foreign_asset_at_asset_hub_westend = Box::new(foreign_asset_at_asset_hub_westend); // 4. Create pool: assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native.clone(), - foreign_asset_at_asset_hub_westend.clone(), + Box::new(asset_native), + Box::new(foreign_asset_at_asset_hub_westend), )); assert_expected_events!( @@ -178,8 +177,8 @@ fn swap_locally_on_chain_using_foreign_assets() { // 5. Add liquidity: assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(sov_penpal_on_ahw.clone()), - asset_native.clone(), - foreign_asset_at_asset_hub_westend.clone(), + Box::new(asset_native), + Box::new(foreign_asset_at_asset_hub_westend), 1_000_000_000_000, 2_000_000_000_000, 0, @@ -197,10 +196,7 @@ fn swap_locally_on_chain_using_foreign_assets() { ); // 6. Swap! - let path = BoundedVec::<_, _>::truncate_from(vec![ - asset_native.clone(), - foreign_asset_at_asset_hub_westend.clone(), - ]); + let path = vec![Box::new(asset_native), Box::new(foreign_asset_at_asset_hub_westend)]; assert_ok!(::AssetConversion::swap_exact_tokens_for_tokens( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), @@ -224,19 +220,19 @@ fn swap_locally_on_chain_using_foreign_assets() { // 7. Remove liquidity assert_ok!(::AssetConversion::remove_liquidity( ::RuntimeOrigin::signed(sov_penpal_on_ahw.clone()), - asset_native, - foreign_asset_at_asset_hub_westend, + Box::new(asset_native), + Box::new(foreign_asset_at_asset_hub_westend), 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. 0, 0, - sov_penpal_on_ahw.clone().into(), + sov_penpal_on_ahw.into(), )); }); } #[test] fn cannot_create_pool_from_pool_assets() { - let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocation::get()); + let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocation::get(); let mut asset_one = asset_hub_westend_runtime::xcm_config::PoolAssetsPalletLocation::get(); asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets"); @@ -261,7 +257,7 @@ fn cannot_create_pool_from_pool_assets() { assert_matches::assert_matches!( ::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native.clone(), + Box::new(asset_native), Box::new(asset_one), ), Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("UnsupportedAsset")) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs new file mode 100644 index 000000000000..a1ee0e4df71c --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs @@ -0,0 +1,1480 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Asset Hub Kusama Runtime +//! +//! Asset Hub Kusama, formerly known as "Statemine", is the canary network for its Polkadot cousin. + +#![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "256"] + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +mod weights; +pub mod xcm_config; + +use assets_common::{ + foreign_creators::ForeignCreators, + local_and_foreign_assets::{LocalAndForeignAssets, MultiLocationConverter}, + matching::FromSiblingParachain, + AssetIdForTrustBackedAssetsConvert, MultiLocationForAssetId, +}; +use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_primitives_core::ParaId; +use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; +use sp_api::impl_runtime_apis; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_runtime::{ + create_runtime_str, generic, impl_opaque_keys, + traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, Verify}, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, Permill, +}; + +use sp_std::prelude::*; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + construct_runtime, + dispatch::DispatchClass, + genesis_builder_helper::{build_config, create_default_config}, + ord_parameter_types, parameter_types, + traits::{ + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, + InstanceFilter, + }, + weights::{ConstantMultiplier, Weight}, + BoundedVec, PalletId, +}; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureRoot, EnsureSigned, EnsureSignedBy, +}; +use pallet_asset_conversion_tx_payment::AssetConversionAdapter; +use pallet_nfts::PalletFeatures; +pub use parachains_common as common; +use parachains_common::{ + impls::DealWithFees, + kusama::{consensus::*, currency::*, fee::WeightToFee}, + AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, BlockNumber, Hash, Header, Nonce, + Signature, AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, + NORMAL_DISPATCH_RATIO, SLOT_DURATION, +}; +use sp_runtime::RuntimeDebug; +use xcm::opaque::v3::MultiLocation; +use xcm_config::{ + FellowshipLocation, ForeignAssetsConvertedConcreteId, GovernanceLocation, KsmLocation, + PoolAssetsConvertedConcreteId, TrustBackedAssetsConvertedConcreteId, XcmConfig, +}; + +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; + +// Polkadot imports +use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; +use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use xcm::latest::prelude::*; +use xcm_executor::XcmExecutor; + +use crate::xcm_config::{ + ForeignCreatorsSovereignAccountOf, LocalAndForeignAssetsMultiLocationMatcher, + TrustBackedAssetsPalletLocation, +}; +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; + +impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + } +} + +#[cfg(feature = "state-trie-version-1")] +#[sp_version::runtime_version] +pub const VERSION: RuntimeVersion = RuntimeVersion { + // Note: "statemine" is the legacy name for this chain. It has been renamed to + // "asset-hub-kusama". Many wallets/tools depend on the `spec_name`, so it remains "statemine" + // for the time being. Wallets/tools should update to treat "asset-hub-kusama" equally. + spec_name: create_runtime_str!("statemine"), + impl_name: create_runtime_str!("statemine"), + authoring_version: 1, + spec_version: 10000, + impl_version: 0, + apis: RUNTIME_API_VERSIONS, + transaction_version: 13, + state_version: 1, +}; + +#[cfg(not(feature = "state-trie-version-1"))] +#[sp_version::runtime_version] +pub const VERSION: RuntimeVersion = RuntimeVersion { + // Note: "statemine" is the legacy name for this change. It has been renamed to + // "asset-hub-kusama". Many wallets/tools depend on the `spec_name`, so it remains "statemine" + // for the time being. Wallets/tools should update to treat "asset-hub-kusama" equally. + spec_name: create_runtime_str!("statemine"), + impl_name: create_runtime_str!("statemine"), + authoring_version: 1, + spec_version: 10000, + impl_version: 0, + apis: RUNTIME_API_VERSIONS, + transaction_version: 13, + state_version: 0, +}; + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } +} + +parameter_types! { + pub const Version: RuntimeVersion = VERSION; + pub RuntimeBlockLength: BlockLength = + BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have some extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); + pub const SS58Prefix: u8 = 2; +} + +// Configure FRAME pallets to include in runtime. +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = RuntimeBlockWeights; + type BlockLength = RuntimeBlockLength; + type AccountId = AccountId; + type RuntimeCall = RuntimeCall; + type Lookup = AccountIdLookup; + type Nonce = Nonce; + type Hash = Hash; + type Hashing = BlakeTwo256; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type BlockHashCount = BlockHashCount; + type DbWeight = RocksDbWeight; + type Version = Version; + type PalletInfo = PalletInfo; + type OnNewAccount = (); + type OnKilledAccount = (); + type AccountData = pallet_balances::AccountData; + type SystemWeightInfo = weights::frame_system::WeightInfo; + type SS58Prefix = SS58Prefix; + type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = Aura; + type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; + type WeightInfo = weights::pallet_timestamp::WeightInfo; +} + +impl pallet_authorship::Config for Runtime { + type FindAuthor = pallet_session::FindAccountFromAuthorIndex; + type EventHandler = (CollatorSelection,); +} + +parameter_types! { + pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = ConstU32<50>; + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = weights::pallet_balances::WeightInfo; + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + // We allow each account to have holds on it from: + // - `NftFractionalization`: 1 + type MaxHolds = ConstU32<1>; + type MaxFreezes = ConstU32<0>; +} + +parameter_types! { + /// Relay Chain `TransactionByteFee` / 10 + pub const TransactionByteFee: Balance = MILLICENTS; +} + +impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = + pallet_transaction_payment::CurrencyAdapter>; + type WeightToFee = WeightToFee; + type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; + type OperationalFeeMultiplier = ConstU8<5>; +} + +parameter_types! { + pub const AssetDeposit: Balance = UNITS / 10; // 1 / 10 UNITS deposit to create asset + pub const AssetAccountDeposit: Balance = deposit(1, 16); + pub const ApprovalDeposit: Balance = EXISTENTIAL_DEPOSIT; + pub const AssetsStringLimit: u32 = 50; + /// Key = 32 bytes, Value = 36 bytes (32+1+1+1+1) + // https://github.com/paritytech/substrate/blob/069917b/frame/assets/src/lib.rs#L257L271 + pub const MetadataDepositBase: Balance = deposit(1, 68); + pub const MetadataDepositPerByte: Balance = deposit(0, 1); +} + +/// We allow root to execute privileged asset operations. +pub type AssetsForceOrigin = EnsureRoot; + +// Called "Trust Backed" assets because these are generally registered by some account, and users of +// the asset assume it has some claimed backing. The pallet is called `Assets` in +// `construct_runtime` to avoid breaking changes on storage reads. +pub type TrustBackedAssetsInstance = pallet_assets::Instance1; +type TrustBackedAssetsCall = pallet_assets::Call; +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetIdForTrustBackedAssets; + type AssetIdParameter = codec::Compact; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = AssetsForceOrigin; + type AssetDeposit = AssetDeposit; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = AssetsStringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = weights::pallet_assets_local::WeightInfo; + type CallbackHandle = (); + type AssetAccountDeposit = AssetAccountDeposit; + type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +parameter_types! { + pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); + pub const AllowMultiAssetPools: bool = false; + // should be non-zero if AllowMultiAssetPools is true, otherwise can be zero + pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0); +} + +ord_parameter_types! { + pub const AssetConversionOrigin: sp_runtime::AccountId32 = + AccountIdConversion::::into_account_truncating(&AssetConversionPalletId::get()); +} + +pub type PoolAssetsInstance = pallet_assets::Instance3; +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type RemoveItemsLimit = ConstU32<1000>; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = + AsEnsureOriginWithArg>; + type ForceOrigin = AssetsForceOrigin; + // Deposits are zero because creation/admin is limited to Asset Conversion pallet. + type AssetDeposit = ConstU128<0>; + type AssetAccountDeposit = ConstU128<0>; + type MetadataDepositBase = ConstU128<0>; + type MetadataDepositPerByte = ConstU128<0>; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = ConstU32<50>; + type Freezer = (); + type Extra = (); + type WeightInfo = weights::pallet_assets_pool::WeightInfo; + type CallbackHandle = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +impl pallet_asset_conversion::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type HigherPrecisionBalance = sp_core::U256; + type Currency = Balances; + type AssetId = MultiLocation; + type Assets = LocalAndForeignAssets< + Assets, + AssetIdForTrustBackedAssetsConvert, + ForeignAssets, + >; + type PoolAssets = PoolAssets; + type PoolAssetId = u32; + type PoolSetupFee = ConstU128<0>; // Asset class deposit fees are sufficient to prevent spam + type PoolSetupFeeReceiver = AssetConversionOrigin; + // should be non-zero if `AllowMultiAssetPools` is true, otherwise can be zero. + type LiquidityWithdrawalFee = LiquidityWithdrawalFee; + type LPFee = ConstU32<3>; + type PalletId = AssetConversionPalletId; + type AllowMultiAssetPools = AllowMultiAssetPools; + type MaxSwapPathLength = ConstU32<4>; + type MultiAssetId = MultiLocation; + type MultiAssetIdConverter = + MultiLocationConverter; + type MintMinLiquidity = ConstU128<100>; + type WeightInfo = weights::pallet_asset_conversion::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = + crate::xcm_config::BenchmarkMultiLocationConverter>; +} + +parameter_types! { + // we just reuse the same deposits + pub const ForeignAssetsAssetDeposit: Balance = AssetDeposit::get(); + pub const ForeignAssetsAssetAccountDeposit: Balance = AssetAccountDeposit::get(); + pub const ForeignAssetsApprovalDeposit: Balance = ApprovalDeposit::get(); + pub const ForeignAssetsAssetsStringLimit: u32 = AssetsStringLimit::get(); + pub const ForeignAssetsMetadataDepositBase: Balance = MetadataDepositBase::get(); + pub const ForeignAssetsMetadataDepositPerByte: Balance = MetadataDepositPerByte::get(); +} + +/// Assets managed by some foreign location. Note: we do not declare a `ForeignAssetsCall` type, as +/// this type is used in proxy definitions. We assume that a foreign location would not want to set +/// an individual, local account as a proxy for the issuance of their assets. This issuance should +/// be managed by the foreign location's governance. +pub type ForeignAssetsInstance = pallet_assets::Instance2; +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = MultiLocationForAssetId; + type AssetIdParameter = MultiLocationForAssetId; + type Currency = Balances; + type CreateOrigin = ForeignCreators< + (FromSiblingParachain>,), + ForeignCreatorsSovereignAccountOf, + AccountId, + >; + type ForceOrigin = AssetsForceOrigin; + type AssetDeposit = ForeignAssetsAssetDeposit; + type MetadataDepositBase = ForeignAssetsMetadataDepositBase; + type MetadataDepositPerByte = ForeignAssetsMetadataDepositPerByte; + type ApprovalDeposit = ForeignAssetsApprovalDeposit; + type StringLimit = ForeignAssetsAssetsStringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = weights::pallet_assets_foreign::WeightInfo; + type CallbackHandle = (); + type AssetAccountDeposit = ForeignAssetsAssetAccountDeposit; + type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = xcm_config::XcmBenchmarkHelper; +} + +parameter_types! { + // One storage item; key size is 32; value is size 4+4+16+32 bytes = 56 bytes. + pub const DepositBase: Balance = deposit(1, 88); + // Additional storage item size of 32 bytes. + pub const DepositFactor: Balance = deposit(0, 32); + pub const MaxSignatories: u32 = 100; +} + +impl pallet_multisig::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type DepositBase = DepositBase; + type DepositFactor = DepositFactor; + type MaxSignatories = MaxSignatories; + type WeightInfo = weights::pallet_multisig::WeightInfo; +} + +impl pallet_utility::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = weights::pallet_utility::WeightInfo; +} + +parameter_types! { + // One storage item; key size 32, value size 8; . + pub const ProxyDepositBase: Balance = deposit(1, 40); + // Additional storage item size of 33 bytes. + pub const ProxyDepositFactor: Balance = deposit(0, 33); + pub const MaxProxies: u16 = 32; + // One storage item; key size 32, value size 16 + pub const AnnouncementDepositBase: Balance = deposit(1, 48); + pub const AnnouncementDepositFactor: Balance = deposit(0, 66); + pub const MaxPending: u16 = 32; +} + +/// The type used to represent the kinds of proxying allowed. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + RuntimeDebug, + MaxEncodedLen, + scale_info::TypeInfo, +)] +pub enum ProxyType { + /// Fully permissioned proxy. Can execute any call on behalf of _proxied_. + Any, + /// Can execute any call that does not transfer funds or assets. + NonTransfer, + /// Proxy with the ability to reject time-delay proxy announcements. + CancelProxy, + /// Assets proxy. Can execute any call from `assets`, **including asset transfers**. + Assets, + /// Owner proxy. Can execute calls related to asset ownership. + AssetOwner, + /// Asset manager. Can execute calls related to asset management. + AssetManager, + /// Collator selection proxy. Can execute calls related to collator selection mechanism. + Collator, +} +impl Default for ProxyType { + fn default() -> Self { + Self::Any + } +} + +impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { + match self { + ProxyType::Any => true, + ProxyType::NonTransfer => !matches!( + c, + RuntimeCall::Balances { .. } | + RuntimeCall::Assets { .. } | + RuntimeCall::NftFractionalization { .. } | + RuntimeCall::Nfts { .. } | + RuntimeCall::Uniques { .. } + ), + ProxyType::CancelProxy => matches!( + c, + RuntimeCall::Proxy(pallet_proxy::Call::reject_announcement { .. }) | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ), + ProxyType::Assets => { + matches!( + c, + RuntimeCall::Assets { .. } | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } | + RuntimeCall::NftFractionalization { .. } | + RuntimeCall::Nfts { .. } | RuntimeCall::Uniques { .. } + ) + }, + ProxyType::AssetOwner => matches!( + c, + RuntimeCall::Assets(TrustBackedAssetsCall::create { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::start_destroy { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::destroy_accounts { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::destroy_approvals { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::finish_destroy { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::transfer_ownership { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::set_team { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::set_metadata { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::clear_metadata { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::set_min_balance { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::create { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::destroy { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::redeposit { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::transfer_ownership { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::set_team { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::set_collection_max_supply { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::lock_collection { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::create { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::destroy { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::transfer_ownership { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::set_team { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::set_metadata { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::set_attribute { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::set_collection_metadata { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::clear_metadata { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::clear_attribute { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::clear_collection_metadata { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::set_collection_max_supply { .. }) | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ), + ProxyType::AssetManager => matches!( + c, + RuntimeCall::Assets(TrustBackedAssetsCall::mint { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::burn { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::freeze { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::block { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::thaw { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::freeze_asset { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::thaw_asset { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::touch_other { .. }) | + RuntimeCall::Assets(TrustBackedAssetsCall::refund_other { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::force_mint { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::update_mint_settings { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::mint_pre_signed { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::set_attributes_pre_signed { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::lock_item_transfer { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::unlock_item_transfer { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::lock_item_properties { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::set_metadata { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::clear_metadata { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::set_collection_metadata { .. }) | + RuntimeCall::Nfts(pallet_nfts::Call::clear_collection_metadata { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::mint { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::burn { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::freeze { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::thaw { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::freeze_collection { .. }) | + RuntimeCall::Uniques(pallet_uniques::Call::thaw_collection { .. }) | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ), + ProxyType::Collator => matches!( + c, + RuntimeCall::CollatorSelection { .. } | + RuntimeCall::Utility { .. } | + RuntimeCall::Multisig { .. } + ), + } + } + + fn is_superset(&self, o: &Self) -> bool { + match (self, o) { + (x, y) if x == y => true, + (ProxyType::Any, _) => true, + (_, ProxyType::Any) => false, + (ProxyType::Assets, ProxyType::AssetOwner) => true, + (ProxyType::Assets, ProxyType::AssetManager) => true, + (ProxyType::NonTransfer, ProxyType::Collator) => true, + _ => false, + } + } +} + +impl pallet_proxy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ProxyDepositBase; + type ProxyDepositFactor = ProxyDepositFactor; + type MaxProxies = MaxProxies; + type WeightInfo = weights::pallet_proxy::WeightInfo; + type MaxPending = MaxPending; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = AnnouncementDepositBase; + type AnnouncementDepositFactor = AnnouncementDepositFactor; +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); +} + +impl cumulus_pallet_parachain_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnSystemEvent = (); + type SelfParaId = parachain_info::Pallet; + type DmpMessageHandler = DmpQueue; + type ReservedDmpWeight = ReservedDmpWeight; + type OutboundXcmpMessageSource = XcmpQueue; + type XcmpMessageHandler = XcmpQueue; + type ReservedXcmpWeight = ReservedXcmpWeight; + type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; + type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, + >; +} + +impl parachain_info::Config for Runtime {} + +impl cumulus_pallet_aura_ext::Config for Runtime {} + +parameter_types! { + // Fellows pluralistic body. + pub const FellowsBodyId: BodyId = BodyId::Technical; +} + +impl cumulus_pallet_xcmp_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; + type ChannelInfo = ParachainSystem; + type VersionWrapper = PolkadotXcm; + type ExecuteOverweightOrigin = EnsureRoot; + type ControllerOrigin = EitherOfDiverse< + EnsureRoot, + EnsureXcm>, + >; + type ControllerOriginConverter = xcm_config::XcmOriginToTransactDispatchOrigin; + type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; + type PriceForSiblingDelivery = NoPriceForMessageDelivery; +} + +impl cumulus_pallet_dmp_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; + type ExecuteOverweightOrigin = EnsureRoot; +} + +parameter_types! { + pub const Period: u32 = 6 * HOURS; + pub const Offset: u32 = 0; +} + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = ::AccountId; + // we don't have stash and controller, thus we don't need the convert as well. + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionManager = CollatorSelection; + // Essentially just Aura, but let's be pedantic. + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + type WeightInfo = weights::pallet_session::WeightInfo; +} + +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = ConstU32<100_000>; + type AllowMultipleBlocksPerSlot = ConstBool; + #[cfg(feature = "experimental")] + type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"PotStake"); + pub const SessionLength: BlockNumber = 6 * HOURS; + // StakingAdmin pluralistic body. + pub const StakingAdminBodyId: BodyId = BodyId::Defense; +} + +/// We allow root and the `StakingAdmin` to execute privileged collator selection operations. +pub type CollatorSelectionUpdateOrigin = EitherOfDiverse< + EnsureRoot, + EnsureXcm>, +>; + +impl pallet_collator_selection::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type UpdateOrigin = CollatorSelectionUpdateOrigin; + type PotId = PotId; + type MaxCandidates = ConstU32<100>; + type MinEligibleCollators = ConstU32<4>; + type MaxInvulnerables = ConstU32<20>; + // should be a multiple of session or things will get inconsistent + type KickThreshold = Period; + type ValidatorId = ::AccountId; + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ValidatorRegistration = Session; + type WeightInfo = weights::pallet_collator_selection::WeightInfo; +} + +impl pallet_asset_conversion_tx_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Fungibles = LocalAndForeignAssets< + Assets, + AssetIdForTrustBackedAssetsConvert, + ForeignAssets, + >; + type OnChargeAssetTransaction = AssetConversionAdapter; +} + +parameter_types! { + pub const UniquesCollectionDeposit: Balance = UNITS / 10; // 1 / 10 UNIT deposit to create a collection + pub const UniquesItemDeposit: Balance = UNITS / 1_000; // 1 / 1000 UNIT deposit to mint an item + pub const UniquesMetadataDepositBase: Balance = deposit(1, 129); + pub const UniquesAttributeDepositBase: Balance = deposit(1, 0); + pub const UniquesDepositPerByte: Balance = deposit(0, 1); +} + +impl pallet_uniques::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type ForceOrigin = AssetsForceOrigin; + type CollectionDeposit = UniquesCollectionDeposit; + type ItemDeposit = UniquesItemDeposit; + type MetadataDepositBase = UniquesMetadataDepositBase; + type AttributeDepositBase = UniquesAttributeDepositBase; + type DepositPerByte = UniquesDepositPerByte; + type StringLimit = ConstU32<128>; + type KeyLimit = ConstU32<32>; + type ValueLimit = ConstU32<64>; + type WeightInfo = weights::pallet_uniques::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type CreateOrigin = AsEnsureOriginWithArg>; + type Locker = (); +} + +parameter_types! { + pub const NftFractionalizationPalletId: PalletId = PalletId(*b"fraction"); + pub NewAssetSymbol: BoundedVec = (*b"FRAC").to_vec().try_into().unwrap(); + pub NewAssetName: BoundedVec = (*b"Frac").to_vec().try_into().unwrap(); +} + +impl pallet_nft_fractionalization::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Deposit = AssetDeposit; + type Currency = Balances; + type NewAssetSymbol = NewAssetSymbol; + type NewAssetName = NewAssetName; + type StringLimit = AssetsStringLimit; + type NftCollectionId = ::CollectionId; + type NftId = ::ItemId; + type AssetBalance = ::Balance; + type AssetId = >::AssetId; + type Assets = Assets; + type Nfts = Nfts; + type PalletId = NftFractionalizationPalletId; + type WeightInfo = pallet_nft_fractionalization::weights::SubstrateWeight; + type RuntimeHoldReason = RuntimeHoldReason; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +parameter_types! { + pub NftsPalletFeatures: PalletFeatures = PalletFeatures::all_enabled(); + pub const NftsMaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; + // re-use the Uniques deposits + pub const NftsCollectionDeposit: Balance = UniquesCollectionDeposit::get(); + pub const NftsItemDeposit: Balance = UniquesItemDeposit::get(); + pub const NftsMetadataDepositBase: Balance = UniquesMetadataDepositBase::get(); + pub const NftsAttributeDepositBase: Balance = UniquesAttributeDepositBase::get(); + pub const NftsDepositPerByte: Balance = UniquesDepositPerByte::get(); +} + +impl pallet_nfts::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = AssetsForceOrigin; + type Locker = (); + type CollectionDeposit = NftsCollectionDeposit; + type ItemDeposit = NftsItemDeposit; + type MetadataDepositBase = NftsMetadataDepositBase; + type AttributeDepositBase = NftsAttributeDepositBase; + type DepositPerByte = NftsDepositPerByte; + type StringLimit = ConstU32<256>; + type KeyLimit = ConstU32<64>; + type ValueLimit = ConstU32<256>; + type ApprovalsLimit = ConstU32<20>; + type ItemAttributesApprovalsLimit = ConstU32<30>; + type MaxTips = ConstU32<10>; + type MaxDeadlineDuration = NftsMaxDeadlineDuration; + type MaxAttributesPerCall = ConstU32<10>; + type Features = NftsPalletFeatures; + type OffchainSignature = Signature; + type OffchainPublic = ::Signer; + type WeightInfo = weights::pallet_nfts::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); +} + +// Create the runtime by composing the FRAME pallets that were previously configured. +construct_runtime!( + pub enum Runtime + { + // System support stuff. + System: frame_system::{Pallet, Call, Config, Storage, Event} = 0, + ParachainSystem: cumulus_pallet_parachain_system::{ + Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned, + } = 1, + // RandomnessCollectiveFlip = 2 removed + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 3, + ParachainInfo: parachain_info::{Pallet, Storage, Config} = 4, + + // Monetary stuff. + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, + AssetTxPayment: pallet_asset_conversion_tx_payment::{Pallet, Event} = 13, + + // Collator support. the order of these 5 are important and shall not change. + Authorship: pallet_authorship::{Pallet, Storage} = 20, + CollatorSelection: pallet_collator_selection::{Pallet, Call, Storage, Event, Config} = 21, + Session: pallet_session::{Pallet, Call, Storage, Event, Config} = 22, + Aura: pallet_aura::{Pallet, Storage, Config} = 23, + AuraExt: cumulus_pallet_aura_ext::{Pallet, Storage, Config} = 24, + + // XCM helpers. + XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 30, + PolkadotXcm: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 31, + CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 32, + DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, + + // Handy utilities. + Utility: pallet_utility::{Pallet, Call, Event} = 40, + Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 41, + Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 42, + + // The main stage. + Assets: pallet_assets::::{Pallet, Call, Storage, Event} = 50, + Uniques: pallet_uniques::{Pallet, Call, Storage, Event} = 51, + Nfts: pallet_nfts::{Pallet, Call, Storage, Event} = 52, + ForeignAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 53, + NftFractionalization: pallet_nft_fractionalization::{Pallet, Call, Storage, Event, HoldReason} = 54, + + PoolAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 55, + AssetConversion: pallet_asset_conversion::{Pallet, Call, Storage, Event} = 56, + + #[cfg(feature = "state-trie-version-1")] + StateTrieMigration: pallet_state_trie_migration = 70, + } +); + +/// The address format for describing accounts. +pub type Address = sp_runtime::MultiAddress; +/// Block type as expected by this runtime. +pub type Block = generic::Block; +/// A Block signed with a Justification +pub type SignedBlock = generic::SignedBlock; +/// BlockId type as expected by this runtime. +pub type BlockId = generic::BlockId; +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, +); +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +/// Migrations to apply on runtime upgrade. +pub type Migrations = (pallet_collator_selection::migration::v1::MigrateToV1,); + +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + Migrations, +>; + +#[cfg(feature = "runtime-benchmarks")] +#[macro_use] +extern crate frame_benchmarking; + +#[cfg(feature = "runtime-benchmarks")] +mod benches { + define_benchmarks!( + [frame_system, SystemBench::] + [pallet_assets, Local] + [pallet_assets, Foreign] + [pallet_assets, Pool] + [pallet_asset_conversion, AssetConversion] + [pallet_balances, Balances] + [pallet_multisig, Multisig] + [pallet_nft_fractionalization, NftFractionalization] + [pallet_nfts, Nfts] + [pallet_proxy, Proxy] + [pallet_session, SessionBench::] + [pallet_uniques, Uniques] + [pallet_utility, Utility] + [pallet_timestamp, Timestamp] + [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_xcmp_queue, XcmpQueue] + // XCM + [pallet_xcm, PolkadotXcm] + // NOTE: Make sure you point to the individual modules below. + [pallet_xcm_benchmarks::fungible, XcmBalances] + [pallet_xcm_benchmarks::generic, XcmGeneric] + ); +} + +impl_runtime_apis! { + impl sp_consensus_aura::AuraApi for Runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + } + + fn authorities() -> Vec { + Aura::authorities().into_inner() + } + } + + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> sp_std::vec::Vec { + Runtime::metadata_versions() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { + System::account_nonce(account) + } + } + + impl pallet_asset_conversion::AssetConversionApi< + Block, + Balance, + MultiLocation, + > for Runtime + { + fn quote_price_exact_tokens_for_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option { + AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee) + } + fn quote_price_tokens_for_exact_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option { + AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee) + } + fn get_reserves(asset1: MultiLocation, asset2: MultiLocation) -> Option<(Balance, Balance)> { + AssetConversion::get_reserves(&asset1, &asset2).ok() + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for Runtime + { + fn query_call_info( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::RuntimeDispatchInfo { + TransactionPayment::query_call_info(call, len) + } + fn query_call_fee_details( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_call_fee_details(call, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl assets_common::runtime_api::FungiblesApi< + Block, + AccountId, + > for Runtime + { + fn query_account_balances(account: AccountId) -> Result { + use assets_common::fungible_conversion::{convert, convert_balance}; + Ok([ + // collect pallet_balance + { + let balance = Balances::free_balance(account.clone()); + if balance > 0 { + vec![convert_balance::(balance)?] + } else { + vec![] + } + }, + // collect pallet_assets (TrustBackedAssets) + convert::<_, _, _, _, TrustBackedAssetsConvertedConcreteId>( + Assets::account_balances(account.clone()) + .iter() + .filter(|(_, balance)| balance > &0) + )?, + // collect pallet_assets (ForeignAssets) + convert::<_, _, _, _, ForeignAssetsConvertedConcreteId>( + ForeignAssets::account_balances(account.clone()) + .iter() + .filter(|(_, balance)| balance > &0) + )?, + // collect pallet_assets (PoolAssets) + convert::<_, _, _, _, PoolAssetsConvertedConcreteId>( + PoolAssets::account_balances(account) + .iter() + .filter(|(_, balance)| balance > &0) + )?, + // collect ... e.g. other tokens + ].concat().into()) + } + } + + impl cumulus_primitives_core::CollectCollationInfo for Runtime { + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { + let weight = Executive::try_runtime_upgrade(checks).unwrap(); + (weight, RuntimeBlockWeights::get().max_block) + } + + fn execute_block( + block: Block, + state_root_check: bool, + signature_check: bool, + select: frame_try_runtime::TryStateSelect, + ) -> Weight { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. + Executive::try_execute_block(block, state_root_check, signature_check, select).unwrap() + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(extra: bool) -> ( + Vec, + Vec, + ) { + use frame_benchmarking::{Benchmarking, BenchmarkList}; + use frame_support::traits::StorageInfoTrait; + use frame_system_benchmarking::Pallet as SystemBench; + use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + + // This is defined once again in dispatch_benchmark, because list_benchmarks! + // and add_benchmarks! are macros exported by define_benchmarks! macros and those types + // are referenced in that call. + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + + // Benchmark files generated for `Assets/ForeignAssets` instances are by default + // `pallet_assets_assets.rs / pallet_assets_foreign_assets`, which is not really nice, + // so with this redefinition we can change names to nicer: + // `pallet_assets_local.rs / pallet_assets_foreign.rs`. + type Local = pallet_assets::Pallet::; + type Foreign = pallet_assets::Pallet::; + type Pool = pallet_assets::Pallet::; + + let mut list = Vec::::new(); + list_benchmarks!(list, extra); + + let storage_info = AllPalletsWithSystem::storage_info(); + (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{Benchmarking, BenchmarkBatch, BenchmarkError}; + use sp_storage::TrackedStorageKey; + + use frame_system_benchmarking::Pallet as SystemBench; + impl frame_system_benchmarking::Config for Runtime { + fn setup_set_code_requirements(code: &sp_std::vec::Vec) -> Result<(), BenchmarkError> { + ParachainSystem::initialize_for_set_code_benchmark(code.len() as u32); + Ok(()) + } + + fn verify_set_code() { + System::assert_last_event(cumulus_pallet_parachain_system::Event::::ValidationFunctionStored.into()); + } + } + + use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + impl cumulus_pallet_session_benchmarking::Config for Runtime {} + + use xcm::latest::prelude::*; + use xcm_config::{KsmLocation, MaxAssetsIntoHolding}; + use pallet_xcm_benchmarks::asset_instance_from; + + parameter_types! { + pub ExistentialDepositMultiAsset: Option = Some(( + KsmLocation::get(), + ExistentialDeposit::get() + ).into()); + } + + impl pallet_xcm_benchmarks::Config for Runtime { + type XcmConfig = xcm_config::XcmConfig; + type AccountIdConverter = xcm_config::LocationToAccountId; + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + XcmConfig, + ExistentialDepositMultiAsset, + xcm_config::PriceForParentDelivery, + >; + fn valid_destination() -> Result { + Ok(KsmLocation::get()) + } + fn worst_case_holding(depositable_count: u32) -> MultiAssets { + // A mix of fungible, non-fungible, and concrete assets. + let holding_non_fungibles = MaxAssetsIntoHolding::get() / 2 - depositable_count; + let holding_fungibles = holding_non_fungibles.saturating_sub(1); + let fungibles_amount: u128 = 100; + let mut assets = (0..holding_fungibles) + .map(|i| { + MultiAsset { + id: Concrete(GeneralIndex(i as u128).into()), + fun: Fungible(fungibles_amount * i as u128), + } + }) + .chain(core::iter::once(MultiAsset { id: Concrete(Here.into()), fun: Fungible(u128::MAX) })) + .chain((0..holding_non_fungibles).map(|i| MultiAsset { + id: Concrete(GeneralIndex(i as u128).into()), + fun: NonFungible(asset_instance_from(i)), + })) + .collect::>(); + + assets.push(MultiAsset { + id: Concrete(KsmLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }); + assets.into() + } + } + + parameter_types! { + pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( + KsmLocation::get(), + MultiAsset { fun: Fungible(UNITS), id: Concrete(KsmLocation::get()) }, + )); + pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; + pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; + } + + impl pallet_xcm_benchmarks::fungible::Config for Runtime { + type TransactAsset = Balances; + + type CheckedAccount = CheckedAccount; + type TrustedTeleporter = TrustedTeleporter; + type TrustedReserve = TrustedReserve; + + fn get_multi_asset() -> MultiAsset { + MultiAsset { + id: Concrete(KsmLocation::get()), + fun: Fungible(UNITS), + } + } + } + + impl pallet_xcm_benchmarks::generic::Config for Runtime { + type TransactAsset = Balances; + type RuntimeCall = RuntimeCall; + + fn worst_case_response() -> (u64, Response) { + (0u64, Response::Version(Default::default())) + } + + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + Ok((KsmLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + } + + fn subscribe_origin() -> Result { + Ok(KsmLocation::get()) + } + + fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { + let origin = KsmLocation::get(); + let assets: MultiAssets = (Concrete(KsmLocation::get()), 1_000 * UNITS).into(); + let ticket = MultiLocation { parents: 0, interior: Here }; + Ok((origin, ticket, assets)) + } + + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn export_message_origin_and_destination( + ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + } + + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + + type Local = pallet_assets::Pallet::; + type Foreign = pallet_assets::Pallet::; + type Pool = pallet_assets::Pallet::; + + let whitelist: Vec = vec![ + // Block Number + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), + // Total Issuance + hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), + // Execution Phase + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), + // Event Count + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), + // System Events + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), + //TODO: use from relay_well_known_keys::ACTIVE_CONFIG + hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385").to_vec().into(), + ]; + + let mut batches = Vec::::new(); + let params = (&config, &whitelist); + add_benchmarks!(params, batches); + + Ok(batches) + } + } + + impl sp_genesis_builder::GenesisBuilder for Runtime { + fn create_default_config() -> Vec { + create_default_config::() + } + + fn build_config(config: Vec) -> sp_genesis_builder::Result { + build_config::(config) + } + } +} + +cumulus_pallet_parachain_system::register_validate_block! { + Runtime = Runtime, + BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, +} + +#[cfg(feature = "state-trie-version-1")] +parameter_types! { + // The deposit configuration for the singed migration. Specially if you want to allow any signed account to do the migration (see `SignedFilter`, these deposits should be high) + pub const MigrationSignedDepositPerItem: Balance = CENTS; + pub const MigrationSignedDepositBase: Balance = 2_000 * CENTS; + pub const MigrationMaxKeyLen: u32 = 512; +} + +#[cfg(feature = "state-trie-version-1")] +impl pallet_state_trie_migration::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type SignedDepositPerItem = MigrationSignedDepositPerItem; + type SignedDepositBase = MigrationSignedDepositBase; + // An origin that can control the whole pallet: should be Root, or a part of your council. + type ControlOrigin = frame_system::EnsureSignedBy; + // specific account for the migration, can trigger the signed migrations. + type SignedFilter = frame_system::EnsureSignedBy; + + // Replace this with weight based on your runtime. + type WeightInfo = pallet_state_trie_migration::weights::SubstrateWeight; + + type MaxKeyLen = MigrationMaxKeyLen; +} + +#[cfg(feature = "state-trie-version-1")] +frame_support::ord_parameter_types! { + pub const MigController: AccountId = AccountId::from(hex_literal::hex!("8458ed39dc4b6f6c7255f7bc42be50c2967db126357c999d44e12ca7ac80dc52")); + pub const RootMigController: AccountId = AccountId::from(hex_literal::hex!("8458ed39dc4b6f6c7255f7bc42be50c2967db126357c999d44e12ca7ac80dc52")); +} + +#[cfg(feature = "state-trie-version-1")] +#[test] +fn ensure_key_ss58() { + use frame_support::traits::SortedMembers; + use sp_core::crypto::Ss58Codec; + let acc = + AccountId::from_ss58check("5F4EbSkZz18X36xhbsjvDNs6NuZ82HyYtq5UiJ1h9SBHJXZD").unwrap(); + //panic!("{:x?}", acc); + assert_eq!(acc, MigController::sorted_members()[0]); + let acc = + AccountId::from_ss58check("5F4EbSkZz18X36xhbsjvDNs6NuZ82HyYtq5UiJ1h9SBHJXZD").unwrap(); + assert_eq!(acc, RootMigController::sorted_members()[0]); + //panic!("{:x?}", acc); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{CENTS, MILLICENTS}; + use parachains_common::kusama::fee; + use sp_runtime::traits::Zero; + use sp_weights::WeightToFee; + + /// We can fit at least 1000 transfers in a block. + #[test] + fn sane_block_weight() { + use pallet_balances::WeightInfo; + let block = RuntimeBlockWeights::get().max_block; + let base = RuntimeBlockWeights::get().get(DispatchClass::Normal).base_extrinsic; + let transfer = + base + weights::pallet_balances::WeightInfo::::transfer_allow_death(); + + let fit = block.checked_div_per_component(&transfer).unwrap_or_default(); + assert!(fit >= 1000, "{} should be at least 1000", fit); + } + + /// The fee for one transfer is at most 1 CENT. + #[test] + fn sane_transfer_fee() { + use pallet_balances::WeightInfo; + let base = RuntimeBlockWeights::get().get(DispatchClass::Normal).base_extrinsic; + let transfer = + base + weights::pallet_balances::WeightInfo::::transfer_allow_death(); + + let fee: Balance = fee::WeightToFee::weight_to_fee(&transfer); + assert!(fee <= CENTS, "{} MILLICENTS should be at most 1000", fee / MILLICENTS); + } + + /// Weight is being charged for both dimensions. + #[test] + fn weight_charged_for_both_components() { + let fee: Balance = fee::WeightToFee::weight_to_fee(&Weight::from_parts(10_000, 0)); + assert!(!fee.is_zero(), "Charges for ref time"); + + let fee: Balance = fee::WeightToFee::weight_to_fee(&Weight::from_parts(0, 10_000)); + assert_eq!(fee, CENTS, "10kb maps to CENT"); + } + + /// Filling up a block by proof size is at most 30 times more expensive than ref time. + /// + /// This is just a sanity check. + #[test] + fn full_block_fee_ratio() { + let block = RuntimeBlockWeights::get().max_block; + let time_fee: Balance = + fee::WeightToFee::weight_to_fee(&Weight::from_parts(block.ref_time(), 0)); + let proof_fee: Balance = + fee::WeightToFee::weight_to_fee(&Weight::from_parts(0, block.proof_size())); + + let proof_o_time = proof_fee.checked_div(time_fee).unwrap_or_default(); + assert!(proof_o_time <= 30, "{} should be at most 30", proof_o_time); + let time_o_proof = time_fee.checked_div(proof_fee).unwrap_or_default(); + assert!(time_o_proof <= 30, "{} should be at most 30", time_o_proof); + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs new file mode 100644 index 000000000000..13da81fe5912 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs @@ -0,0 +1,640 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{ + AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, ParachainInfo, + ParachainSystem, PolkadotXcm, PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue, +}; +use crate::{ForeignAssets, CENTS}; +use assets_common::{ + local_and_foreign_assets::MatchesLocalAndForeignAssetsMultiLocation, + matching::{FromSiblingParachain, IsForeignConcreteAsset}, +}; +use frame_support::{ + match_types, parameter_types, + traits::{ConstU32, Contains, Everything, Nothing, PalletInfoAccess}, +}; +use frame_system::EnsureRoot; +use pallet_xcm::XcmPassthrough; +use parachains_common::{ + impls::ToStakingPot, + xcm_config::{AssetFeeAsExistentialDepositMultiplier, ConcreteAssetFromSystem}, +}; +use polkadot_parachain_primitives::primitives::Sibling; +use polkadot_runtime_common::xcm_sender::ExponentialPrice; +use sp_runtime::traits::ConvertInto; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, + EnsureXcmOrigin, FungiblesAdapter, HashedDescription, IsConcrete, LocalMint, NoChecking, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, +}; +use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; + +#[cfg(feature = "runtime-benchmarks")] +use {cumulus_primitives_core::ParaId, sp_core::Get}; + +parameter_types! { + pub const KsmLocation: MultiLocation = MultiLocation::parent(); + pub const RelayNetwork: Option = Some(NetworkId::Kusama); + pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); + pub UniversalLocation: InteriorMultiLocation = + X2(GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())); + pub UniversalLocationNetworkId: NetworkId = UniversalLocation::get().global_consensus().unwrap(); + pub TrustBackedAssetsPalletLocation: MultiLocation = + PalletInstance(::index() as u8).into(); + pub ForeignAssetsPalletLocation: MultiLocation = + PalletInstance(::index() as u8).into(); + pub PoolAssetsPalletLocation: MultiLocation = + PalletInstance(::index() as u8).into(); + pub CheckingAccount: AccountId = PolkadotXcm::check_account(); + pub const GovernanceLocation: MultiLocation = MultiLocation::parent(); + pub const FellowshipLocation: MultiLocation = MultiLocation::parent(); +} + +/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// when determining ownership of accounts for asset transacting and when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin. +pub type LocationToAccountId = ( + // The parent (Relay-chain) origin converts to the parent `AccountId`. + ParentIsPreset, + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + // Straight up local `AccountId32` origins just alias directly to `AccountId`. + AccountId32Aliases, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, +); + +/// Means for transacting the native currency on this chain. +pub type CurrencyTransactor = CurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // Convert an XCM MultiLocation into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports of `Balances`. + (), +>; + +/// `AssetId`/`Balance` converter for `PoolAssets`. +pub type TrustBackedAssetsConvertedConcreteId = + assets_common::TrustBackedAssetsConvertedConcreteId; + +/// Means for transacting assets besides the native currency on this chain. +pub type FungiblesTransactor = FungiblesAdapter< + // Use this fungibles implementation: + Assets, + // Use this currency when it is a fungible asset matching the given location or name: + TrustBackedAssetsConvertedConcreteId, + // Convert an XCM MultiLocation into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We only want to allow teleports of known assets. We use non-zero issuance as an indication + // that this asset is known. + LocalMint>, + // The account to use for tracking teleports. + CheckingAccount, +>; + +/// `AssetId/Balance` converter for `TrustBackedAssets` +pub type ForeignAssetsConvertedConcreteId = assets_common::ForeignAssetsConvertedConcreteId< + ( + // Ignore `TrustBackedAssets` explicitly + StartsWith, + // Ignore assets that start explicitly with our `GlobalConsensus(NetworkId)`, means: + // - foreign assets from our consensus should be: `MultiLocation {parents: 1, + // X*(Parachain(xyz), ..)}` + // - foreign assets outside our consensus with the same `GlobalConsensus(NetworkId)` won't + // be accepted here + StartsWithExplicitGlobalConsensus, + ), + Balance, +>; + +/// Means for transacting foreign assets from different global consensus. +pub type ForeignFungiblesTransactor = FungiblesAdapter< + // Use this fungibles implementation: + ForeignAssets, + // Use this currency when it is a fungible asset matching the given location or name: + ForeignAssetsConvertedConcreteId, + // Convert an XCM MultiLocation into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We dont need to check teleports here. + NoChecking, + // The account to use for tracking teleports. + CheckingAccount, +>; + +/// `AssetId`/`Balance` converter for `PoolAssets`. +pub type PoolAssetsConvertedConcreteId = + assets_common::PoolAssetsConvertedConcreteId; + +/// Means for transacting asset conversion pool assets on this chain. +pub type PoolFungiblesTransactor = FungiblesAdapter< + // Use this fungibles implementation: + PoolAssets, + // Use this currency when it is a fungible asset matching the given location or name: + PoolAssetsConvertedConcreteId, + // Convert an XCM MultiLocation into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We only want to allow teleports of known assets. We use non-zero issuance as an indication + // that this asset is known. + LocalMint>, + // The account to use for tracking teleports. + CheckingAccount, +>; + +/// Means for transacting assets on this chain. +pub type AssetTransactors = + (CurrencyTransactor, FungiblesTransactor, ForeignFungiblesTransactor, PoolFungiblesTransactor); + +/// Simple `MultiLocation` matcher for Local and Foreign asset `MultiLocation`. +pub struct LocalAndForeignAssetsMultiLocationMatcher; +impl MatchesLocalAndForeignAssetsMultiLocation for LocalAndForeignAssetsMultiLocationMatcher { + fn is_local(location: &MultiLocation) -> bool { + use assets_common::fungible_conversion::MatchesMultiLocation; + TrustBackedAssetsConvertedConcreteId::contains(location) + } + fn is_foreign(location: &MultiLocation) -> bool { + use assets_common::fungible_conversion::MatchesMultiLocation; + ForeignAssetsConvertedConcreteId::contains(location) + } +} +impl Contains for LocalAndForeignAssetsMultiLocationMatcher { + fn contains(location: &MultiLocation) -> bool { + Self::is_local(location) || Self::is_foreign(location) + } +} + +/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, +/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can +/// biases the kind of local `Origin` it will become. +pub type XcmOriginToTransactDispatchOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain which they control. + SovereignSignedViaLocation, + // Native converter for Relay-chain (Parent) location; will convert to a `Relay` origin when + // recognised. + RelayChainAsNative, + // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when + // recognised. + SiblingParachainAsNative, + // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a + // transaction from the Root origin. + ParentAsSuperuser, + // Native signed account converter; this just converts an `AccountId32` origin into a normal + // `RuntimeOrigin::Signed` origin of the same 32-byte value. + SignedAccountId32AsNative, + // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. + XcmPassthrough, +); + +parameter_types! { + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub XcmAssetFeesReceiver: Option = Authorship::author(); +} + +match_types! { + pub type ParentOrParentsPlurality: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Plurality { .. }) } + }; + pub type ParentOrSiblings: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(_) } + }; +} + +/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly +/// account for proof size weights. +/// +/// Calls that are allowed through this filter must: +/// 1. Have a fixed weight; +/// 2. Cannot lead to another call being made; +/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. +pub struct SafeCallFilter; +impl Contains for SafeCallFilter { + fn contains(call: &RuntimeCall) -> bool { + #[cfg(feature = "runtime-benchmarks")] + { + if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { + return true + } + } + + matches!( + call, + RuntimeCall::PolkadotXcm(pallet_xcm::Call::force_xcm_version { .. }) | + RuntimeCall::System( + frame_system::Call::set_heap_pages { .. } | + frame_system::Call::set_code { .. } | + frame_system::Call::set_code_without_checks { .. } | + frame_system::Call::kill_prefix { .. }, + ) | RuntimeCall::ParachainSystem(..) | + RuntimeCall::Timestamp(..) | + RuntimeCall::Balances(..) | + RuntimeCall::CollatorSelection( + pallet_collator_selection::Call::set_desired_candidates { .. } | + pallet_collator_selection::Call::set_candidacy_bond { .. } | + pallet_collator_selection::Call::register_as_candidate { .. } | + pallet_collator_selection::Call::leave_intent { .. } | + pallet_collator_selection::Call::set_invulnerables { .. } | + pallet_collator_selection::Call::add_invulnerable { .. } | + pallet_collator_selection::Call::remove_invulnerable { .. }, + ) | RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::XcmpQueue(..) | + RuntimeCall::DmpQueue(..) | + RuntimeCall::Assets( + pallet_assets::Call::create { .. } | + pallet_assets::Call::force_create { .. } | + pallet_assets::Call::start_destroy { .. } | + pallet_assets::Call::destroy_accounts { .. } | + pallet_assets::Call::destroy_approvals { .. } | + pallet_assets::Call::finish_destroy { .. } | + pallet_assets::Call::block { .. } | + pallet_assets::Call::mint { .. } | + pallet_assets::Call::burn { .. } | + pallet_assets::Call::transfer { .. } | + pallet_assets::Call::transfer_keep_alive { .. } | + pallet_assets::Call::force_transfer { .. } | + pallet_assets::Call::freeze { .. } | + pallet_assets::Call::thaw { .. } | + pallet_assets::Call::freeze_asset { .. } | + pallet_assets::Call::thaw_asset { .. } | + pallet_assets::Call::transfer_ownership { .. } | + pallet_assets::Call::set_team { .. } | + pallet_assets::Call::set_metadata { .. } | + pallet_assets::Call::clear_metadata { .. } | + pallet_assets::Call::force_set_metadata { .. } | + pallet_assets::Call::force_clear_metadata { .. } | + pallet_assets::Call::force_asset_status { .. } | + pallet_assets::Call::approve_transfer { .. } | + pallet_assets::Call::cancel_approval { .. } | + pallet_assets::Call::force_cancel_approval { .. } | + pallet_assets::Call::transfer_approved { .. } | + pallet_assets::Call::touch { .. } | + pallet_assets::Call::touch_other { .. } | + pallet_assets::Call::refund { .. } | + pallet_assets::Call::refund_other { .. }, + ) | RuntimeCall::ForeignAssets( + pallet_assets::Call::create { .. } | + pallet_assets::Call::force_create { .. } | + pallet_assets::Call::start_destroy { .. } | + pallet_assets::Call::destroy_accounts { .. } | + pallet_assets::Call::destroy_approvals { .. } | + pallet_assets::Call::finish_destroy { .. } | + pallet_assets::Call::block { .. } | + pallet_assets::Call::mint { .. } | + pallet_assets::Call::burn { .. } | + pallet_assets::Call::transfer { .. } | + pallet_assets::Call::transfer_keep_alive { .. } | + pallet_assets::Call::force_transfer { .. } | + pallet_assets::Call::freeze { .. } | + pallet_assets::Call::thaw { .. } | + pallet_assets::Call::freeze_asset { .. } | + pallet_assets::Call::thaw_asset { .. } | + pallet_assets::Call::transfer_ownership { .. } | + pallet_assets::Call::set_team { .. } | + pallet_assets::Call::set_metadata { .. } | + pallet_assets::Call::clear_metadata { .. } | + pallet_assets::Call::force_set_metadata { .. } | + pallet_assets::Call::force_clear_metadata { .. } | + pallet_assets::Call::force_asset_status { .. } | + pallet_assets::Call::approve_transfer { .. } | + pallet_assets::Call::cancel_approval { .. } | + pallet_assets::Call::force_cancel_approval { .. } | + pallet_assets::Call::transfer_approved { .. } | + pallet_assets::Call::touch { .. } | + pallet_assets::Call::touch_other { .. } | + pallet_assets::Call::refund { .. } | + pallet_assets::Call::refund_other { .. }, + ) | RuntimeCall::PoolAssets( + pallet_assets::Call::force_create { .. } | + pallet_assets::Call::block { .. } | + pallet_assets::Call::burn { .. } | + pallet_assets::Call::transfer { .. } | + pallet_assets::Call::transfer_keep_alive { .. } | + pallet_assets::Call::force_transfer { .. } | + pallet_assets::Call::freeze { .. } | + pallet_assets::Call::thaw { .. } | + pallet_assets::Call::freeze_asset { .. } | + pallet_assets::Call::thaw_asset { .. } | + pallet_assets::Call::transfer_ownership { .. } | + pallet_assets::Call::set_team { .. } | + pallet_assets::Call::set_metadata { .. } | + pallet_assets::Call::clear_metadata { .. } | + pallet_assets::Call::force_set_metadata { .. } | + pallet_assets::Call::force_clear_metadata { .. } | + pallet_assets::Call::force_asset_status { .. } | + pallet_assets::Call::approve_transfer { .. } | + pallet_assets::Call::cancel_approval { .. } | + pallet_assets::Call::force_cancel_approval { .. } | + pallet_assets::Call::transfer_approved { .. } | + pallet_assets::Call::touch { .. } | + pallet_assets::Call::touch_other { .. } | + pallet_assets::Call::refund { .. } | + pallet_assets::Call::refund_other { .. }, + ) | RuntimeCall::AssetConversion( + pallet_asset_conversion::Call::create_pool { .. } | + pallet_asset_conversion::Call::add_liquidity { .. } | + pallet_asset_conversion::Call::remove_liquidity { .. } | + pallet_asset_conversion::Call::swap_tokens_for_exact_tokens { .. } | + pallet_asset_conversion::Call::swap_exact_tokens_for_tokens { .. }, + ) | RuntimeCall::NftFractionalization( + pallet_nft_fractionalization::Call::fractionalize { .. } | + pallet_nft_fractionalization::Call::unify { .. }, + ) | RuntimeCall::Nfts( + pallet_nfts::Call::create { .. } | + pallet_nfts::Call::force_create { .. } | + pallet_nfts::Call::destroy { .. } | + pallet_nfts::Call::mint { .. } | + pallet_nfts::Call::force_mint { .. } | + pallet_nfts::Call::burn { .. } | + pallet_nfts::Call::transfer { .. } | + pallet_nfts::Call::lock_item_transfer { .. } | + pallet_nfts::Call::unlock_item_transfer { .. } | + pallet_nfts::Call::lock_collection { .. } | + pallet_nfts::Call::transfer_ownership { .. } | + pallet_nfts::Call::set_team { .. } | + pallet_nfts::Call::force_collection_owner { .. } | + pallet_nfts::Call::force_collection_config { .. } | + pallet_nfts::Call::approve_transfer { .. } | + pallet_nfts::Call::cancel_approval { .. } | + pallet_nfts::Call::clear_all_transfer_approvals { .. } | + pallet_nfts::Call::lock_item_properties { .. } | + pallet_nfts::Call::set_attribute { .. } | + pallet_nfts::Call::force_set_attribute { .. } | + pallet_nfts::Call::clear_attribute { .. } | + pallet_nfts::Call::approve_item_attributes { .. } | + pallet_nfts::Call::cancel_item_attributes_approval { .. } | + pallet_nfts::Call::set_metadata { .. } | + pallet_nfts::Call::clear_metadata { .. } | + pallet_nfts::Call::set_collection_metadata { .. } | + pallet_nfts::Call::clear_collection_metadata { .. } | + pallet_nfts::Call::set_accept_ownership { .. } | + pallet_nfts::Call::set_collection_max_supply { .. } | + pallet_nfts::Call::update_mint_settings { .. } | + pallet_nfts::Call::set_price { .. } | + pallet_nfts::Call::buy_item { .. } | + pallet_nfts::Call::pay_tips { .. } | + pallet_nfts::Call::create_swap { .. } | + pallet_nfts::Call::cancel_swap { .. } | + pallet_nfts::Call::claim_swap { .. }, + ) | RuntimeCall::Uniques( + pallet_uniques::Call::create { .. } | + pallet_uniques::Call::force_create { .. } | + pallet_uniques::Call::destroy { .. } | + pallet_uniques::Call::mint { .. } | + pallet_uniques::Call::burn { .. } | + pallet_uniques::Call::transfer { .. } | + pallet_uniques::Call::freeze { .. } | + pallet_uniques::Call::thaw { .. } | + pallet_uniques::Call::freeze_collection { .. } | + pallet_uniques::Call::thaw_collection { .. } | + pallet_uniques::Call::transfer_ownership { .. } | + pallet_uniques::Call::set_team { .. } | + pallet_uniques::Call::approve_transfer { .. } | + pallet_uniques::Call::cancel_approval { .. } | + pallet_uniques::Call::force_item_status { .. } | + pallet_uniques::Call::set_attribute { .. } | + pallet_uniques::Call::clear_attribute { .. } | + pallet_uniques::Call::set_metadata { .. } | + pallet_uniques::Call::clear_metadata { .. } | + pallet_uniques::Call::set_collection_metadata { .. } | + pallet_uniques::Call::clear_collection_metadata { .. } | + pallet_uniques::Call::set_accept_ownership { .. } | + pallet_uniques::Call::set_collection_max_supply { .. } | + pallet_uniques::Call::set_price { .. } | + pallet_uniques::Call::buy_item { .. } + ) + ) + } +} + +pub type Barrier = TrailingSetTopicAsId< + DenyThenTry< + DenyReserveTransferToRelayChain, + ( + TakeWeightCredit, + // Expected responses are OK. + AllowKnownQueryResponses, + // Allow XCMs with some computed origins to pass through. + WithComputedOrigin< + ( + // If the message is one that immediately attempts to pay for execution, then + // allow it. + AllowTopLevelPaidExecutionFrom, + // Parent and its pluralities (i.e. governance bodies) get free execution. + AllowExplicitUnpaidExecutionFrom, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, + ), + UniversalLocation, + ConstU32<8>, + >, + ), + >, +>; + +pub type AssetFeeAsExistentialDepositMultiplierFeeCharger = AssetFeeAsExistentialDepositMultiplier< + Runtime, + WeightToFee, + pallet_assets::BalanceToAssetBalance, + TrustBackedAssetsInstance, +>; + +/// Cases where a remote origin is accepted as trusted Teleporter for a given asset: +/// +/// - KSM with the parent Relay Chain and sibling system parachains; and +/// - Sibling parachains' assets from where they originate (as `ForeignCreators`). +pub type TrustedTeleporters = ( + ConcreteAssetFromSystem, + IsForeignConcreteAsset>>, +); + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = AssetTransactors; + type OriginConverter = XcmOriginToTransactDispatchOrigin; + // Asset Hub Kusama does not recognize a reserve location for any asset. This does not prevent + // Asset Hub acting _as_ a reserve location for KSM and assets created under `pallet-assets`. + // For KSM, users must use teleport where allowed (e.g. with the Relay Chain). + type IsReserve = (); + type IsTeleporter = TrustedTeleporters; + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = WeightInfoBounds< + crate::weights::xcm::AssetHubKusamaXcmWeight, + RuntimeCall, + MaxInstructions, + >; + type Trader = ( + UsingComponents>, + cumulus_primitives_utility::TakeFirstAssetTrader< + AccountId, + AssetFeeAsExistentialDepositMultiplierFeeCharger, + TrustBackedAssetsConvertedConcreteId, + Assets, + cumulus_primitives_utility::XcmFeesTo32ByteAccount< + FungiblesTransactor, + AccountId, + XcmAssetFeesReceiver, + >, + >, + ); + type ResponseHandler = PolkadotXcm; + type AssetTrap = PolkadotXcm; + type AssetClaims = PolkadotXcm; + type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = WithOriginFilter; + type SafeCallFilter = SafeCallFilter; + type Aliasers = Nothing; +} + +/// Converts a local signed origin into an XCM multilocation. +/// Forms the basis for local origins sending/executing XCMs. +pub type LocalOriginToLocation = SignedToAccountId32; + +parameter_types! { + /// The asset ID for the asset that we use to pay for message delivery fees. + pub FeeAssetId: AssetId = Concrete(KsmLocation::get()); + /// The base fee for the message delivery fees. + pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); +} + +pub type PriceForParentDelivery = + ExponentialPrice; + +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = WithUniqueTopic<( + // Two routers - use UMP to communicate with the relay chain: + cumulus_primitives_utility::ParentAsUmp, + // ..and XCMP to communicate with the sibling chains. + XcmpQueue, +)>; + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parent.into()); +} + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + // We want to disallow users sending (arbitrary) XCMs from this chain. + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // We support local origins dispatching XCM executions in principle... + type ExecuteXcmOrigin = EnsureXcmOrigin; + // ... but disallow generic XCM execution. As a result only teleports and reserve transfers are + // allowed. + type XcmExecuteFilter = Nothing; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Everything; + type Weigher = WeightInfoBounds< + crate::weights::xcm::AssetHubKusamaXcmWeight, + RuntimeCall, + MaxInstructions, + >; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; + type WeightInfo = crate::weights::pallet_xcm::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; + type AdminOrigin = EnsureRoot; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); +} + +impl cumulus_pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} + +pub type ForeignCreatorsSovereignAccountOf = ( + SiblingParachainConvertsVia, + AccountId32Aliases, + ParentIsPreset, +); + +/// Simple conversion of `u32` into an `AssetId` for use in benchmarking. +pub struct XcmBenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] +impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { + fn create_asset_id_parameter(id: u32) -> MultiLocation { + MultiLocation { parents: 1, interior: X1(Parachain(id)) } + } +} + +#[cfg(feature = "runtime-benchmarks")] +pub struct BenchmarkMultiLocationConverter { + _phantom: sp_std::marker::PhantomData, +} + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_conversion::BenchmarkHelper + for BenchmarkMultiLocationConverter +where + SelfParaId: Get, +{ + fn asset_id(asset_id: u32) -> MultiLocation { + MultiLocation { + parents: 1, + interior: X3( + Parachain(SelfParaId::get().into()), + PalletInstance(::index() as u8), + GeneralIndex(asset_id.into()), + ), + } + } + fn multiasset_id(asset_id: u32) -> MultiLocation { + Self::asset_id(asset_id) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index e00327a3ef6d..408f489505dc 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -320,7 +320,6 @@ impl pallet_asset_conversion::Config for Runtime { type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; type Currency = Balances; - type AssetBalance = Balance; type AssetId = MultiLocation; type Assets = LocalAndForeignAssets< Assets, @@ -337,7 +336,7 @@ impl pallet_asset_conversion::Config for Runtime { type PalletId = AssetConversionPalletId; type AllowMultiAssetPools = AllowMultiAssetPools; type MaxSwapPathLength = ConstU32<4>; - type MultiAssetId = Box; + type MultiAssetId = MultiLocation; type MultiAssetIdConverter = MultiLocationConverter; type MintMinLiquidity = ConstU128<100>; @@ -1143,17 +1142,18 @@ impl_runtime_apis! { impl pallet_asset_conversion::AssetConversionApi< Block, Balance, - u128, - Box, + MultiLocation, > for Runtime { - fn quote_price_exact_tokens_for_tokens(asset1: Box, asset2: Box, amount: u128, include_fee: bool) -> Option { + fn quote_price_exact_tokens_for_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee) } - fn quote_price_tokens_for_exact_tokens(asset1: Box, asset2: Box, amount: u128, include_fee: bool) -> Option { + + fn quote_price_tokens_for_exact_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee) } - fn get_reserves(asset1: Box, asset2: Box) -> Option<(Balance, Balance)> { + + fn get_reserves(asset1: MultiLocation, asset2: MultiLocation) -> Option<(Balance, Balance)> { AssetConversion::get_reserves(&asset1, &asset2).ok() } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index 003b71093e0a..76acced11480 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -678,8 +678,7 @@ pub struct BenchmarkMultiLocationConverter { } #[cfg(feature = "runtime-benchmarks")] -impl - pallet_asset_conversion::BenchmarkHelper> +impl pallet_asset_conversion::BenchmarkHelper for BenchmarkMultiLocationConverter where SelfParaId: frame_support::traits::Get, @@ -694,8 +693,8 @@ where ), } } - fn multiasset_id(asset_id: u32) -> sp_std::boxed::Box { - sp_std::boxed::Box::new(Self::asset_id(asset_id)) + fn multiasset_id(asset_id: u32) -> MultiLocation { + Self::asset_id(asset_id) } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index ceb021873505..e6a074df9e61 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -302,7 +302,6 @@ impl pallet_asset_conversion::Config for Runtime { type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; type Currency = Balances; - type AssetBalance = Balance; type AssetId = MultiLocation; type Assets = LocalAndForeignAssets< Assets, @@ -318,7 +317,7 @@ impl pallet_asset_conversion::Config for Runtime { type PalletId = AssetConversionPalletId; type AllowMultiAssetPools = AllowMultiAssetPools; type MaxSwapPathLength = ConstU32<4>; - type MultiAssetId = Box; + type MultiAssetId = MultiLocation; type MultiAssetIdConverter = MultiLocationConverter; type MintMinLiquidity = ConstU128<100>; @@ -1218,19 +1217,18 @@ impl_runtime_apis! { impl pallet_asset_conversion::AssetConversionApi< Block, Balance, - u128, - Box, + MultiLocation, > for Runtime { - fn quote_price_exact_tokens_for_tokens(asset1: Box, asset2: Box, amount: u128, include_fee: bool) -> Option { + fn quote_price_exact_tokens_for_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee) } - fn quote_price_tokens_for_exact_tokens(asset1: Box, asset2: Box, amount: u128, include_fee: bool) -> Option { + fn quote_price_tokens_for_exact_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee) } - fn get_reserves(asset1: Box, asset2: Box) -> Option<(Balance, Balance)> { + fn get_reserves(asset1: MultiLocation, asset2: MultiLocation) -> Option<(Balance, Balance)> { AssetConversion::get_reserves(&asset1, &asset2).ok() } } @@ -1710,10 +1708,7 @@ pub mod migrations { /// `MultiLocation { parents: 1, interior: Here }` pub struct NativeAssetParents0ToParents1Migration(sp_std::marker::PhantomData); impl< - T: pallet_asset_conversion::Config< - MultiAssetId = Box, - AssetId = MultiLocation, - >, + T: pallet_asset_conversion::Config, > OnRuntimeUpgrade for NativeAssetParents0ToParents1Migration where ::PoolAssetId: Into, @@ -1739,15 +1734,15 @@ pub mod migrations { pallet_asset_conversion::Pallet::::get_pool_account(&old_pool_id); reads.saturating_accrue(1); let pool_asset_id = pool_info.lp_token.clone(); - if old_pool_id.0.as_ref() != &invalid_native_asset { + if old_pool_id.0 != invalid_native_asset { // skip, if ok continue } // fix new account let new_pool_id = pallet_asset_conversion::Pallet::::get_pool_id( - Box::new(valid_native_asset), - old_pool_id.1.clone(), + valid_native_asset, + old_pool_id.1, ); let new_pool_account = pallet_asset_conversion::Pallet::::get_pool_account(&new_pool_id); @@ -1786,10 +1781,10 @@ pub mod migrations { // move LocalOrForeignAssets let _ = T::Assets::transfer( - *old_pool_id.1.as_ref(), + old_pool_id.1, &old_pool_account, &new_pool_account, - T::Assets::balance(*old_pool_id.1.as_ref(), &old_pool_account), + T::Assets::balance(old_pool_id.1, &old_pool_account), Preservation::Expendable, ); reads.saturating_accrue(1); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index 4bcc2bad5f66..2b5edb02b4a2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -700,8 +700,7 @@ pub struct BenchmarkMultiLocationConverter { } #[cfg(feature = "runtime-benchmarks")] -impl - pallet_asset_conversion::BenchmarkHelper> +impl pallet_asset_conversion::BenchmarkHelper for BenchmarkMultiLocationConverter where SelfParaId: Get, @@ -717,8 +716,8 @@ where } } - fn multiasset_id(asset_id: u32) -> sp_std::boxed::Box { - sp_std::boxed::Box::new(Self::asset_id(asset_id)) + fn multiasset_id(asset_id: u32) -> MultiLocation { + Self::asset_id(asset_id) } } diff --git a/cumulus/parachains/runtimes/assets/common/src/local_and_foreign_assets.rs b/cumulus/parachains/runtimes/assets/common/src/local_and_foreign_assets.rs index ed5887985563..8aedd04e1cd2 100644 --- a/cumulus/parachains/runtimes/assets/common/src/local_and_foreign_assets.rs +++ b/cumulus/parachains/runtimes/assets/common/src/local_and_foreign_assets.rs @@ -23,39 +23,38 @@ use frame_support::traits::{ use pallet_asset_conversion::{MultiAssetIdConversionResult, MultiAssetIdConverter}; use parachains_common::AccountId; use sp_runtime::{traits::MaybeEquivalence, DispatchError, DispatchResult}; -use sp_std::{boxed::Box, marker::PhantomData}; +use sp_std::marker::PhantomData; use xcm::latest::MultiLocation; pub struct MultiLocationConverter, MultiLocationMatcher> { _phantom: PhantomData<(NativeAssetLocation, MultiLocationMatcher)>, } -impl - MultiAssetIdConverter, MultiLocation> +impl MultiAssetIdConverter for MultiLocationConverter where NativeAssetLocation: Get, MultiLocationMatcher: Contains, { - fn get_native() -> Box { - Box::new(NativeAssetLocation::get()) + fn get_native() -> MultiLocation { + NativeAssetLocation::get() } - fn is_native(asset_id: &Box) -> bool { + fn is_native(asset_id: &MultiLocation) -> bool { *asset_id == Self::get_native() } fn try_convert( - asset_id: &Box, - ) -> MultiAssetIdConversionResult, MultiLocation> { - if Self::is_native(&asset_id) { + asset_id: &MultiLocation, + ) -> MultiAssetIdConversionResult { + if Self::is_native(asset_id) { return MultiAssetIdConversionResult::Native } - if MultiLocationMatcher::contains(&asset_id) { - MultiAssetIdConversionResult::Converted(*asset_id.clone()) + if MultiLocationMatcher::contains(asset_id) { + MultiAssetIdConversionResult::Converted(*asset_id) } else { - MultiAssetIdConversionResult::Unsupported(asset_id.clone()) + MultiAssetIdConversionResult::Unsupported(*asset_id) } } } @@ -451,27 +450,27 @@ mod tests { interior: X2(GlobalConsensus(ByGenesis([1; 32])), Parachain(2222)), }; - assert!(C::is_native(&Box::new(native_asset))); - assert!(!C::is_native(&Box::new(local_asset))); - assert!(!C::is_native(&Box::new(pool_asset))); - assert!(!C::is_native(&Box::new(foreign_asset1))); - assert!(!C::is_native(&Box::new(foreign_asset2))); + assert!(C::is_native(&native_asset)); + assert!(!C::is_native(&local_asset)); + assert!(!C::is_native(&pool_asset)); + assert!(!C::is_native(&foreign_asset1)); + assert!(!C::is_native(&foreign_asset2)); - assert_eq!(C::try_convert(&Box::new(native_asset)), MultiAssetIdConversionResult::Native); + assert_eq!(C::try_convert(&native_asset), MultiAssetIdConversionResult::Native); assert_eq!( - C::try_convert(&Box::new(local_asset)), + C::try_convert(&local_asset), MultiAssetIdConversionResult::Converted(local_asset) ); assert_eq!( - C::try_convert(&Box::new(pool_asset)), - MultiAssetIdConversionResult::Unsupported(Box::new(pool_asset)) + C::try_convert(&pool_asset), + MultiAssetIdConversionResult::Unsupported(pool_asset) ); assert_eq!( - C::try_convert(&Box::new(foreign_asset1)), + C::try_convert(&foreign_asset1), MultiAssetIdConversionResult::Converted(foreign_asset1) ); assert_eq!( - C::try_convert(&Box::new(foreign_asset2)), + C::try_convert(&foreign_asset2), MultiAssetIdConversionResult::Converted(foreign_asset2) ); } diff --git a/prdoc/pr_1677.prdoc b/prdoc/pr_1677.prdoc new file mode 100644 index 000000000000..9c5bee386ae3 --- /dev/null +++ b/prdoc/pr_1677.prdoc @@ -0,0 +1,22 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "pallet-asset-conversion: Swap Credit" + +doc: + - audience: Runtime Dev + description: | + Introduces a swap implementation that allows the exchange of a credit (aka Negative Imbalance) of one asset for a credit of another asset. + + This is particularly useful when a credit swap is required but may not have sufficient value to meet the ED constraint, hence cannot be deposited to temp account before. An example use case is when XCM fees are paid using an asset held in the XCM executor registry and has to be swapped for native currency. + + Additional Updates: + - encapsulates the existing `Swap` trait impl within a transactional context, since partial storage mutation is possible when an error occurs; + - supplied `Currency` and `Assets` impls must be implemented over the same `Balance` type, the `AssetBalance` generic type is dropped. This helps to avoid numerous type conversion and overflow cases. If those types are different it should be handled outside of the pallet; + - `Box` asset kind on a pallet level, unbox on a runtime level - here [why](https://substrate.stackexchange.com/questions/10039/boxed-argument-of-a-dispatchable/10103#10103); + - `path` uses `Vec` now, instead of `BoundedVec` since it is never used in PoV; + - removes the `Transfer` event due to it's redundancy with the events emitted by `fungible/s` implementations; + - modifies the `SwapExecuted` event type; + +crates: [ ] + diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 1002022d61e7..7c763960490c 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1653,7 +1653,6 @@ parameter_types! { impl pallet_asset_conversion::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; - type AssetBalance = ::Balance; type HigherPrecisionBalance = sp_core::U256; type Assets = Assets; type Balance = u128; @@ -2580,15 +2579,14 @@ impl_runtime_apis! { impl pallet_asset_conversion::AssetConversionApi< Block, Balance, - u128, NativeOrAssetId > for Runtime { - fn quote_price_exact_tokens_for_tokens(asset1: NativeOrAssetId, asset2: NativeOrAssetId, amount: u128, include_fee: bool) -> Option { + fn quote_price_exact_tokens_for_tokens(asset1: NativeOrAssetId, asset2: NativeOrAssetId, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee) } - fn quote_price_tokens_for_exact_tokens(asset1: NativeOrAssetId, asset2: NativeOrAssetId, amount: u128, include_fee: bool) -> Option { + fn quote_price_tokens_for_exact_tokens(asset1: NativeOrAssetId, asset2: NativeOrAssetId, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee) } diff --git a/substrate/frame/asset-conversion/src/benchmarking.rs b/substrate/frame/asset-conversion/src/benchmarking.rs index 87b541cd4744..628953d0558f 100644 --- a/substrate/frame/asset-conversion/src/benchmarking.rs +++ b/substrate/frame/asset-conversion/src/benchmarking.rs @@ -21,7 +21,6 @@ use super::*; use frame_benchmarking::{benchmarks, whitelisted_caller}; use frame_support::{ assert_ok, - storage::bounded_vec::BoundedVec, traits::{ fungible::{Inspect as InspectFungible, Mutate as MutateFungible, Unbalanced}, fungibles::{Create, Inspect, Mutate}, @@ -49,7 +48,7 @@ where fn create_asset(asset: &T::MultiAssetId) -> (T::AccountId, AccountIdLookupOf) where - T::AssetBalance: From, + T::Balance: From, T::Currency: Unbalanced, T::Assets: Create + Mutate, { @@ -70,7 +69,7 @@ fn create_asset_and_pool( asset2: &T::MultiAssetId, ) -> (T::PoolAssetId, T::AccountId, AccountIdLookupOf) where - T::AssetBalance: From, + T::Balance: From, T::Currency: Unbalanced, T::Assets: Create + Mutate, T::PoolAssetId: Into, @@ -80,8 +79,8 @@ where assert_ok!(AssetConversion::::create_pool( SystemOrigin::Signed(caller.clone()).into(), - asset1.clone(), - asset2.clone() + Box::new(asset1.clone()), + Box::new(asset2.clone()) )); let lp_token = get_lp_token_id::(); @@ -99,7 +98,6 @@ fn assert_last_event(generic_event: ::RuntimeEvent) { benchmarks! { where_clause { where - T::AssetBalance: From + Into, T::Currency: Unbalanced, T::Balance: From + Into, T::Assets: Create + Mutate, @@ -110,7 +108,7 @@ benchmarks! { let asset1 = T::MultiAssetIdConverter::get_native(); let asset2 = T::BenchmarkHelper::multiasset_id(0); let (caller, _) = create_asset::(&asset2); - }: _(SystemOrigin::Signed(caller.clone()), asset1.clone(), asset2.clone()) + }: _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone())) verify { let lp_token = get_lp_token_id::(); let pool_id = (asset1.clone(), asset2.clone()); @@ -128,7 +126,7 @@ benchmarks! { let (lp_token, caller, _) = create_asset_and_pool::(&asset1, &asset2); let ed: u128 = T::Currency::minimum_balance().into(); let add_amount = 1000 + ed; - }: _(SystemOrigin::Signed(caller.clone()), asset1.clone(), asset2.clone(), add_amount.into(), 1000.into(), 0.into(), 0.into(), caller.clone()) + }: _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()), add_amount.into(), 1000.into(), 0.into(), 0.into(), caller.clone()) verify { let pool_id = (asset1.clone(), asset2.clone()); let lp_minted = AssetConversion::::calc_lp_amount_for_zero_supply(&add_amount.into(), &1000.into()).unwrap().into(); @@ -157,8 +155,8 @@ benchmarks! { AssetConversion::::add_liquidity( SystemOrigin::Signed(caller.clone()).into(), - asset1.clone(), - asset2.clone(), + Box::new(asset1.clone()), + Box::new(asset2.clone()), add_amount.into(), 1000.into(), 0.into(), @@ -166,7 +164,7 @@ benchmarks! { caller.clone(), )?; let total_supply = >::total_issuance(lp_token.clone()); - }: _(SystemOrigin::Signed(caller.clone()), asset1, asset2, remove_lp_amount.into(), 0.into(), 0.into(), caller.clone()) + }: _(SystemOrigin::Signed(caller.clone()), Box::new(asset1), Box::new(asset2), remove_lp_amount.into(), 0.into(), 0.into(), caller.clone()) verify { let new_total_supply = >::total_issuance(lp_token.clone()); assert_eq!( @@ -185,8 +183,8 @@ benchmarks! { AssetConversion::::add_liquidity( SystemOrigin::Signed(caller.clone()).into(), - native.clone(), - asset1.clone(), + Box::new(native.clone()), + Box::new(asset1.clone()), (100 * ed).into(), 200.into(), 0.into(), @@ -199,29 +197,45 @@ benchmarks! { // if we only allow the native-asset pools, then the worst case scenario would be to swap // asset1-native-asset2 if !T::AllowMultiAssetPools::get() { - AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), native.clone(), asset2.clone())?; + AssetConversion::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + Box::new(native.clone()), + Box::new(asset2.clone()) + )?; AssetConversion::::add_liquidity( SystemOrigin::Signed(caller.clone()).into(), - native.clone(), - asset2.clone(), + Box::new(native.clone()), + Box::new(asset2.clone()), (500 * ed).into(), 1000.into(), 0.into(), 0.into(), caller.clone(), )?; - path = vec![asset1.clone(), native.clone(), asset2.clone()]; + path = vec![ + Box::new(asset1.clone()), + Box::new(native.clone()), + Box::new(asset2.clone()) + ]; swap_amount = 100.into(); } else { let asset3 = T::BenchmarkHelper::multiasset_id(3); - AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset1.clone(), asset2.clone())?; + AssetConversion::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + Box::new(asset1.clone()), + Box::new(asset2.clone()) + )?; let (_, _) = create_asset::(&asset3); - AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset2.clone(), asset3.clone())?; + AssetConversion::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + Box::new(asset2.clone()), + Box::new(asset3.clone()) + )?; AssetConversion::::add_liquidity( SystemOrigin::Signed(caller.clone()).into(), - asset1.clone(), - asset2.clone(), + Box::new(asset1.clone()), + Box::new(asset2.clone()), 200.into(), 2000.into(), 0.into(), @@ -230,19 +244,22 @@ benchmarks! { )?; AssetConversion::::add_liquidity( SystemOrigin::Signed(caller.clone()).into(), - asset2.clone(), - asset3.clone(), + Box::new(asset2.clone()), + Box::new(asset3.clone()), 2000.into(), 2000.into(), 0.into(), 0.into(), caller.clone(), )?; - path = vec![native.clone(), asset1.clone(), asset2.clone(), asset3.clone()]; + path = vec![ + Box::new(native.clone()), + Box::new(asset1.clone()), + Box::new(asset2.clone()), + Box::new(asset3.clone()) + ]; swap_amount = ed.into(); } - - let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap(); let native_balance = T::Currency::balance(&caller); let asset1_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(1), &caller); }: _(SystemOrigin::Signed(caller.clone()), path, swap_amount, 1.into(), caller.clone(), false) @@ -266,8 +283,8 @@ benchmarks! { AssetConversion::::add_liquidity( SystemOrigin::Signed(caller.clone()).into(), - native.clone(), - asset1.clone(), + Box::new(native.clone()), + Box::new(asset1.clone()), (1000 * ed).into(), 500.into(), 0.into(), @@ -279,28 +296,44 @@ benchmarks! { // if we only allow the native-asset pools, then the worst case scenario would be to swap // asset1-native-asset2 if !T::AllowMultiAssetPools::get() { - AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), native.clone(), asset2.clone())?; + AssetConversion::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + Box::new(native.clone()), + Box::new(asset2.clone()) + )?; AssetConversion::::add_liquidity( SystemOrigin::Signed(caller.clone()).into(), - native.clone(), - asset2.clone(), + Box::new(native.clone()), + Box::new(asset2.clone()), (500 * ed).into(), 1000.into(), 0.into(), 0.into(), caller.clone(), )?; - path = vec![asset1.clone(), native.clone(), asset2.clone()]; + path = vec![ + Box::new(asset1.clone()), + Box::new(native.clone()), + Box::new(asset2.clone()) + ]; } else { - AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset1.clone(), asset2.clone())?; + AssetConversion::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + Box::new(asset1.clone()), + Box::new(asset2.clone()) + )?; let asset3 = T::BenchmarkHelper::multiasset_id(3); let (_, _) = create_asset::(&asset3); - AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset2.clone(), asset3.clone())?; + AssetConversion::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + Box::new(asset2.clone()), + Box::new(asset3.clone()) + )?; AssetConversion::::add_liquidity( SystemOrigin::Signed(caller.clone()).into(), - asset1.clone(), - asset2.clone(), + Box::new(asset1.clone()), + Box::new(asset2.clone()), 2000.into(), 2000.into(), 0.into(), @@ -309,18 +342,22 @@ benchmarks! { )?; AssetConversion::::add_liquidity( SystemOrigin::Signed(caller.clone()).into(), - asset2.clone(), - asset3.clone(), + Box::new(asset2.clone()), + Box::new(asset3.clone()), 2000.into(), 2000.into(), 0.into(), 0.into(), caller.clone(), )?; - path = vec![native.clone(), asset1.clone(), asset2.clone(), asset3.clone()]; + path = vec![ + Box::new(native.clone()), + Box::new(asset1.clone()), + Box::new(asset2.clone()), + Box::new(asset3.clone()) + ]; } - let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap(); let asset2_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(2), &caller); let asset3_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(3), &caller); }: _(SystemOrigin::Signed(caller.clone()), path.clone(), 100.into(), (1000 * ed).into(), caller.clone(), false) diff --git a/substrate/frame/asset-conversion/src/lib.rs b/substrate/frame/asset-conversion/src/lib.rs index 5cbc2821ce6c..b9ff5e955082 100644 --- a/substrate/frame/asset-conversion/src/lib.rs +++ b/substrate/frame/asset-conversion/src/lib.rs @@ -53,63 +53,57 @@ //! (This can be run against the kitchen sync node in the `node` folder of this repo.) #![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{DefensiveOption, Incrementable}; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; - -mod types; -pub mod weights; - +#[cfg(test)] +mod mock; +mod swap; #[cfg(test)] mod tests; +mod types; +pub mod weights; -#[cfg(test)] -mod mock; +pub use pallet::*; +pub use swap::*; +pub use types::*; +pub use weights::WeightInfo; use codec::Codec; use frame_support::{ - ensure, - traits::tokens::{AssetId, Balance}, -}; -use frame_system::{ - ensure_signed, - pallet_prelude::{BlockNumberFor, OriginFor}, + storage::{with_storage_layer, with_transaction}, + traits::{ + fungible::{ + Balanced as BalancedFungible, Credit as CreditFungible, Inspect as InspectFungible, + Mutate as MutateFungible, + }, + fungibles::{Balanced, Create, Credit as CreditFungibles, Inspect, Mutate}, + tokens::{ + AssetId, Balance, + Fortitude::Polite, + Precision::Exact, + Preservation::{Expendable, Preserve}, + }, + AccountTouch, ContainsPair, Imbalance, Incrementable, + }, + PalletId, }; -pub use pallet::*; -use sp_arithmetic::traits::Unsigned; +use sp_core::Get; use sp_runtime::{ traits::{ - CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Ensure, MaybeDisplay, TrailingZeroInput, + CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Ensure, IntegerSquareRoot, MaybeDisplay, + One, TrailingZeroInput, Zero, }, - DispatchError, + DispatchError, RuntimeDebug, Saturating, TokenError, TransactionOutcome, }; -use sp_std::prelude::*; -pub use types::*; -pub use weights::WeightInfo; +use sp_std::{boxed::Box, collections::btree_set::BTreeSet, vec::Vec}; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{ - pallet_prelude::*, - traits::{ - fungible::{Inspect as InspectFungible, Mutate as MutateFungible}, - fungibles::{Create, Inspect, Mutate}, - tokens::{ - Fortitude::Polite, - Precision::Exact, - Preservation::{Expendable, Preserve}, - }, - AccountTouch, ContainsPair, - }, - BoundedBTreeSet, PalletId, - }; - use sp_arithmetic::Permill; - use sp_runtime::{ - traits::{IntegerSquareRoot, One, Zero}, - Saturating, - }; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_arithmetic::{traits::Unsigned, Permill}; #[pallet::pallet] pub struct Pallet(_); @@ -121,23 +115,19 @@ pub mod pallet { /// Currency type that this works on. type Currency: InspectFungible - + MutateFungible; + + MutateFungible + + BalancedFungible; - /// The `Currency::Balance` type of the native currency. + /// The type in which the assets for swapping are measured. type Balance: Balance; - /// The type used to describe the amount of fractions converted into assets. - type AssetBalance: Balance; - - /// A type used for conversions between `Balance` and `AssetBalance`. + /// A type used for calculations concerning the `Balance` type to avoid possible overflows. type HigherPrecisionBalance: IntegerSquareRoot + One + Ensure + Unsigned + From - + From + From - + TryInto + TryInto; /// Identifier for the class of non-native asset. @@ -159,14 +149,15 @@ pub mod pallet { type PoolAssetId: AssetId + PartialOrd + Incrementable + From; /// Registry for the assets. - type Assets: Inspect + type Assets: Inspect + Mutate + AccountTouch - + ContainsPair; + + ContainsPair + + Balanced; /// Registry for the lp tokens. Ideally only this pallet should have create permissions on /// the assets. - type PoolAssets: Inspect + type PoolAssets: Inspect + Create + Mutate + AccountTouch; @@ -188,7 +179,7 @@ pub mod pallet { /// The minimum LP token amount that could be minted. Ameliorates rounding errors. #[pallet::constant] - type MintMinLiquidity: Get; + type MintMinLiquidity: Get; /// The max number of hops in a swap. #[pallet::constant] @@ -248,13 +239,13 @@ pub mod pallet { /// The pool id of the pool that the liquidity was added to. pool_id: PoolIdOf, /// The amount of the first asset that was added to the pool. - amount1_provided: T::AssetBalance, + amount1_provided: T::Balance, /// The amount of the second asset that was added to the pool. - amount2_provided: T::AssetBalance, + amount2_provided: T::Balance, /// The id of the lp token that was minted. lp_token: T::PoolAssetId, /// The amount of lp tokens that were minted of that id. - lp_token_minted: T::AssetBalance, + lp_token_minted: T::Balance, }, /// A successful call of the `RemoveLiquidity` extrinsic will create this event. @@ -266,13 +257,13 @@ pub mod pallet { /// The pool id that the liquidity was removed from. pool_id: PoolIdOf, /// The amount of the first asset that was removed from the pool. - amount1: T::AssetBalance, + amount1: T::Balance, /// The amount of the second asset that was removed from the pool. - amount2: T::AssetBalance, + amount2: T::Balance, /// The id of the lp token that was burned. lp_token: T::PoolAssetId, /// The amount of lp tokens that were burned of that id. - lp_token_burned: T::AssetBalance, + lp_token_burned: T::Balance, /// Liquidity withdrawal fee (%). withdrawal_fee: Permill, }, @@ -283,24 +274,23 @@ pub mod pallet { who: T::AccountId, /// The account that the assets were transferred to. send_to: T::AccountId, - /// The route of asset ids that the swap went through. - /// E.g. A -> Dot -> B - path: BoundedVec, /// The amount of the first asset that was swapped. - amount_in: T::AssetBalance, + amount_in: T::Balance, /// The amount of the second asset that was received. - amount_out: T::AssetBalance, + amount_out: T::Balance, + /// The route of asset IDs with amounts that the swap went through. + /// E.g. (A, amount_in) -> (Dot, amount_out) -> (B, amount_out) + path: BalancePath, }, - /// An amount has been transferred from one account to another. - Transfer { - /// The account that the assets were transferred from. - from: T::AccountId, - /// The account that the assets were transferred to. - to: T::AccountId, - /// The asset that was transferred. - asset: T::MultiAssetId, - /// The amount of the asset that was transferred. - amount: T::AssetBalance, + /// Assets have been converted from one to another. + SwapCreditExecuted { + /// The amount of the first asset that was swapped. + amount_in: T::Balance, + /// The amount of the second asset that was received. + amount_out: T::Balance, + /// The route of asset IDs with amounts that the swap went through. + /// E.g. (A, amount_in) -> (Dot, amount_out) -> (B, amount_out) + path: BalancePath, }, } @@ -361,10 +351,8 @@ pub mod pallet { NonUniquePath, /// It was not possible to get or increment the Id of the pool. IncorrectPoolAssetId, - /// Unable to find an element in an array/vec that should have one-to-one correspondence - /// with another. For example, an array of assets constituting a `path` should have a - /// corresponding array of `amounts` along the path. - CorrespondenceError, + /// The destination account cannot exist with the swapped funds. + BelowMinimum, } #[pallet::hooks] @@ -388,14 +376,14 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::create_pool())] pub fn create_pool( origin: OriginFor, - asset1: T::MultiAssetId, - asset2: T::MultiAssetId, + asset1: Box, + asset2: Box, ) -> DispatchResult { let sender = ensure_signed(origin)?; ensure!(asset1 != asset2, Error::::EqualAssets); // prepare pool_id - let pool_id = Self::get_pool_id(asset1, asset2); + let pool_id = Self::get_pool_id(*asset1, *asset2); ensure!(!Pools::::contains_key(&pool_id), Error::::PoolExists); let (asset1, asset2) = &pool_id; if !T::AllowMultiAssetPools::get() && !T::MultiAssetIdConverter::is_native(asset1) { @@ -466,20 +454,20 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::add_liquidity())] pub fn add_liquidity( origin: OriginFor, - asset1: T::MultiAssetId, - asset2: T::MultiAssetId, - amount1_desired: T::AssetBalance, - amount2_desired: T::AssetBalance, - amount1_min: T::AssetBalance, - amount2_min: T::AssetBalance, + asset1: Box, + asset2: Box, + amount1_desired: T::Balance, + amount2_desired: T::Balance, + amount1_min: T::Balance, + amount2_min: T::Balance, mint_to: T::AccountId, ) -> DispatchResult { let sender = ensure_signed(origin)?; - let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); + let pool_id = Self::get_pool_id(*asset1.clone(), *asset2); // swap params if needed let (amount1_desired, amount2_desired, amount1_min, amount2_min) = - if pool_id.0 == asset1 { + if pool_id.0 == *asset1 { (amount1_desired, amount2_desired, amount1_min, amount2_min) } else { (amount2_desired, amount1_desired, amount2_min, amount1_min) @@ -497,8 +485,8 @@ pub mod pallet { let reserve1 = Self::get_balance(&pool_account, asset1)?; let reserve2 = Self::get_balance(&pool_account, asset2)?; - let amount1: T::AssetBalance; - let amount2: T::AssetBalance; + let amount1: T::Balance; + let amount2: T::Balance; if reserve1.is_zero() || reserve2.is_zero() { amount1 = amount1_desired; amount2 = amount2_desired; @@ -537,7 +525,7 @@ pub mod pallet { let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone()); - let lp_token_amount: T::AssetBalance; + let lp_token_amount: T::Balance; if total_supply.is_zero() { lp_token_amount = Self::calc_lp_amount_for_zero_supply(&amount1, &amount2)?; T::PoolAssets::mint_into( @@ -578,18 +566,18 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::remove_liquidity())] pub fn remove_liquidity( origin: OriginFor, - asset1: T::MultiAssetId, - asset2: T::MultiAssetId, - lp_token_burn: T::AssetBalance, - amount1_min_receive: T::AssetBalance, - amount2_min_receive: T::AssetBalance, + asset1: Box, + asset2: Box, + lp_token_burn: T::Balance, + amount1_min_receive: T::Balance, + amount2_min_receive: T::Balance, withdraw_to: T::AccountId, ) -> DispatchResult { let sender = ensure_signed(origin)?; - let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); + let pool_id = Self::get_pool_id(*asset1.clone(), *asset2); // swap params if needed - let (amount1_min_receive, amount2_min_receive) = if pool_id.0 == asset1 { + let (amount1_min_receive, amount2_min_receive) = if pool_id.0 == *asset1 { (amount1_min_receive, amount2_min_receive) } else { (amount2_min_receive, amount1_min_receive) @@ -657,16 +645,16 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::swap_exact_tokens_for_tokens())] pub fn swap_exact_tokens_for_tokens( origin: OriginFor, - path: BoundedVec, - amount_in: T::AssetBalance, - amount_out_min: T::AssetBalance, + path: Vec>, + amount_in: T::Balance, + amount_out_min: T::Balance, send_to: T::AccountId, keep_alive: bool, ) -> DispatchResult { let sender = ensure_signed(origin)?; Self::do_swap_exact_tokens_for_tokens( sender, - path, + path.into_iter().map(|a| *a).collect(), amount_in, Some(amount_out_min), send_to, @@ -685,16 +673,16 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::swap_tokens_for_exact_tokens())] pub fn swap_tokens_for_exact_tokens( origin: OriginFor, - path: BoundedVec, - amount_out: T::AssetBalance, - amount_in_max: T::AssetBalance, + path: Vec>, + amount_out: T::Balance, + amount_in_max: T::Balance, send_to: T::AccountId, keep_alive: bool, ) -> DispatchResult { let sender = ensure_signed(origin)?; Self::do_swap_tokens_for_exact_tokens( sender, - path, + path.into_iter().map(|a| *a).collect(), amount_out, Some(amount_in_max), send_to, @@ -713,25 +701,27 @@ pub mod pallet { /// respecting `keep_alive`. /// /// If successful, returns the amount of `path[1]` acquired for the `amount_in`. - pub fn do_swap_exact_tokens_for_tokens( + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used + /// only inside a transactional storage context and an Err result must imply a storage + /// rollback. + pub(crate) fn do_swap_exact_tokens_for_tokens( sender: T::AccountId, - path: BoundedVec, - amount_in: T::AssetBalance, - amount_out_min: Option, + path: Vec, + amount_in: T::Balance, + amount_out_min: Option, send_to: T::AccountId, keep_alive: bool, - ) -> Result { + ) -> Result { ensure!(amount_in > Zero::zero(), Error::::ZeroAmount); if let Some(amount_out_min) = amount_out_min { ensure!(amount_out_min > Zero::zero(), Error::::ZeroAmount); } Self::validate_swap_path(&path)?; + let path = Self::balance_path_from_amount_in(amount_in, path)?; - let amounts = Self::get_amounts_out(&amount_in, &path)?; - let amount_out = - *amounts.last().defensive_ok_or("get_amounts_out() returned an empty result")?; - + let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::::InvalidPath)?; if let Some(amount_out_min) = amount_out_min { ensure!( amount_out >= amount_out_min, @@ -739,7 +729,15 @@ pub mod pallet { ); } - Self::do_swap(sender, &amounts, path, send_to, keep_alive)?; + Self::swap(&sender, &path, &send_to, keep_alive)?; + + Self::deposit_event(Event::SwapExecuted { + who: sender, + send_to, + amount_in, + amount_out, + path, + }); Ok(amount_out) } @@ -751,25 +749,27 @@ pub mod pallet { /// respecting `keep_alive`. /// /// If successful returns the amount of the `path[0]` taken to provide `path[1]`. - pub fn do_swap_tokens_for_exact_tokens( + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used + /// only inside a transactional storage context and an Err result must imply a storage + /// rollback. + pub(crate) fn do_swap_tokens_for_exact_tokens( sender: T::AccountId, - path: BoundedVec, - amount_out: T::AssetBalance, - amount_in_max: Option, + path: Vec, + amount_out: T::Balance, + amount_in_max: Option, send_to: T::AccountId, keep_alive: bool, - ) -> Result { + ) -> Result { ensure!(amount_out > Zero::zero(), Error::::ZeroAmount); if let Some(amount_in_max) = amount_in_max { ensure!(amount_in_max > Zero::zero(), Error::::ZeroAmount); } Self::validate_swap_path(&path)?; + let path = Self::balance_path_from_amount_out(amount_out, path)?; - let amounts = Self::get_amounts_in(&amount_out, &path)?; - let amount_in = - *amounts.first().defensive_ok_or("get_amounts_in() returned an empty result")?; - + let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::::InvalidPath)?; if let Some(amount_in_max) = amount_in_max { ensure!( amount_in <= amount_in_max, @@ -777,131 +777,251 @@ pub mod pallet { ); } - Self::do_swap(sender, &amounts, path, send_to, keep_alive)?; + Self::swap(&sender, &path, &send_to, keep_alive)?; + + Self::deposit_event(Event::SwapExecuted { + who: sender, + send_to, + amount_in, + amount_out, + path, + }); + Ok(amount_in) } + /// Swap exactly `credit_in` of asset `path[0]` for asset `path[last]`. If `amount_out_min` + /// is provided and the swap can't achieve at least this amount, an error is returned. + /// + /// On a successful swap, the function returns the `credit_out` of `path[last]` obtained + /// from the `credit_in`. On failure, it returns an `Err` containing the original + /// `credit_in` and the associated error code. + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used + /// only inside a transactional storage context and an Err result must imply a storage + /// rollback. + pub(crate) fn do_swap_exact_credit_tokens_for_tokens( + path: Vec, + credit_in: Credit, + amount_out_min: Option, + ) -> Result, (Credit, DispatchError)> { + let amount_in = credit_in.peek(); + let inspect_path = |credit_asset| { + ensure!(path.get(0).map_or(false, |a| *a == credit_asset), Error::::InvalidPath); + ensure!(!amount_in.is_zero(), Error::::ZeroAmount); + ensure!(amount_out_min.map_or(true, |a| !a.is_zero()), Error::::ZeroAmount); + + Self::validate_swap_path(&path)?; + let path = Self::balance_path_from_amount_in(amount_in, path)?; + + let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::::InvalidPath)?; + ensure!( + amount_out_min.map_or(true, |a| amount_out >= a), + Error::::ProvidedMinimumNotSufficientForSwap + ); + Ok((path, amount_out)) + }; + let (path, amount_out) = match inspect_path(credit_in.asset()) { + Ok((p, a)) => (p, a), + Err(e) => return Err((credit_in, e)), + }; + + let credit_out = Self::credit_swap(credit_in, &path)?; + + Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path }); + + Ok(credit_out) + } + + /// Swaps a portion of `credit_in` of `path[0]` asset to obtain the desired `amount_out` of + /// the `path[last]` asset. The provided `credit_in` must be adequate to achieve the target + /// `amount_out`, or an error will occur. + /// + /// On success, the function returns a (`credit_out`, `credit_change`) tuple, where + /// `credit_out` represents the acquired amount of the `path[last]` asset, and + /// `credit_change` is the remaining portion from the `credit_in`. On failure, an `Err` with + /// the initial `credit_in` and error code is returned. + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used + /// only inside a transactional storage context and an Err result must imply a storage + /// rollback. + pub(crate) fn do_swap_credit_tokens_for_exact_tokens( + path: Vec, + credit_in: Credit, + amount_out: T::Balance, + ) -> Result<(Credit, Credit), (Credit, DispatchError)> { + let amount_in_max = credit_in.peek(); + let inspect_path = |credit_asset| { + ensure!(path.get(0).map_or(false, |a| a == &credit_asset), Error::::InvalidPath); + ensure!(amount_in_max > Zero::zero(), Error::::ZeroAmount); + ensure!(amount_out > Zero::zero(), Error::::ZeroAmount); + + Self::validate_swap_path(&path)?; + let path = Self::balance_path_from_amount_out(amount_out, path)?; + + let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::::InvalidPath)?; + ensure!( + amount_in <= amount_in_max, + Error::::ProvidedMaximumNotSufficientForSwap + ); + + Ok((path, amount_in)) + }; + let (path, amount_in) = match inspect_path(credit_in.asset()) { + Ok((p, a)) => (p, a), + Err(e) => return Err((credit_in, e)), + }; + + let (credit_in, credit_change) = credit_in.split(amount_in); + let credit_out = Self::credit_swap(credit_in, &path)?; + + Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path }); + + Ok((credit_out, credit_change)) + } + /// Transfer an `amount` of `asset_id`, respecting the `keep_alive` requirements. fn transfer( asset_id: &T::MultiAssetId, from: &T::AccountId, to: &T::AccountId, - amount: T::AssetBalance, + amount: T::Balance, keep_alive: bool, - ) -> Result { - let result = match T::MultiAssetIdConverter::try_convert(asset_id) { + ) -> Result { + let preservation = match keep_alive { + true => Preserve, + false => Expendable, + }; + match T::MultiAssetIdConverter::try_convert(asset_id) { MultiAssetIdConversionResult::Converted(asset_id) => - T::Assets::transfer(asset_id, from, to, amount, Expendable), - MultiAssetIdConversionResult::Native => { - let preservation = match keep_alive { - true => Preserve, - false => Expendable, - }; - let amount = Self::convert_asset_balance_to_native_balance(amount)?; - Ok(Self::convert_native_balance_to_asset_balance(T::Currency::transfer( - from, - to, - amount, - preservation, - )?)?) - }, + T::Assets::transfer(asset_id, from, to, amount, preservation), + MultiAssetIdConversionResult::Native => + Ok(T::Currency::transfer(from, to, amount, preservation)?), MultiAssetIdConversionResult::Unsupported(_) => Err(Error::::UnsupportedAsset.into()), - }; - - if result.is_ok() { - Self::deposit_event(Event::Transfer { - from: from.clone(), - to: to.clone(), - asset: (*asset_id).clone(), - amount, - }); } - result } - /// Convert a `Balance` type to an `AssetBalance`. - pub(crate) fn convert_native_balance_to_asset_balance( - amount: T::Balance, - ) -> Result> { - T::HigherPrecisionBalance::from(amount) - .try_into() - .map_err(|_| Error::::Overflow) - } - - /// Convert an `AssetBalance` type to a `Balance`. - pub(crate) fn convert_asset_balance_to_native_balance( - amount: T::AssetBalance, - ) -> Result> { - T::HigherPrecisionBalance::from(amount) - .try_into() - .map_err(|_| Error::::Overflow) + /// The balance of `who` is increased in order to counter `credit`. If the whole of `credit` + /// cannot be countered, then nothing is changed and the original `credit` is returned in an + /// `Err`. + fn resolve(who: &T::AccountId, credit: Credit) -> Result<(), Credit> { + match credit { + Credit::Native(c) => T::Currency::resolve(who, c).map_err(|c| c.into()), + Credit::Asset(c) => T::Assets::resolve(who, c).map_err(|c| c.into()), + } } - /// Convert a `HigherPrecisionBalance` type to an `AssetBalance`. - pub(crate) fn convert_hpb_to_asset_balance( - amount: T::HigherPrecisionBalance, - ) -> Result> { - amount.try_into().map_err(|_| Error::::Overflow) + /// Removes `value` balance of `asset` from `who` account if possible. + fn withdraw( + asset: &T::MultiAssetId, + who: &T::AccountId, + value: T::Balance, + keep_alive: bool, + ) -> Result, DispatchError> { + let preservation = match keep_alive { + true => Preserve, + false => Expendable, + }; + match T::MultiAssetIdConverter::try_convert(asset) { + MultiAssetIdConversionResult::Converted(asset) => { + if preservation == Preserve { + // TODO drop the ensure! when this issue addressed + // https://github.com/paritytech/polkadot-sdk/issues/1698 + let free = + T::Assets::reducible_balance(asset.clone(), who, preservation, Polite); + ensure!(free >= value, TokenError::NotExpendable); + } + T::Assets::withdraw(asset, who, value, Exact, preservation, Polite) + .map(|c| c.into()) + }, + MultiAssetIdConversionResult::Native => { + if preservation == Preserve { + // TODO drop the ensure! when this issue addressed + // https://github.com/paritytech/polkadot-sdk/issues/1698 + let free = T::Currency::reducible_balance(who, preservation, Polite); + ensure!(free >= value, TokenError::NotExpendable); + } + T::Currency::withdraw(who, value, Exact, preservation, Polite).map(|c| c.into()) + }, + MultiAssetIdConversionResult::Unsupported(_) => + Err(Error::::UnsupportedAsset.into()), + } } - /// Swap assets along a `path`, depositing in `send_to`. - pub(crate) fn do_swap( - sender: T::AccountId, - amounts: &Vec, - path: BoundedVec, - send_to: T::AccountId, + /// Swap assets along the `path`, withdrawing from `sender` and depositing in `send_to`. + /// + /// Note: It's assumed that the provided `path` is valid. + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used + /// only inside a transactional storage context and an Err result must imply a storage + /// rollback. + fn swap( + sender: &T::AccountId, + path: &BalancePath, + send_to: &T::AccountId, keep_alive: bool, ) -> Result<(), DispatchError> { - ensure!(amounts.len() > 1, Error::::CorrespondenceError); - if let Some([asset1, asset2]) = &path.get(0..2) { - let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); - let pool_account = Self::get_pool_account(&pool_id); - // amounts should always contain a corresponding element to path. - let first_amount = amounts.first().ok_or(Error::::CorrespondenceError)?; - - Self::transfer(asset1, &sender, &pool_account, *first_amount, keep_alive)?; - - let mut i = 0; - let path_len = path.len() as u32; - for assets_pair in path.windows(2) { - if let [asset1, asset2] = assets_pair { - let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); - let pool_account = Self::get_pool_account(&pool_id); - - let amount_out = - amounts.get((i + 1) as usize).ok_or(Error::::CorrespondenceError)?; - - let to = if i < path_len - 2 { - let asset3 = path.get((i + 2) as usize).ok_or(Error::::PathError)?; - Self::get_pool_account(&Self::get_pool_id( + let (asset_in, amount_in) = path.first().ok_or(Error::::InvalidPath)?; + let credit_in = Self::withdraw(asset_in, sender, *amount_in, keep_alive)?; + + let credit_out = Self::credit_swap(credit_in, path).map_err(|(_, e)| e)?; + Self::resolve(send_to, credit_out).map_err(|_| Error::::BelowMinimum)?; + + Ok(()) + } + + /// Swap assets along the specified `path`, consuming `credit_in` and producing + /// `credit_out`. + /// + /// If an error occurs, `credit_in` is returned back. + /// + /// Note: It's assumed that the provided `path` is valid and `credit_in` corresponds to the + /// first asset in the `path`. + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used + /// only inside a transactional storage context and an Err result must imply a storage + /// rollback. + fn credit_swap( + credit_in: Credit, + path: &BalancePath, + ) -> Result, (Credit, DispatchError)> { + let resolve_path = || -> Result, DispatchError> { + for pos in 0..=path.len() { + if let Some([(asset1, _), (asset2, amount_out)]) = path.get(pos..=pos + 1) { + let pool_from = Self::get_pool_account(&Self::get_pool_id( + asset1.clone(), + asset2.clone(), + )); + if let Some((asset3, _)) = path.get(pos + 2) { + let pool_to = Self::get_pool_account(&Self::get_pool_id( asset2.clone(), asset3.clone(), - )) + )); + Self::transfer(asset2, &pool_from, &pool_to, *amount_out, true)?; } else { - send_to.clone() - }; - - let reserve = Self::get_balance(&pool_account, asset2)?; - let reserve_left = reserve.saturating_sub(*amount_out); - Self::validate_minimal_amount(reserve_left, asset2) - .map_err(|_| Error::::ReserveLeftLessThanMinimal)?; - - Self::transfer(asset2, &pool_account, &to, *amount_out, true)?; + let credit_out = Self::withdraw(asset2, &pool_from, *amount_out, true)?; + return Ok(credit_out) + } } - i.saturating_inc(); } - Self::deposit_event(Event::SwapExecuted { - who: sender, - send_to, - path, - amount_in: *first_amount, - amount_out: *amounts.last().expect("Always has more than 1 element"), - }); - } else { return Err(Error::::InvalidPath.into()) - } - Ok(()) + }; + + let credit_out = match resolve_path() { + Ok(c) => c, + Err(e) => return Err((credit_in, e)), + }; + + let pool_to = if let Some([(asset1, _), (asset2, _)]) = path.get(0..2) { + Self::get_pool_account(&Self::get_pool_id(asset1.clone(), asset2.clone())) + } else { + return Err((credit_in, Error::::InvalidPath.into())) + }; + + Self::resolve(&pool_to, credit_in).map_err(|c| (c, Error::::BelowMinimum.into()))?; + + Ok(credit_out) } /// The account ID of the pool. @@ -916,19 +1036,17 @@ pub mod pallet { } /// Get the `owner`'s balance of `asset`, which could be the chain's native asset or another - /// fungible. Returns a value in the form of an `AssetBalance`. + /// fungible. Returns a value in the form of an `Balance`. fn get_balance( owner: &T::AccountId, asset: &T::MultiAssetId, - ) -> Result> { + ) -> Result> { match T::MultiAssetIdConverter::try_convert(asset) { MultiAssetIdConversionResult::Converted(asset_id) => Ok( <::Assets>::reducible_balance(asset_id, owner, Expendable, Polite), ), MultiAssetIdConversionResult::Native => - Self::convert_native_balance_to_asset_balance( - <::Currency>::reducible_balance(owner, Expendable, Polite), - ), + Ok(<::Currency>::reducible_balance(owner, Expendable, Polite)), MultiAssetIdConversionResult::Unsupported(_) => Err(Error::::UnsupportedAsset.into()), } @@ -963,7 +1081,7 @@ pub mod pallet { pub fn get_reserves( asset1: &T::MultiAssetId, asset2: &T::MultiAssetId, - ) -> Result<(T::AssetBalance, T::AssetBalance), Error> { + ) -> Result<(T::Balance, T::Balance), Error> { let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); let pool_account = Self::get_pool_account(&pool_id); @@ -978,51 +1096,62 @@ pub mod pallet { } /// Leading to an amount at the end of a `path`, get the required amounts in. - pub(crate) fn get_amounts_in( - amount_out: &T::AssetBalance, - path: &BoundedVec, - ) -> Result, DispatchError> { - let mut amounts: Vec = vec![*amount_out]; - - for assets_pair in path.windows(2).rev() { - if let [asset1, asset2] = assets_pair { - let (reserve_in, reserve_out) = Self::get_reserves(asset1, asset2)?; - let prev_amount = amounts.last().expect("Always has at least one element"); - let amount_in = Self::get_amount_in(prev_amount, &reserve_in, &reserve_out)?; - amounts.push(amount_in); - } + pub(crate) fn balance_path_from_amount_out( + amount_out: T::Balance, + path: Vec, + ) -> Result, DispatchError> { + let mut balance_path: BalancePath = Vec::with_capacity(path.len()); + let mut amount_in: T::Balance = amount_out; + + let mut iter = path.into_iter().rev().peekable(); + while let Some(asset2) = iter.next() { + let asset1 = match iter.peek() { + Some(a) => a, + None => { + balance_path.push((asset2, amount_in)); + break + }, + }; + let (reserve_in, reserve_out) = Self::get_reserves(asset1, &asset2)?; + balance_path.push((asset2, amount_in)); + amount_in = Self::get_amount_in(&amount_in, &reserve_in, &reserve_out)?; } + balance_path.reverse(); - amounts.reverse(); - Ok(amounts) + Ok(balance_path) } /// Following an amount into a `path`, get the corresponding amounts out. - pub(crate) fn get_amounts_out( - amount_in: &T::AssetBalance, - path: &BoundedVec, - ) -> Result, DispatchError> { - let mut amounts: Vec = vec![*amount_in]; - - for assets_pair in path.windows(2) { - if let [asset1, asset2] = assets_pair { - let (reserve_in, reserve_out) = Self::get_reserves(asset1, asset2)?; - let prev_amount = amounts.last().expect("Always has at least one element"); - let amount_out = Self::get_amount_out(prev_amount, &reserve_in, &reserve_out)?; - amounts.push(amount_out); - } + pub(crate) fn balance_path_from_amount_in( + amount_in: T::Balance, + path: Vec, + ) -> Result, DispatchError> { + let mut balance_path: BalancePath = Vec::with_capacity(path.len()); + let mut amount_out: T::Balance = amount_in; + + let mut iter = path.into_iter().peekable(); + while let Some(asset1) = iter.next() { + let asset2 = match iter.peek() { + Some(a) => a, + None => { + balance_path.push((asset1, amount_out)); + break + }, + }; + let (reserve_in, reserve_out) = Self::get_reserves(&asset1, asset2)?; + balance_path.push((asset1, amount_out)); + amount_out = Self::get_amount_out(&amount_out, &reserve_in, &reserve_out)?; } - - Ok(amounts) + Ok(balance_path) } /// Used by the RPC service to provide current prices. pub fn quote_price_exact_tokens_for_tokens( asset1: T::MultiAssetId, asset2: T::MultiAssetId, - amount: T::AssetBalance, + amount: T::Balance, include_fee: bool, - ) -> Option { + ) -> Option { let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); let pool_account = Self::get_pool_account(&pool_id); @@ -1043,9 +1172,9 @@ pub mod pallet { pub fn quote_price_tokens_for_exact_tokens( asset1: T::MultiAssetId, asset2: T::MultiAssetId, - amount: T::AssetBalance, + amount: T::Balance, include_fee: bool, - ) -> Option { + ) -> Option { let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); let pool_account = Self::get_pool_account(&pool_id); @@ -1064,18 +1193,18 @@ pub mod pallet { /// Calculates the optimal amount from the reserves. pub fn quote( - amount: &T::AssetBalance, - reserve1: &T::AssetBalance, - reserve2: &T::AssetBalance, - ) -> Result> { - // amount * reserve2 / reserve1 + amount: &T::Balance, + reserve1: &T::Balance, + reserve2: &T::Balance, + ) -> Result> { + // (amount * reserve2) / reserve1 Self::mul_div(amount, reserve2, reserve1) } pub(super) fn calc_lp_amount_for_zero_supply( - amount1: &T::AssetBalance, - amount2: &T::AssetBalance, - ) -> Result> { + amount1: &T::Balance, + amount2: &T::Balance, + ) -> Result> { let amount1 = T::HigherPrecisionBalance::from(*amount1); let amount2 = T::HigherPrecisionBalance::from(*amount2); @@ -1089,11 +1218,7 @@ pub mod pallet { result.try_into().map_err(|_| Error::::Overflow) } - fn mul_div( - a: &T::AssetBalance, - b: &T::AssetBalance, - c: &T::AssetBalance, - ) -> Result> { + fn mul_div(a: &T::Balance, b: &T::Balance, c: &T::Balance) -> Result> { let a = T::HigherPrecisionBalance::from(*a); let b = T::HigherPrecisionBalance::from(*b); let c = T::HigherPrecisionBalance::from(*c); @@ -1112,10 +1237,10 @@ pub mod pallet { /// Given an input amount of an asset and pair reserves, returns the maximum output amount /// of the other asset. pub fn get_amount_out( - amount_in: &T::AssetBalance, - reserve_in: &T::AssetBalance, - reserve_out: &T::AssetBalance, - ) -> Result> { + amount_in: &T::Balance, + reserve_in: &T::Balance, + reserve_out: &T::Balance, + ) -> Result> { let amount_in = T::HigherPrecisionBalance::from(*amount_in); let reserve_in = T::HigherPrecisionBalance::from(*reserve_in); let reserve_out = T::HigherPrecisionBalance::from(*reserve_out); @@ -1147,10 +1272,10 @@ pub mod pallet { /// Given an output amount of an asset and pair reserves, returns a required input amount /// of the other asset. pub fn get_amount_in( - amount_out: &T::AssetBalance, - reserve_in: &T::AssetBalance, - reserve_out: &T::AssetBalance, - ) -> Result> { + amount_out: &T::Balance, + reserve_in: &T::Balance, + reserve_out: &T::Balance, + ) -> Result> { let amount_out = T::HigherPrecisionBalance::from(*amount_out); let reserve_in = T::HigherPrecisionBalance::from(*reserve_in); let reserve_out = T::HigherPrecisionBalance::from(*reserve_out); @@ -1185,10 +1310,7 @@ pub mod pallet { } /// Ensure that a `value` meets the minimum balance requirements of an `asset` class. - fn validate_minimal_amount( - value: T::AssetBalance, - asset: &T::MultiAssetId, - ) -> Result<(), ()> { + fn validate_minimal_amount(value: T::Balance, asset: &T::MultiAssetId) -> Result<(), ()> { if T::MultiAssetIdConverter::is_native(asset) { let ed = T::Currency::minimum_balance(); ensure!( @@ -1208,18 +1330,16 @@ pub mod pallet { } /// Ensure that a path is valid. - fn validate_swap_path( - path: &BoundedVec, - ) -> Result<(), DispatchError> { + fn validate_swap_path(path: &Vec) -> Result<(), DispatchError> { ensure!(path.len() >= 2, Error::::InvalidPath); + ensure!(path.len() as u32 <= T::MaxSwapPathLength::get(), Error::::InvalidPath); // validate all the pools in the path are unique - let mut pools = BoundedBTreeSet::, T::MaxSwapPathLength>::new(); + let mut pools = BTreeSet::>::new(); for assets_pair in path.windows(2) { if let [asset1, asset2] = assets_pair { let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); - let new_element = - pools.try_insert(pool_id).map_err(|_| Error::::Overflow)?; + let new_element = pools.insert(pool_id); if !new_element { return Err(Error::::NonUniquePath.into()) } @@ -1238,69 +1358,24 @@ pub mod pallet { } } -impl Swap for Pallet { - fn swap_exact_tokens_for_tokens( - sender: T::AccountId, - path: Vec, - amount_in: T::HigherPrecisionBalance, - amount_out_min: Option, - send_to: T::AccountId, - keep_alive: bool, - ) -> Result { - let path = path.try_into().map_err(|_| Error::::PathError)?; - let amount_out_min = amount_out_min.map(Self::convert_hpb_to_asset_balance).transpose()?; - let amount_out = Self::do_swap_exact_tokens_for_tokens( - sender, - path, - Self::convert_hpb_to_asset_balance(amount_in)?, - amount_out_min, - send_to, - keep_alive, - )?; - Ok(amount_out.into()) - } - - fn swap_tokens_for_exact_tokens( - sender: T::AccountId, - path: Vec, - amount_out: T::HigherPrecisionBalance, - amount_in_max: Option, - send_to: T::AccountId, - keep_alive: bool, - ) -> Result { - let path = path.try_into().map_err(|_| Error::::PathError)?; - let amount_in_max = amount_in_max.map(Self::convert_hpb_to_asset_balance).transpose()?; - let amount_in = Self::do_swap_tokens_for_exact_tokens( - sender, - path, - Self::convert_hpb_to_asset_balance(amount_out)?, - amount_in_max, - send_to, - keep_alive, - )?; - Ok(amount_in.into()) - } -} - sp_api::decl_runtime_apis! { /// This runtime api allows people to query the size of the liquidity pools /// and quote prices for swaps. - pub trait AssetConversionApi where - Balance: Codec + MaybeDisplay, - AssetBalance: frame_support::traits::tokens::Balance, + pub trait AssetConversionApi where + Balance: frame_support::traits::tokens::Balance + MaybeDisplay, AssetId: Codec { /// Provides a quote for [`Pallet::swap_tokens_for_exact_tokens`]. /// /// Note that the price may have changed by the time the transaction is executed. /// (Use `amount_in_max` to control slippage.) - fn quote_price_tokens_for_exact_tokens(asset1: AssetId, asset2: AssetId, amount: AssetBalance, include_fee: bool) -> Option; + fn quote_price_tokens_for_exact_tokens(asset1: AssetId, asset2: AssetId, amount: Balance, include_fee: bool) -> Option; /// Provides a quote for [`Pallet::swap_exact_tokens_for_tokens`]. /// /// Note that the price may have changed by the time the transaction is executed. /// (Use `amount_out_min` to control slippage.) - fn quote_price_exact_tokens_for_tokens(asset1: AssetId, asset2: AssetId, amount: AssetBalance, include_fee: bool) -> Option; + fn quote_price_exact_tokens_for_tokens(asset1: AssetId, asset2: AssetId, amount: Balance, include_fee: bool) -> Option; /// Returns the size of the liquidity pool for the given asset pair. fn get_reserves(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)>; diff --git a/substrate/frame/asset-conversion/src/mock.rs b/substrate/frame/asset-conversion/src/mock.rs index c84263b07963..4850eb1e833c 100644 --- a/substrate/frame/asset-conversion/src/mock.rs +++ b/substrate/frame/asset-conversion/src/mock.rs @@ -154,7 +154,6 @@ ord_parameter_types! { impl Config for Test { type RuntimeEvent = RuntimeEvent; type Currency = Balances; - type AssetBalance = ::Balance; type AssetId = u32; type PoolAssetId = u32; type Assets = Assets; @@ -169,7 +168,7 @@ impl Config for Test { type MaxSwapPathLength = ConstU32<4>; type MintMinLiquidity = ConstU128<100>; // 100 is good enough when the main currency has 12 decimals. - type Balance = u128; + type Balance = ::Balance; type HigherPrecisionBalance = sp_core::U256; type MultiAssetId = NativeOrAssetId; diff --git a/substrate/frame/asset-conversion/src/swap.rs b/substrate/frame/asset-conversion/src/swap.rs new file mode 100644 index 000000000000..7f59b8f9b804 --- /dev/null +++ b/substrate/frame/asset-conversion/src/swap.rs @@ -0,0 +1,210 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits and implementations for swap between the various asset classes. + +use super::*; + +/// Trait for providing methods to swap between the various asset classes. +pub trait Swap { + /// Measure units of the asset classes for swapping. + type Balance: Balance; + /// Kind of assets that are going to be swapped. + type MultiAssetId; + + /// Returns the upper limit on the length of the swap path. + fn max_path_len() -> u32; + + /// Swap exactly `amount_in` of asset `path[0]` for asset `path[last]`. + /// If an `amount_out_min` is specified, it will return an error if it is unable to acquire + /// the amount desired. + /// + /// Withdraws the `path[0]` asset from `sender`, deposits the `path[last]` asset to `send_to`, + /// respecting `keep_alive`. + /// + /// If successful, returns the amount of `path[last]` acquired for the `amount_in`. + /// + /// This operation is expected to be atomic. + fn swap_exact_tokens_for_tokens( + sender: AccountId, + path: Vec, + amount_in: Self::Balance, + amount_out_min: Option, + send_to: AccountId, + keep_alive: bool, + ) -> Result; + + /// Take the `path[0]` asset and swap some amount for `amount_out` of the `path[last]`. If an + /// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be + /// too costly. + /// + /// Withdraws `path[0]` asset from `sender`, deposits `path[last]` asset to `send_to`, + /// respecting `keep_alive`. + /// + /// If successful returns the amount of the `path[0]` taken to provide `path[last]`. + /// + /// This operation is expected to be atomic. + fn swap_tokens_for_exact_tokens( + sender: AccountId, + path: Vec, + amount_out: Self::Balance, + amount_in_max: Option, + send_to: AccountId, + keep_alive: bool, + ) -> Result; +} + +/// Trait providing methods to swap between the various asset classes. +pub trait SwapCredit { + /// Measure units of the asset classes for swapping. + type Balance: Balance; + /// Kind of assets that are going to be swapped. + type MultiAssetId; + /// Credit implying a negative imbalance in the system that can be placed into an account or + /// alter the total supply. + type Credit; + + /// Returns the upper limit on the length of the swap path. + fn max_path_len() -> u32; + + /// Swap exactly `credit_in` of asset `path[0]` for asset `path[last]`. If `amount_out_min` is + /// provided and the swap can't achieve at least this amount, an error is returned. + /// + /// On a successful swap, the function returns the `credit_out` of `path[last]` obtained from + /// the `credit_in`. On failure, it returns an `Err` containing the original `credit_in` and the + /// associated error code. + /// + /// This operation is expected to be atomic. + fn swap_exact_tokens_for_tokens( + path: Vec, + credit_in: Self::Credit, + amount_out_min: Option, + ) -> Result; + + /// Swaps a portion of `credit_in` of `path[0]` asset to obtain the desired `amount_out` of + /// the `path[last]` asset. The provided `credit_in` must be adequate to achieve the target + /// `amount_out`, or an error will occur. + /// + /// On success, the function returns a (`credit_out`, `credit_change`) tuple, where `credit_out` + /// represents the acquired amount of the `path[last]` asset, and `credit_change` is the + /// remaining portion from the `credit_in`. On failure, an `Err` with the initial `credit_in` + /// and error code is returned. + /// + /// This operation is expected to be atomic. + fn swap_tokens_for_exact_tokens( + path: Vec, + credit_in: Self::Credit, + amount_out: Self::Balance, + ) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)>; +} + +impl Swap for Pallet { + type Balance = T::Balance; + type MultiAssetId = T::MultiAssetId; + + fn max_path_len() -> u32 { + T::MaxSwapPathLength::get() + } + + fn swap_exact_tokens_for_tokens( + sender: T::AccountId, + path: Vec, + amount_in: Self::Balance, + amount_out_min: Option, + send_to: T::AccountId, + keep_alive: bool, + ) -> Result { + let amount_out = with_storage_layer(|| { + Self::do_swap_exact_tokens_for_tokens( + sender, + path, + amount_in, + amount_out_min, + send_to, + keep_alive, + ) + })?; + Ok(amount_out.into()) + } + + fn swap_tokens_for_exact_tokens( + sender: T::AccountId, + path: Vec, + amount_out: Self::Balance, + amount_in_max: Option, + send_to: T::AccountId, + keep_alive: bool, + ) -> Result { + let amount_in = with_storage_layer(|| { + Self::do_swap_tokens_for_exact_tokens( + sender, + path, + amount_out, + amount_in_max, + send_to, + keep_alive, + ) + })?; + Ok(amount_in.into()) + } +} + +impl SwapCredit for Pallet { + type Balance = T::Balance; + type MultiAssetId = T::MultiAssetId; + type Credit = Credit; + + fn max_path_len() -> u32 { + T::MaxSwapPathLength::get() + } + + fn swap_exact_tokens_for_tokens( + path: Vec, + credit_in: Self::Credit, + amount_out_min: Option, + ) -> Result { + with_transaction(|| -> TransactionOutcome> { + let res = Self::do_swap_exact_credit_tokens_for_tokens(path, credit_in, amount_out_min); + match &res { + Ok(_) => TransactionOutcome::Commit(Ok(res)), + // wrapping `res` with `Ok`, since our `Err` doesn't satisfy the + // `From` bound of the `with_transaction` function. + Err(_) => TransactionOutcome::Rollback(Ok(res)), + } + }) + // should never map an error since `with_transaction` above never returns it. + .map_err(|_| (Self::Credit::native_zero(), DispatchError::Corruption))? + } + + fn swap_tokens_for_exact_tokens( + path: Vec, + credit_in: Self::Credit, + amount_out: Self::Balance, + ) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)> { + with_transaction(|| -> TransactionOutcome> { + let res = Self::do_swap_credit_tokens_for_exact_tokens(path, credit_in, amount_out); + match &res { + Ok(_) => TransactionOutcome::Commit(Ok(res)), + // wrapping `res` with `Ok`, since our `Err` doesn't satisfy the + // `From` bound of the `with_transaction` function. + Err(_) => TransactionOutcome::Rollback(Ok(res)), + } + }) + // should never map an error since `with_transaction` above never returns it. + .map_err(|_| (Self::Credit::native_zero(), DispatchError::Corruption))? + } +} diff --git a/substrate/frame/asset-conversion/src/tests.rs b/substrate/frame/asset-conversion/src/tests.rs index 1c1267ab87b3..db6aca01dece 100644 --- a/substrate/frame/asset-conversion/src/tests.rs +++ b/substrate/frame/asset-conversion/src/tests.rs @@ -17,9 +17,9 @@ use crate::{mock::*, *}; use frame_support::{ - assert_noop, assert_ok, + assert_noop, assert_ok, assert_storage_noop, instances::Instance1, - traits::{fungible::Inspect, fungibles::InspectEnumerable, Get}, + traits::{fungible, fungibles, fungibles::InspectEnumerable, Get}, }; use sp_arithmetic::Permill; use sp_runtime::{DispatchError, TokenError}; @@ -65,13 +65,17 @@ fn pool_assets() -> Vec { } fn create_tokens(owner: u128, tokens: Vec>) { + create_tokens_with_ed(owner, tokens, 1) +} + +fn create_tokens_with_ed(owner: u128, tokens: Vec>, ed: u128) { for token_id in tokens { let MultiAssetIdConversionResult::Converted(asset_id) = NativeOrAssetIdConverter::try_convert(&token_id) else { unreachable!("invalid token") }; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, owner, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, owner, false, ed)); } } @@ -91,8 +95,8 @@ fn get_ed() -> u128 { } macro_rules! bvec { - ($( $x:tt )*) => { - vec![$( $x )*].try_into().unwrap() + ($( $x:ident ),*) => { + vec![$( Box::new( $x ), )*] } } @@ -145,7 +149,11 @@ fn can_create_pool() { let lp_token = AssetConversion::get_next_pool_asset_id(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 1000)); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_1)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2), + Box::new(token_1) + )); let setup_fee = <::PoolSetupFee as Get<::Balance>>::get(); let pool_account = <::PoolSetupFeeReceiver as Get>::get(); @@ -170,24 +178,40 @@ fn can_create_pool() { assert_eq!(pool_assets(), vec![lp_token]); assert_noop!( - AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_1), + AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_1) + ), Error::::EqualAssets ); assert_noop!( - AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_2), + AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2), + Box::new(token_2) + ), Error::::EqualAssets ); // validate we can create Asset(1)/Asset(2) pool let token_1 = NativeOrAssetId::Asset(1); create_tokens(user, vec![token_1]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); // validate we can force the first asset to be the Native currency only AllowMultiAssetPools::set(&false); let token_1 = NativeOrAssetId::Asset(3); assert_noop!( - AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2), + AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + ), Error::::PoolMustContainNativeCurrency ); }); @@ -203,19 +227,31 @@ fn create_same_pool_twice_should_fail() { create_tokens(user, vec![token_2]); let lp_token = AssetConversion::get_next_pool_asset_id(); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_1)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2), + Box::new(token_1) + )); let expected_free = lp_token + 1; assert_eq!(expected_free, AssetConversion::get_next_pool_asset_id()); assert_noop!( - AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_1), + AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2), + Box::new(token_1) + ), Error::::PoolExists ); assert_eq!(expected_free, AssetConversion::get_next_pool_asset_id()); // Try switching the same tokens around: assert_noop!( - AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2), + AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + ), Error::::PoolExists ); assert_eq!(expected_free, AssetConversion::get_next_pool_asset_id()); @@ -235,7 +271,11 @@ fn different_pools_should_have_different_lp_tokens() { create_tokens(user, vec![token_2, token_3]); let lp_token2_1 = AssetConversion::get_next_pool_asset_id(); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_1)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2), + Box::new(token_1) + )); let lp_token3_1 = AssetConversion::get_next_pool_asset_id(); assert_eq!( @@ -248,7 +288,11 @@ fn different_pools_should_have_different_lp_tokens() { }] ); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_3, token_1)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_3), + Box::new(token_1) + )); assert_eq!( events(), [Event::::PoolCreated { @@ -273,9 +317,17 @@ fn can_add_liquidity() { create_tokens(user, vec![token_2, token_3]); let lp_token1 = AssetConversion::get_next_pool_asset_id(); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); let lp_token2 = AssetConversion::get_next_pool_asset_id(); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_3)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_3) + )); let ed = get_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 * 2 + ed)); @@ -284,8 +336,8 @@ fn can_add_liquidity() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 10000, 10, 10000, @@ -313,8 +365,8 @@ fn can_add_liquidity() { // try to pass the non-native - native assets, the result should be the same assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_3, - token_1, + Box::new(token_3), + Box::new(token_1), 10, 10000, 10, @@ -349,7 +401,11 @@ fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() { let token_2 = NativeOrAssetId::Asset(2); create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 1000)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); @@ -357,8 +413,8 @@ fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() { assert_noop!( AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 1, 1, 1, @@ -371,8 +427,8 @@ fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() { assert_noop!( AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), get_ed(), 1, 1, @@ -393,8 +449,16 @@ fn add_tiny_liquidity_directly_to_pool_address() { let token_3 = NativeOrAssetId::Asset(3); create_tokens(user, vec![token_2, token_3]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_3)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_3) + )); let ed = get_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 * 2 + ed)); @@ -407,8 +471,8 @@ fn add_tiny_liquidity_directly_to_pool_address() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 10000, 10, 10000, @@ -421,8 +485,8 @@ fn add_tiny_liquidity_directly_to_pool_address() { assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, pallet_account, 1)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_3, + Box::new(token_1), + Box::new(token_3), 10000, 10, 10000, @@ -442,15 +506,25 @@ fn can_remove_liquidity() { create_tokens(user, vec![token_2]); let lp_token = AssetConversion::get_next_pool_asset_id(); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000000000)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 100000)); + let ed_token_1 = >::minimum_balance(); + let ed_token_2 = >::minimum_balance(2); + assert_ok!(Balances::force_set_balance( + RuntimeOrigin::root(), + user, + 10000000000 + ed_token_1 + )); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 100000 + ed_token_2)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 1000000000, 100000, 1000000000, @@ -463,8 +537,8 @@ fn can_remove_liquidity() { assert_ok!(AssetConversion::remove_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), total_lp_received, 0, 0, @@ -487,8 +561,8 @@ fn can_remove_liquidity() { assert_eq!(balance(pool_account, token_2), 10001); assert_eq!(pool_balance(pool_account, lp_token), 100); - assert_eq!(balance(user, token_1), 10000000000 - 1000000000 + 899991000); - assert_eq!(balance(user, token_2), 89999); + assert_eq!(balance(user, token_1), 10000000000 - 1000000000 + 899991000 + ed_token_1); + assert_eq!(balance(user, token_2), 89999 + ed_token_2); assert_eq!(pool_balance(user, lp_token), 0); }); } @@ -502,15 +576,19 @@ fn can_not_redeem_more_lp_tokens_than_were_minted() { let lp_token = AssetConversion::get_next_pool_asset_id(); create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + get_ed())); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 10000, 10, 10000, @@ -524,8 +602,8 @@ fn can_not_redeem_more_lp_tokens_than_were_minted() { assert_noop!( AssetConversion::remove_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 216 + 1, // Try and redeem 10 lp tokens while only 9 minted. 0, 0, @@ -544,15 +622,19 @@ fn can_quote_price() { let token_2 = NativeOrAssetId::Asset(2); create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 100000)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 10000, 200, 1, @@ -765,15 +847,19 @@ fn quote_price_exact_tokens_for_tokens_matches_execution() { let token_2 = NativeOrAssetId::Asset(2); create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 100000)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 10000, 200, 1, @@ -813,15 +899,19 @@ fn quote_price_tokens_for_exact_tokens_matches_execution() { let token_2 = NativeOrAssetId::Asset(2); create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 100000)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 10000, 200, 1, @@ -864,7 +954,11 @@ fn can_swap_with_native() { let pool_id = (token_1, token_2); create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); let ed = get_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + ed)); @@ -875,8 +969,8 @@ fn can_swap_with_native() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), liquidity1, liquidity2, 1, @@ -914,7 +1008,11 @@ fn can_swap_with_realistic_values() { let dot = NativeOrAssetId::Native; let usd = NativeOrAssetId::Asset(2); create_tokens(user, vec![usd]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), dot, usd)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(dot), + Box::new(usd) + )); const UNIT: u128 = 1_000_000_000; @@ -925,8 +1023,8 @@ fn can_swap_with_realistic_values() { let liquidity_usd = 1_000_000 * UNIT; assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - dot, - usd, + Box::new(dot), + Box::new(usd), liquidity_dot, liquidity_usd, 1, @@ -948,9 +1046,9 @@ fn can_swap_with_realistic_values() { assert!(events().contains(&Event::::SwapExecuted { who: user, send_to: user, - path: bvec![usd, dot], amount_in: 10 * UNIT, // usd amount_out: 1_993_980_120, // About 2 dot after div by UNIT. + path: vec![(usd, 10 * UNIT), (dot, 1_993_980_120)], })); }); } @@ -963,7 +1061,11 @@ fn can_not_swap_in_pool_with_no_liquidity_added_yet() { let token_2 = NativeOrAssetId::Asset(2); create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); // Check can't swap an empty pool assert_noop!( @@ -990,7 +1092,11 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() { let lp_token = AssetConversion::get_next_pool_asset_id(); create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); let ed = get_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + ed)); @@ -1001,8 +1107,8 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), liquidity1, liquidity2, 1, @@ -1027,8 +1133,8 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() { assert_ok!(AssetConversion::remove_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), lp_token_minted, 1, 1, @@ -1050,7 +1156,7 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() { user, false, ), - Error::::ReserveLeftLessThanMinimal + TokenError::NotExpendable, ); assert_ok!(AssetConversion::swap_tokens_for_exact_tokens( @@ -1109,7 +1215,11 @@ fn swap_should_not_work_if_too_much_slippage() { let token_2 = NativeOrAssetId::Asset(2); create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + get_ed())); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); @@ -1119,8 +1229,8 @@ fn swap_should_not_work_if_too_much_slippage() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), liquidity1, liquidity2, 1, @@ -1153,7 +1263,11 @@ fn can_swap_tokens_for_exact_tokens() { let pool_id = (token_1, token_2); create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); let ed = get_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); @@ -1168,8 +1282,8 @@ fn can_swap_tokens_for_exact_tokens() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), liquidity1, liquidity2, 1, @@ -1215,7 +1329,11 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() { let lp_token = AssetConversion::get_next_pool_asset_id(); create_tokens(user2, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user2), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user2), + Box::new(token_1), + Box::new(token_2) + )); let ed = get_ed(); let base1 = 10000; @@ -1235,8 +1353,8 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user2), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), liquidity1, liquidity2, 1, @@ -1283,8 +1401,8 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() { assert_ok!(AssetConversion::remove_liquidity( RuntimeOrigin::signed(user2), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), lp_token_minted, 0, 0, @@ -1302,17 +1420,22 @@ fn swap_when_existential_deposit_would_cause_reaping_but_keep_alive_set() { let token_2 = NativeOrAssetId::Asset(2); create_tokens(user2, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user2), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user2), + Box::new(token_1), + Box::new(token_2) + )); let ed = get_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 101)); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 10000 + ed)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 2, user2, 1000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 2, user, 2)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user2), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 10000, 200, 1, @@ -1343,6 +1466,197 @@ fn swap_when_existential_deposit_would_cause_reaping_but_keep_alive_set() { ), DispatchError::Token(TokenError::NotExpendable) ); + + assert_noop!( + AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_2, token_1], + 51, // amount_out + 2, // amount_in_max + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_2, token_1], + 2, // amount_in + 1, // amount_out_min + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + }); +} + +#[test] +fn swap_when_existential_deposit_would_cause_reaping_pool_account() { + new_test_ext().execute_with(|| { + let user = 1; + let user2 = 2; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let token_3 = NativeOrAssetId::Asset(3); + + let ed_assets = 100; + create_tokens_with_ed(user2, vec![token_2, token_3], ed_assets); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user2), + Box::new(token_1), + Box::new(token_2) + )); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user2), + Box::new(token_1), + Box::new(token_3) + )); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user2), + Box::new(token_2), + Box::new(token_3) + )); + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 2, user2, 400 + ed_assets)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 3, user2, 20000 + ed_assets)); + + assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 2, user, 400 + ed_assets)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 3, user, 20000 + ed_assets)); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user2), + Box::new(token_1), + Box::new(token_2), + 10000, + 200, + 1, + 1, + user2, + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user2), + Box::new(token_1), + Box::new(token_3), + 200, + 10000, + 1, + 1, + user2, + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user2), + Box::new(token_2), + Box::new(token_3), + 200, + 10000, + 1, + 1, + user2, + )); + + // causes an account removal for asset token 2 + assert_noop!( + AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_1, token_2], + 110, // amount_out + 20000, // amount_in_max + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + // causes an account removal for asset token 2 + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_1, token_2], + 15000, // amount_in + 110, // amount_out_min + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + // causes an account removal for native token 1 + assert_noop!( + AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_3, token_1], + 110, // amount_out + 20000, // amount_in_max + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + // causes an account removal for native token 1 + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_3, token_1], + 15000, // amount_in + 110, // amount_out_min + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + // causes an account removal for native token 1 locate in the middle of a swap path + let amount_in = AssetConversion::balance_path_from_amount_out( + 110, + vec![token_3, token_1].try_into().unwrap(), + ) + .unwrap() + .first() + .map(|(_, a)| *a) + .unwrap(); + + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_3, token_1, token_2], + amount_in, // amount_in + 1, // amount_out_min + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + // causes an account removal for asset token 2 locate in the middle of a swap path + let amount_in = AssetConversion::balance_path_from_amount_out( + 110, + vec![token_1, token_2].try_into().unwrap(), + ) + .unwrap() + .first() + .map(|(_, a)| *a) + .unwrap(); + + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_1, token_2, token_3], + amount_in, // amount_in + 1, // amount_out_min + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); }); } @@ -1354,7 +1668,11 @@ fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() { let token_2 = NativeOrAssetId::Asset(2); create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + get_ed())); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); @@ -1364,8 +1682,8 @@ fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), liquidity1, liquidity2, 1, @@ -1398,8 +1716,16 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { let token_3 = NativeOrAssetId::Asset(3); create_tokens(user, vec![token_2, token_3]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_3)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2), + Box::new(token_3) + )); let ed = get_ed(); let base1 = 10000; @@ -1414,8 +1740,8 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), liquidity1, liquidity2, 1, @@ -1424,8 +1750,8 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { )); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_2, - token_3, + Box::new(token_2), + Box::new(token_3), liquidity2, liquidity3, 1, @@ -1497,8 +1823,16 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() { let token_3 = NativeOrAssetId::Asset(3); create_tokens(user, vec![token_2, token_3]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_3)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2), + Box::new(token_3) + )); let ed = get_ed(); let base1 = 10000; @@ -1513,8 +1847,8 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), liquidity1, liquidity2, 1, @@ -1523,8 +1857,8 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() { )); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_2, - token_3, + Box::new(token_2), + Box::new(token_3), liquidity2, liquidity3, 1, @@ -1568,6 +1902,7 @@ fn can_not_swap_same_asset() { new_test_ext().execute_with(|| { let user = 1; let token_1 = NativeOrAssetId::Asset(1); + let token_2 = NativeOrAssetId::Native; create_tokens(user, vec![token_1]); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 1, user, 1000)); @@ -1577,8 +1912,8 @@ fn can_not_swap_same_asset() { assert_noop!( AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_1, + Box::new(token_1), + Box::new(token_1), liquidity1, liquidity2, 1, @@ -1604,7 +1939,7 @@ fn can_not_swap_same_asset() { assert_noop!( AssetConversion::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![NativeOrAssetId::Native, NativeOrAssetId::Native], + bvec![token_2, token_2], exchange_amount, 1, user, @@ -1664,7 +1999,11 @@ fn cannot_block_pool_creation() { // User can still create the pool create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); // User has to transfer one Asset(2) token to the pool account (otherwise add_liquidity will // fail with `AssetTwoDepositDidNotMeetMinimum`) @@ -1675,8 +2014,8 @@ fn cannot_block_pool_creation() { // add_liquidity shouldn't fail because of the number of consumers assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 10000, 100, 10000, @@ -1685,3 +2024,429 @@ fn cannot_block_pool_creation() { )); }); } + +#[test] +fn swap_transactional() { + new_test_ext().execute_with(|| { + let user = 1; + let user2 = 2; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let token_3 = NativeOrAssetId::Asset(3); + + let asset_ed = 150; + create_tokens_with_ed(user, vec![token_2, token_3], asset_ed); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_3) + )); + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 3, user, 1000)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user2, 1000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 3, user2, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2), + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_3), + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + let pool_1 = AssetConversion::get_pool_account(&(token_1, token_2)); + let pool_2 = AssetConversion::get_pool_account(&(token_1, token_3)); + + assert_eq!(Balances::balance(&pool_1), liquidity1); + assert_eq!(Assets::balance(2, &pool_1), liquidity2); + assert_eq!(Balances::balance(&pool_2), liquidity1); + assert_eq!(Assets::balance(3, &pool_2), liquidity2); + + // the amount that would cause a transfer from the last pool in the path to fail + let expected_out = liquidity2 - asset_ed + 1; + let amount_in = AssetConversion::balance_path_from_amount_out( + expected_out, + vec![token_2, token_1, token_3].try_into().unwrap(), + ) + .unwrap() + .first() + .map(|(_, a)| *a) + .unwrap(); + + // swap credit with `swap_tokens_for_exact_tokens` transactional + let credit_in = Assets::issue(2, amount_in); + let credit_in_err_expected = Assets::issue(2, amount_in); + // avoiding drop of any credit, to assert any storage mutation from an actual call. + let error; + assert_storage_noop!( + error = >::swap_tokens_for_exact_tokens( + vec![token_2, token_1, token_3], + credit_in.into(), + expected_out, + ) + .unwrap_err() + ); + assert_eq!(error, (credit_in_err_expected.into(), TokenError::NotExpendable.into())); + + // swap credit with `swap_exact_tokens_for_tokens` transactional + let credit_in = Assets::issue(2, amount_in); + let credit_in_err_expected = Assets::issue(2, amount_in); + // avoiding drop of any credit, to assert any storage mutation from an actual call. + let error; + assert_storage_noop!( + error = >::swap_exact_tokens_for_tokens( + vec![token_2, token_1, token_3], + credit_in.into(), + Some(expected_out), + ) + .unwrap_err() + ); + assert_eq!(error, (credit_in_err_expected.into(), TokenError::NotExpendable.into())); + + // swap with `swap_exact_tokens_for_tokens` transactional + assert_noop!( + >::swap_exact_tokens_for_tokens( + user2, + vec![token_2, token_1, token_3], + amount_in.into(), + Some(expected_out.into()), + user2, + true, + ), + TokenError::NotExpendable + ); + + // swap with `swap_exact_tokens_for_tokens` transactional + assert_noop!( + >::swap_tokens_for_exact_tokens( + user2, + vec![token_2, token_1, token_3], + expected_out.into(), + Some(amount_in.into()), + user2, + true, + ), + TokenError::NotExpendable + ); + + assert_eq!(Balances::balance(&pool_1), liquidity1); + assert_eq!(Assets::balance(2, &pool_1), liquidity2); + assert_eq!(Balances::balance(&pool_2), liquidity1); + assert_eq!(Assets::balance(3, &pool_2), liquidity2); + }) +} + +#[test] +fn swap_credit_returns_change() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2), + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + let expected_change = Balances::issue(100); + let expected_credit_out = Assets::issue(2, 20); + + let amount_in_max = + AssetConversion::get_amount_in(&expected_credit_out.peek(), &liquidity1, &liquidity2) + .unwrap(); + + let credit_in = Balances::issue(amount_in_max + expected_change.peek()); + assert_ok!( + >::swap_tokens_for_exact_tokens( + vec![token_1, token_2], + credit_in.into(), + expected_credit_out.peek(), + ), + (expected_credit_out.into(), expected_change.into()) + ); + }) +} + +#[test] +fn swap_credit_insufficient_amount_bounds() { + new_test_ext().execute_with(|| { + let user = 1; + let user2 = 2; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user2, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2), + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + // provided `credit_in` is not sufficient to swap for desired `amount_out_min` + let amount_out_min = 20; + let amount_in = + AssetConversion::get_amount_in(&(amount_out_min - 1), &liquidity2, &liquidity1) + .unwrap(); + let credit_in = Balances::issue(amount_in); + let expected_credit_in = Balances::issue(amount_in); + let error = >::swap_exact_tokens_for_tokens( + vec![token_1, token_2], + credit_in.into(), + Some(amount_out_min), + ) + .unwrap_err(); + assert_eq!( + error, + (expected_credit_in.into(), Error::::ProvidedMinimumNotSufficientForSwap.into()) + ); + + // provided `credit_in` is not sufficient to swap for desired `amount_out` + let amount_out = 20; + let amount_in_max = + AssetConversion::get_amount_in(&(amount_out - 1), &liquidity2, &liquidity1).unwrap(); + let credit_in = Balances::issue(amount_in_max); + let expected_credit_in = Balances::issue(amount_in_max); + let error = >::swap_tokens_for_exact_tokens( + vec![token_1, token_2], + credit_in.into(), + amount_out, + ) + .unwrap_err(); + assert_eq!( + error, + (expected_credit_in.into(), Error::::ProvidedMaximumNotSufficientForSwap.into()) + ); + }) +} + +#[test] +fn swap_credit_zero_amount() { + new_test_ext().execute_with(|| { + let user = 1; + let user2 = 2; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user2, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2), + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + // swap with zero credit fails for `swap_exact_tokens_for_tokens` + let credit_in = Credit::native_zero(); + let expected_credit_in = Credit::native_zero(); + let error = >::swap_exact_tokens_for_tokens( + vec![token_1, token_2], + credit_in, + None, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in, Error::::ZeroAmount.into())); + + // swap with zero credit fails for `swap_tokens_for_exact_tokens` + let credit_in = Credit::native_zero(); + let expected_credit_in = Credit::native_zero(); + let error = >::swap_tokens_for_exact_tokens( + vec![token_1, token_2], + credit_in, + 10, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in, Error::::ZeroAmount.into())); + + // swap with zero amount_out_min fails for `swap_exact_tokens_for_tokens` + let credit_in = Balances::issue(10); + let expected_credit_in = Balances::issue(10); + let error = >::swap_exact_tokens_for_tokens( + vec![token_1, token_2], + credit_in.into(), + Some(0), + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in.into(), Error::::ZeroAmount.into())); + + // swap with zero amount_out fails with `swap_tokens_for_exact_tokens` fails + let credit_in = Balances::issue(10); + let expected_credit_in = Balances::issue(10); + let error = >::swap_tokens_for_exact_tokens( + vec![token_1, token_2], + credit_in.into(), + 0, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in.into(), Error::::ZeroAmount.into())); + }); +} + +#[test] +fn swap_credit_invalid_path() { + new_test_ext().execute_with(|| { + let user = 1; + let user2 = 2; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2) + )); + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user2, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1), + Box::new(token_2), + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + // swap with credit_in.asset different from path[0] asset fails + let credit_in = Balances::issue(10); + let expected_credit_in = Balances::issue(10); + let error = >::swap_exact_tokens_for_tokens( + vec![token_2, token_1], + credit_in.into(), + None, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in.into(), Error::::InvalidPath.into())); + + // swap with credit_in.asset different from path[0] asset fails + let credit_in = Assets::issue(2, 10); + let expected_credit_in = Assets::issue(2, 10); + let error = >::swap_tokens_for_exact_tokens( + vec![token_1, token_2], + credit_in.into(), + 10, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in.into(), Error::::InvalidPath.into())); + + // swap with path.len < 2 fails + let credit_in = Balances::issue(10); + let expected_credit_in = Balances::issue(10); + let error = >::swap_exact_tokens_for_tokens( + vec![token_2], + credit_in.into(), + None, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in.into(), Error::::InvalidPath.into())); + + // swap with path.len < 2 fails + let credit_in = Assets::issue(2, 10); + let expected_credit_in = Assets::issue(2, 10); + let error = >::swap_tokens_for_exact_tokens( + vec![], + credit_in.into(), + 10, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in.into(), Error::::InvalidPath.into())); + }); +} diff --git a/substrate/frame/asset-conversion/src/types.rs b/substrate/frame/asset-conversion/src/types.rs index ffdc63ce0ce7..d5ad8f590653 100644 --- a/substrate/frame/asset-conversion/src/types.rs +++ b/substrate/frame/asset-conversion/src/types.rs @@ -27,6 +27,16 @@ use sp_std::{cmp::Ordering, marker::PhantomData}; /// migration. pub(super) type PoolIdOf = (::MultiAssetId, ::MultiAssetId); +/// Represents a swap path with associated asset amounts indicating how much of the asset needs to +/// be deposited to get the following asset's amount withdrawn (this is inclusive of fees). +/// +/// Example: +/// Given path [(asset1, amount_in), (asset2, amount_out2), (asset3, amount_out3)], can be resolved: +/// 1. `asset(asset1, amount_in)` take from `user` and move to the pool(asset1, asset2); +/// 2. `asset(asset2, amount_out2)` transfer from pool(asset1, asset2) to pool(asset2, asset3); +/// 3. `asset(asset3, amount_out3)` move from pool(asset2, asset3) to `user`. +pub(super) type BalancePath = Vec<(::MultiAssetId, ::Balance)>; + /// Stores the lp_token asset id a particular pool has been assigned. #[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] pub struct PoolInfo { @@ -83,43 +93,6 @@ where } } -/// Trait for providing methods to swap between the various asset classes. -pub trait Swap { - /// Swap exactly `amount_in` of asset `path[0]` for asset `path[1]`. - /// If an `amount_out_min` is specified, it will return an error if it is unable to acquire - /// the amount desired. - /// - /// Withdraws the `path[0]` asset from `sender`, deposits the `path[1]` asset to `send_to`, - /// respecting `keep_alive`. - /// - /// If successful, returns the amount of `path[1]` acquired for the `amount_in`. - fn swap_exact_tokens_for_tokens( - sender: AccountId, - path: Vec, - amount_in: Balance, - amount_out_min: Option, - send_to: AccountId, - keep_alive: bool, - ) -> Result; - - /// Take the `path[0]` asset and swap some amount for `amount_out` of the `path[1]`. If an - /// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be - /// too costly. - /// - /// Withdraws `path[0]` asset from `sender`, deposits `path[1]` asset to `send_to`, - /// respecting `keep_alive`. - /// - /// If successful returns the amount of the `path[0]` taken to provide `path[1]`. - fn swap_tokens_for_exact_tokens( - sender: AccountId, - path: Vec, - amount_out: Balance, - amount_in_max: Option, - send_to: AccountId, - keep_alive: bool, - ) -> Result; -} - /// An implementation of MultiAssetId that can be either Native or an asset. #[derive(Decode, Encode, Default, MaxEncodedLen, TypeInfo, Clone, Copy, Debug)] pub enum NativeOrAssetId @@ -186,3 +159,99 @@ impl MultiAssetIdConverter, Asset } } } + +/// Credit of [Config::Currency]. +/// +/// Implies a negative imbalance in the system that can be placed into an account or alter the total +/// supply. +pub type NativeCredit = + CreditFungible<::AccountId, ::Currency>; + +/// Credit (aka negative imbalance) of [Config::Assets]. +/// +/// Implies a negative imbalance in the system that can be placed into an account or alter the total +/// supply. +pub type AssetCredit = + CreditFungibles<::AccountId, ::Assets>; + +/// Credit that can be either [`NativeCredit`] or [`AssetCredit`]. +/// +/// Implies a negative imbalance in the system that can be placed into an account or alter the total +/// supply. +#[derive(RuntimeDebug, Eq, PartialEq)] +pub enum Credit { + /// Native credit. + Native(NativeCredit), + /// Asset credit. + Asset(AssetCredit), +} + +impl From> for Credit { + fn from(value: NativeCredit) -> Self { + Credit::Native(value) + } +} + +impl From> for Credit { + fn from(value: AssetCredit) -> Self { + Credit::Asset(value) + } +} + +impl TryInto> for Credit { + type Error = (); + fn try_into(self) -> Result, ()> { + match self { + Credit::Native(c) => Ok(c), + _ => Err(()), + } + } +} + +impl TryInto> for Credit { + type Error = (); + fn try_into(self) -> Result, ()> { + match self { + Credit::Asset(c) => Ok(c), + _ => Err(()), + } + } +} + +impl Credit { + /// Create zero native credit. + pub fn native_zero() -> Self { + NativeCredit::::zero().into() + } + + /// Amount of `self`. + pub fn peek(&self) -> T::Balance { + match self { + Credit::Native(c) => c.peek(), + Credit::Asset(c) => c.peek(), + } + } + + /// Asset class of `self`. + pub fn asset(&self) -> T::MultiAssetId { + match self { + Credit::Native(_) => T::MultiAssetIdConverter::get_native(), + Credit::Asset(c) => c.asset().into(), + } + } + + /// Consume `self` and return two independent instances; the first is guaranteed to be at most + /// `amount` and the second will be the remainder. + pub fn split(self, amount: T::Balance) -> (Self, Self) { + match self { + Credit::Native(c) => { + let (left, right) = c.split(amount); + (left.into(), right.into()) + }, + Credit::Asset(c) => { + let (left, right) = c.split(amount); + (left.into(), right.into()) + }, + } + } +} diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs index 081e8e53db2b..3f9766385178 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs @@ -238,7 +238,6 @@ ord_parameter_types! { impl pallet_asset_conversion::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; - type AssetBalance = ::Balance; type AssetId = u32; type PoolAssetId = u32; type Assets = Assets; diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs index 0d090211d035..ccea9e55bbed 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs @@ -83,8 +83,8 @@ impl OnChargeAssetTransaction for AssetConversionAdapter where T: Config, C: Inspect<::AccountId>, - CON: Swap, - T::HigherPrecisionBalance: From> + TryInto>, + CON: Swap, MultiAssetId = T::MultiAssetId>, + BalanceOf: Into>, T::MultiAssetId: From>, BalanceOf: IsType<::AccountId>>::Balance>, { @@ -117,22 +117,18 @@ where let asset_consumed = CON::swap_tokens_for_exact_tokens( who.clone(), vec![asset_id.into(), T::MultiAssetIdConverter::get_native()], - T::HigherPrecisionBalance::from(native_asset_required), + native_asset_required, None, who.clone(), true, ) .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?; - let asset_consumed = asset_consumed - .try_into() - .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?; - ensure!(asset_consumed > Zero::zero(), InvalidTransaction::Payment); // charge the fee in native currency ::withdraw_fee(who, call, info, fee, tip) - .map(|r| (r, native_asset_required, asset_consumed)) + .map(|r| (r, native_asset_required, asset_consumed.into())) } /// Correct the fee and swap the refund back to asset. @@ -175,8 +171,7 @@ where T::MultiAssetIdConverter::get_native(), // we provide the native asset_id.into(), // we want asset_id back ], - T::HigherPrecisionBalance::from(swap_back), /* amount of the native asset to - * convert to `asset_id` */ + swap_back, // amount of the native asset to convert to `asset_id` None, // no minimum amount back who.clone(), // we will refund to `who` false, // no need to keep alive diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs index 9e9b74a0ddb2..d50a05164235 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs @@ -19,7 +19,10 @@ use frame_support::{ assert_ok, dispatch::{DispatchInfo, PostDispatchInfo}, pallet_prelude::*, - traits::{fungible::Inspect, fungibles::Mutate}, + traits::{ + fungible::Inspect, + fungibles::{Inspect as FungiblesInspect, Mutate}, + }, weights::Weight, }; use frame_system as system; @@ -110,22 +113,32 @@ fn default_post_info() -> PostDispatchInfo { fn setup_lp(asset_id: u32, balance_factor: u64) { let lp_provider = 5; + let ed = Balances::minimum_balance(); + let ed_asset = Assets::minimum_balance(asset_id); assert_ok!(Balances::force_set_balance( RuntimeOrigin::root(), lp_provider, - 10_000 * balance_factor + 10_000 * balance_factor + ed, )); let lp_provider_account = ::Lookup::unlookup(lp_provider); - assert_ok!(Assets::mint_into(asset_id.into(), &lp_provider_account, 10_000 * balance_factor)); + assert_ok!(Assets::mint_into( + asset_id.into(), + &lp_provider_account, + 10_000 * balance_factor + ed_asset + )); let token_1 = NativeOrAssetId::Native; let token_2 = NativeOrAssetId::Asset(asset_id); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(lp_provider), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(lp_provider), + Box::new(token_1), + Box::new(token_2) + )); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(lp_provider), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 1_000 * balance_factor, // 1 desired 10_000 * balance_factor, // 2 desired 1, // 1 min