Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add xcm benchmarks #1129

Merged
merged 16 commits into from
Jan 15, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: add fungible xcm benchmarks wrapper
ashutoshvarma committed Jan 9, 2024
commit 292d47f1f2e321787d4b2e3fce66b1d594c653e4
66 changes: 66 additions & 0 deletions pallets/astar-xcm-benchmarks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
[package]
name = "astar-xcm-benchmarks"
version = "0.1.0"
description = "Pallet for providing benchmarks for xcm instructions"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
repository.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
log = { workspace = true }
serde = { workspace = true, optional = true }

# Substrate
frame-support = { workspace = true }
frame-system = { workspace = true }
parity-scale-codec = { workspace = true, optional = true, features = ["derive"] }
scale-info = { workspace = true, optional = true, features = ["derive"] }
sp-std = { workspace = true }

# Polkadot / XCM
pallet-xcm-benchmarks = { workspace = true }
xcm = { workspace = true }
xcm-executor = { workspace = true }

# Benchmarks
frame-benchmarking = { workspace = true, optional = true }

# Astar
astar-primitives = { workspace = true }

[dev-dependencies]
pallet-assets = { workspace = true, features = ["std"] }
pallet-balances = { workspace = true, features = ["std"] }
sp-core = { workspace = true, features = ["std"] }
sp-io = { workspace = true, features = ["std"] }
sp-runtime = { workspace = true, features = ["std"] }
xcm-builder = { workspace = true, features = ["std"] }

[features]
default = ["std"]
std = [
"astar-primitives/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"pallet-xcm-benchmarks/std",
"parity-scale-codec/std",
"serde",
"sp-std/std",
"xcm/std",
]

try-runtime = ["frame-support/try-runtime"]

runtime-benchmarks = [
"frame-benchmarking",
"frame-system/runtime-benchmarks",
"pallet-assets/runtime-benchmarks",
"pallet-xcm-benchmarks/runtime-benchmarks",
"parity-scale-codec",
"scale-info",
"xcm-executor/runtime-benchmarks",
]
212 changes: 212 additions & 0 deletions pallets/astar-xcm-benchmarks/src/fungible/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// This file is part of Astar.

// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later

// Astar 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.

// Astar 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 Astar. If not, see <http://www.gnu.org/licenses/>.

use super::*;
use frame_benchmarking::v2::*;
use frame_support::{dispatch::Weight, traits::fungible::Inspect};
use pallet_xcm_benchmarks::{account_and_location, new_executor, AssetTransactorOf};
use sp_std::vec;
use sp_std::vec::Vec;
use xcm::latest::prelude::*;
use xcm_executor::traits::{Convert, TransactAsset};

#[benchmarks(
where
<
<
T::TransactAsset
as
Inspect<T::AccountId>
>::Balance
as
TryInto<u128>
>::Error: sp_std::fmt::Debug,
)]
mod benchmarks {
use super::*;

/// Re-write for fungibles assets (like pallet_assets's assets) as
/// upstream benchmark does not take ED (assets's min_balance) into consideration
#[benchmark]
fn transfer_asset() -> Result<(), BenchmarkError> {
let (sender_account, sender_location) = account_and_location::<T>(1);
let asset_to_deposit = T::get_multi_asset();
// take out ED from given asset
let (asset_to_send, min_balance) =
take_minimum_balance::<T>(asset_to_deposit.clone()).unwrap();
let assets: MultiAssets = vec![asset_to_send.clone()].into();
// this xcm doesn't use holding

let dest_location = T::valid_destination()?;
let dest_account = T::AccountIdConverter::convert(dest_location.clone()).unwrap();

<AssetTransactorOf<T>>::deposit_asset(
&asset_to_deposit,
&sender_location,
&XcmContext {
origin: Some(sender_location.clone()),
message_hash: [0; 32],
topic: None,
},
)
.unwrap();

let mut executor = new_executor::<T>(sender_location);
let instruction = Instruction::TransferAsset {
assets,
beneficiary: dest_location,
};
let xcm = Xcm(vec![instruction]);

#[block]
{
executor.bench_process(xcm)?;
}

assert_eq!(T::TransactAsset::balance(&sender_account), min_balance);
assert!(!T::TransactAsset::balance(&dest_account).is_zero());
Ok(())
}

/// Re-write for fungibles assets (like pallet_assets's assets) as
/// upstream benchmark does not take ED (assets's min_balance) into consideration
#[benchmark]
fn transfer_reserve_asset() -> Result<(), BenchmarkError> {
let (sender_account, sender_location) = account_and_location::<T>(1);
let dest_location = T::valid_destination()?;
let dest_account = T::AccountIdConverter::convert(dest_location.clone()).unwrap();

let asset_to_deposit = T::get_multi_asset();
// take out ED from given asset
let (asset_to_send, min_balance) =
take_minimum_balance::<T>(asset_to_deposit.clone()).unwrap();
let assets: MultiAssets = vec![asset_to_send].into();

<AssetTransactorOf<T>>::deposit_asset(
&asset_to_deposit,
&sender_location,
&XcmContext {
origin: Some(sender_location.clone()),
message_hash: [0; 32],
topic: None,
},
)
.unwrap();
assert!(T::TransactAsset::balance(&dest_account).is_zero());

let mut executor = new_executor::<T>(sender_location);
let instruction = Instruction::TransferReserveAsset {
assets,
dest: dest_location,
xcm: Xcm::new(),
};
let xcm = Xcm(vec![instruction]);

#[block]
{
executor.bench_process(xcm)?;
}

assert_eq!(T::TransactAsset::balance(&sender_account), min_balance);
assert!(!T::TransactAsset::balance(&dest_account).is_zero());
Ok(())
}

#[benchmark]
fn receive_teleported_asset() -> Result<(), BenchmarkError> {
#[block]
{}
Err(BenchmarkError::Override(BenchmarkResult::from_weight(
Weight::MAX,
)))
}

impl_benchmark_test_suite!(
Pallet,
crate::fungible::mock::new_test_ext(),
crate::fungible::mock::Test
);
}

pub struct XcmFungibleBenchmarks<T>(sp_std::marker::PhantomData<T>);
// Benchmarks wrapper
impl<T: Config> frame_benchmarking::Benchmarking for XcmFungibleBenchmarks<T>
where
<<T::TransactAsset as Inspect<T::AccountId>>::Balance as TryInto<u128>>::Error:
sp_std::fmt::Debug,
{
fn benchmarks(extra: bool) -> Vec<frame_benchmarking::BenchmarkMetadata> {
// all the fungible xcm benchmarks
use pallet_xcm_benchmarks::fungible::Pallet as PalletXcmFungibleBench;
PalletXcmFungibleBench::<T>::benchmarks(extra)
}
fn run_benchmark(
extrinsic: &[u8],
c: &[(frame_benchmarking::BenchmarkParameter, u32)],
whitelist: &[frame_benchmarking::TrackedStorageKey],
verify: bool,
internal_repeats: u32,
) -> Result<Vec<frame_benchmarking::BenchmarkResult>, frame_benchmarking::BenchmarkError> {
use pallet_xcm_benchmarks::fungible::Pallet as PalletXcmFungibleBench;

use crate::fungible::Pallet as AstarXcmFungibleBench;
if AstarXcmFungibleBench::<T>::benchmarks(true)
.iter()
.any(|x| x.name == extrinsic)
{
AstarXcmFungibleBench::<T>::run_benchmark(
extrinsic,
c,
whitelist,
verify,
internal_repeats,
)
} else {
PalletXcmFungibleBench::<T>::run_benchmark(
extrinsic,
c,
whitelist,
verify,
internal_repeats,
)
}
}
}

/// Take out the ED from given MultiAsset (if fungible)
fn take_minimum_balance<T: Config>(
mut asset: MultiAsset,
) -> Result<
(
MultiAsset,
<T::TransactAsset as Inspect<T::AccountId>>::Balance,
),
(),
>
where
<<T::TransactAsset as Inspect<T::AccountId>>::Balance as TryInto<u128>>::Error:
sp_std::fmt::Debug,
{
let minimum_balance = T::TransactAsset::minimum_balance();

if let Fungible(fun) = asset.fun {
asset.fun = Fungible(fun.saturating_sub(minimum_balance.try_into().map_err(|_| ())?));
}

Ok((asset, minimum_balance))
}
289 changes: 289 additions & 0 deletions pallets/astar-xcm-benchmarks/src/fungible/mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
// This file is part of Astar.

// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later

// Astar 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.

