pallet-asset-conversion: Swap Credit (#1677)

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;

related issue: 
- https://github.com/paritytech/polkadot-sdk/issues/105

related PRs:
- (required for) https://github.com/paritytech/polkadot-sdk/pull/1845
- (caused) https://github.com/paritytech/polkadot-sdk/pull/1717

// DONE make the pallet work only with `fungibles` trait and make it
free from the concept of a `native` asset -
https://github.com/paritytech/polkadot-sdk/issues/1842

---------

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
This commit is contained in:
Muharem
2023-12-19 17:31:18 +01:00
committed by GitHub
parent 84d6342cd2
commit 5ce04514eb
20 changed files with 3932 additions and 647 deletions
@@ -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 = <AssetHubRococo as Chain>::RuntimeEvent;
@@ -47,8 +46,8 @@ fn swap_locally_on_chain_using_local_assets() {
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::create_pool(
<AssetHubRococo as Chain>::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!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::add_liquidity(
<AssetHubRococo as Chain>::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!(
<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::swap_exact_tokens_for_tokens(
@@ -101,8 +100,8 @@ fn swap_locally_on_chain_using_local_assets() {
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::remove_liquidity(
<AssetHubRococo as Chain>::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!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::create_pool(
<AssetHubRococo as Chain>::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!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::add_liquidity(
<AssetHubRococo as Chain>::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!(
<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::swap_exact_tokens_for_tokens(
@@ -231,8 +226,8 @@ fn swap_locally_on_chain_using_foreign_assets() {
// 7. Remove liquidity
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::remove_liquidity(
<AssetHubRococo as Chain>::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!(
<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::create_pool(
<AssetHubRococo as Chain>::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"))
@@ -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 = <AssetHubWestend as Chain>::RuntimeEvent;
@@ -44,8 +44,8 @@ fn swap_locally_on_chain_using_local_assets() {
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::create_pool(
<AssetHubWestend as Chain>::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!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::add_liquidity(
<AssetHubWestend as Chain>::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!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::swap_exact_tokens_for_tokens(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
@@ -96,8 +96,8 @@ fn swap_locally_on_chain_using_local_assets() {
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::remove_liquidity(
<AssetHubWestend as Chain>::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!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::create_pool(
<AssetHubWestend as Chain>::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!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::add_liquidity(
<AssetHubWestend as Chain>::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!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::swap_exact_tokens_for_tokens(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
@@ -224,19 +220,19 @@ fn swap_locally_on_chain_using_foreign_assets() {
// 7. Remove liquidity
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::remove_liquidity(
<AssetHubWestend as Chain>::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!(
<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::create_pool(
<AssetHubWestend as Chain>::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"))
File diff suppressed because it is too large Load Diff
@@ -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<NetworkId> = 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(<Assets as PalletInfoAccess>::index() as u8).into();
pub ForeignAssetsPalletLocation: MultiLocation =
PalletInstance(<ForeignAssets as PalletInfoAccess>::index() as u8).into();
pub PoolAssetsPalletLocation: MultiLocation =
PalletInstance(<PoolAssets as PalletInfoAccess>::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<AccountId>,
// Sibling parachain origins convert to AccountId via the `ParaId::into`.
SiblingParachainConvertsVia<Sibling, AccountId>,
// Straight up local `AccountId32` origins just alias directly to `AccountId`.
AccountId32Aliases<RelayNetwork, AccountId>,
// Foreign locations alias into accounts according to a hash of their standard description.
HashedDescription<AccountId, DescribeFamily<DescribeAllTerminal>>,
);
/// 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<KsmLocation>,
// 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<TrustBackedAssetsPalletLocation, Balance>;
/// 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<parachains_common::impls::NonZeroIssuance<AccountId, Assets>>,
// The account to use for tracking teleports.
CheckingAccount,
>;
/// `AssetId/Balance` converter for `TrustBackedAssets`
pub type ForeignAssetsConvertedConcreteId = assets_common::ForeignAssetsConvertedConcreteId<
(
// Ignore `TrustBackedAssets` explicitly
StartsWith<TrustBackedAssetsPalletLocation>,
// 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<UniversalLocationNetworkId>,
),
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<PoolAssetsPalletLocation, Balance>;
/// 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<parachains_common::impls::NonZeroIssuance<AccountId, PoolAssets>>,
// 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<MultiLocation> 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<LocationToAccountId, RuntimeOrigin>,
// Native converter for Relay-chain (Parent) location; will convert to a `Relay` origin when
// recognised.
RelayChainAsNative<RelayChainOrigin, RuntimeOrigin>,
// Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
// recognised.
SiblingParachainAsNative<cumulus_pallet_xcm::Origin, RuntimeOrigin>,
// Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a
// transaction from the Root origin.
ParentAsSuperuser<RuntimeOrigin>,
// Native signed account converter; this just converts an `AccountId32` origin into a normal
// `RuntimeOrigin::Signed` origin of the same 32-byte value.
SignedAccountId32AsNative<RelayNetwork, RuntimeOrigin>,
// Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
XcmPassthrough<RuntimeOrigin>,
);
parameter_types! {
pub const MaxInstructions: u32 = 100;
pub const MaxAssetsIntoHolding: u32 = 64;
pub XcmAssetFeesReceiver: Option<AccountId> = Authorship::author();
}
match_types! {
pub type ParentOrParentsPlurality: impl Contains<MultiLocation> = {
MultiLocation { parents: 1, interior: Here } |
MultiLocation { parents: 1, interior: X1(Plurality { .. }) }
};
pub type ParentOrSiblings: impl Contains<MultiLocation> = {
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<RuntimeCall> 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<PolkadotXcm>,
// 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<Everything>,
// Parent and its pluralities (i.e. governance bodies) get free execution.
AllowExplicitUnpaidExecutionFrom<ParentOrParentsPlurality>,
// Subscriptions for version tracking are OK.
AllowSubscriptionsFrom<ParentOrSiblings>,
),
UniversalLocation,
ConstU32<8>,
>,
),
>,
>;
pub type AssetFeeAsExistentialDepositMultiplierFeeCharger = AssetFeeAsExistentialDepositMultiplier<
Runtime,
WeightToFee,
pallet_assets::BalanceToAssetBalance<Balances, Runtime, ConvertInto, TrustBackedAssetsInstance>,
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<KsmLocation>,
IsForeignConcreteAsset<FromSiblingParachain<parachain_info::Pallet<Runtime>>>,
);
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>,
RuntimeCall,
MaxInstructions,
>;
type Trader = (
UsingComponents<WeightToFee, KsmLocation, AccountId, Balances, ToStakingPot<Runtime>>,
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<SafeCallFilter>;
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<RuntimeOrigin, AccountId, RelayNetwork>;
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<FeeAssetId, BaseDeliveryFee, TransactionByteFee, ParachainSystem>;
/// 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<ParachainSystem, PolkadotXcm, PriceForParentDelivery>,
// ..and XCMP to communicate with the sibling chains.
XcmpQueue,
)>;
#[cfg(feature = "runtime-benchmarks")]
parameter_types! {
pub ReachableDest: Option<MultiLocation> = 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<RuntimeOrigin, ()>;
type XcmRouter = XcmRouter;
// We support local origins dispatching XCM executions in principle...
type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
// ... but disallow generic XCM execution. As a result only teleports and reserve transfers are
// allowed.
type XcmExecuteFilter = Nothing;
type XcmExecutor = XcmExecutor<XcmConfig>;
type XcmTeleportFilter = Everything;
type XcmReserveTransferFilter = Everything;
type Weigher = WeightInfoBounds<
crate::weights::xcm::AssetHubKusamaXcmWeight<RuntimeCall>,
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<Runtime>;
#[cfg(feature = "runtime-benchmarks")]
type ReachableDest = ReachableDest;
type AdminOrigin = EnsureRoot<AccountId>;
type MaxRemoteLockConsumers = ConstU32<0>;
type RemoteLockConsumerIdentifier = ();
}
impl cumulus_pallet_xcm::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type XcmExecutor = XcmExecutor<XcmConfig>;
}
pub type ForeignCreatorsSovereignAccountOf = (
SiblingParachainConvertsVia<Sibling, AccountId>,
AccountId32Aliases<RelayNetwork, AccountId>,
ParentIsPreset<AccountId>,
);
/// Simple conversion of `u32` into an `AssetId` for use in benchmarking.
pub struct XcmBenchmarkHelper;
#[cfg(feature = "runtime-benchmarks")]
impl pallet_assets::BenchmarkHelper<MultiLocation> for XcmBenchmarkHelper {
fn create_asset_id_parameter(id: u32) -> MultiLocation {
MultiLocation { parents: 1, interior: X1(Parachain(id)) }
}
}
#[cfg(feature = "runtime-benchmarks")]
pub struct BenchmarkMultiLocationConverter<SelfParaId> {
_phantom: sp_std::marker::PhantomData<SelfParaId>,
}
#[cfg(feature = "runtime-benchmarks")]
impl<SelfParaId> pallet_asset_conversion::BenchmarkHelper<MultiLocation, MultiLocation>
for BenchmarkMultiLocationConverter<SelfParaId>
where
SelfParaId: Get<ParaId>,
{
fn asset_id(asset_id: u32) -> MultiLocation {
MultiLocation {
parents: 1,
interior: X3(
Parachain(SelfParaId::get().into()),
PalletInstance(<Assets as PalletInfoAccess>::index() as u8),
GeneralIndex(asset_id.into()),
),
}
}
fn multiasset_id(asset_id: u32) -> MultiLocation {
Self::asset_id(asset_id)
}
}
@@ -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<MultiLocation>;
type MultiAssetId = MultiLocation;
type MultiAssetIdConverter =
MultiLocationConverter<TokenLocation, LocalAndForeignAssetsMultiLocationMatcher>;
type MintMinLiquidity = ConstU128<100>;
@@ -1143,17 +1142,18 @@ impl_runtime_apis! {
impl pallet_asset_conversion::AssetConversionApi<
Block,
Balance,
u128,
Box<MultiLocation>,
MultiLocation,
> for Runtime
{
fn quote_price_exact_tokens_for_tokens(asset1: Box<MultiLocation>, asset2: Box<MultiLocation>, amount: u128, include_fee: bool) -> Option<Balance> {
fn quote_price_exact_tokens_for_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option<Balance> {
AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee)
}
fn quote_price_tokens_for_exact_tokens(asset1: Box<MultiLocation>, asset2: Box<MultiLocation>, amount: u128, include_fee: bool) -> Option<Balance> {
fn quote_price_tokens_for_exact_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option<Balance> {
AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee)
}
fn get_reserves(asset1: Box<MultiLocation>, asset2: Box<MultiLocation>) -> Option<(Balance, Balance)> {
fn get_reserves(asset1: MultiLocation, asset2: MultiLocation) -> Option<(Balance, Balance)> {
AssetConversion::get_reserves(&asset1, &asset2).ok()
}
}
@@ -678,8 +678,7 @@ pub struct BenchmarkMultiLocationConverter<SelfParaId> {
}
#[cfg(feature = "runtime-benchmarks")]
impl<SelfParaId>
pallet_asset_conversion::BenchmarkHelper<MultiLocation, sp_std::boxed::Box<MultiLocation>>
impl<SelfParaId> pallet_asset_conversion::BenchmarkHelper<MultiLocation, MultiLocation>
for BenchmarkMultiLocationConverter<SelfParaId>
where
SelfParaId: frame_support::traits::Get<ParaId>,
@@ -694,8 +693,8 @@ where
),
}
}
fn multiasset_id(asset_id: u32) -> sp_std::boxed::Box<MultiLocation> {
sp_std::boxed::Box::new(Self::asset_id(asset_id))
fn multiasset_id(asset_id: u32) -> MultiLocation {
Self::asset_id(asset_id)
}
}
@@ -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<MultiLocation>;
type MultiAssetId = MultiLocation;
type MultiAssetIdConverter =
MultiLocationConverter<WestendLocation, LocalAndForeignAssetsMultiLocationMatcher>;
type MintMinLiquidity = ConstU128<100>;
@@ -1218,19 +1217,18 @@ impl_runtime_apis! {
impl pallet_asset_conversion::AssetConversionApi<
Block,
Balance,
u128,
Box<MultiLocation>,
MultiLocation,
> for Runtime
{
fn quote_price_exact_tokens_for_tokens(asset1: Box<MultiLocation>, asset2: Box<MultiLocation>, amount: u128, include_fee: bool) -> Option<Balance> {
fn quote_price_exact_tokens_for_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option<Balance> {
AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee)
}
fn quote_price_tokens_for_exact_tokens(asset1: Box<MultiLocation>, asset2: Box<MultiLocation>, amount: u128, include_fee: bool) -> Option<Balance> {
fn quote_price_tokens_for_exact_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option<Balance> {
AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee)
}
fn get_reserves(asset1: Box<MultiLocation>, asset2: Box<MultiLocation>) -> 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<T>(sp_std::marker::PhantomData<T>);
impl<
T: pallet_asset_conversion::Config<
MultiAssetId = Box<MultiLocation>,
AssetId = MultiLocation,
>,
T: pallet_asset_conversion::Config<MultiAssetId = MultiLocation, AssetId = MultiLocation>,
> OnRuntimeUpgrade for NativeAssetParents0ToParents1Migration<T>
where
<T as pallet_asset_conversion::Config>::PoolAssetId: Into<u32>,
@@ -1739,15 +1734,15 @@ pub mod migrations {
pallet_asset_conversion::Pallet::<T>::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::<T>::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::<T>::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);
@@ -700,8 +700,7 @@ pub struct BenchmarkMultiLocationConverter<SelfParaId> {
}
#[cfg(feature = "runtime-benchmarks")]
impl<SelfParaId>
pallet_asset_conversion::BenchmarkHelper<MultiLocation, sp_std::boxed::Box<MultiLocation>>
impl<SelfParaId> pallet_asset_conversion::BenchmarkHelper<MultiLocation, MultiLocation>
for BenchmarkMultiLocationConverter<SelfParaId>
where
SelfParaId: Get<ParaId>,
@@ -717,8 +716,8 @@ where
}
}
fn multiasset_id(asset_id: u32) -> sp_std::boxed::Box<MultiLocation> {
sp_std::boxed::Box::new(Self::asset_id(asset_id))
fn multiasset_id(asset_id: u32) -> MultiLocation {
Self::asset_id(asset_id)
}
}
@@ -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<NativeAssetLocation: Get<MultiLocation>, MultiLocationMatcher> {
_phantom: PhantomData<(NativeAssetLocation, MultiLocationMatcher)>,
}
impl<NativeAssetLocation, MultiLocationMatcher>
MultiAssetIdConverter<Box<MultiLocation>, MultiLocation>
impl<NativeAssetLocation, MultiLocationMatcher> MultiAssetIdConverter<MultiLocation, MultiLocation>
for MultiLocationConverter<NativeAssetLocation, MultiLocationMatcher>
where
NativeAssetLocation: Get<MultiLocation>,
MultiLocationMatcher: Contains<MultiLocation>,
{
fn get_native() -> Box<MultiLocation> {
Box::new(NativeAssetLocation::get())
fn get_native() -> MultiLocation {
NativeAssetLocation::get()
}
fn is_native(asset_id: &Box<MultiLocation>) -> bool {
fn is_native(asset_id: &MultiLocation) -> bool {
*asset_id == Self::get_native()
}
fn try_convert(
asset_id: &Box<MultiLocation>,
) -> MultiAssetIdConversionResult<Box<MultiLocation>, MultiLocation> {
if Self::is_native(&asset_id) {
asset_id: &MultiLocation,
) -> MultiAssetIdConversionResult<MultiLocation, MultiLocation> {
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)
);
}
+22
View File
@@ -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: [ ]
+2 -4
View File
@@ -1653,7 +1653,6 @@ parameter_types! {
impl pallet_asset_conversion::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type AssetBalance = <Self as pallet_balances::Config>::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<u32>
> for Runtime
{
fn quote_price_exact_tokens_for_tokens(asset1: NativeOrAssetId<u32>, asset2: NativeOrAssetId<u32>, amount: u128, include_fee: bool) -> Option<Balance> {
fn quote_price_exact_tokens_for_tokens(asset1: NativeOrAssetId<u32>, asset2: NativeOrAssetId<u32>, amount: Balance, include_fee: bool) -> Option<Balance> {
AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee)
}
fn quote_price_tokens_for_exact_tokens(asset1: NativeOrAssetId<u32>, asset2: NativeOrAssetId<u32>, amount: u128, include_fee: bool) -> Option<Balance> {
fn quote_price_tokens_for_exact_tokens(asset1: NativeOrAssetId<u32>, asset2: NativeOrAssetId<u32>, amount: Balance, include_fee: bool) -> Option<Balance> {
AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee)
}
@@ -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<T: Config>(asset: &T::MultiAssetId) -> (T::AccountId, AccountIdLookupOf<T>)
where
T::AssetBalance: From<u128>,
T::Balance: From<u128>,
T::Currency: Unbalanced<T::AccountId>,
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
{
@@ -70,7 +69,7 @@ fn create_asset_and_pool<T: Config>(
asset2: &T::MultiAssetId,
) -> (T::PoolAssetId, T::AccountId, AccountIdLookupOf<T>)
where
T::AssetBalance: From<u128>,
T::Balance: From<u128>,
T::Currency: Unbalanced<T::AccountId>,
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
T::PoolAssetId: Into<u32>,
@@ -80,8 +79,8 @@ where
assert_ok!(AssetConversion::<T>::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::<T>();
@@ -99,7 +98,6 @@ fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
benchmarks! {
where_clause {
where
T::AssetBalance: From<u128> + Into<u128>,
T::Currency: Unbalanced<T::AccountId>,
T::Balance: From<u128> + Into<u128>,
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
@@ -110,7 +108,7 @@ benchmarks! {
let asset1 = T::MultiAssetIdConverter::get_native();
let asset2 = T::BenchmarkHelper::multiasset_id(0);
let (caller, _) = create_asset::<T>(&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::<T>();
let pool_id = (asset1.clone(), asset2.clone());
@@ -128,7 +126,7 @@ benchmarks! {
let (lp_token, caller, _) = create_asset_and_pool::<T>(&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::<T>::calc_lp_amount_for_zero_supply(&add_amount.into(), &1000.into()).unwrap().into();
@@ -157,8 +155,8 @@ benchmarks! {
AssetConversion::<T>::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 = <T::PoolAssets as Inspect<T::AccountId>>::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 = <T::PoolAssets as Inspect<T::AccountId>>::total_issuance(lp_token.clone());
assert_eq!(
@@ -185,8 +183,8 @@ benchmarks! {
AssetConversion::<T>::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::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), native.clone(), asset2.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(native.clone()),
Box::new(asset2.clone())
)?;
AssetConversion::<T>::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::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset1.clone(), asset2.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(asset1.clone()),
Box::new(asset2.clone())
)?;
let (_, _) = create_asset::<T>(&asset3);
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset2.clone(), asset3.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(asset2.clone()),
Box::new(asset3.clone())
)?;
AssetConversion::<T>::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::<T>::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::<T>::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::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), native.clone(), asset2.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(native.clone()),
Box::new(asset2.clone())
)?;
AssetConversion::<T>::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::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset1.clone(), asset2.clone())?;
AssetConversion::<T>::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::<T>(&asset3);
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset2.clone(), asset3.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(asset2.clone()),
Box::new(asset3.clone())
)?;
AssetConversion::<T>::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::<T>::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)
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -154,7 +154,6 @@ ord_parameter_types! {
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type AssetBalance = <Self as pallet_balances::Config>::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 = <Self as pallet_balances::Config>::Balance;
type HigherPrecisionBalance = sp_core::U256;
type MultiAssetId = NativeOrAssetId<u32>;
@@ -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<AccountId> {
/// 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<Self::MultiAssetId>,
amount_in: Self::Balance,
amount_out_min: Option<Self::Balance>,
send_to: AccountId,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError>;
/// 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<Self::MultiAssetId>,
amount_out: Self::Balance,
amount_in_max: Option<Self::Balance>,
send_to: AccountId,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError>;
}
/// Trait providing methods to swap between the various asset classes.
pub trait SwapCredit<AccountId> {
/// 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<Self::MultiAssetId>,
credit_in: Self::Credit,
amount_out_min: Option<Self::Balance>,
) -> Result<Self::Credit, (Self::Credit, DispatchError)>;
/// 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<Self::MultiAssetId>,
credit_in: Self::Credit,
amount_out: Self::Balance,
) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)>;
}
impl<T: Config> Swap<T::AccountId> for Pallet<T> {
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<Self::MultiAssetId>,
amount_in: Self::Balance,
amount_out_min: Option<Self::Balance>,
send_to: T::AccountId,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError> {
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<Self::MultiAssetId>,
amount_out: Self::Balance,
amount_in_max: Option<Self::Balance>,
send_to: T::AccountId,
keep_alive: bool,
) -> Result<Self::Balance, DispatchError> {
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<T: Config> SwapCredit<T::AccountId> for Pallet<T> {
type Balance = T::Balance;
type MultiAssetId = T::MultiAssetId;
type Credit = Credit<T>;
fn max_path_len() -> u32 {
T::MaxSwapPathLength::get()
}
fn swap_exact_tokens_for_tokens(
path: Vec<Self::MultiAssetId>,
credit_in: Self::Credit,
amount_out_min: Option<Self::Balance>,
) -> Result<Self::Credit, (Self::Credit, DispatchError)> {
with_transaction(|| -> TransactionOutcome<Result<_, DispatchError>> {
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<DispatchError>` 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<Self::MultiAssetId>,
credit_in: Self::Credit,
amount_out: Self::Balance,
) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)> {
with_transaction(|| -> TransactionOutcome<Result<_, DispatchError>> {
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<DispatchError>` 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))?
}
}
File diff suppressed because it is too large Load Diff
+106 -37
View File
@@ -27,6 +27,16 @@ use sp_std::{cmp::Ordering, marker::PhantomData};
/// migration.
pub(super) type PoolIdOf<T> = (<T as Config>::MultiAssetId, <T as Config>::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<T> = Vec<(<T as Config>::MultiAssetId, <T as Config>::Balance)>;
/// Stores the lp_token asset id a particular pool has been assigned.
#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
pub struct PoolInfo<PoolAssetId> {
@@ -83,43 +93,6 @@ where
}
}
/// Trait for providing methods to swap between the various asset classes.
pub trait Swap<AccountId, Balance, MultiAssetId> {
/// 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<MultiAssetId>,
amount_in: Balance,
amount_out_min: Option<Balance>,
send_to: AccountId,
keep_alive: bool,
) -> Result<Balance, DispatchError>;
/// 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<MultiAssetId>,
amount_out: Balance,
amount_in_max: Option<Balance>,
send_to: AccountId,
keep_alive: bool,
) -> Result<Balance, DispatchError>;
}
/// An implementation of MultiAssetId that can be either Native or an asset.
#[derive(Decode, Encode, Default, MaxEncodedLen, TypeInfo, Clone, Copy, Debug)]
pub enum NativeOrAssetId<AssetId>
@@ -186,3 +159,99 @@ impl<AssetId: Ord + Clone> MultiAssetIdConverter<NativeOrAssetId<AssetId>, 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<T> =
CreditFungible<<T as frame_system::Config>::AccountId, <T as Config>::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<T> =
CreditFungibles<<T as frame_system::Config>::AccountId, <T as Config>::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<T: Config> {
/// Native credit.
Native(NativeCredit<T>),
/// Asset credit.
Asset(AssetCredit<T>),
}
impl<T: Config> From<NativeCredit<T>> for Credit<T> {
fn from(value: NativeCredit<T>) -> Self {
Credit::Native(value)
}
}
impl<T: Config> From<AssetCredit<T>> for Credit<T> {
fn from(value: AssetCredit<T>) -> Self {
Credit::Asset(value)
}
}
impl<T: Config> TryInto<NativeCredit<T>> for Credit<T> {
type Error = ();
fn try_into(self) -> Result<NativeCredit<T>, ()> {
match self {
Credit::Native(c) => Ok(c),
_ => Err(()),
}
}
}
impl<T: Config> TryInto<AssetCredit<T>> for Credit<T> {
type Error = ();
fn try_into(self) -> Result<AssetCredit<T>, ()> {
match self {
Credit::Asset(c) => Ok(c),
_ => Err(()),
}
}
}
impl<T: Config> Credit<T> {
/// Create zero native credit.
pub fn native_zero() -> Self {
NativeCredit::<T>::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())
},
}
}
}
@@ -238,7 +238,6 @@ ord_parameter_types! {
impl pallet_asset_conversion::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type AssetBalance = <Self as pallet_balances::Config>::Balance;
type AssetId = u32;
type PoolAssetId = u32;
type Assets = Assets;
@@ -83,8 +83,8 @@ impl<T, C, CON> OnChargeAssetTransaction<T> for AssetConversionAdapter<C, CON>
where
T: Config,
C: Inspect<<T as frame_system::Config>::AccountId>,
CON: Swap<T::AccountId, T::HigherPrecisionBalance, T::MultiAssetId>,
T::HigherPrecisionBalance: From<BalanceOf<T>> + TryInto<AssetBalanceOf<T>>,
CON: Swap<T::AccountId, Balance = BalanceOf<T>, MultiAssetId = T::MultiAssetId>,
BalanceOf<T>: Into<AssetBalanceOf<T>>,
T::MultiAssetId: From<AssetIdOf<T>>,
BalanceOf<T>: IsType<<C as Inspect<<T as frame_system::Config>::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
<T::OnChargeTransaction>::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
@@ -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 = <Runtime as system::Config>::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