Files
pezkuwi-sdk/pezkuwi/runtime/zagros/src/tests.rs
T

510 lines
16 KiB
Rust

// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Tests for the Zagros Runtime Configuration
use std::collections::HashSet;
use crate::{xcm_config::LocationConverter, *};
use approx::assert_relative_eq;
use frame_support::traits::WhitelistedStorageKeys;
use pallet_staking::EraPayout;
use sp_core::{crypto::Ss58Codec, hexdisplay::HexDisplay};
use sp_keyring::Sr25519Keyring::Alice;
use xcm_runtime_apis::conversions::LocationToAccountHelper;
const MILLISECONDS_PER_HOUR: u64 = 60 * 60 * 1000;
#[test]
fn remove_keys_weight_is_sensible() {
use pezkuwi_runtime_common::crowdloan::WeightInfo;
let max_weight = <Runtime as crowdloan::Config>::WeightInfo::refund(RemoveKeysLimit::get());
// Max remove keys limit should be no more than half the total block weight.
assert!((max_weight * 2).all_lt(BlockWeights::get().max_block));
}
#[test]
fn sample_size_is_sensible() {
use pezkuwi_runtime_common::auctions::WeightInfo;
// Need to clean up all samples at the end of an auction.
let samples: BlockNumber = EndingPeriod::get() / SampleLength::get();
let max_weight: frame_support::weights::Weight =
RocksDbWeight::get().reads_writes(samples.into(), samples.into());
// Max sample cleanup should be no more than half the total block weight.
assert!((max_weight * 2).all_lt(BlockWeights::get().max_block));
assert!((<Runtime as auctions::Config>::WeightInfo::on_initialize() * 2)
.all_lt(BlockWeights::get().max_block));
}
#[test]
fn call_size() {
RuntimeCall::assert_size_under(256);
}
#[test]
fn sanity_check_teleport_assets_weight() {
// This test sanity checks that at least 50 teleports can exist in a block.
// Usually when XCM runs into an issue, it will return a weight of `Weight::MAX`,
// so this test will certainly ensure that this problem does not occur.
use frame_support::dispatch::GetDispatchInfo;
let weight = pallet_xcm::Call::<Runtime>::limited_teleport_assets {
dest: Box::new(Here.into()),
beneficiary: Box::new(Here.into()),
assets: Box::new((Here, 200_000).into()),
fee_asset_id: Box::new(Here.into()),
weight_limit: Unlimited,
}
.get_dispatch_info()
.call_weight;
assert!((weight * 50).all_lt(BlockWeights::get().max_block));
}
#[test]
fn check_whitelist() {
let whitelist: HashSet<String> = AllPalletsWithSystem::whitelisted_storage_keys()
.iter()
.map(|e| HexDisplay::from(&e.key).to_string())
.collect();
// Block number
assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac"));
// Total issuance
assert!(whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80"));
// Execution phase
assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a"));
// Event count
assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850"));
// System events
assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"));
// Configuration ActiveConfig
assert!(whitelist.contains("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385"));
// XcmPallet VersionDiscoveryQueue
assert!(whitelist.contains("1405f2411d0af5a7ff397e7c9dc68d194a222ba0333561192e474c59ed8e30e1"));
// XcmPallet SafeXcmVersion
assert!(whitelist.contains("1405f2411d0af5a7ff397e7c9dc68d196323ae84c43568be0d1394d5d0d522c4"));
}
#[test]
fn check_treasury_pallet_id() {
assert_eq!(
<Treasury as frame_support::traits::PalletInfoAccess>::index() as u8,
zagros_runtime_constants::TREASURY_PALLET_ID
);
}
#[cfg(all(test, feature = "try-runtime"))]
mod remote_tests {
use super::*;
use frame_support::traits::{TryState, TryStateSelect::All};
use frame_try_runtime::{runtime_decl_for_try_runtime::TryRuntime, UpgradeCheckSelect};
use remote_externalities::{
Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig, Transport,
};
use std::env::var;
#[tokio::test]
async fn run_migrations() {
if var("RUN_MIGRATION_TESTS").is_err() {
return;
}
sp_tracing::try_init_simple();
let transport: Transport =
var("WS").unwrap_or("wss://zagros-rpc.pezkuwichain.io:443".to_string()).into();
let maybe_state_snapshot: Option<SnapshotConfig> = var("SNAP").map(|s| s.into()).ok();
let mut ext = Builder::<Block>::default()
.mode(if let Some(state_snapshot) = maybe_state_snapshot {
Mode::OfflineOrElseOnline(
OfflineConfig { state_snapshot: state_snapshot.clone() },
OnlineConfig {
transport,
state_snapshot: Some(state_snapshot),
..Default::default()
},
)
} else {
Mode::Online(OnlineConfig { transport, ..Default::default() })
})
.build()
.await
.unwrap();
ext.execute_with(|| Runtime::on_runtime_upgrade(UpgradeCheckSelect::PreAndPost));
}
#[tokio::test]
async fn delegate_stake_migration() {
// Intended to be run only manually.
if var("RUN_MIGRATION_TESTS").is_err() {
return;
}
use frame_support::assert_ok;
sp_tracing::try_init_simple();
let transport: Transport = var("WS").unwrap_or("ws://127.0.0.1:9900".to_string()).into();
let maybe_state_snapshot: Option<SnapshotConfig> = var("SNAP").map(|s| s.into()).ok();
let online_config = OnlineConfig {
transport,
state_snapshot: maybe_state_snapshot.clone(),
child_trie: false,
pallets: vec![
"Staking".into(),
"System".into(),
"Balances".into(),
"NominationPools".into(),
"DelegatedStaking".into(),
],
..Default::default()
};
let mut ext = Builder::<Block>::default()
.mode(if let Some(state_snapshot) = maybe_state_snapshot {
Mode::OfflineOrElseOnline(
OfflineConfig { state_snapshot: state_snapshot.clone() },
online_config,
)
} else {
Mode::Online(online_config)
})
.build()
.await
.unwrap();
ext.execute_with(|| {
// create an account with some balance
let alice = AccountId::from([1u8; 32]);
use frame_support::traits::Currency;
let _ = Balances::deposit_creating(&alice, 100_000 * UNITS);
// iterate over all pools
pallet_nomination_pools::BondedPools::<Runtime>::iter_keys().for_each(|k| {
if pallet_nomination_pools::Pallet::<Runtime>::api_pool_needs_delegate_migration(k)
{
assert_ok!(
pallet_nomination_pools::Pallet::<Runtime>::migrate_pool_to_delegate_stake(
RuntimeOrigin::signed(alice.clone()).into(),
k,
)
);
}
});
// member migration stats
let mut success = 0;
let mut direct_stakers = 0;
let mut unexpected_errors = 0;
// iterate over all pool members
pallet_nomination_pools::PoolMembers::<Runtime>::iter_keys().for_each(|k| {
if pallet_nomination_pools::Pallet::<Runtime>::api_member_needs_delegate_migration(
k.clone(),
) {
// reasons migrations can fail:
let is_direct_staker = pallet_staking::Bonded::<Runtime>::contains_key(&k);
let migration = pallet_nomination_pools::Pallet::<Runtime>::migrate_delegation(
RuntimeOrigin::signed(alice.clone()).into(),
sp_runtime::MultiAddress::Id(k.clone()),
);
if is_direct_staker {
// if the member is a direct staker, the migration should fail until pool
// member unstakes all funds from pallet-staking.
direct_stakers += 1;
assert_eq!(
migration.unwrap_err(),
pallet_delegated_staking::Error::<Runtime>::AlreadyStaking.into()
);
} else if migration.is_err() {
unexpected_errors += 1;
log::error!(target: "remote_test", "Unexpected error {:?} while migrating {:?}", migration.unwrap_err(), k);
} else {
success += 1;
}
}
});
log::info!(
target: "remote_test",
"Migration stats: success: {}, direct_stakers: {}, unexpected_errors: {}",
success,
direct_stakers,
unexpected_errors
);
});
ext.execute_with(|| {
AllPalletsWithSystem::try_state(System::block_number(), All).unwrap();
});
}
#[tokio::test]
async fn staking_curr_fun_migrate() {
// Intended to be run only manually.
if var("RUN_MIGRATION_TESTS").is_err() {
return;
}
sp_tracing::try_init_simple();
let transport: Transport = var("WS").unwrap_or("ws://127.0.0.1:9944".to_string()).into();
let maybe_state_snapshot: Option<SnapshotConfig> = var("SNAP").map(|s| s.into()).ok();
let online_config = OnlineConfig {
transport,
state_snapshot: maybe_state_snapshot.clone(),
child_trie: false,
pallets: vec![
"Staking".into(),
"System".into(),
"Balances".into(),
"NominationPools".into(),
"DelegatedStaking".into(),
"VoterList".into(),
],
..Default::default()
};
let mut ext = Builder::<Block>::default()
.mode(if let Some(state_snapshot) = maybe_state_snapshot {
Mode::OfflineOrElseOnline(
OfflineConfig { state_snapshot: state_snapshot.clone() },
online_config,
)
} else {
Mode::Online(online_config)
})
.build()
.await
.unwrap();
ext.execute_with(|| {
// create an account with some balance
let alice = AccountId::from([1u8; 32]);
use frame_support::traits::Currency;
let _ = Balances::deposit_creating(&alice, 100_000 * UNITS);
let mut success = 0;
let mut err = 0;
let mut no_migration_needed = 0;
let mut force_withdraw_acc = 0;
let mut force_withdraw_count = 0;
let mut max_force_withdraw = 0;
// iterate over all stakers
pallet_staking::Ledger::<Runtime>::iter().for_each(|(ctrl, ledger)| {
match pallet_staking::Pallet::<Runtime>::migrate_currency(
RuntimeOrigin::signed(alice.clone()).into(),
ledger.stash.clone(),
) {
Ok(_) => {
let updated_ledger =
pallet_staking::Ledger::<Runtime>::get(&ctrl).expect("ledger exists");
let force_withdraw = ledger.total - updated_ledger.total;
if force_withdraw > 0 {
force_withdraw_acc += force_withdraw;
force_withdraw_count += 1;
max_force_withdraw = max_force_withdraw.max(force_withdraw);
log::debug!(target: "remote_test", "Force withdraw from stash {:?}: value {:?}", ledger.stash, force_withdraw);
}
success += 1;
},
Err(e) => {
if e == pallet_staking::Error::<Runtime>::AlreadyMigrated.into() {
no_migration_needed += 1;
} else {
log::error!(target: "remote_test", "Error migrating {:?}: {:?}", ledger.stash, e);
err += 1;
}
},
}
});
log::info!(
target: "remote_test",
"Migration stats: success: {}, err: {}, total force withdrawn stake: {}, count {}, maximum amount {}, no_migration_needed: {}",
success,
err,
force_withdraw_acc,
force_withdraw_count,
max_force_withdraw,
no_migration_needed
);
});
ext.execute_with(|| {
AllPalletsWithSystem::try_state(System::block_number(), All).unwrap();
});
}
}
#[test]
fn location_conversion_works() {
// the purpose of hardcoded values is to catch an unintended location conversion logic change.
struct TestCase {
description: &'static str,
location: Location,
expected_account_id_str: &'static str,
}
let test_cases = vec![
// DescribeTerminus
TestCase {
description: "DescribeTerminus Child",
location: Location::new(0, [Teyrchain(1111)]),
expected_account_id_str: "5Ec4AhP4h37t7TFsAZ4HhFq6k92usAAJDUC3ADSZ4H4Acru3",
},
// DescribePalletTerminal
TestCase {
description: "DescribePalletTerminal Child",
location: Location::new(0, [Teyrchain(1111), PalletInstance(50)]),
expected_account_id_str: "5FjEBrKn3STAFsZpQF4jzwxUYHNGnNgzdZqSQfTzeJ82XKp6",
},
// DescribeAccountId32Terminal
TestCase {
description: "DescribeAccountId32Terminal Child",
location: Location::new(
0,
[Teyrchain(1111), AccountId32 { network: None, id: AccountId::from(Alice).into() }],
),
expected_account_id_str: "5EEMro9RRDpne4jn9TuD7cTB6Amv1raVZ3xspSkqb2BF3FJH",
},
// DescribeAccountKey20Terminal
TestCase {
description: "DescribeAccountKey20Terminal Child",
location: Location::new(
0,
[Teyrchain(1111), AccountKey20 { network: None, key: [0u8; 20] }],
),
expected_account_id_str: "5HohjXdjs6afcYcgHHSstkrtGfxgfGKsnZ1jtewBpFiGu4DL",
},
// DescribeTreasuryVoiceTerminal
TestCase {
description: "DescribeTreasuryVoiceTerminal Child",
location: Location::new(
0,
[Teyrchain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }],
),
expected_account_id_str: "5GenE4vJgHvwYVcD6b4nBvH5HNY4pzpVHWoqwFpNMFT7a2oX",
},
// DescribeBodyTerminal
TestCase {
description: "DescribeBodyTerminal Child",
location: Location::new(
0,
[Teyrchain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }],
),
expected_account_id_str: "5DPgGBFTTYm1dGbtB1VWHJ3T3ScvdrskGGx6vSJZNP1WNStV",
},
];
for tc in test_cases {
let expected =
AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string");
let got = LocationToAccountHelper::<AccountId, LocationConverter>::convert_location(
tc.location.into(),
)
.unwrap();
assert_eq!(got, expected, "{}", tc.description);
}
}
#[test]
fn staking_inflation_correct_single_era() {
let (to_stakers, to_treasury) = super::EraPayout::era_payout(
123, // ignored
456, // ignored
MILLISECONDS_PER_HOUR,
);
assert_relative_eq!(to_stakers as f64, (4_046 * CENTS) as f64, max_relative = 0.01);
assert_relative_eq!(to_treasury as f64, (714 * CENTS) as f64, max_relative = 0.01);
// Total per hour is ~47.6 ZGR
assert_relative_eq!(
(to_stakers as f64 + to_treasury as f64),
(4_760 * CENTS) as f64,
max_relative = 0.001
);
}
#[test]
fn staking_inflation_correct_longer_era() {
// Twice the era duration means twice the emission:
let (to_stakers, to_treasury) = super::EraPayout::era_payout(
123, // ignored
456, // ignored
2 * MILLISECONDS_PER_HOUR,
);
assert_relative_eq!(to_stakers as f64, (4_046 * CENTS) as f64 * 2.0, max_relative = 0.001);
assert_relative_eq!(to_treasury as f64, (714 * CENTS) as f64 * 2.0, max_relative = 0.001);
}
#[test]
fn staking_inflation_correct_whole_year() {
let (to_stakers, to_treasury) = super::EraPayout::era_payout(
123, // ignored
456, // ignored
(36525 * 24 * MILLISECONDS_PER_HOUR) / 100, // 1 year
);
// Our yearly emissions is about 417k ZGR:
let yearly_emission = 417_307 * UNITS;
assert_relative_eq!(
to_stakers as f64 + to_treasury as f64,
yearly_emission as f64,
max_relative = 0.001
);
assert_relative_eq!(to_stakers as f64, yearly_emission as f64 * 0.85, max_relative = 0.001);
assert_relative_eq!(to_treasury as f64, yearly_emission as f64 * 0.15, max_relative = 0.001);
}
// 10 years into the future, our values do not overflow.
#[test]
fn staking_inflation_correct_not_overflow() {
let (to_stakers, to_treasury) = super::EraPayout::era_payout(
123, // ignored
456, // ignored
(36525 * 24 * MILLISECONDS_PER_HOUR) / 10, // 10 years
);
let initial_ti: i128 = 5_216_342_402_773_185_773;
let projected_total_issuance = (to_stakers as i128 + to_treasury as i128) + initial_ti;
// In 2034, there will be about 9.39 million ZGR in existence.
assert_relative_eq!(
projected_total_issuance as f64,
(9_390_000 * UNITS) as f64,
max_relative = 0.001
);
}
// Print percent per year, just as convenience.
#[test]
fn staking_inflation_correct_print_percent() {
let (to_stakers, to_treasury) = super::EraPayout::era_payout(
123, // ignored
456, // ignored
(36525 * 24 * MILLISECONDS_PER_HOUR) / 100, // 1 year
);
let yearly_emission = to_stakers + to_treasury;
let mut ti: i128 = 5_216_342_402_773_185_773;
for y in 0..10 {
let new_ti = ti + yearly_emission as i128;
let inflation = 100.0 * (new_ti - ti) as f64 / ti as f64;
println!("Year {y} inflation: {inflation}%");
ti = new_ti;
assert!(inflation <= 8.0 && inflation > 2.0, "sanity check");
}
}