// Astar 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 Astar. If not, see <http://www.gnu.org/licenses/>.

//! A mock runtime for XCM benchmarking.
use core::marker::PhantomData;

use crate::{fungible as xcm_fungible_benchmark, mock::*, *};
use astar_primitives::xcm::ReserveAssetFilter;
use frame_benchmarking::BenchmarkError;
use frame_support::{
assert_ok, parameter_types,
traits::{fungible::ItemOf, AsEnsureOriginWithArg, ConstU32, Everything, Nothing},
weights::Weight,
};
use frame_system::{EnsureRoot, EnsureSigned};
use sp_core::{ConstU64, Get, H256};
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
};
use xcm::latest::prelude::*;
use xcm_builder::{AllowUnpaidExecutionFrom, FungiblesAdapter, MintLocation, NoChecking};

type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
type Balance = u64;
type AccountId = u64;
type AssetId = u128;

// For testing the pallet, we construct a mock runtime.
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
Assets: pallet_assets::{Pallet, Call, Storage, Event<T>},
XcmBalancesBenchmark: xcm_fungible_benchmark::{Pallet},
}
);

parameter_types! {
pub const BlockHashCount: u64 = 250;
pub BlockWeights: frame_system::limits::BlockWeights =
frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, u64::MAX));
pub UniversalLocation: InteriorMultiLocation = Here;
}
impl frame_system::Config for Test {
type BaseCallFilter = Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type RuntimeCall = RuntimeCall;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = ConstU32<16>;
}

parameter_types! {
pub const ExistentialDeposit: u64 = 10;
}

impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = Balance;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type HoldIdentifier = ();
type FreezeIdentifier = ();
type MaxHolds = ConstU32<0>;
type MaxFreezes = ConstU32<0>;
}

impl pallet_assets::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Balance = Balance;
type AssetId = AssetId;
type AssetIdParameter = AssetId;
type Currency = Balances;
type CreateOrigin = AsEnsureOriginWithArg<EnsureSigned<AccountId>>;
type ForceOrigin = EnsureRoot<AccountId>;
type AssetDeposit = ConstU64<10>;
type MetadataDepositBase = ConstU64<10>;
type MetadataDepositPerByte = ConstU64<1>;
type AssetAccountDeposit = ConstU64<10>;
type ApprovalDeposit = ConstU64<10>;
type StringLimit = ConstU32<50>;
type Freezer = ();
type Extra = ();
type RemoveItemsLimit = ConstU32<100>;
type CallbackHandle = ();
type WeightInfo = ();
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = ();
}

parameter_types! {
pub const AssetDeposit: u64 = 100 * ExistentialDeposit::get();
pub const ApprovalDeposit: u64 = 1 * ExistentialDeposit::get();
pub const StringLimit: u32 = 50;
pub const MetadataDepositBase: u64 = 10 * ExistentialDeposit::get();
pub const MetadataDepositPerByte: u64 = 1 * ExistentialDeposit::get();
}

pub struct MatchOnlyAsset<Asset>(PhantomData<Asset>);
impl<Asset: Get<AssetId>> xcm_executor::traits::MatchesFungibles<AssetId, Balance>
for MatchOnlyAsset<Asset>
{
fn matches_fungibles(
a: &MultiAsset,
) -> core::result::Result<(AssetId, Balance), xcm_executor::traits::prelude::Error> {
use sp_runtime::traits::SaturatedConversion;
match a {
MultiAsset {
fun: Fungible(amount),
..
} => Ok((Asset::get(), (*amount).saturated_into::<u64>())),
_ => Err(xcm_executor::traits::prelude::Error::AssetNotHandled),
}
}
}

// Use ONLY assets as the asset transactor.
pub type AssetTransactor = FungiblesAdapter<
Assets,
MatchOnlyAsset<TransactAssetId>,
AccountIdConverter,
AccountId,
NoChecking,
DummyCheckingAccount,
>;

parameter_types! {
/// Maximum number of instructions in a single XCM fragment. A sanity check against weight
/// calculations getting too crazy.
pub const MaxInstructions: u32 = 100;
pub const MaxAssetsIntoHolding: u32 = 64;
}

pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type XcmSender = DevNull;
type AssetTransactor = AssetTransactor;
type OriginConverter = ();
type IsReserve = ReserveAssetFilter;
type IsTeleporter = ();
type UniversalLocation = UniversalLocation;
type Barrier = AllowUnpaidExecutionFrom<Everything>;
type Weigher = xcm_builder::FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
type Trader = xcm_builder::FixedRateOfFungible<WeightPrice, ()>;
type ResponseHandler = DevNull;
type AssetTrap = ();
type AssetLocker = ();
type AssetExchanger = ();
type AssetClaims = ();
type SubscriptionService = ();
type PalletInstancesInfo = AllPalletsWithSystem;
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
type FeeManager = ();
type MessageExporter = ();
type UniversalAliases = Nothing;
type CallDispatcher = RuntimeCall;
type SafeCallFilter = Everything;
}

impl pallet_xcm_benchmarks::Config for Test {
type XcmConfig = XcmConfig;
type AccountIdConverter = AccountIdConverter;
fn valid_destination() -> Result<MultiLocation, BenchmarkError> {
let valid_destination: MultiLocation = X1(AccountId32 {
network: None,
id: [0u8; 32],
})
.into();

Ok(valid_destination)
}
fn worst_case_holding(_depositable_count: u32) -> MultiAssets {
crate::mock::mock_worst_case_holding()
}
}

parameter_types! {
pub DummyCheckingAccount: AccountId = 0;
pub const CheckingAccount: Option<(u64, MintLocation)> = Some((100, MintLocation::Local));
pub const ChildTeleporter: MultiLocation = Parachain(1000).into_location();
pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some((
ChildTeleporter::get(),
MultiAsset { id: Concrete(Here.into_location()), fun: Fungible(100) },
));
pub const TeleportConcreteFungible: (MultiAssetFilter, MultiLocation) =
(Wild(AllOf { fun: WildFungible, id: Concrete(Here.into_location()) }), ChildTeleporter::get());
pub const ReserveConcreteFungible: (MultiAssetFilter, MultiLocation) =
(Wild(AllOf { fun: WildFungible, id: Concrete(Here.into_location()) }), ChildTeleporter::get());
pub const TransactAssetId: u128 = 1;
pub const TransactAssetLocation: MultiLocation = MultiLocation { parents: 0, interior: X1(GeneralIndex(TransactAssetId::get())) };
pub const WeightPrice: (xcm::latest::AssetId, u128, u128) = (Concrete(TransactAssetLocation::get()), 1_000_000, 1024);
}

impl pallet_xcm_benchmarks::fungible::Config for Test {
type TransactAsset = ItemOf<Assets, TransactAssetId, AccountId>;
type CheckedAccount = CheckingAccount;
type TrustedTeleporter = TrustedTeleporter;

fn get_multi_asset() -> MultiAsset {
let min_balance = 100u64;
let asset_location: MultiLocation = GeneralIndex(TransactAssetId::get()).into();

assert_ok!(Assets::force_create(
RuntimeOrigin::root(),
TransactAssetId::get(),
0u64,
true,
min_balance,
));

MultiAsset {
id: Concrete(asset_location),
fun: Fungible((min_balance * 100).into()),
}
}
}

impl fungible::Config for Test {}
impl Config for Test {}

#[cfg(feature = "runtime-benchmarks")]
pub fn new_test_ext() -> sp_io::TestExternalities {
use sp_runtime::BuildStorage;
let t = GenesisConfig {
..Default::default()
}
.build_storage()
.unwrap();

let mut ext = sp_io::TestExternalities::from(t);
ext.execute_with(|| {
System::set_block_number(1);
// assert_ok!(Assets::force_create(
// RuntimeOrigin::root(),
// TransactAssetId::get(),
// 0u64,
// true,
// 100,
// ));
// register the transact asset
});
ext
}
36 changes: 36 additions & 0 deletions pallets/astar-xcm-benchmarks/src/fungible/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// This file is part of Astar.

// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later

// Astar 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.

// Astar 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 Astar. If not, see <http://www.gnu.org/licenses/>.

pub use pallet::*;

pub mod benchmarking;

#[cfg(test)]
pub mod mock;

#[frame_support::pallet]
pub mod pallet {
#[pallet::config]
pub trait Config<I: 'static = ()>:
frame_system::Config + crate::Config + pallet_xcm_benchmarks::fungible::Config
{
}

#[pallet::pallet]
pub struct Pallet<T, I = ()>(_);
}