feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
// 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 crate::local_and_foreign_assets::ForeignAssetReserveData;
|
||||
use core::{fmt::Debug, marker::PhantomData};
|
||||
use cumulus_primitives_core::ParaId;
|
||||
use pezsp_runtime::traits::Get;
|
||||
use xcm::latest::prelude::*;
|
||||
|
||||
/// Simple conversion of `u32` into an `AssetId` and `ReserveData` for use in benchmarking.
|
||||
pub struct LocationAssetsBenchmarkHelper;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl pezpallet_assets::BenchmarkHelper<xcm::v5::Location, ForeignAssetReserveData>
|
||||
for LocationAssetsBenchmarkHelper
|
||||
{
|
||||
fn create_asset_id_parameter(id: u32) -> xcm::v5::Location {
|
||||
xcm::v5::Location::new(1, [xcm::v5::Junction::Teyrchain(id)])
|
||||
}
|
||||
fn create_reserve_id_parameter(id: u32) -> ForeignAssetReserveData {
|
||||
(xcm::v5::Location::new(1, [xcm::v5::Junction::Teyrchain(id)]), false).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates asset pairs for liquidity pools with `Target` always being the first asset.
|
||||
pub struct AssetPairFactory<Target, SelfParaId, PalletId, L = Location>(
|
||||
PhantomData<(Target, SelfParaId, PalletId, L)>,
|
||||
);
|
||||
impl<Target: Get<L>, SelfParaId: Get<ParaId>, PalletId: Get<u32>, L: TryFrom<Location> + Debug>
|
||||
pezpallet_asset_conversion::BenchmarkHelper<L> for AssetPairFactory<Target, SelfParaId, PalletId, L>
|
||||
where
|
||||
<L as TryFrom<Location>>::Error: Debug,
|
||||
{
|
||||
fn create_pair(seed1: u32, seed2: u32) -> (L, L) {
|
||||
let with_id = Location::new(
|
||||
1,
|
||||
[
|
||||
Teyrchain(SelfParaId::get().into()),
|
||||
PalletInstance(PalletId::get() as u8),
|
||||
GeneralIndex(seed2.into()),
|
||||
],
|
||||
);
|
||||
if seed1 % 2 == 0 {
|
||||
(
|
||||
with_id
|
||||
.try_into()
|
||||
.map_err(|error| {
|
||||
tracing::error!(
|
||||
target: "xcm",
|
||||
?error,
|
||||
"Failed to create asset pairs when seed1 is even",
|
||||
);
|
||||
"Something went wrong"
|
||||
})
|
||||
.unwrap(),
|
||||
Target::get(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Target::get(),
|
||||
with_id
|
||||
.try_into()
|
||||
.map_err(|error| {
|
||||
tracing::error!(target: "xcm", ?error, "Failed to create asset pairs");
|
||||
"Something went wrong"
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezcumulus.
|
||||
// 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.
|
||||
|
||||
//! The ERC20 Asset Transactor.
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use ethereum_standards::IERC20;
|
||||
use pezframe_support::traits::{fungible::Inspect, OriginTrait};
|
||||
use pezframe_system::pezpallet_prelude::OriginFor;
|
||||
use pezpallet_revive::{
|
||||
precompiles::alloy::{
|
||||
primitives::{Address, U256 as EU256},
|
||||
sol_types::SolCall,
|
||||
},
|
||||
AddressMapper, ContractResult, ExecConfig, MomentOf,
|
||||
};
|
||||
use pezsp_core::{Get, H160, H256, U256};
|
||||
use pezsp_runtime::Weight;
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_executor::{
|
||||
traits::{ConvertLocation, Error as MatchError, MatchesFungibles, TransactAsset},
|
||||
AssetsInHolding,
|
||||
};
|
||||
|
||||
type BalanceOf<T> = <<T as pezpallet_revive::Config>::Currency as Inspect<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
>>::Balance;
|
||||
|
||||
/// An Asset Transactor that deals with ERC20 tokens.
|
||||
pub struct ERC20Transactor<
|
||||
T,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
GasLimit,
|
||||
StorageDepositLimit,
|
||||
AccountId,
|
||||
TransfersCheckingAccount,
|
||||
>(
|
||||
PhantomData<(
|
||||
T,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
GasLimit,
|
||||
StorageDepositLimit,
|
||||
AccountId,
|
||||
TransfersCheckingAccount,
|
||||
)>,
|
||||
);
|
||||
|
||||
impl<
|
||||
AccountId: Eq + Clone,
|
||||
T: pezpallet_revive::Config<AccountId = AccountId>,
|
||||
AccountIdConverter: ConvertLocation<AccountId>,
|
||||
Matcher: MatchesFungibles<H160, u128>,
|
||||
GasLimit: Get<Weight>,
|
||||
StorageDepositLimit: Get<BalanceOf<T>>,
|
||||
TransfersCheckingAccount: Get<AccountId>,
|
||||
> TransactAsset
|
||||
for ERC20Transactor<
|
||||
T,
|
||||
Matcher,
|
||||
AccountIdConverter,
|
||||
GasLimit,
|
||||
StorageDepositLimit,
|
||||
AccountId,
|
||||
TransfersCheckingAccount,
|
||||
>
|
||||
where
|
||||
BalanceOf<T>: Into<U256> + TryFrom<U256>,
|
||||
MomentOf<T>: Into<U256>,
|
||||
T::Hash: pezframe_support::traits::IsType<H256>,
|
||||
{
|
||||
fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
|
||||
// We don't support teleports.
|
||||
Err(XcmError::Unimplemented)
|
||||
}
|
||||
|
||||
fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {
|
||||
// We don't support teleports.
|
||||
}
|
||||
|
||||
fn can_check_out(_destination: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
|
||||
// We don't support teleports.
|
||||
Err(XcmError::Unimplemented)
|
||||
}
|
||||
|
||||
fn check_out(_destination: &Location, _what: &Asset, _context: &XcmContext) {
|
||||
// We don't support teleports.
|
||||
}
|
||||
|
||||
fn withdraw_asset_with_surplus(
|
||||
what: &Asset,
|
||||
who: &Location,
|
||||
_context: Option<&XcmContext>,
|
||||
) -> Result<(AssetsInHolding, Weight), XcmError> {
|
||||
tracing::trace!(
|
||||
target: "xcm::transactor::erc20::withdraw",
|
||||
?what, ?who,
|
||||
);
|
||||
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
|
||||
let who = AccountIdConverter::convert_location(who)
|
||||
.ok_or(MatchError::AccountIdConversionFailed)?;
|
||||
// We need to map the 32 byte checking account to a 20 byte account.
|
||||
let checking_account_eth = T::AddressMapper::to_address(&TransfersCheckingAccount::get());
|
||||
let checking_address = Address::from(Into::<[u8; 20]>::into(checking_account_eth));
|
||||
let gas_limit = GasLimit::get();
|
||||
// To withdraw, we actually transfer to the checking account.
|
||||
// We do this using the solidity ERC20 interface.
|
||||
let data =
|
||||
IERC20::transferCall { to: checking_address, value: EU256::from(amount) }.abi_encode();
|
||||
let ContractResult { result, gas_consumed, storage_deposit, .. } =
|
||||
pezpallet_revive::Pallet::<T>::bare_call(
|
||||
OriginFor::<T>::signed(who.clone()),
|
||||
asset_id,
|
||||
U256::zero(),
|
||||
gas_limit,
|
||||
StorageDepositLimit::get(),
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
);
|
||||
// We need to return this surplus for the executor to allow refunding it.
|
||||
let surplus = gas_limit.saturating_sub(gas_consumed);
|
||||
tracing::trace!(target: "xcm::transactor::erc20::withdraw", ?gas_consumed, ?surplus, ?storage_deposit);
|
||||
if let Ok(return_value) = result {
|
||||
tracing::trace!(target: "xcm::transactor::erc20::withdraw", ?return_value, "Return value by withdraw_asset");
|
||||
if return_value.did_revert() {
|
||||
tracing::debug!(target: "xcm::transactor::erc20::withdraw", "ERC20 contract reverted");
|
||||
Err(XcmError::FailedToTransactAsset("ERC20 contract reverted"))
|
||||
} else {
|
||||
let is_success = IERC20::transferCall::abi_decode_returns_validate(&return_value.data).map_err(|error| {
|
||||
tracing::debug!(target: "xcm::transactor::erc20::withdraw", ?error, "ERC20 contract result couldn't decode");
|
||||
XcmError::FailedToTransactAsset("ERC20 contract result couldn't decode")
|
||||
})?;
|
||||
if is_success {
|
||||
tracing::trace!(target: "xcm::transactor::erc20::withdraw", "ERC20 contract was successful");
|
||||
Ok((what.clone().into(), surplus))
|
||||
} else {
|
||||
tracing::debug!(target: "xcm::transactor::erc20::withdraw", "contract transfer failed");
|
||||
Err(XcmError::FailedToTransactAsset("ERC20 contract transfer failed"))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::debug!(target: "xcm::transactor::erc20::withdraw", ?result, "Error");
|
||||
// This error could've been duplicate smart contract, out of gas, etc.
|
||||
// If the issue is gas, there's nothing the user can change in the XCM
|
||||
// that will make this work since there's a hardcoded gas limit.
|
||||
Err(XcmError::FailedToTransactAsset("ERC20 contract execution errored"))
|
||||
}
|
||||
}
|
||||
|
||||
fn deposit_asset_with_surplus(
|
||||
what: &Asset,
|
||||
who: &Location,
|
||||
_context: Option<&XcmContext>,
|
||||
) -> Result<Weight, XcmError> {
|
||||
tracing::trace!(
|
||||
target: "xcm::transactor::erc20::deposit",
|
||||
?what, ?who,
|
||||
);
|
||||
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
|
||||
let who = AccountIdConverter::convert_location(who)
|
||||
.ok_or(MatchError::AccountIdConversionFailed)?;
|
||||
// We need to map the 32 byte beneficiary account to a 20 byte account.
|
||||
let eth_address = T::AddressMapper::to_address(&who);
|
||||
let address = Address::from(Into::<[u8; 20]>::into(eth_address));
|
||||
// To deposit, we actually transfer from the checking account to the beneficiary.
|
||||
// We do this using the solidity ERC20 interface.
|
||||
let data = IERC20::transferCall { to: address, value: EU256::from(amount) }.abi_encode();
|
||||
let gas_limit = GasLimit::get();
|
||||
let ContractResult { result, gas_consumed, storage_deposit, .. } =
|
||||
pezpallet_revive::Pallet::<T>::bare_call(
|
||||
OriginFor::<T>::signed(TransfersCheckingAccount::get()),
|
||||
asset_id,
|
||||
U256::zero(),
|
||||
gas_limit,
|
||||
StorageDepositLimit::get(),
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
);
|
||||
// We need to return this surplus for the executor to allow refunding it.
|
||||
let surplus = gas_limit.saturating_sub(gas_consumed);
|
||||
tracing::trace!(target: "xcm::transactor::erc20::deposit", ?gas_consumed, ?surplus, ?storage_deposit);
|
||||
if let Ok(return_value) = result {
|
||||
tracing::trace!(target: "xcm::transactor::erc20::deposit", ?return_value, "Return value");
|
||||
if return_value.did_revert() {
|
||||
tracing::debug!(target: "xcm::transactor::erc20::deposit", "Contract reverted");
|
||||
Err(XcmError::FailedToTransactAsset("ERC20 contract reverted"))
|
||||
} else {
|
||||
let is_success = IERC20::transferCall::abi_decode_returns_validate(&return_value.data).map_err(|error| {
|
||||
tracing::debug!(target: "xcm::transactor::erc20::deposit", ?error, "ERC20 contract result couldn't decode");
|
||||
XcmError::FailedToTransactAsset("ERC20 contract result couldn't decode")
|
||||
})?;
|
||||
if is_success {
|
||||
tracing::trace!(target: "xcm::transactor::erc20::deposit", "ERC20 contract was successful");
|
||||
Ok(surplus)
|
||||
} else {
|
||||
tracing::debug!(target: "xcm::transactor::erc20::deposit", "contract transfer failed");
|
||||
Err(XcmError::FailedToTransactAsset("ERC20 contract transfer failed"))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::debug!(target: "xcm::transactor::erc20::deposit", ?result, "Error");
|
||||
// This error could've been duplicate smart contract, out of gas, etc.
|
||||
// If the issue is gas, there's nothing the user can change in the XCM
|
||||
// that will make this work since there's a hardcoded gas limit.
|
||||
Err(XcmError::FailedToTransactAsset("ERC20 contract execution errored"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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 core::fmt::Debug;
|
||||
use pezframe_support::traits::{
|
||||
ContainsPair, EnsureOrigin, EnsureOriginWithArg, Everything, OriginTrait,
|
||||
};
|
||||
use pezpallet_xcm::{EnsureXcm, Origin as XcmOrigin};
|
||||
use xcm::latest::Location;
|
||||
use xcm_executor::traits::ConvertLocation;
|
||||
|
||||
/// `EnsureOriginWithArg` impl for `CreateOrigin` that allows only XCM origins that are locations
|
||||
/// containing the class location.
|
||||
pub struct ForeignCreators<IsForeign, AccountOf, AccountId, L = Location>(
|
||||
core::marker::PhantomData<(IsForeign, AccountOf, AccountId, L)>,
|
||||
);
|
||||
impl<
|
||||
IsForeign: ContainsPair<L, L>,
|
||||
AccountOf: ConvertLocation<AccountId>,
|
||||
AccountId: Clone,
|
||||
RuntimeOrigin: From<XcmOrigin> + OriginTrait + Clone + Debug,
|
||||
L: TryFrom<Location> + TryInto<Location> + Clone + Debug,
|
||||
> EnsureOriginWithArg<RuntimeOrigin, L> for ForeignCreators<IsForeign, AccountOf, AccountId, L>
|
||||
where
|
||||
for<'a> &'a RuntimeOrigin::PalletsOrigin: TryInto<&'a XcmOrigin>,
|
||||
{
|
||||
type Success = AccountId;
|
||||
|
||||
fn try_origin(
|
||||
origin: RuntimeOrigin,
|
||||
asset_location: &L,
|
||||
) -> Result<Self::Success, RuntimeOrigin> {
|
||||
tracing::trace!(target: "xcm::try_origin", ?origin, ?asset_location, "ForeignCreators");
|
||||
let origin_location = EnsureXcm::<Everything, L>::try_origin(origin.clone())?;
|
||||
if !IsForeign::contains(asset_location, &origin_location) {
|
||||
tracing::trace!(target: "xcm::try_origin", ?asset_location, ?origin_location, "ForeignCreators: no match");
|
||||
return Err(origin);
|
||||
}
|
||||
let latest_location: Location =
|
||||
origin_location.clone().try_into().map_err(|_| origin.clone())?;
|
||||
AccountOf::convert_location(&latest_location).ok_or(origin)
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn try_successful_origin(a: &L) -> Result<RuntimeOrigin, ()> {
|
||||
let latest_location: Location = (*a).clone().try_into().map_err(|_| ())?;
|
||||
Ok(pezpallet_xcm::Origin::Xcm(latest_location).into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// 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.
|
||||
|
||||
//! Runtime API definition for assets.
|
||||
|
||||
use crate::runtime_api::FungiblesAccessError;
|
||||
use alloc::vec::Vec;
|
||||
use core::borrow::Borrow;
|
||||
use pezframe_support::traits::Contains;
|
||||
use pezsp_runtime::traits::MaybeEquivalence;
|
||||
use xcm::latest::{Asset, Location};
|
||||
use xcm_builder::{ConvertedConcreteId, MatchedConvertedConcreteId};
|
||||
use xcm_executor::traits::MatchesFungibles;
|
||||
|
||||
/// Converting any [`(AssetId, Balance)`] to [`Asset`]
|
||||
pub trait AssetConverter<AssetId, Balance, ConvertAssetId, ConvertBalance>:
|
||||
MatchesFungibles<AssetId, Balance>
|
||||
where
|
||||
AssetId: Clone,
|
||||
Balance: Clone,
|
||||
ConvertAssetId: MaybeEquivalence<Location, AssetId>,
|
||||
ConvertBalance: MaybeEquivalence<u128, Balance>,
|
||||
{
|
||||
fn convert_ref(value: impl Borrow<(AssetId, Balance)>) -> Result<Asset, FungiblesAccessError>;
|
||||
}
|
||||
|
||||
/// Checks for `Location`.
|
||||
pub trait MatchesLocation<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertBalance>:
|
||||
MatchesFungibles<AssetId, Balance>
|
||||
where
|
||||
AssetId: Clone,
|
||||
Balance: Clone,
|
||||
MatchAssetId: Contains<Location>,
|
||||
ConvertAssetId: MaybeEquivalence<Location, AssetId>,
|
||||
ConvertBalance: MaybeEquivalence<u128, Balance>,
|
||||
{
|
||||
fn contains(location: &Location) -> bool;
|
||||
}
|
||||
|
||||
impl<
|
||||
AssetId: Clone,
|
||||
Balance: Clone,
|
||||
ConvertAssetId: MaybeEquivalence<Location, AssetId>,
|
||||
ConvertBalance: MaybeEquivalence<u128, Balance>,
|
||||
> AssetConverter<AssetId, Balance, ConvertAssetId, ConvertBalance>
|
||||
for ConvertedConcreteId<AssetId, Balance, ConvertAssetId, ConvertBalance>
|
||||
{
|
||||
fn convert_ref(value: impl Borrow<(AssetId, Balance)>) -> Result<Asset, FungiblesAccessError> {
|
||||
let (asset_id, balance) = value.borrow();
|
||||
match ConvertAssetId::convert_back(asset_id) {
|
||||
Some(asset_id_as_location) => match ConvertBalance::convert_back(balance) {
|
||||
Some(amount) => Ok((asset_id_as_location, amount).into()),
|
||||
None => Err(FungiblesAccessError::AmountToBalanceConversionFailed),
|
||||
},
|
||||
None => Err(FungiblesAccessError::AssetIdConversionFailed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
AssetId: Clone,
|
||||
Balance: Clone,
|
||||
MatchAssetId: Contains<Location>,
|
||||
ConvertAssetId: MaybeEquivalence<Location, AssetId>,
|
||||
ConvertBalance: MaybeEquivalence<u128, Balance>,
|
||||
> AssetConverter<AssetId, Balance, ConvertAssetId, ConvertBalance>
|
||||
for MatchedConvertedConcreteId<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertBalance>
|
||||
{
|
||||
fn convert_ref(value: impl Borrow<(AssetId, Balance)>) -> Result<Asset, FungiblesAccessError> {
|
||||
let (asset_id, balance) = value.borrow();
|
||||
match ConvertAssetId::convert_back(asset_id) {
|
||||
Some(asset_id_as_location) => match ConvertBalance::convert_back(balance) {
|
||||
Some(amount) => Ok((asset_id_as_location, amount).into()),
|
||||
None => Err(FungiblesAccessError::AmountToBalanceConversionFailed),
|
||||
},
|
||||
None => Err(FungiblesAccessError::AssetIdConversionFailed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
AssetId: Clone,
|
||||
Balance: Clone,
|
||||
MatchAssetId: Contains<Location>,
|
||||
ConvertAssetId: MaybeEquivalence<Location, AssetId>,
|
||||
ConvertBalance: MaybeEquivalence<u128, Balance>,
|
||||
> MatchesLocation<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertBalance>
|
||||
for MatchedConvertedConcreteId<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertBalance>
|
||||
{
|
||||
fn contains(location: &Location) -> bool {
|
||||
MatchAssetId::contains(location)
|
||||
}
|
||||
}
|
||||
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
impl<
|
||||
AssetId: Clone,
|
||||
Balance: Clone,
|
||||
MatchAssetId: Contains<Location>,
|
||||
ConvertAssetId: MaybeEquivalence<Location, AssetId>,
|
||||
ConvertBalance: MaybeEquivalence<u128, Balance>,
|
||||
> MatchesLocation<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertBalance> for Tuple
|
||||
{
|
||||
fn contains(location: &Location) -> bool {
|
||||
for_tuples!( #(
|
||||
match Tuple::contains(location) { o @ true => return o, _ => () }
|
||||
)* );
|
||||
tracing::trace!(target: "xcm::contains", ?location, "MatchesLocation: no match");
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to convert collections with [`(AssetId, Balance)`] to [`Asset`]
|
||||
pub fn convert<'a, AssetId, Balance, ConvertAssetId, ConvertBalance, Converter>(
|
||||
items: impl Iterator<Item = &'a (AssetId, Balance)>,
|
||||
) -> Result<Vec<Asset>, FungiblesAccessError>
|
||||
where
|
||||
AssetId: Clone + 'a,
|
||||
Balance: Clone + 'a,
|
||||
ConvertAssetId: MaybeEquivalence<Location, AssetId>,
|
||||
ConvertBalance: MaybeEquivalence<u128, Balance>,
|
||||
Converter: AssetConverter<AssetId, Balance, ConvertAssetId, ConvertBalance>,
|
||||
{
|
||||
items.map(Converter::convert_ref).collect()
|
||||
}
|
||||
|
||||
/// Helper function to convert `Balance` with Location` to `Asset`
|
||||
pub fn convert_balance<T: pezframe_support::pezpallet_prelude::Get<Location>, Balance: TryInto<u128>>(
|
||||
balance: Balance,
|
||||
) -> Result<Asset, FungiblesAccessError> {
|
||||
match balance.try_into() {
|
||||
Ok(balance) => Ok((T::get(), balance).into()),
|
||||
Err(_) => Err(FungiblesAccessError::AmountToBalanceConversionFailed),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pezframe_support::traits::Everything;
|
||||
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_executor::traits::{Identity, JustTry};
|
||||
|
||||
type Converter = MatchedConvertedConcreteId<Location, u64, Everything, Identity, JustTry>;
|
||||
|
||||
#[test]
|
||||
fn converted_concrete_id_fungible_multi_asset_conversion_roundtrip_works() {
|
||||
let location = Location::new(0, [GlobalConsensus(ByGenesis([0; 32]))]);
|
||||
let amount = 123456_u64;
|
||||
let expected_multi_asset = Asset {
|
||||
id: AssetId(Location::new(0, [GlobalConsensus(ByGenesis([0; 32]))])),
|
||||
fun: Fungible(123456_u128),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Converter::matches_fungibles(&expected_multi_asset).map_err(|_| ()),
|
||||
Ok((location.clone(), amount))
|
||||
);
|
||||
|
||||
assert_eq!(Converter::convert_ref((location, amount)), Ok(expected_multi_asset));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converted_concrete_id_fungible_multi_asset_conversion_collection_works() {
|
||||
let data = vec![
|
||||
(Location::new(0, [GlobalConsensus(ByGenesis([0; 32]))]), 123456_u64),
|
||||
(Location::new(1, [GlobalConsensus(ByGenesis([1; 32]))]), 654321_u64),
|
||||
];
|
||||
|
||||
let expected_data = vec![
|
||||
Asset {
|
||||
id: AssetId(Location::new(0, [GlobalConsensus(ByGenesis([0; 32]))])),
|
||||
fun: Fungible(123456_u128),
|
||||
},
|
||||
Asset {
|
||||
id: AssetId(Location::new(1, [GlobalConsensus(ByGenesis([1; 32]))])),
|
||||
fun: Fungible(654321_u128),
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(convert::<_, _, _, _, Converter>(data.iter()), Ok(expected_data));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
// 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.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod benchmarks;
|
||||
mod erc20_transactor;
|
||||
pub mod foreign_creators;
|
||||
pub mod fungible_conversion;
|
||||
pub mod local_and_foreign_assets;
|
||||
pub mod matching;
|
||||
pub mod migrations;
|
||||
pub mod runtime_api;
|
||||
|
||||
pub use erc20_transactor::ERC20Transactor;
|
||||
|
||||
extern crate alloc;
|
||||
extern crate core;
|
||||
|
||||
use crate::matching::{LocalLocationPattern, ParentLocation};
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, EncodeLike};
|
||||
use core::{cmp::PartialEq, marker::PhantomData};
|
||||
use pezframe_support::traits::{Contains, Equals, EverythingBut};
|
||||
use pezsp_core::H160;
|
||||
use pezsp_runtime::traits::{MaybeEquivalence, TryConvertInto};
|
||||
use teyrchains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId};
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::{
|
||||
AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, WithLatestLocationConverter,
|
||||
};
|
||||
use xcm_executor::traits::JustTry;
|
||||
|
||||
/// `Location` vs `AssetIdForTrustBackedAssets` converter for `TrustBackedAssets`
|
||||
pub type AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation, L = Location> =
|
||||
AsPrefixedGeneralIndex<
|
||||
TrustBackedAssetsPalletLocation,
|
||||
AssetIdForTrustBackedAssets,
|
||||
TryConvertInto,
|
||||
L,
|
||||
>;
|
||||
|
||||
/// `Location` vs `CollectionId` converter for `Uniques`
|
||||
pub type CollectionIdForUniquesConvert<UniquesPalletLocation> =
|
||||
AsPrefixedGeneralIndex<UniquesPalletLocation, CollectionId, TryConvertInto>;
|
||||
|
||||
/// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets`
|
||||
pub type TrustBackedAssetsConvertedConcreteId<
|
||||
TrustBackedAssetsPalletLocation,
|
||||
Balance,
|
||||
L = Location,
|
||||
> = MatchedConvertedConcreteId<
|
||||
AssetIdForTrustBackedAssets,
|
||||
Balance,
|
||||
StartsWith<TrustBackedAssetsPalletLocation>,
|
||||
AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation, L>,
|
||||
TryConvertInto,
|
||||
>;
|
||||
|
||||
/// [`MatchedConvertedConcreteId`] converter dedicated for `Uniques`
|
||||
pub type UniquesConvertedConcreteId<UniquesPalletLocation> = MatchedConvertedConcreteId<
|
||||
CollectionId,
|
||||
ItemId,
|
||||
// The asset starts with the uniques pallet. The `CollectionId` of the asset is specified as a
|
||||
// junction within the pallet itself.
|
||||
StartsWith<UniquesPalletLocation>,
|
||||
CollectionIdForUniquesConvert<UniquesPalletLocation>,
|
||||
TryConvertInto,
|
||||
>;
|
||||
|
||||
/// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets`,
|
||||
/// it is a similar implementation to `TrustBackedAssetsConvertedConcreteId`,
|
||||
/// but it converts `AssetId` to `xcm::v*::Location` type instead of `AssetIdForTrustBackedAssets =
|
||||
/// u32`
|
||||
pub type TrustBackedAssetsAsLocation<
|
||||
TrustBackedAssetsPalletLocation,
|
||||
Balance,
|
||||
L,
|
||||
LocationConverter = WithLatestLocationConverter<L>,
|
||||
> = MatchedConvertedConcreteId<
|
||||
L,
|
||||
Balance,
|
||||
StartsWith<TrustBackedAssetsPalletLocation>,
|
||||
LocationConverter,
|
||||
TryConvertInto,
|
||||
>;
|
||||
|
||||
/// [`MatchedConvertedConcreteId`] converter dedicated for storing `ForeignAssets` with `AssetId` as
|
||||
/// `Location`.
|
||||
///
|
||||
/// Excludes by default:
|
||||
/// - parent as relay chain
|
||||
/// - all local Locations
|
||||
///
|
||||
/// `AdditionalLocationExclusionFilter` can customize additional excluded Locations
|
||||
pub type ForeignAssetsConvertedConcreteId<
|
||||
AdditionalLocationExclusionFilter,
|
||||
Balance,
|
||||
AssetId,
|
||||
LocationToAssetIdConverter = WithLatestLocationConverter<AssetId>,
|
||||
BalanceConverter = TryConvertInto,
|
||||
> = MatchedConvertedConcreteId<
|
||||
AssetId,
|
||||
Balance,
|
||||
EverythingBut<(
|
||||
// Excludes relay/parent chain currency
|
||||
Equals<ParentLocation>,
|
||||
// Here we rely on fact that something like this works:
|
||||
// assert!(Location::new(1,
|
||||
// [Teyrchain(100)]).starts_with(&Location::parent()));
|
||||
// assert!([Teyrchain(100)].into().starts_with(&Here));
|
||||
StartsWith<LocalLocationPattern>,
|
||||
// Here we can exclude more stuff or leave it as `()`
|
||||
AdditionalLocationExclusionFilter,
|
||||
)>,
|
||||
LocationToAssetIdConverter,
|
||||
BalanceConverter,
|
||||
>;
|
||||
|
||||
/// `Contains<Location>` implementation that matches locations with no parents,
|
||||
/// a `PalletInstance` and an `AccountKey20` junction.
|
||||
pub struct IsLocalAccountKey20;
|
||||
impl Contains<Location> for IsLocalAccountKey20 {
|
||||
fn contains(location: &Location) -> bool {
|
||||
matches!(location.unpack(), (0, [AccountKey20 { .. }]))
|
||||
}
|
||||
}
|
||||
|
||||
/// Fallible converter from a location to a `H160` that matches any location ending with
|
||||
/// an `AccountKey20` junction.
|
||||
pub struct AccountKey20ToH160;
|
||||
impl MaybeEquivalence<Location, H160> for AccountKey20ToH160 {
|
||||
fn convert(location: &Location) -> Option<H160> {
|
||||
match location.unpack() {
|
||||
(0, [AccountKey20 { key, .. }]) => Some((*key).into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_back(key: &H160) -> Option<Location> {
|
||||
Some(Location::new(0, [AccountKey20 { key: (*key).into(), network: None }]))
|
||||
}
|
||||
}
|
||||
|
||||
/// [`xcm_executor::traits::MatchesFungibles`] implementation that matches
|
||||
/// ERC20 tokens.
|
||||
pub type ERC20Matcher =
|
||||
MatchedConvertedConcreteId<H160, u128, IsLocalAccountKey20, AccountKey20ToH160, JustTry>;
|
||||
|
||||
pub type AssetIdForPoolAssets = u32;
|
||||
|
||||
/// `Location` vs `AssetIdForPoolAssets` converter for `PoolAssets`.
|
||||
pub type AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation, L = Location> =
|
||||
AsPrefixedGeneralIndex<PoolAssetsPalletLocation, AssetIdForPoolAssets, TryConvertInto, L>;
|
||||
/// [`MatchedConvertedConcreteId`] converter dedicated for `PoolAssets`
|
||||
pub type PoolAssetsConvertedConcreteId<PoolAssetsPalletLocation, Balance> =
|
||||
MatchedConvertedConcreteId<
|
||||
AssetIdForPoolAssets,
|
||||
Balance,
|
||||
StartsWith<PoolAssetsPalletLocation>,
|
||||
AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation>,
|
||||
TryConvertInto,
|
||||
>;
|
||||
|
||||
/// Adapter implementation for accessing pools (`pezpallet_asset_conversion`) that uses `AssetKind` as
|
||||
/// a `xcm::v*` which could be different from the `xcm::latest`.
|
||||
pub struct PoolAdapter<Runtime>(PhantomData<Runtime>);
|
||||
impl<
|
||||
Runtime: pezpallet_asset_conversion::Config<PoolId = (L, L), AssetKind = L>,
|
||||
L: TryFrom<Location> + TryInto<Location> + Clone + Decode + EncodeLike + PartialEq,
|
||||
> PoolAdapter<Runtime>
|
||||
{
|
||||
/// Returns a vector of all assets in a pool with `asset`.
|
||||
///
|
||||
/// Should only be used in runtime APIs since it iterates over the whole
|
||||
/// `pezpallet_asset_conversion::Pools` map.
|
||||
///
|
||||
/// It takes in any version of an XCM Location but always returns the latest one.
|
||||
/// This is to allow some margin of migrating the pools when updating the XCM version.
|
||||
///
|
||||
/// An error of type `()` is returned if the version conversion fails for XCM locations.
|
||||
/// This error should be mapped by the caller to a more descriptive one.
|
||||
pub fn get_assets_in_pool_with(asset: Location) -> Result<Vec<AssetId>, ()> {
|
||||
// convert latest to the `L` version.
|
||||
let asset: L = asset.try_into().map_err(|_| ())?;
|
||||
Self::iter_assets_in_pool_with(&asset)
|
||||
.map(|location| {
|
||||
// convert `L` to the latest `AssetId`
|
||||
location.try_into().map_err(|_| ()).map(AssetId)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
/// Provides a current prices. Wrapper over
|
||||
/// `pezpallet_asset_conversion::Pallet::<T>::quote_price_tokens_for_exact_tokens`.
|
||||
///
|
||||
/// An error of type `()` is returned if the version conversion fails for XCM locations.
|
||||
/// This error should be mapped by the caller to a more descriptive one.
|
||||
pub fn quote_price_tokens_for_exact_tokens(
|
||||
asset_1: Location,
|
||||
asset_2: Location,
|
||||
amount: Runtime::Balance,
|
||||
include_fees: bool,
|
||||
) -> Result<Option<Runtime::Balance>, ()> {
|
||||
// Convert latest to the `L` version.
|
||||
let asset_1: L = asset_1.try_into().map_err(|_| ())?;
|
||||
let asset_2: L = asset_2.try_into().map_err(|_| ())?;
|
||||
|
||||
// Quote swap price.
|
||||
Ok(pezpallet_asset_conversion::Pallet::<Runtime>::quote_price_tokens_for_exact_tokens(
|
||||
asset_1,
|
||||
asset_2,
|
||||
amount,
|
||||
include_fees,
|
||||
))
|
||||
}
|
||||
|
||||
/// Helper function for filtering pool.
|
||||
pub fn iter_assets_in_pool_with(asset: &L) -> impl Iterator<Item = L> + '_ {
|
||||
pezpallet_asset_conversion::Pools::<Runtime>::iter_keys().filter_map(|(asset_1, asset_2)| {
|
||||
if asset_1 == *asset {
|
||||
Some(asset_2)
|
||||
} else if asset_2 == *asset {
|
||||
Some(asset_1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pezsp_runtime::traits::MaybeEquivalence;
|
||||
use xcm_builder::{StartsWithExplicitGlobalConsensus, WithLatestLocationConverter};
|
||||
use xcm_executor::traits::{Error as MatchError, MatchesFungibles};
|
||||
|
||||
#[test]
|
||||
fn asset_id_for_trust_backed_assets_convert_works() {
|
||||
pezframe_support::parameter_types! {
|
||||
pub TrustBackedAssetsPalletLocation: Location = Location::new(5, [PalletInstance(13)]);
|
||||
}
|
||||
let local_asset_id = 123456789 as AssetIdForTrustBackedAssets;
|
||||
let expected_reverse_ref =
|
||||
Location::new(5, [PalletInstance(13), GeneralIndex(local_asset_id.into())]);
|
||||
|
||||
assert_eq!(
|
||||
AssetIdForTrustBackedAssetsConvert::<TrustBackedAssetsPalletLocation>::convert_back(
|
||||
&local_asset_id
|
||||
)
|
||||
.unwrap(),
|
||||
expected_reverse_ref
|
||||
);
|
||||
assert_eq!(
|
||||
AssetIdForTrustBackedAssetsConvert::<TrustBackedAssetsPalletLocation>::convert(
|
||||
&expected_reverse_ref
|
||||
)
|
||||
.unwrap(),
|
||||
local_asset_id
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trust_backed_assets_match_fungibles_works() {
|
||||
pezframe_support::parameter_types! {
|
||||
pub TrustBackedAssetsPalletLocation: Location = Location::new(0, [PalletInstance(13)]);
|
||||
}
|
||||
// set up a converter
|
||||
type TrustBackedAssetsConvert =
|
||||
TrustBackedAssetsConvertedConcreteId<TrustBackedAssetsPalletLocation, u128>;
|
||||
|
||||
let test_data = vec![
|
||||
// missing GeneralIndex
|
||||
(ma_1000(0, [PalletInstance(13)].into()), Err(MatchError::AssetIdConversionFailed)),
|
||||
(
|
||||
ma_1000(0, [PalletInstance(13), GeneralKey { data: [0; 32], length: 32 }].into()),
|
||||
Err(MatchError::AssetIdConversionFailed),
|
||||
),
|
||||
(
|
||||
ma_1000(0, [PalletInstance(13), Teyrchain(1000)].into()),
|
||||
Err(MatchError::AssetIdConversionFailed),
|
||||
),
|
||||
// OK
|
||||
(ma_1000(0, [PalletInstance(13), GeneralIndex(1234)].into()), Ok((1234, 1000))),
|
||||
(
|
||||
ma_1000(0, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()),
|
||||
Ok((1234, 1000)),
|
||||
),
|
||||
(
|
||||
ma_1000(
|
||||
0,
|
||||
[
|
||||
PalletInstance(13),
|
||||
GeneralIndex(1234),
|
||||
GeneralIndex(2222),
|
||||
GeneralKey { data: [0; 32], length: 32 },
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
Ok((1234, 1000)),
|
||||
),
|
||||
// wrong pallet instance
|
||||
(
|
||||
ma_1000(0, [PalletInstance(77), GeneralIndex(1234)].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
(
|
||||
ma_1000(0, [PalletInstance(77), GeneralIndex(1234), GeneralIndex(2222)].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
// wrong parent
|
||||
(
|
||||
ma_1000(1, [PalletInstance(13), GeneralIndex(1234)].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
(
|
||||
ma_1000(1, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
(
|
||||
ma_1000(1, [PalletInstance(77), GeneralIndex(1234)].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
(
|
||||
ma_1000(1, [PalletInstance(77), GeneralIndex(1234), GeneralIndex(2222)].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
// wrong parent
|
||||
(
|
||||
ma_1000(2, [PalletInstance(13), GeneralIndex(1234)].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
(
|
||||
ma_1000(2, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
// missing GeneralIndex
|
||||
(ma_1000(0, [PalletInstance(77)].into()), Err(MatchError::AssetNotHandled)),
|
||||
(ma_1000(1, [PalletInstance(13)].into()), Err(MatchError::AssetNotHandled)),
|
||||
(ma_1000(2, [PalletInstance(13)].into()), Err(MatchError::AssetNotHandled)),
|
||||
];
|
||||
|
||||
for (asset, expected_result) in test_data {
|
||||
assert_eq!(
|
||||
<TrustBackedAssetsConvert as MatchesFungibles<AssetIdForTrustBackedAssets, u128>>::matches_fungibles(&asset.clone().try_into().unwrap()),
|
||||
expected_result, "asset: {:?}", asset);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn foreign_assets_converted_concrete_id_converter_works() {
|
||||
pezframe_support::parameter_types! {
|
||||
pub Teyrchain100Pattern: Location = Location::new(1, [Teyrchain(100)]);
|
||||
pub UniversalLocationNetworkId: NetworkId = NetworkId::ByGenesis([9; 32]);
|
||||
}
|
||||
|
||||
// set up a converter which uses `xcm::v4::Location` under the hood
|
||||
type Convert = ForeignAssetsConvertedConcreteId<
|
||||
(
|
||||
StartsWith<Teyrchain100Pattern>,
|
||||
StartsWithExplicitGlobalConsensus<UniversalLocationNetworkId>,
|
||||
),
|
||||
u128,
|
||||
xcm::v4::Location,
|
||||
WithLatestLocationConverter<xcm::v4::Location>,
|
||||
>;
|
||||
|
||||
let test_data = vec![
|
||||
// excluded as local
|
||||
(ma_1000(0, Here), Err(MatchError::AssetNotHandled)),
|
||||
(ma_1000(0, [Teyrchain(100)].into()), Err(MatchError::AssetNotHandled)),
|
||||
(
|
||||
ma_1000(0, [PalletInstance(13), GeneralIndex(1234)].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
// excluded as parent
|
||||
(ma_1000(1, Here), Err(MatchError::AssetNotHandled)),
|
||||
// excluded as additional filter - Teyrchain100Pattern
|
||||
(ma_1000(1, [Teyrchain(100)].into()), Err(MatchError::AssetNotHandled)),
|
||||
(
|
||||
ma_1000(1, [Teyrchain(100), GeneralIndex(1234)].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
(
|
||||
ma_1000(1, [Teyrchain(100), PalletInstance(13), GeneralIndex(1234)].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
// excluded as additional filter - StartsWithExplicitGlobalConsensus
|
||||
(
|
||||
ma_1000(1, [GlobalConsensus(NetworkId::ByGenesis([9; 32]))].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
(
|
||||
ma_1000(2, [GlobalConsensus(NetworkId::ByGenesis([9; 32]))].into()),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
(
|
||||
ma_1000(
|
||||
2,
|
||||
[
|
||||
GlobalConsensus(NetworkId::ByGenesis([9; 32])),
|
||||
Teyrchain(200),
|
||||
GeneralIndex(1234),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
Err(MatchError::AssetNotHandled),
|
||||
),
|
||||
// ok
|
||||
(
|
||||
ma_1000(1, [Teyrchain(200)].into()),
|
||||
Ok((xcm::v4::Location::new(1, [xcm::v4::Junction::Teyrchain(200)]), 1000)),
|
||||
),
|
||||
(
|
||||
ma_1000(2, [Teyrchain(200)].into()),
|
||||
Ok((xcm::v4::Location::new(2, [xcm::v4::Junction::Teyrchain(200)]), 1000)),
|
||||
),
|
||||
(
|
||||
ma_1000(1, [Teyrchain(200), GeneralIndex(1234)].into()),
|
||||
Ok((
|
||||
xcm::v4::Location::new(
|
||||
1,
|
||||
[xcm::v4::Junction::Teyrchain(200), xcm::v4::Junction::GeneralIndex(1234)],
|
||||
),
|
||||
1000,
|
||||
)),
|
||||
),
|
||||
(
|
||||
ma_1000(2, [Teyrchain(200), GeneralIndex(1234)].into()),
|
||||
Ok((
|
||||
xcm::v4::Location::new(
|
||||
2,
|
||||
[xcm::v4::Junction::Teyrchain(200), xcm::v4::Junction::GeneralIndex(1234)],
|
||||
),
|
||||
1000,
|
||||
)),
|
||||
),
|
||||
(
|
||||
ma_1000(2, [GlobalConsensus(NetworkId::ByGenesis([7; 32]))].into()),
|
||||
Ok((
|
||||
xcm::v4::Location::new(
|
||||
2,
|
||||
[xcm::v4::Junction::GlobalConsensus(xcm::v4::NetworkId::ByGenesis(
|
||||
[7; 32],
|
||||
))],
|
||||
),
|
||||
1000,
|
||||
)),
|
||||
),
|
||||
(
|
||||
ma_1000(
|
||||
2,
|
||||
[
|
||||
GlobalConsensus(NetworkId::ByGenesis([7; 32])),
|
||||
Teyrchain(200),
|
||||
GeneralIndex(1234),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
Ok((
|
||||
xcm::v4::Location::new(
|
||||
2,
|
||||
[
|
||||
xcm::v4::Junction::GlobalConsensus(xcm::v4::NetworkId::ByGenesis(
|
||||
[7; 32],
|
||||
)),
|
||||
xcm::v4::Junction::Teyrchain(200),
|
||||
xcm::v4::Junction::GeneralIndex(1234),
|
||||
],
|
||||
),
|
||||
1000,
|
||||
)),
|
||||
),
|
||||
];
|
||||
|
||||
for (asset, expected_result) in test_data {
|
||||
assert_eq!(
|
||||
<Convert as MatchesFungibles<xcm::v4::Location, u128>>::matches_fungibles(
|
||||
&asset.clone().try_into().unwrap()
|
||||
),
|
||||
expected_result,
|
||||
"asset: {:?}",
|
||||
asset
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create Asset
|
||||
fn ma_1000(parents: u8, interior: Junctions) -> Asset {
|
||||
(Location::new(parents, interior), 1000).into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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 codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_support::traits::Get;
|
||||
use pezsp_runtime::{
|
||||
traits::{Convert, MaybeEquivalence},
|
||||
Either,
|
||||
Either::{Left, Right},
|
||||
};
|
||||
use xcm::latest::Location;
|
||||
|
||||
/// Information about reserve locations for Foreign Assets.
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
PartialEq,
|
||||
Eq,
|
||||
scale_info::TypeInfo,
|
||||
MaxEncodedLen,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
pub struct ForeignAssetReserveData {
|
||||
/// A location acting as trusted reserve.
|
||||
pub reserve: Location,
|
||||
/// Whether asset is teleportable between local chain and `reserve`.
|
||||
pub teleportable: bool,
|
||||
}
|
||||
|
||||
impl From<(Location, bool)> for ForeignAssetReserveData {
|
||||
fn from((reserve, teleportable): (Location, bool)) -> Self {
|
||||
ForeignAssetReserveData { reserve, teleportable }
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a given [`Location`] to [`Either::Left`] when equal to `Target`, or
|
||||
/// [`Either::Right`] otherwise.
|
||||
///
|
||||
/// Suitable for use as a `Criterion` with [`pezframe_support::traits::tokens::fungible::UnionOf`].
|
||||
pub struct TargetFromLeft<Target, L = Location>(PhantomData<(Target, L)>);
|
||||
impl<Target: Get<L>, L: PartialEq + Eq> Convert<L, Either<(), L>> for TargetFromLeft<Target, L> {
|
||||
fn convert(l: L) -> Either<(), L> {
|
||||
Target::get().eq(&l).then(|| Left(())).map_or(Right(l), |n| n)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a given [`Location`] to [`Either::Left`] based on the `Equivalence` criteria.
|
||||
/// Returns [`Either::Right`] if not equivalent.
|
||||
///
|
||||
/// Suitable for use as a `Criterion` with [`pezframe_support::traits::tokens::fungibles::UnionOf`].
|
||||
pub struct LocalFromLeft<Equivalence, AssetId, L = Location>(
|
||||
PhantomData<(Equivalence, AssetId, L)>,
|
||||
);
|
||||
impl<Equivalence, AssetId, L> Convert<L, Either<AssetId, L>>
|
||||
for LocalFromLeft<Equivalence, AssetId, L>
|
||||
where
|
||||
Equivalence: MaybeEquivalence<L, AssetId>,
|
||||
{
|
||||
fn convert(l: L) -> Either<AssetId, L> {
|
||||
match Equivalence::convert(&l) {
|
||||
Some(id) => Left(id),
|
||||
None => Right(l),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
// 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 crate::local_and_foreign_assets::ForeignAssetReserveData;
|
||||
use core::fmt::Debug;
|
||||
use cumulus_primitives_core::ParaId;
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::Get,
|
||||
traits::{tokens::ProvideAssetReserves, Contains, ContainsPair},
|
||||
};
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::ensure_is_remote;
|
||||
|
||||
pezframe_support::parameter_types! {
|
||||
pub LocalLocationPattern: Location = Location::new(0, Here);
|
||||
pub ParentLocation: Location = Location::parent();
|
||||
}
|
||||
|
||||
/// Accepts an asset if it is from the origin.
|
||||
pub struct IsForeignConcreteAsset<IsForeign>(core::marker::PhantomData<IsForeign>);
|
||||
impl<IsForeign: ContainsPair<Location, Location>> ContainsPair<Asset, Location>
|
||||
for IsForeignConcreteAsset<IsForeign>
|
||||
{
|
||||
fn contains(asset: &Asset, origin: &Location) -> bool {
|
||||
let result = matches!(asset.id, AssetId(ref id) if IsForeign::contains(id, origin));
|
||||
tracing::trace!(target: "xcm::contains", ?asset, ?origin, ?result, "IsForeignConcreteAsset");
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if `a` is from sibling location `b`. Checks that `Location-a` starts with
|
||||
/// `Location-b`, and that the `ParaId` of `b` is not equal to `a`.
|
||||
pub struct FromSiblingTeyrchain<SelfParaId, L = Location>(
|
||||
core::marker::PhantomData<(SelfParaId, L)>,
|
||||
);
|
||||
impl<SelfParaId: Get<ParaId>, L: TryFrom<Location> + TryInto<Location> + Clone + Debug>
|
||||
ContainsPair<L, L> for FromSiblingTeyrchain<SelfParaId, L>
|
||||
{
|
||||
fn contains(a: &L, b: &L) -> bool {
|
||||
tracing::trace!(target: "xcm:contains", ?a, ?b, "FromSiblingTeyrchain");
|
||||
// We convert locations to latest
|
||||
let a = match ((*a).clone().try_into(), (*b).clone().try_into()) {
|
||||
(Ok(a), Ok(b)) if a.starts_with(&b) => a, // `a` needs to be from `b` at least
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
// here we check if sibling
|
||||
match a.unpack() {
|
||||
(1, interior) => {
|
||||
matches!(interior.first(), Some(Teyrchain(sibling_para_id)) if sibling_para_id.ne(&u32::from(SelfParaId::get())))
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if asset `a` is coming from a trusted Reserve location `b`, then checks whether the local
|
||||
/// chain is also a reserve of `a`. Assets can be teleported between their reserve locations.
|
||||
pub struct TeleportableAssetWithTrustedReserve<SelfParaId, ReserveProvider, L = Location>(
|
||||
core::marker::PhantomData<(SelfParaId, ReserveProvider, L)>,
|
||||
);
|
||||
impl<
|
||||
SelfParaId: Get<ParaId>,
|
||||
L: TryFrom<Location> + TryInto<Location> + Clone + Debug,
|
||||
ReserveProvider: ProvideAssetReserves<Location, ForeignAssetReserveData>,
|
||||
> ContainsPair<L, L> for TeleportableAssetWithTrustedReserve<SelfParaId, ReserveProvider, L>
|
||||
{
|
||||
fn contains(a: &L, b: &L) -> bool {
|
||||
tracing::trace!(target: "xcm::contains", ?a, ?b, "TeleportableAssetWithTrustedReserve");
|
||||
// We convert locations to latest
|
||||
let (a, b) = match ((*a).clone().try_into(), (*b).clone().try_into()) {
|
||||
(Ok(a), Ok(b)) => (a, b),
|
||||
_ => return false,
|
||||
};
|
||||
let reserves = ReserveProvider::reserves(&a);
|
||||
tracing::trace!(target: "xcm::contains", ?reserves, "TeleportableAssetWithTrustedReserve");
|
||||
// check if `b` is reserve for `a` and teleportable flag is set
|
||||
let filter = (b, true).into();
|
||||
reserves.contains(&filter)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if asset `a` is coming from a trusted Reserve location `b`.
|
||||
/// Then checks that the local chain is NOT itself also reserve of `a`, otherwise a teleport is in
|
||||
/// order.
|
||||
pub struct NonTeleportableAssetFromTrustedReserve<SelfParaId, ReserveProvider, L = Location>(
|
||||
core::marker::PhantomData<(SelfParaId, ReserveProvider, L)>,
|
||||
);
|
||||
impl<
|
||||
SelfParaId: Get<ParaId>,
|
||||
L: TryFrom<Location> + TryInto<Location> + Clone + Debug,
|
||||
ReserveProvider: ProvideAssetReserves<Location, ForeignAssetReserveData>,
|
||||
> ContainsPair<L, L> for NonTeleportableAssetFromTrustedReserve<SelfParaId, ReserveProvider, L>
|
||||
{
|
||||
fn contains(a: &L, b: &L) -> bool {
|
||||
tracing::trace!(target: "xcm::contains", ?a, ?b, "NonTeleportableAssetFromTrustedReserve");
|
||||
// We convert locations to latest
|
||||
let (a, b) = match ((*a).clone().try_into(), (*b).clone().try_into()) {
|
||||
(Ok(a), Ok(b)) => (a, b),
|
||||
_ => return false,
|
||||
};
|
||||
let reserves = ReserveProvider::reserves(&a);
|
||||
tracing::trace!(target: "xcm::contains", ?reserves, "NonTeleportableAssetFromTrustedReserve");
|
||||
// check if `b` is reserve for `a` and teleportable flag is NOT set
|
||||
let filter = (b, false).into();
|
||||
reserves.contains(&filter)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if `a` is from the expected global consensus network. Checks that `Location-a`
|
||||
/// starts with `Location-b`, and that network is a foreign consensus system.
|
||||
pub struct FromNetwork<UniversalLocation, ExpectedNetworkId, L = Location>(
|
||||
core::marker::PhantomData<(UniversalLocation, ExpectedNetworkId, L)>,
|
||||
);
|
||||
impl<
|
||||
UniversalLocation: Get<InteriorLocation>,
|
||||
ExpectedNetworkId: Get<NetworkId>,
|
||||
L: TryFrom<Location> + TryInto<Location> + Clone + Debug,
|
||||
> ContainsPair<L, L> for FromNetwork<UniversalLocation, ExpectedNetworkId, L>
|
||||
{
|
||||
fn contains(a: &L, b: &L) -> bool {
|
||||
tracing::trace!(target: "xcm:contains", ?a, ?b, "FromNetwork");
|
||||
// We convert locations to latest
|
||||
let a = match ((*a).clone().try_into(), (*b).clone().try_into()) {
|
||||
(Ok(a), Ok(b)) if a.starts_with(&b) => a, // `a` needs to be from `b` at least
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
let universal_source = UniversalLocation::get();
|
||||
|
||||
// ensure that `a` is remote and from the expected network
|
||||
match ensure_is_remote(universal_source.clone(), a.clone()) {
|
||||
Ok((network_id, _)) => network_id == ExpectedNetworkId::get(),
|
||||
Err(e) => {
|
||||
tracing::debug!(target: "xcm::contains", origin = ?a, ?universal_source, error = ?e, "FromNetwork origin is not remote to the universal_source");
|
||||
false
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept an asset if it is native to `AssetsAllowedNetworks` and it is coming from
|
||||
/// `OriginLocation`.
|
||||
pub struct RemoteAssetFromLocation<AssetsAllowedNetworks, OriginLocation>(
|
||||
core::marker::PhantomData<(AssetsAllowedNetworks, OriginLocation)>,
|
||||
);
|
||||
impl<
|
||||
L: TryInto<Location> + Clone,
|
||||
AssetsAllowedNetworks: Contains<Location>,
|
||||
OriginLocation: Get<Location>,
|
||||
> ContainsPair<L, L> for RemoteAssetFromLocation<AssetsAllowedNetworks, OriginLocation>
|
||||
{
|
||||
fn contains(asset: &L, origin: &L) -> bool {
|
||||
let Ok(asset) = asset.clone().try_into() else {
|
||||
return false;
|
||||
};
|
||||
let Ok(origin) = origin.clone().try_into() else {
|
||||
return false;
|
||||
};
|
||||
let expected_origin = OriginLocation::get();
|
||||
// ensure `origin` is expected `OriginLocation`
|
||||
if !expected_origin.eq(&origin) {
|
||||
tracing::trace!(
|
||||
target: "xcm::contains",
|
||||
?asset,
|
||||
?origin,
|
||||
?expected_origin,
|
||||
"RemoteAssetFromLocation: Asset is not from expected origin"
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
tracing::trace!(
|
||||
target: "xcm::contains",
|
||||
?asset,
|
||||
?origin,
|
||||
"RemoteAssetFromLocation",
|
||||
);
|
||||
}
|
||||
|
||||
// ensure `asset` is from remote consensus listed in `AssetsAllowedNetworks`
|
||||
AssetsAllowedNetworks::contains(&asset)
|
||||
}
|
||||
}
|
||||
impl<AssetsAllowedNetworks: Contains<Location>, OriginLocation: Get<Location>>
|
||||
ContainsPair<Asset, Location> for RemoteAssetFromLocation<AssetsAllowedNetworks, OriginLocation>
|
||||
{
|
||||
fn contains(asset: &Asset, origin: &Location) -> bool {
|
||||
tracing::trace!(target: "xcm:contains", ?asset, ?origin, "RemoteAssetFromLocation");
|
||||
<Self as ContainsPair<Location, Location>>::contains(&asset.id.0, origin)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pezframe_support::parameter_types;
|
||||
use xcm::latest::{PEZKUWICHAIN_GENESIS_HASH, ZAGROS_GENESIS_HASH};
|
||||
|
||||
parameter_types! {
|
||||
pub UniversalLocation: InteriorLocation = [GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH)), Teyrchain(1000)].into();
|
||||
pub ExpectedNetworkId: NetworkId = ByGenesis(ZAGROS_GENESIS_HASH);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_network_contains_works() {
|
||||
// asset and origin from foreign consensus works
|
||||
let asset: Location = (
|
||||
Parent,
|
||||
Parent,
|
||||
GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)),
|
||||
Teyrchain(1000),
|
||||
PalletInstance(1),
|
||||
GeneralIndex(1),
|
||||
)
|
||||
.into();
|
||||
let origin: Location =
|
||||
(Parent, Parent, GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)), Teyrchain(1000))
|
||||
.into();
|
||||
assert!(FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
|
||||
|
||||
// asset and origin from local consensus fails
|
||||
let asset: Location = (
|
||||
Parent,
|
||||
Parent,
|
||||
GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH)),
|
||||
Teyrchain(1000),
|
||||
PalletInstance(1),
|
||||
GeneralIndex(1),
|
||||
)
|
||||
.into();
|
||||
let origin: Location = (
|
||||
Parent,
|
||||
Parent,
|
||||
GlobalConsensus(ByGenesis(PEZKUWICHAIN_GENESIS_HASH)),
|
||||
Teyrchain(1000),
|
||||
)
|
||||
.into();
|
||||
assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
|
||||
|
||||
// asset and origin from here fails
|
||||
let asset: Location = (PalletInstance(1), GeneralIndex(1)).into();
|
||||
let origin: Location = Here.into();
|
||||
assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
|
||||
|
||||
// asset from different consensus fails
|
||||
let asset: Location = (
|
||||
Parent,
|
||||
Parent,
|
||||
GlobalConsensus(Pezkuwi),
|
||||
Teyrchain(1000),
|
||||
PalletInstance(1),
|
||||
GeneralIndex(1),
|
||||
)
|
||||
.into();
|
||||
let origin: Location =
|
||||
(Parent, Parent, GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)), Teyrchain(1000))
|
||||
.into();
|
||||
assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
|
||||
|
||||
// origin from different consensus fails
|
||||
let asset: Location = (
|
||||
Parent,
|
||||
Parent,
|
||||
GlobalConsensus(ByGenesis(ZAGROS_GENESIS_HASH)),
|
||||
Teyrchain(1000),
|
||||
PalletInstance(1),
|
||||
GeneralIndex(1),
|
||||
)
|
||||
.into();
|
||||
let origin: Location = (Parent, Parent, GlobalConsensus(Pezkuwi), Teyrchain(1000)).into();
|
||||
assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
|
||||
|
||||
// asset and origin from unexpected consensus fails
|
||||
let asset: Location = (
|
||||
Parent,
|
||||
Parent,
|
||||
GlobalConsensus(Pezkuwi),
|
||||
Teyrchain(1000),
|
||||
PalletInstance(1),
|
||||
GeneralIndex(1),
|
||||
)
|
||||
.into();
|
||||
let origin: Location = (Parent, Parent, GlobalConsensus(Pezkuwi), Teyrchain(1000)).into();
|
||||
assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// 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.
|
||||
|
||||
/// `pezpallet-assets` has been enhanced with asset reserves information so that AH foreign assets
|
||||
/// can be registered as either teleportable or reserve-based.
|
||||
/// Originally, all foreign assets were exclusively teleportable, whereas now, after creation the
|
||||
/// asset `Owner` also needs to set the asset's trusted reserves and teleport relationships.
|
||||
///
|
||||
/// This migration adds the origin chain of each existing foreign asset as a trusted reserve for it.
|
||||
/// It marks Sibling Teyrchain foreign assets as teleportable between the local chain and the
|
||||
/// sibling teyrchain.
|
||||
/// It marks external ecosystems (bridged) foreign assets as non-teleportable between the local
|
||||
/// chain and the external ecosystem.
|
||||
/// For new assets, the reserve location(s) and teleport status need to be explicitly configured by
|
||||
/// the asset's `Owner`.
|
||||
///
|
||||
/// See <https://github.com/pezkuwichain/kurdistan-sdk/issues/129> for more info.
|
||||
pub mod foreign_assets_reserves {
|
||||
use crate::*;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use core::{fmt::Debug, marker::PhantomData};
|
||||
use pezframe_support::{
|
||||
migrations::{MigrationId, SteppedMigration, SteppedMigrationError},
|
||||
weights::WeightMeter,
|
||||
};
|
||||
use pezpallet_assets::WeightInfo;
|
||||
|
||||
const MIGRATIONS_ID: &[u8; 23] = b"foreign-assets-reserves";
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
#[derive(Encode, Decode)]
|
||||
struct TryRuntimeState<T: pezpallet_assets::Config<I>, I: 'static> {
|
||||
assets: Vec<T::AssetId>,
|
||||
}
|
||||
|
||||
/// Progressive states of a migration. The migration starts with the first variant and ends with
|
||||
/// the last.
|
||||
#[derive(Decode, Encode, MaxEncodedLen, Eq, PartialEq)]
|
||||
pub enum MigrationState<A> {
|
||||
Asset(A),
|
||||
Finished,
|
||||
}
|
||||
|
||||
/// Trait for plugging in the type that chooses the correct reserves information per asset_id
|
||||
/// for this migration.
|
||||
pub trait ForeignAssetsReservesProvider {
|
||||
type ReserveData: Debug;
|
||||
fn reserves_for(asset_id: &Location) -> Vec<Self::ReserveData>;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn check_reserves_for(asset_id: &Location, reserves: Vec<Self::ReserveData>) -> bool;
|
||||
}
|
||||
|
||||
/// The resulting state of the step and the actual weight consumed.
|
||||
type StepResultOf<T, I> = MigrationState<<T as pezpallet_assets::Config<I>>::AssetId>;
|
||||
|
||||
/// This migration adds assets reserves information for Foreign Assets registered on Asset Hub.
|
||||
/// The migration can be used by multiple runtimes that need to populate reserves information
|
||||
/// for existing assets. The actual reserves information is provided by an external type
|
||||
/// `ReservesProvider` implementing the `ForeignAssetsReservesProvider` trait.
|
||||
pub struct ForeignAssetsReservesMigration<T, I, ReservesProvider>(
|
||||
PhantomData<(T, I, ReservesProvider)>,
|
||||
);
|
||||
impl<T, I, ReservesProvider> SteppedMigration
|
||||
for ForeignAssetsReservesMigration<T, I, ReservesProvider>
|
||||
where
|
||||
ReservesProvider: ForeignAssetsReservesProvider,
|
||||
T: pezpallet_assets::Config<
|
||||
I,
|
||||
AssetId = Location,
|
||||
ReserveData = ReservesProvider::ReserveData,
|
||||
>,
|
||||
I: 'static,
|
||||
{
|
||||
type Cursor = StepResultOf<T, I>;
|
||||
type Identifier = MigrationId<23>;
|
||||
|
||||
fn id() -> Self::Identifier {
|
||||
// this migration doesn't change pallet storage version, from and to are both `1`
|
||||
MigrationId { pezpallet_id: *MIGRATIONS_ID, version_from: 1, version_to: 1 }
|
||||
}
|
||||
|
||||
fn step(
|
||||
mut cursor: Option<Self::Cursor>,
|
||||
meter: &mut WeightMeter,
|
||||
) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
|
||||
// Check that we have enough weight for at least the next step. If we don't, then the
|
||||
// migration cannot be complete.
|
||||
let required =
|
||||
<T as pezpallet_assets::Config<I>>::WeightInfo::migration_v2_foreign_asset_set_reserve_weight();
|
||||
tracing::debug!(target: "runtime::ForeignAssetsReservesMigration", ?meter, ?required);
|
||||
if !meter.can_consume(required) {
|
||||
return Err(SteppedMigrationError::InsufficientWeight { required });
|
||||
}
|
||||
|
||||
loop {
|
||||
if !meter.can_consume(required) {
|
||||
break;
|
||||
}
|
||||
|
||||
let next = match &cursor {
|
||||
// At first, start migrating assets.
|
||||
None => Self::asset_step(None),
|
||||
// Migrate any remaining assets.
|
||||
Some(MigrationState::Asset(maybe_last_asset)) =>
|
||||
Self::asset_step(Some(maybe_last_asset)),
|
||||
// After the last asset, migration is finished.
|
||||
Some(MigrationState::Finished) => {
|
||||
tracing::info!(target: "runtime::ForeignAssetsReservesMigration", "migration finished");
|
||||
return Ok(None);
|
||||
},
|
||||
};
|
||||
|
||||
cursor = Some(next);
|
||||
meter.consume(required);
|
||||
}
|
||||
|
||||
Ok(cursor)
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, pezsp_runtime::TryRuntimeError> {
|
||||
let assets = pezpallet_assets::Asset::<T, I>::iter_keys().collect();
|
||||
tracing::info!(target: "runtime::ForeignAssetsReservesMigration::pre_upgrade", ?assets);
|
||||
let state = TryRuntimeState::<T, I> { assets };
|
||||
Ok(state.encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: Vec<u8>) -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
let prev_state = TryRuntimeState::<T, I>::decode(&mut &state[..])
|
||||
.expect("Failed to decode the previous storage state");
|
||||
for id in prev_state.assets {
|
||||
let reserves = pezpallet_assets::Pallet::<T, I>::get_reserves_data(id.clone());
|
||||
tracing::info!(target: "runtime::ForeignAssetsReservesMigration::post_upgrade", ?id, ?reserves, "verify asset");
|
||||
assert!(ReservesProvider::check_reserves_for(&id, reserves));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I, ReservesProvider> ForeignAssetsReservesMigration<T, I, ReservesProvider>
|
||||
where
|
||||
ReservesProvider: ForeignAssetsReservesProvider,
|
||||
T: pezpallet_assets::Config<
|
||||
I,
|
||||
AssetId = Location,
|
||||
ReserveData = ReservesProvider::ReserveData,
|
||||
>,
|
||||
I: 'static,
|
||||
{
|
||||
fn asset_step(maybe_last_key: Option<&T::AssetId>) -> StepResultOf<T, I> {
|
||||
tracing::debug!(target: "runtime::ForeignAssetsReservesMigration::asset_step", ?maybe_last_key);
|
||||
let mut iter = if let Some(last_key) = maybe_last_key {
|
||||
pezpallet_assets::Asset::<T, I>::iter_keys_from(
|
||||
pezpallet_assets::Asset::<T, I>::hashed_key_for(last_key),
|
||||
)
|
||||
} else {
|
||||
pezpallet_assets::Asset::<T, I>::iter_keys()
|
||||
};
|
||||
if let Some(asset_id) = iter.next() {
|
||||
let reserves = ReservesProvider::reserves_for(&asset_id);
|
||||
tracing::info!(
|
||||
target: "runtime::ForeignAssetsReservesMigration::asset_step",
|
||||
?asset_id, ?reserves, "updating reserves for"
|
||||
);
|
||||
if let Err(e) = pezpallet_assets::Pallet::<T, I>::unchecked_update_reserves(
|
||||
asset_id.clone(),
|
||||
reserves,
|
||||
) {
|
||||
tracing::error!(
|
||||
target: "runtime::ForeignAssetsReservesMigration::asset_step",
|
||||
?e, ?asset_id, "failed migrating reserves for asset"
|
||||
);
|
||||
}
|
||||
MigrationState::Asset(asset_id)
|
||||
} else {
|
||||
MigrationState::Finished
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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.
|
||||
|
||||
//! Runtime API definition for fungibles.
|
||||
|
||||
use codec::{Codec, Decode, Encode};
|
||||
use pezsp_runtime::RuntimeDebug;
|
||||
#[cfg(feature = "std")]
|
||||
use {alloc::vec::Vec, xcm::latest::Asset};
|
||||
|
||||
/// The possible errors that can happen querying the storage of assets.
|
||||
#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)]
|
||||
pub enum FungiblesAccessError {
|
||||
/// `Location` to `AssetId`/`ClassId` conversion failed.
|
||||
AssetIdConversionFailed,
|
||||
/// `u128` amount to currency `Balance` conversion failed.
|
||||
AmountToBalanceConversionFailed,
|
||||
}
|
||||
|
||||
pezsp_api::decl_runtime_apis! {
|
||||
/// The API for querying account's balances from runtime.
|
||||
#[api_version(2)]
|
||||
pub trait FungiblesApi<AccountId>
|
||||
where
|
||||
AccountId: Codec,
|
||||
{
|
||||
/// Returns the list of all [`Asset`] that an `AccountId` has.
|
||||
#[changed_in(2)]
|
||||
fn query_account_balances(account: AccountId) -> Result<Vec<Asset>, FungiblesAccessError>;
|
||||
|
||||
/// Returns the list of all [`Asset`] that an `AccountId` has.
|
||||
fn query_account_balances(account: AccountId) -> Result<xcm::VersionedAssets, FungiblesAccessError>;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user