// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see .
//! Mock runtime for tests.
//! Implements both runtime APIs for fee estimation and getting the messages for transfers.
use codec::Encode;
use core::{cell::RefCell, marker::PhantomData};
use frame_support::{
construct_runtime, derive_impl, parameter_types, sp_runtime,
sp_runtime::{
traits::{Dispatchable, Get, IdentityLookup, MaybeEquivalence, TryConvert},
BuildStorage, SaturatedConversion,
},
traits::{
AsEnsureOriginWithArg, ConstU128, ConstU32, Contains, ContainsPair, Everything, Nothing,
OriginTrait,
},
weights::WeightToFee as WeightToFeeT,
};
use frame_system::{EnsureRoot, RawOrigin as SystemRawOrigin};
use pallet_xcm::TestWeightInfo;
use xcm::{prelude::*, Version as XcmVersion};
use xcm_builder::{
AllowTopLevelPaidExecutionFrom, ConvertedConcreteId, EnsureXcmOrigin, FixedRateOfFungible,
FixedWeightBounds, FungibleAdapter, FungiblesAdapter, IsConcrete, MintLocation, NoChecking,
TakeWeightCredit,
};
use xcm_executor::{
traits::{ConvertLocation, JustTry},
XcmExecutor,
};
use xcm_runtime_apis::{
conversions::{Error as LocationToAccountApiError, LocationToAccountApi},
dry_run::{CallDryRunEffects, DryRunApi, Error as XcmDryRunApiError, XcmDryRunEffects},
fees::{Error as XcmPaymentApiError, XcmPaymentApi},
};
construct_runtime! {
pub enum TestRuntime {
System: frame_system,
Balances: pallet_balances,
AssetsPallet: pallet_assets,
XcmPallet: pallet_xcm,
}
}
pub type SignedExtra = (frame_system::CheckWeight,);
pub type TestXt = sp_runtime::testing::TestXt;
type Block = sp_runtime::testing::Block;
type Balance = u128;
type AssetIdForAssetsPallet = u32;
type AccountId = u64;
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for TestRuntime {
type Block = Block;
type AccountId = AccountId;
type AccountData = pallet_balances::AccountData;
type Lookup = IdentityLookup;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for TestRuntime {
type AccountStore = System;
type Balance = Balance;
type ExistentialDeposit = ExistentialDeposit;
}
#[derive_impl(pallet_assets::config_preludes::TestDefaultConfig)]
impl pallet_assets::Config for TestRuntime {
type AssetId = AssetIdForAssetsPallet;
type Balance = Balance;
type Currency = Balances;
type CreateOrigin = AsEnsureOriginWithArg>;
type ForceOrigin = frame_system::EnsureRoot;
type Freezer = ();
type AssetDeposit = ConstU128<1>;
type AssetAccountDeposit = ConstU128<10>;
type MetadataDepositBase = ConstU128<1>;
type MetadataDepositPerByte = ConstU128<1>;
type ApprovalDeposit = ConstU128<1>;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = ();
}
thread_local! {
pub static SENT_XCM: RefCell)>> = const { RefCell::new(Vec::new()) };
}
pub(crate) fn sent_xcm() -> Vec<(Location, Xcm<()>)> {
SENT_XCM.with(|q| (*q.borrow()).clone())
}
pub struct TestXcmSender;
impl SendXcm for TestXcmSender {
type Ticket = (Location, Xcm<()>);
fn validate(
dest: &mut Option,
msg: &mut Option>,
) -> SendResult {
let ticket = (dest.take().unwrap(), msg.take().unwrap());
let fees: Assets = (HereLocation::get(), DeliveryFees::get()).into();
Ok((ticket, fees))
}
fn deliver(ticket: Self::Ticket) -> Result {
let hash = fake_message_hash(&ticket.1);
SENT_XCM.with(|q| q.borrow_mut().push(ticket));
Ok(hash)
}
}
pub(crate) fn fake_message_hash(message: &Xcm) -> XcmHash {
message.using_encoded(sp_io::hashing::blake2_256)
}
pub type XcmRouter = TestXcmSender;
parameter_types! {
pub const DeliveryFees: u128 = 20; // Random value.
pub const ExistentialDeposit: u128 = 1; // Random value.
pub const BaseXcmWeight: Weight = Weight::from_parts(100, 10); // Random value.
pub const MaxInstructions: u32 = 100;
pub const NativeTokenPerSecondPerByte: (AssetId, u128, u128) = (AssetId(HereLocation::get()), 1, 1);
pub UniversalLocation: InteriorLocation = [GlobalConsensus(NetworkId::Westend), Parachain(2000)].into();
pub static AdvertisedXcmVersion: XcmVersion = 4;
pub const HereLocation: Location = Location::here();
pub const RelayLocation: Location = Location::parent();
pub const MaxAssetsIntoHolding: u32 = 64;
pub CheckAccount: AccountId = XcmPallet::check_account();
pub LocalCheckAccount: (AccountId, MintLocation) = (CheckAccount::get(), MintLocation::Local);
pub const AnyNetwork: Option = None;
}
/// Simple `WeightToFee` implementation that adds the ref_time by the proof_size.
pub struct WeightToFee;
impl WeightToFeeT for WeightToFee {
type Balance = Balance;
fn weight_to_fee(weight: &Weight) -> Self::Balance {
Self::Balance::saturated_from(weight.ref_time())
.saturating_add(Self::Balance::saturated_from(weight.proof_size()))
}
}
type Weigher = FixedWeightBounds;
/// Matches the pair (NativeToken, AssetHub).
/// This is used in the `IsTeleporter` configuration item, meaning we accept our native token
/// coming from AssetHub as a teleport.
pub struct NativeTokenToAssetHub;
impl ContainsPair for NativeTokenToAssetHub {
fn contains(asset: &Asset, origin: &Location) -> bool {
matches!(asset.id.0.unpack(), (0, [])) && matches!(origin.unpack(), (1, [Parachain(1000)]))
}
}
/// Matches the pair (RelayToken, AssetHub).
/// This is used in the `IsReserve` configuration item, meaning we accept the relay token
/// coming from AssetHub as a reserve asset transfer.
pub struct RelayTokenToAssetHub;
impl ContainsPair for RelayTokenToAssetHub {
fn contains(asset: &Asset, origin: &Location) -> bool {
matches!(asset.id.0.unpack(), (1, [])) && matches!(origin.unpack(), (1, [Parachain(1000)]))
}
}
/// Converts locations that are only the `AccountIndex64` junction into local u64 accounts.
pub struct AccountIndex64Aliases(PhantomData<(Network, AccountId)>);
impl>, AccountId: From> ConvertLocation
for AccountIndex64Aliases
{
fn convert_location(location: &Location) -> Option {
let index = match location.unpack() {
(0, [AccountIndex64 { index, network: None }]) => index,
(0, [AccountIndex64 { index, network }]) if *network == Network::get() => index,
_ => return None,
};
Some((*index).into())
}
}
/// Custom location converter to turn sibling chains into u64 accounts.
pub struct SiblingChainToIndex64;
impl ConvertLocation for SiblingChainToIndex64 {
fn convert_location(location: &Location) -> Option {
let index = match location.unpack() {
(1, [Parachain(id)]) => id,
_ => return None,
};
Some((*index).into())
}
}
/// We alias local account locations to actual local accounts.
/// We also allow sovereign accounts for other sibling chains.
pub type LocationToAccountId = (AccountIndex64Aliases, SiblingChainToIndex64);
pub type NativeTokenTransactor = FungibleAdapter<
// We use pallet-balances for handling this fungible asset.
Balances,
// The fungible asset handled by this transactor is the native token of the chain.
IsConcrete,
// How we convert locations to accounts.
LocationToAccountId,
// We need to specify the AccountId type.
AccountId,
// We mint the native tokens locally, so we track how many we've sent away via teleports.
LocalCheckAccount,
>;
pub struct LocationToAssetIdForAssetsPallet;
impl MaybeEquivalence for LocationToAssetIdForAssetsPallet {
fn convert(location: &Location) -> Option {
match location.unpack() {
(1, []) => Some(1 as AssetIdForAssetsPallet),
_ => None,
}
}
fn convert_back(id: &AssetIdForAssetsPallet) -> Option {
match id {
1 => Some(Location::new(1, [])),
_ => None,
}
}
}
/// AssetTransactor for handling the relay chain token.
pub type RelayTokenTransactor = FungiblesAdapter<
// We use pallet-assets for handling the relay token.
AssetsPallet,
// Matches the relay token.
ConvertedConcreteId,
// How we convert locations to accounts.
LocationToAccountId,
// We need to specify the AccountId type.
AccountId,
// We don't track teleports.
NoChecking,
(),
>;
pub type AssetTransactors = (NativeTokenTransactor, RelayTokenTransactor);
pub struct HereAndInnerLocations;
impl Contains for HereAndInnerLocations {
fn contains(location: &Location) -> bool {
matches!(location.unpack(), (0, []) | (0, _))
}
}
pub type Barrier = (
TakeWeightCredit, // We need this for pallet-xcm's extrinsics to work.
AllowTopLevelPaidExecutionFrom, /* TODO: Technically, we should allow
* messages from "AssetHub". */
);
pub type Trader = FixedRateOfFungible;
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type XcmSender = XcmRouter;
type AssetTransactor = AssetTransactors;
type OriginConverter = ();
type IsReserve = RelayTokenToAssetHub;
type IsTeleporter = NativeTokenToAssetHub;
type UniversalLocation = UniversalLocation;
type Barrier = Barrier;
type Weigher = Weigher;
type Trader = Trader;
type ResponseHandler = ();
type AssetTrap = ();
type AssetLocker = ();
type AssetExchanger = ();
type AssetClaims = ();
type SubscriptionService = ();
type PalletInstancesInfo = AllPalletsWithSystem;
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
type FeeManager = ();
type MessageExporter = ();
type UniversalAliases = ();
type CallDispatcher = RuntimeCall;
type SafeCallFilter = Nothing;
type Aliasers = Nothing;
type TransactionalProcessor = ();
type HrmpNewChannelOpenRequestHandler = ();
type HrmpChannelAcceptedHandler = ();
type HrmpChannelClosingHandler = ();
type XcmRecorder = XcmPallet;
}
/// Converts a signed origin of a u64 account into a location with only the `AccountIndex64`
/// junction.
pub struct SignedToAccountIndex64(
PhantomData<(RuntimeOrigin, AccountId)>,
);
impl> TryConvert
for SignedToAccountIndex64
where
RuntimeOrigin::PalletsOrigin: From>
+ TryInto, Error = RuntimeOrigin::PalletsOrigin>,
{
fn try_convert(origin: RuntimeOrigin) -> Result {
origin.try_with_caller(|caller| match caller.try_into() {
Ok(SystemRawOrigin::Signed(who)) =>
Ok(Junction::AccountIndex64 { network: None, index: who.into() }.into()),
Ok(other) => Err(other.into()),
Err(other) => Err(other),
})
}
}
pub type LocalOriginToLocation = SignedToAccountIndex64;
impl pallet_xcm::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type SendXcmOrigin = EnsureXcmOrigin;
type XcmRouter = XcmRouter;
type ExecuteXcmOrigin = EnsureXcmOrigin;
type XcmExecuteFilter = Nothing;
type XcmExecutor = XcmExecutor;
type XcmTeleportFilter = Everything; // Put everything instead of something more restricted.
type XcmReserveTransferFilter = Everything; // Same.
type Weigher = Weigher;
type UniversalLocation = UniversalLocation;
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
type AdvertisedXcmVersion = AdvertisedXcmVersion;
type AdminOrigin = EnsureRoot;
type TrustedLockers = ();
type SovereignAccountOf = ();
type Currency = Balances;
type CurrencyMatcher = IsConcrete;
type MaxLockers = ConstU32<0>;
type MaxRemoteLockConsumers = ConstU32<0>;
type RemoteLockConsumerIdentifier = ();
type WeightInfo = TestWeightInfo;
}
#[allow(dead_code)]
pub fn new_test_ext_with_balances(balances: Vec<(AccountId, Balance)>) -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap();
pallet_balances::GenesisConfig:: { balances }
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
#[allow(dead_code)]
pub fn new_test_ext_with_balances_and_assets(
balances: Vec<(AccountId, Balance)>,
assets: Vec<(AssetIdForAssetsPallet, AccountId, Balance)>,
) -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap();
pallet_balances::GenesisConfig:: { balances }
.assimilate_storage(&mut t)
.unwrap();
pallet_assets::GenesisConfig:: {
assets: vec![
// id, owner, is_sufficient, min_balance.
// We don't actually need this to be sufficient, since we use the native assets in
// tests for the existential deposit.
(1, 0, true, 1),
],
metadata: vec![
// id, name, symbol, decimals.
(1, "Relay Token".into(), "RLY".into(), 12),
],
accounts: assets,
next_asset_id: None,
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
#[derive(Clone)]
pub(crate) struct TestClient;
pub(crate) struct RuntimeApi {
_inner: TestClient,
}
impl sp_api::ProvideRuntimeApi for TestClient {
type Api = RuntimeApi;
fn runtime_api(&self) -> sp_api::ApiRef {
RuntimeApi { _inner: self.clone() }.into()
}
}
sp_api::mock_impl_runtime_apis! {
impl LocationToAccountApi for RuntimeApi {
fn convert_location(location: VersionedLocation) -> Result {
let location = location.try_into().map_err(|_| LocationToAccountApiError::VersionedConversionFailed)?;
LocationToAccountId::convert_location(&location)
.ok_or(LocationToAccountApiError::Unsupported)
}
}
impl XcmPaymentApi for RuntimeApi {
fn query_acceptable_payment_assets(xcm_version: XcmVersion) -> Result, XcmPaymentApiError> {
Ok(vec![
VersionedAssetId::from(AssetId(HereLocation::get()))
.into_version(xcm_version)
.map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?
])
}
fn query_xcm_weight(message: VersionedXcm<()>) -> Result {
XcmPallet::query_xcm_weight(message)
}
fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result {
match asset.try_as::() {
Ok(asset_id) if asset_id.0 == HereLocation::get() => {
Ok(WeightToFee::weight_to_fee(&weight))
},
Ok(asset_id) => {
log::trace!(
target: "xcm::XcmPaymentApi::query_weight_to_asset_fee",
"query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!"
);
Err(XcmPaymentApiError::AssetNotFound)
},
Err(_) => {
log::trace!(
target: "xcm::XcmPaymentApi::query_weight_to_asset_fee",
"query_weight_to_asset_fee - failed to convert asset: {asset:?}!"
);
Err(XcmPaymentApiError::VersionedConversionFailed)
}
}
}
fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>) -> Result {
XcmPallet::query_delivery_fees(destination, message)
}
}
impl DryRunApi for RuntimeApi {
fn dry_run_call(origin: OriginCaller, call: RuntimeCall) -> Result, XcmDryRunApiError> {
use xcm_executor::RecordXcm;
pallet_xcm::Pallet::::set_record_xcm(true);
let result = call.dispatch(origin.into());
pallet_xcm::Pallet::::set_record_xcm(false);
let local_xcm = pallet_xcm::Pallet::::recorded_xcm();
let forwarded_xcms = sent_xcm()
.into_iter()
.map(|(location, message)| (
VersionedLocation::from(location),
vec![VersionedXcm::from(message)],
)).collect();
let events: Vec = System::read_events_no_consensus().map(|record| record.event.clone()).collect();
Ok(CallDryRunEffects {
local_xcm: local_xcm.map(VersionedXcm::<()>::from),
forwarded_xcms,
emitted_events: events,
execution_result: result,
})
}
fn dry_run_xcm(origin_location: VersionedLocation, xcm: VersionedXcm) -> Result, XcmDryRunApiError> {
let origin_location: Location = origin_location.try_into().map_err(|error| {
log::error!(
target: "xcm::DryRunApi::dry_run_xcm",
"Location version conversion failed with error: {:?}",
error,
);
XcmDryRunApiError::VersionedConversionFailed
})?;
let xcm: Xcm = xcm.try_into().map_err(|error| {
log::error!(
target: "xcm::DryRunApi::dry_run_xcm",
"Xcm version conversion failed with error {:?}",
error,
);
XcmDryRunApiError::VersionedConversionFailed
})?;
let mut hash = fake_message_hash(&xcm);
let result = XcmExecutor::::prepare_and_execute(
origin_location,
xcm,
&mut hash,
Weight::MAX, // Max limit available for execution.
Weight::zero(),
);
let forwarded_xcms = sent_xcm()
.into_iter()
.map(|(location, message)| (
VersionedLocation::from(location),
vec![VersionedXcm::from(message)],
)).collect();
let events: Vec = System::events().iter().map(|record| record.event.clone()).collect();
Ok(XcmDryRunEffects {
forwarded_xcms,
emitted_events: events,
execution_result: result,
})
}
}
}