diff --git a/Cargo.lock b/Cargo.lock index d7b307c7..bf63bc0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8507,6 +8507,31 @@ dependencies = [ "sp-tracing", ] +[[package]] +name = "pallet-tangle-lst-benchmarking" +version = "35.0.0" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-bags-list", + "pallet-balances", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-tangle-lst", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-runtime-interface", + "sp-staking", + "sp-std", +] + [[package]] name = "pallet-timestamp" version = "36.0.1" @@ -14242,6 +14267,7 @@ dependencies = [ "pallet-staking-reward-curve", "pallet-sudo", "pallet-tangle-lst", + "pallet-tangle-lst-benchmarking", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", @@ -14371,6 +14397,7 @@ dependencies = [ "pallet-staking-reward-curve", "pallet-sudo", "pallet-tangle-lst", + "pallet-tangle-lst-benchmarking", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", diff --git a/Cargo.toml b/Cargo.toml index 8e669673..7d24bb7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "pallets/*", "pallets/services/rpc", "pallets/services/rpc/runtime-api", + "pallets/tangle-lst/benchmarking", "frost", "frost/frost-*", "precompiles/pallet-democracy", @@ -118,7 +119,7 @@ pallet-services = { path = "pallets/services", default-features = false } pallet-services-rpc-runtime-api = { path = "pallets/services/rpc/runtime-api", default-features = false } pallet-services-rpc = { path = "pallets/services/rpc" } pallet-multi-asset-delegation = { path = "pallets/multi-asset-delegation", default-features = false } - +pallet-tangle-lst-benchmarking = { path = "pallets/tangle-lst/benchmarking", default-features = false } k256 = { version = "0.13.3", default-features = false } p256 = { version = "0.13.2", default-features = false } diff --git a/pallets/tangle-lst/benchmarking/Cargo.toml b/pallets/tangle-lst/benchmarking/Cargo.toml new file mode 100644 index 00000000..89daeb4e --- /dev/null +++ b/pallets/tangle-lst/benchmarking/Cargo.toml @@ -0,0 +1,70 @@ +[package] +name = "pallet-tangle-lst-benchmarking" +version = "35.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage = "https://substrate.io" +repository.workspace = true +description = "FRAME nomination pools pallet benchmarking (polkadot v1.15.0)" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# parity +parity-scale-codec = { features = ["derive"], workspace = true } +scale-info = { features = ["derive"], workspace = true } +frame-benchmarking.workspace = true +frame-election-provider-support.workspace = true +frame-support.workspace = true +frame-system.workspace = true +pallet-bags-list.workspace = true +pallet-staking.workspace = true +pallet-tangle-lst.workspace = true +sp-runtime.workspace = true +sp-runtime-interface.workspace = true +sp-staking.workspace = true +pallet-assets.workspace = true +sp-std.workspace = true + +[dev-dependencies] +pallet-balances = { workspace = true } +pallet-timestamp = { workspace = true } +pallet-staking-reward-curve = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } + +[features] +default = ["std"] + +std = [ + "parity-scale-codec/std", + "frame-benchmarking/std", + "frame-election-provider-support/std", + "frame-support/std", + "frame-system/std", + "pallet-bags-list/std", + "pallet-tangle-lst/std", + "pallet-staking/std", + "scale-info/std", + "sp-runtime-interface/std", + "sp-runtime/std", + "sp-staking/std", + "pallet-assets/std", + "sp-std/std", +] + +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-election-provider-support/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-bags-list/runtime-benchmarks", + "pallet-tangle-lst/runtime-benchmarks", + "pallet-staking/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "sp-staking/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", +] diff --git a/pallets/tangle-lst/benchmarking/src/inner.rs b/pallets/tangle-lst/benchmarking/src/inner.rs new file mode 100644 index 00000000..88d70bac --- /dev/null +++ b/pallets/tangle-lst/benchmarking/src/inner.rs @@ -0,0 +1,352 @@ +//! Benchmarks for the nomination Lst coupled with the staking and bags list pallets. + +use alloc::{vec, vec::Vec}; +use frame_benchmarking::v1::{account, whitelist_account}; +use frame_election_provider_support::SortedListProvider; +use frame_support::traits::Currency; +use frame_support::{ + assert_ok, ensure, + traits::{ + fungible::{Inspect, Mutate, Unbalanced}, + tokens::Preservation, + Get, Imbalance, + }, +}; +use frame_system::RawOrigin as RuntimeOrigin; +use pallet_staking::MaxNominationsOf; +use pallet_tangle_lst::{ + BalanceOf, BondExtra, BondedPoolInner, BondedPools, ClaimPermission, ClaimPermissions, + Commission, CommissionChangeRate, CommissionClaimPermission, ConfigOp, GlobalMaxCommission, + MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Lst, PoolId, PoolRoles, PoolState, + RewardPools, SubPoolsStorage, +}; +use sp_runtime::{ + traits::{Bounded, StaticLookup, Zero}, + Perbill, +}; +use sp_staking::EraIndex; +use sp_staking::StakingInterface; +// `frame_benchmarking::benchmarks!` macro needs this +use pallet_tangle_lst::Call; + +type CurrencyOf = ::Currency; + +const USER_SEED: u32 = 0; +const MAX_SPANS: u32 = 100; + +pub(crate) type VoterBagsListInstance = pallet_bags_list::Instance1; +pub trait Config: + pallet_tangle_lst::Config + pallet_staking::Config + pallet_bags_list::Config +{ +} + +pub struct Pallet(Lst); + +fn create_funded_user_with_balance( + string: &'static str, + n: u32, + balance: BalanceOf, +) -> T::AccountId { + let user = account(string, n, USER_SEED); + T::Currency::make_free_balance_be(&user, balance); + user +} + +// Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free +// balance. +fn create_pool_account( + n: u32, + balance: BalanceOf, + commission: Option, +) -> (T::AccountId, T::AccountId) { + let ed = CurrencyOf::::minimum_balance(); + let pool_creator: T::AccountId = + create_funded_user_with_balance::("pool_creator", n, ed + balance * 2u32.into()); + let pool_creator_lookup = T::Lookup::unlookup(pool_creator.clone()); + + Lst::::create( + RuntimeOrigin::Signed(pool_creator.clone()).into(), + balance, + pool_creator_lookup.clone(), + pool_creator_lookup.clone(), + pool_creator_lookup, + Default::default(), + ) + .unwrap(); + + if let Some(c) = commission { + let pool_id = pallet_tangle_lst::LastPoolId::::get(); + Lst::::set_commission( + RuntimeOrigin::Signed(pool_creator.clone()).into(), + pool_id, + Some((c, pool_creator.clone())), + ) + .expect("pool just created, commission can be set by root; qed"); + } + + let pool_account = pallet_tangle_lst::BondedPools::::iter() + .find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator) + .map(|(pool_id, _)| Lst::::create_bonded_account(pool_id)) + .expect("pool_creator created a pool above"); + + (pool_creator, pool_account) +} + +fn vote_to_balance(vote: u64) -> Result, &'static str> { + vote.try_into().map_err(|_| "could not convert u64 to Balance") +} + +frame_benchmarking::benchmarks! { + where_clause { + where + T: pallet_staking::Config, + pallet_staking::BalanceOf: From, + BalanceOf: Into, + } + + join { + let origin_weight = Lst::::depositor_min_bond() * 2u32.into(); + + // setup the worst case list scenario. + let joiner_free = CurrencyOf::::minimum_balance() * 100u32.into(); + + let joiner: T::AccountId + = create_funded_user_with_balance::("joiner", 0, joiner_free); + + whitelist_account!(joiner); + }: _(RuntimeOrigin::Signed(joiner.clone()), 100u32.into(), 1) + verify { + assert_eq!(CurrencyOf::::free_balance(&joiner), joiner_free); + } + + unbond { + let member_id : T::AccountId = account("member", USER_SEED, 0); + whitelist_account!(member_id); + // create a pool with a single member + let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); + let min_create_bond = Lst::::depositor_min_bond(); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); + Lst::::join(RuntimeOrigin::Signed(member_id.clone()).into(), min_join_bond, 1) + .unwrap(); + let member_id_lookup = T::Lookup::unlookup(member_id.clone()); + }: _(RuntimeOrigin::Signed(member_id.clone()), member_id_lookup, 1_u32.into(), 100_u32.into()) + + pool_withdraw_unbonded { + let s in 0 .. MAX_SPANS; + + let min_create_bond = Lst::::depositor_min_bond(); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); + + // Add a new member + let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); + let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); + Lst::::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1) + .unwrap(); + + // Sanity check join worked + assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); + + // Unbond the new member + Lst::::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone(), 1_u32.into()).unwrap(); + + // Sanity check that unbond worked + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); + // Set the current era + pallet_staking::CurrentEra::::put(EraIndex::max_value()); + + // Add `s` count of slashing spans to storage. + pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); + whitelist_account!(pool_account); + }: _(RuntimeOrigin::Signed(pool_account.clone()), 1, s) + verify { + // The joiners funds didn't change + assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); + // The unlocking chunk was removed + assert_eq!(pallet_staking::Ledger::::get(pool_account).unwrap().unlocking.len(), 0); + } + + create { + let min_create_bond = Lst::::depositor_min_bond(); + let depositor: T::AccountId = account("depositor", USER_SEED, 0); + let depositor_lookup = T::Lookup::unlookup(depositor.clone()); + + // Give the depositor some balance to bond + // it needs to transfer min balance to reward account as well so give additional min balance. + CurrencyOf::::make_free_balance_be(&depositor, min_create_bond + CurrencyOf::::minimum_balance() * 2u32.into()); + // Make sure no Lst exist at a pre-condition for our verify checks + assert_eq!(RewardPools::::count(), 0); + assert_eq!(BondedPools::::count(), 0); + + whitelist_account!(depositor); + }: _( + RuntimeOrigin::Signed(depositor.clone()), + min_create_bond, + depositor_lookup.clone(), + depositor_lookup.clone(), + depositor_lookup, + Default::default() + ) + verify { + assert_eq!(RewardPools::::count(), 1); + assert_eq!(BondedPools::::count(), 1); + let (_, new_pool) = BondedPools::::iter().next().unwrap(); + assert_eq!( + new_pool, + BondedPoolInner { + commission: Commission::default(), + roles: PoolRoles { + depositor: depositor.clone(), + root: Some(depositor.clone()), + nominator: Some(depositor.clone()), + bouncer: Some(depositor.clone()), + }, + state: PoolState::Open, + metadata: Default::default(), + } + ); + } + + nominate { + let n in 1 .. MaxNominationsOf::::get(); + + // Create a pool + let min_create_bond = Lst::::depositor_min_bond() * 2u32.into(); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); + + // Create some accounts to nominate. For the sake of benchmarking they don't need to be + // actual validators + let validators: Vec<_> = (0..n) + .map(|i| account("stash", USER_SEED, i)) + .collect(); + + whitelist_account!(depositor); + }:_(RuntimeOrigin::Signed(depositor.clone()), 1, validators) + verify { + assert_eq!(RewardPools::::count(), 1); + assert_eq!(BondedPools::::count(), 1); + let (_, new_pool) = BondedPools::::iter().next().unwrap(); + assert_eq!( + new_pool, + BondedPoolInner { + commission: Commission::default(), + roles: PoolRoles { + depositor: depositor.clone(), + root: Some(depositor.clone()), + nominator: Some(depositor.clone()), + bouncer: Some(depositor.clone()), + }, + state: PoolState::Open, + metadata: Default::default(), + } + ); + } + + set_metadata { + let n in 1 .. ::MaxMetadataLen::get(); + + // Create a pool + let (depositor, pool_account) = create_pool_account::(0, Lst::::depositor_min_bond() * 2u32.into(), None); + + // Create metadata of the max possible size + let metadata: Vec = (0..n).map(|_| 42).collect(); + + whitelist_account!(depositor); + }:_(RuntimeOrigin::Signed(depositor), 1, metadata.clone()) + verify { + assert_eq!(Metadata::::get(&1), metadata); + } + + set_configs { + }:_( + RuntimeOrigin::Root, + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(Perbill::max_value()) + ) verify { + assert_eq!(MinJoinBond::::get(), BalanceOf::::max_value()); + assert_eq!(MinCreateBond::::get(), BalanceOf::::max_value()); + assert_eq!(MaxPools::::get(), Some(u32::MAX)); + assert_eq!(MaxPoolMembers::::get(), Some(u32::MAX)); + assert_eq!(MaxPoolMembersPerLst::::get(), Some(u32::MAX)); + assert_eq!(GlobalMaxCommission::::get(), Some(Perbill::max_value())); + } + + update_roles { + let first_id = pallet_tangle_lst::LastPoolId::::get() + 1; + let (root, _) = create_pool_account::(0, Lst::::depositor_min_bond() * 2u32.into(), None); + let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED); + }:_( + RuntimeOrigin::Signed(root.clone()), + first_id, + ConfigOp::Set(random.clone()), + ConfigOp::Set(random.clone()), + ConfigOp::Set(random.clone()) + ) verify { + assert_eq!( + pallet_tangle_lst::BondedPools::::get(first_id).unwrap().roles, + pallet_tangle_lst::PoolRoles { + depositor: root, + nominator: Some(random.clone()), + bouncer: Some(random.clone()), + root: Some(random), + }, + ) + } + + chill { + // Create a pool + let (depositor, pool_account) = create_pool_account::(0, Lst::::depositor_min_bond() * 2u32.into(), None); + + // Nominate with the pool. + let validators: Vec<_> = (0..MaxNominationsOf::::get()) + .map(|i| account("stash", USER_SEED, i)) + .collect(); + + assert!(T::Staking::nominations(Lst::from(pool_account.clone())).is_some()); + + whitelist_account!(depositor); + }:_(RuntimeOrigin::Signed(depositor.clone()), 1) + verify { + assert!(T::Staking::nominations(Lst::from(pool_account.clone())).is_none()); + } + + set_commission { + // Create a pool - do not set a commission yet. + let (depositor, pool_account) = create_pool_account::(0, Lst::::depositor_min_bond() * 2u32.into(), None); + // set a max commission + Lst::::set_commission_max(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), Perbill::from_percent(50)).unwrap(); + // set a change rate + Lst::::set_commission_change_rate(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), CommissionChangeRate { + max_increase: Perbill::from_percent(20), + min_delay: 0u32.into(), + }).unwrap(); + // set a claim permission to an account. + Lst::::set_commission_claim_permission( + RuntimeOrigin::Signed(depositor.clone()).into(), + 1u32.into(), + Some(CommissionClaimPermission::Account(depositor.clone())) + ).unwrap(); + + }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some((Perbill::from_percent(20), depositor.clone()))) + verify { + assert_eq!(BondedPools::::get(1).unwrap().commission, Commission { + current: Some((Perbill::from_percent(20), depositor.clone())), + max: Some(Perbill::from_percent(50)), + change_rate: Some(CommissionChangeRate { + max_increase: Perbill::from_percent(20), + min_delay: 0u32.into() + }), + throttle_from: Some(1u32.into()), + claim_permission: Some(CommissionClaimPermission::Account(depositor)), + }); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(), + crate::mock::Runtime + ); +} diff --git a/pallets/tangle-lst/benchmarking/src/lib.rs b/pallets/tangle-lst/benchmarking/src/lib.rs new file mode 100644 index 00000000..5dc5dd59 --- /dev/null +++ b/pallets/tangle-lst/benchmarking/src/lib.rs @@ -0,0 +1,15 @@ +//! Benchmarks for the nomination pools coupled with the staking and bags list pallets. + +#![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "256"] + +extern crate alloc; + +#[cfg(feature = "runtime-benchmarks")] +pub mod inner; + +#[cfg(feature = "runtime-benchmarks")] +pub use inner::*; + +#[cfg(all(feature = "runtime-benchmarks", test))] +pub(crate) mod mock; diff --git a/pallets/tangle-lst/benchmarking/src/mock.rs b/pallets/tangle-lst/benchmarking/src/mock.rs new file mode 100644 index 00000000..f7116ecb --- /dev/null +++ b/pallets/tangle-lst/benchmarking/src/mock.rs @@ -0,0 +1,458 @@ +// This file is part of Substrate. + +use super::*; +use frame_election_provider_support::VoteWeight; +use frame_support::traits::AsEnsureOriginWithArg; +use frame_support::traits::Hooks; +use frame_support::traits::OnFinalize; +use frame_support::{assert_ok, derive_impl, parameter_types, PalletId}; +use frame_system::RawOrigin; +use pallet_tangle_lst::BondedPools; +use pallet_tangle_lst::Config; +use pallet_tangle_lst::Event; +use pallet_tangle_lst::LastPoolId; +use pallet_tangle_lst::PoolId; +use pallet_tangle_lst::PoolState; +use sp_core::U256; +use sp_runtime::traits::ConstU128; +use sp_runtime::traits::ConstU32; +use sp_runtime::traits::ConstU64; +use sp_runtime::traits::Convert; +use sp_runtime::traits::Zero; +use sp_runtime::DispatchError; +use sp_runtime::DispatchResult; +use sp_runtime::Perbill; +use sp_runtime::{BuildStorage, FixedU128}; +use sp_runtime_interface::sp_tracing; +use sp_staking::EraIndex; +use sp_staking::{OnStakingUpdate, Stake}; +use sp_std::collections::btree_map::BTreeMap; + +pub type BlockNumber = u64; +pub type AccountId = u128; +pub type Balance = u128; +pub type RewardCounter = FixedU128; +pub type AssetId = u32; +// This sneaky little hack allows us to write code exactly as we would do in the pallet in the tests +// as well, e.g. `StorageItem::::get()`. +pub type T = Runtime; +pub type Currency = ::Currency; + +// Ext builder creates a pool with id 1. +pub fn default_bonded_account() -> AccountId { + Lst::create_bonded_account(1) +} + +// Ext builder creates a pool with id 1. +pub fn default_reward_account() -> AccountId { + Lst::create_reward_account(1) +} + +parameter_types! { + pub static MinJoinBondConfig: Balance = 2; + pub static CurrentEra: EraIndex = 0; + pub static BondingDuration: EraIndex = 3; + pub storage BondedBalanceMap: BTreeMap = Default::default(); + // map from a user to a vec of eras and amounts being unlocked in each era. + pub storage UnbondingBalanceMap: BTreeMap> = Default::default(); + #[derive(Clone, PartialEq)] + pub static MaxUnbonding: u32 = 8; + pub static StakingMinBond: Balance = 10; + pub storage Nominations: Option> = None; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type SS58Prefix = (); + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = sp_runtime::traits::IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = (); + type DbWeight = (); + type BlockLength = (); + type BlockWeights = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +parameter_types! { + pub static ExistentialDeposit: Balance = 5; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = frame_support::traits::ConstU32<1024>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<1>; + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); +} + +pallet_staking_reward_curve::build! { + const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} +parameter_types! { + pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; +} +#[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] +impl pallet_staking::Config for Runtime { + type Currency = Balances; + type CurrencyBalance = Balance; + type UnixTime = pallet_timestamp::Pallet; + type AdminOrigin = frame_system::EnsureRoot; + type EraPayout = pallet_staking::ConvertCurve; + type ElectionProvider = + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = VoterList; + type TargetList = pallet_staking::UseValidatorsMap; + type EventListeners = (); +} + +parameter_types! { + pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; +} + +impl pallet_bags_list::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type BagThresholds = BagThresholds; + type ScoreProvider = Staking; + type Score = VoteWeight; +} + +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub static PostUnbondingPoolsWindow: u32 = 2; + pub static MaxMetadataLen: u32 = 2; + pub static CheckLevel: u8 = 255; + pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); +} + +impl pallet_tangle_lst::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type RuntimeFreezeReason = RuntimeFreezeReason; + type RewardCounter = RewardCounter; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type Staking = Staking; + type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type PalletId = PoolsPalletId; + type MaxMetadataLen = MaxMetadataLen; + type MaxUnbonding = MaxUnbonding; + type MaxNameLength = ConstU32<50>; + type Fungibles = Assets; + type AssetId = AssetId; + type PoolId = PoolId; + type ForceOrigin = frame_system::EnsureRoot; + type MaxPointsToBalance = frame_support::traits::ConstU8<10>; +} + +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = u128; + type AssetId = AssetId; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type WeightInfo = (); + type CallbackHandle = (); + type Extra = (); + type RemoveItemsLimit = ConstU32<5>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +type Block = frame_system::mocking::MockBlock; +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + Timestamp: pallet_timestamp, + Balances: pallet_balances, + Staking: pallet_staking, + VoterList: pallet_bags_list::, + Assets: pallet_assets, + Lst: pallet_tangle_lst, + } +); + +pub struct ExtBuilder { + members: Vec<(AccountId, Balance)>, + max_members: Option, + max_members_per_pool: Option, + global_max_commission: Option, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + members: Default::default(), + max_members: Some(4), + max_members_per_pool: Some(3), + global_max_commission: Some(Perbill::from_percent(90)), + } + } +} + +#[cfg_attr(feature = "fuzzing", allow(dead_code))] +impl ExtBuilder { + // Add members to pool 0. + pub fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self { + self.members = members; + self + } + + pub fn ed(self, ed: Balance) -> Self { + ExistentialDeposit::set(ed); + self + } + + pub fn min_bond(self, min: Balance) -> Self { + StakingMinBond::set(min); + self + } + + pub fn min_join_bond(self, min: Balance) -> Self { + MinJoinBondConfig::set(min); + self + } + + pub fn with_check(self, level: u8) -> Self { + CheckLevel::set(level); + self + } + + pub fn max_members(mut self, max: Option) -> Self { + self.max_members = max; + self + } + + pub fn max_members_per_pool(mut self, max: Option) -> Self { + self.max_members_per_pool = max; + self + } + + pub fn global_max_commission(mut self, commission: Option) -> Self { + self.global_max_commission = commission; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut storage = + frame_system::GenesisConfig::::default().build_storage().unwrap(); + + let _ = pallet_tangle_lst::GenesisConfig:: { + min_join_bond: MinJoinBondConfig::get(), + min_create_bond: 2, + max_pools: Some(2), + max_members_per_pool: self.max_members_per_pool, + max_members: self.max_members, + global_max_commission: self.global_max_commission, + } + .assimilate_storage(&mut storage); + + let mut ext = sp_io::TestExternalities::from(storage); + + ext.execute_with(|| { + use frame_support::traits::Currency; + // for events to be deposited. + frame_system::Pallet::::set_block_number(1); + + // make a pool + let amount_to_bond = Lst::depositor_min_bond(); + ::Currency::make_free_balance_be( + &10u32.into(), + amount_to_bond * 5, + ); + assert_ok!(Lst::create( + RawOrigin::Signed(10).into(), + amount_to_bond, + 900, + 901, + 902, + Default::default() + )); + assert_ok!(Lst::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1])); + let last_pool = LastPoolId::::get(); + for (account_id, bonded) in self.members { + ::Currency::make_free_balance_be( + &account_id, + bonded * 2, + ); + assert_ok!(Lst::join(RawOrigin::Signed(account_id).into(), bonded, last_pool)); + } + }); + + ext + } + + pub fn build_and_execute(self, test: impl FnOnce()) { + self.build().execute_with(|| { + test(); + //Pools::do_try_state(CheckLevel::get()).unwrap(); + }) + } +} + +pub fn unsafe_set_state(pool_id: PoolId, state: PoolState) { + BondedPools::::try_mutate(pool_id, |maybe_bonded_pool| { + maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { + bonded_pool.state = state; + }) + }) + .unwrap() +} + +parameter_types! { + storage PoolsEvents: u32 = 0; + storage BalancesEvents: u32 = 0; +} + +/// Helper to run a specified amount of blocks. +pub fn run_blocks(n: u64) { + let current_block = System::block_number(); + run_to_block(n + current_block); +} + +/// Helper to run to a specific block. +pub fn run_to_block(n: u64) { + let current_block = System::block_number(); + assert!(n > current_block); + while System::block_number() < n { + as OnFinalize>::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + Lst::on_initialize(System::block_number()); + } +} + +/// All events of this pallet. +pub fn pool_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Lst(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = PoolsEvents::get(); + PoolsEvents::set(&(events.len() as u32)); + events.into_iter().skip(already_seen as usize).collect() +} + +/// All events of the `Balances` pallet. +pub fn balances_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Balances(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = BalancesEvents::get(); + BalancesEvents::set(&(events.len() as u32)); + events.into_iter().skip(already_seen as usize).collect() +} + +/// Same as `fully_unbond`, in permissioned setting. +pub fn fully_unbond_permissioned(pool_id: PoolId, member: AccountId) -> DispatchResult { + let points = Assets::balance(pool_id, member); + Lst::unbond(RuntimeOrigin::signed(member), member, pool_id, points) +} + +#[derive(PartialEq, Debug)] +pub enum RewardImbalance { + // There is no reward deficit. + Surplus(Balance), + // There is a reward deficit. + Deficit(Balance), +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let _ = pallet_tangle_lst::GenesisConfig:: { + min_join_bond: 2, + min_create_bond: 2, + max_pools: Some(3), + max_members_per_pool: Some(3), + max_members: Some(3 * 3), + global_max_commission: Some(Perbill::from_percent(50)), + } + .assimilate_storage(&mut storage); + sp_io::TestExternalities::from(storage) +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn u256_to_balance_convert_works() { + assert_eq!(U256ToBalance::convert(0u32.into()), Zero::zero()); + assert_eq!(U256ToBalance::convert(Balance::MAX.into()), Balance::MAX) + } + + #[test] + #[should_panic] + fn u256_to_balance_convert_panics_correctly() { + U256ToBalance::convert(U256::from(Balance::MAX).saturating_add(1u32.into())); + } + + #[test] + fn balance_to_u256_convert_works() { + assert_eq!(BalanceToU256::convert(0u32.into()), U256::zero()); + assert_eq!(BalanceToU256::convert(Balance::MAX), Balance::MAX.into()) + } +} diff --git a/runtime/testnet/Cargo.toml b/runtime/testnet/Cargo.toml index a5ee366c..175dd306 100644 --- a/runtime/testnet/Cargo.toml +++ b/runtime/testnet/Cargo.toml @@ -97,6 +97,7 @@ pallet-services-rpc-runtime-api = { workspace = true } tangle-primitives = { workspace = true, features = ["verifying"] } tangle-crypto-primitives = { workspace = true } pallet-multi-asset-delegation = { workspace = true } +pallet-tangle-lst-benchmarking = { optional = true, workspace = true } # Frontier dependencies fp-evm = { workspace = true } @@ -184,6 +185,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "pallet-multi-asset-delegation/runtime-benchmarks", + "pallet-tangle-lst-benchmarking/runtime-benchmarks", ] default = ["std", "with-rocksdb-weights", "evm-tracing"] local-testing = [] diff --git a/runtime/testnet/src/lib.rs b/runtime/testnet/src/lib.rs index 6776680f..b663df94 100644 --- a/runtime/testnet/src/lib.rs +++ b/runtime/testnet/src/lib.rs @@ -1928,6 +1928,7 @@ mod benches { [pallet_balances, Balances] [pallet_timestamp, Timestamp] [pallet_services, Services] + [pallet_tangle_lst_benchmarking, LstBench] ); } @@ -2593,6 +2594,7 @@ impl_runtime_apis! { ) { use frame_benchmarking::{Benchmarking, BenchmarkList}; use frame_support::traits::StorageInfoTrait; + use pallet_tangle_lst_benchmarking::Pallet as LstBench; let mut list = Vec::::new(); list_benchmark!(list, extra, pallet_services, Services); @@ -2610,6 +2612,7 @@ impl_runtime_apis! { use sp_storage::TrackedStorageKey; impl frame_system_benchmarking::Config for Runtime {} impl baseline::Config for Runtime {} + use pallet_tangle_lst_benchmarking::Pallet as LstBench; use frame_support::traits::WhitelistedStorageKeys; let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys();