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:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -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>;
}
}