add post economic security era test

This commit is contained in:
akildemir 2024-09-02 15:43:17 +03:00
parent e7eacb54af
commit b767bab1e0
3 changed files with 256 additions and 12 deletions

View file

@ -337,17 +337,21 @@ pub mod pallet {
} }
// stake the rewards // stake the rewards
for (p, score) in scores { let mut total_reward_distributed = 0u64;
let p_reward = u64::try_from( for (i, (p, score)) in scores.iter().enumerate() {
u128::from(reward).saturating_mul(u128::from(score)) / u128::from(total_score), let p_reward = if i == (scores.len() - 1) {
) reward.saturating_sub(total_reward_distributed)
.unwrap(); } else {
u64::try_from(
u128::from(reward).saturating_mul(u128::from(*score)) / u128::from(total_score),
)
.unwrap()
};
Coins::<T>::mint(p, Balance { coin: Coin::Serai, amount: Amount(p_reward) }).unwrap(); Coins::<T>::mint(*p, Balance { coin: Coin::Serai, amount: Amount(p_reward) }).unwrap();
if ValidatorSets::<T>::distribute_block_rewards(n, p, Amount(p_reward)).is_err() { ValidatorSets::<T>::distribute_block_rewards(n, *p, Amount(p_reward)).unwrap();
// TODO: log the failure
continue; total_reward_distributed = total_reward_distributed.saturating_add(p_reward);
}
} }
} }

View file

@ -158,7 +158,7 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
insecure_pair_from_name("Eve").public(), insecure_pair_from_name("Eve").public(),
insecure_pair_from_name("Ferdie").public(), insecure_pair_from_name("Ferdie").public(),
]; ];
let validators = vec![insecure_pair_from_name("Alice").public()]; let validators = accounts.clone();
let networks = NETWORKS let networks = NETWORKS
.iter() .iter()

View file

@ -16,6 +16,7 @@ use genesis_liquidity_pallet::{
use validator_sets_pallet::{Pallet as ValidatorSets, primitives::Session}; use validator_sets_pallet::{Pallet as ValidatorSets, primitives::Session};
use coins_pallet::Pallet as Coins; use coins_pallet::Pallet as Coins;
use dex_pallet::Pallet as Dex; use dex_pallet::Pallet as Dex;
use economic_security::Pallet as EconomicSecurity;
use serai_primitives::*; use serai_primitives::*;
use validator_sets_primitives::{KeyPair, ValidatorSet}; use validator_sets_primitives::{KeyPair, ValidatorSet};
@ -29,7 +30,8 @@ fn set_up_genesis() -> u64 {
let mut address = SeraiAddress::new([0; 32]); let mut address = SeraiAddress::new([0; 32]);
OsRng.fill_bytes(&mut address.0); OsRng.fill_bytes(&mut address.0);
let balance = Balance { coin, amount: Amount(OsRng.next_u64() % 10u64.pow(coin.decimals())) }; let balance =
Balance { coin, amount: Amount(OsRng.next_u64() % (10_000 * 10u64.pow(coin.decimals()))) };
Coins::<Test>::mint(GENESIS_LIQUIDITY_ACCOUNT.into(), balance).unwrap(); Coins::<Test>::mint(GENESIS_LIQUIDITY_ACCOUNT.into(), balance).unwrap();
GenesisLiquidity::<Test>::add_coin_liquidity(address.into(), balance).unwrap(); GenesisLiquidity::<Test>::add_coin_liquidity(address.into(), balance).unwrap();
@ -99,6 +101,90 @@ fn set_keys_for_session() {
} }
} }
fn make_fake_swap_volume() {
let acc = insecure_pair_from_name("random").public();
for _ in 0 .. 10 {
let path_len = (OsRng.next_u32() % 2) + 2;
let coins = &COINS[1 ..];
let path = if path_len == 2 {
let coin = coins[(OsRng.next_u32() as usize) % coins.len()];
let in_or_out = (OsRng.next_u32() % 2) == 0;
if in_or_out {
vec![coin, Coin::Serai]
} else {
vec![Coin::Serai, coin]
}
} else {
let in_coin = coins[(OsRng.next_u32() as usize) % coins.len()];
let coins_without_in_coin = coins.iter().filter(|&c| *c != in_coin).collect::<Vec<_>>();
let out_coin =
coins_without_in_coin[(OsRng.next_u32() as usize) % coins_without_in_coin.len()];
vec![in_coin, Coin::Serai, *out_coin]
};
let one_in_coin = 10u64.pow(path[0].decimals());
Coins::<Test>::mint(acc, Balance { coin: path[0], amount: Amount(2 * one_in_coin) }).unwrap();
let amount_in = OsRng.next_u64() % (one_in_coin);
Dex::<Test>::swap_exact_tokens_for_tokens(
RawOrigin::Signed(acc).into(),
path.try_into().unwrap(),
amount_in,
1,
acc,
)
.unwrap();
}
}
fn get_session_swap_volumes(
last_swap_volume: &mut BTreeMap<Coin, u64>,
) -> (BTreeMap<Coin, u64>, BTreeMap<NetworkId, u64>, u64) {
let mut volume_per_coin: BTreeMap<Coin, u64> = BTreeMap::new();
for c in COINS {
// this should return 0 for SRI and so it shouldn't affect the total volume.
let current_volume = Dex::<Test>::swap_volume(c).unwrap_or(0);
let last_volume = last_swap_volume.get(&c).unwrap_or(&0);
let vol_this_epoch = current_volume.saturating_sub(*last_volume);
// update the current volume
last_swap_volume.insert(c, current_volume);
volume_per_coin.insert(c, vol_this_epoch);
}
// aggregate per network
let mut total_volume = 0u64;
let mut volume_per_network: BTreeMap<NetworkId, u64> = BTreeMap::new();
for (c, vol) in &volume_per_coin {
volume_per_network.insert(
c.network(),
(*volume_per_network.get(&c.network()).unwrap_or(&0)).saturating_add(*vol),
);
total_volume = total_volume.saturating_add(*vol);
}
(volume_per_coin, volume_per_network, total_volume)
}
fn get_pool_vs_validator_rewards(n: NetworkId, reward: u64) -> (u64, u64) {
if n == NetworkId::Serai {
(reward, 0)
} else {
// calculate pool vs validator share
let capacity = ValidatorSets::<Test>::total_allocated_stake(n).unwrap_or(Amount(0)).0;
let required = ValidatorSets::<Test>::required_stake_for_network(n);
let unused_capacity = capacity.saturating_sub(required);
let distribution = unused_capacity.saturating_mul(ACCURACY_MULTIPLIER) / capacity;
let total = DESIRED_DISTRIBUTION.saturating_add(distribution);
let validators_reward = DESIRED_DISTRIBUTION.saturating_mul(reward) / total;
let network_pool_reward = reward.saturating_sub(validators_reward);
(validators_reward, network_pool_reward)
}
}
#[test] #[test]
fn check_pre_ec_security_initial_period_emissions() { fn check_pre_ec_security_initial_period_emissions() {
new_test_ext().execute_with(|| { new_test_ext().execute_with(|| {
@ -169,6 +255,17 @@ fn check_pre_ec_security_emissions() {
block_number += 2 * MONTHS; block_number += 2 * MONTHS;
System::set_block_number(block_number); System::set_block_number(block_number);
// make a fresh session
set_keys_for_session();
ValidatorSets::<Test>::new_session();
for network in NETWORKS {
ValidatorSets::<Test>::retire_set(ValidatorSet { session: Session(0), network });
}
// move the block for the next session
block_number += <<Test as pallet_babe::Config>::EpochDuration as Get<u64>>::get();
System::set_block_number(block_number);
for _ in 0 .. 5 { for _ in 0 .. 5 {
// set session keys. we need this here before reading the current stakes for session 0. // set session keys. we need this here before reading the current stakes for session 0.
// We need it for other sessions to be able to retire the set. // We need it for other sessions to be able to retire the set.
@ -222,3 +319,146 @@ fn check_pre_ec_security_emissions() {
} }
}); });
} }
#[test]
fn check_post_ec_security_emissions() {
new_test_ext().execute_with(|| {
// set up genesis liquidity
let mut block_number = set_up_genesis();
// make all networks reach economic security
set_keys_for_session();
let (distances, _) = distances();
for (network, distance) in distances {
if network == NetworkId::Serai {
continue;
}
let participants =
ValidatorSets::<Test>::participants_for_latest_decided_set(network).unwrap();
let al_per_key_share = ValidatorSets::<Test>::allocation_per_key_share(network).unwrap().0;
// we want some unused capacity so we stake more SRI than necessary
let mut key_shares = (distance / al_per_key_share) + 10;
'outer: while key_shares > 0 {
for (account, _) in &participants {
ValidatorSets::<Test>::distribute_block_rewards(
network,
*account,
Amount(al_per_key_share),
)
.unwrap();
if key_shares > 0 {
key_shares -= 1;
} else {
break 'outer;
}
}
}
}
// update TAS
ValidatorSets::<Test>::new_session();
for network in NETWORKS {
ValidatorSets::<Test>::retire_set(ValidatorSet { session: Session(0), network });
}
// make sure we reached economic security
<EconomicSecurity<Test> as Hooks<BlockNumber>>::on_initialize(block_number);
for n in NETWORKS.iter().filter(|&n| *n != NetworkId::Serai).collect::<Vec<_>>() {
EconomicSecurity::<Test>::economic_security_block(*n).unwrap();
}
// move the block number for the next session
block_number += <<Test as pallet_babe::Config>::EpochDuration as Get<u64>>::get();
System::set_block_number(block_number);
let mut last_swap_volume = BTreeMap::new();
for _ in 0 .. 5 {
set_keys_for_session();
// make some fake swap volume
make_fake_swap_volume();
let (vpc, vpn, total_volume) = get_session_swap_volumes(&mut last_swap_volume);
// get current stakes & each pool SRI amounts
let mut current_stake = BTreeMap::new();
let mut current_pool_coins = BTreeMap::new();
for n in NETWORKS {
current_stake.insert(n, ValidatorSets::<Test>::total_allocated_stake(n).unwrap().0);
for c in n.coins() {
let acc = Dex::<Test>::get_pool_account(*c);
current_pool_coins.insert(c, Coins::<Test>::balance(acc, Coin::Serai).0);
}
}
// trigger rewards distribution for the past session
ValidatorSets::<Test>::new_session();
<Emissions as Hooks<BlockNumber>>::on_initialize(block_number + 1);
// calculate the total reward for this epoch
let session = ValidatorSets::<Test>::session(NetworkId::Serai).unwrap_or(Session(0));
let block_count = ValidatorSets::<Test>::session_begin_block(NetworkId::Serai, session) -
ValidatorSets::<Test>::session_begin_block(NetworkId::Serai, Session(session.0 - 1));
let reward_this_epoch = block_count * REWARD_PER_BLOCK;
let reward_per_network = vpn
.iter()
.map(|(n, volume)| {
let reward = if *n == NetworkId::Serai {
reward_this_epoch / 5
} else {
let reward = reward_this_epoch - (reward_this_epoch / 5);
// TODO: It is highly unlikely but what to do in case of 0 total volume?
if total_volume != 0 {
u64::try_from(
u128::from(reward).saturating_mul(u128::from(*volume)) / u128::from(total_volume),
)
.unwrap()
} else {
0
}
};
(*n, reward)
})
.collect::<BTreeMap<NetworkId, u64>>();
for (n, reward) in reward_per_network {
let (validator_rewards, network_pool_rewards) = get_pool_vs_validator_rewards(n, reward);
ValidatorSets::<Test>::retire_set(ValidatorSet {
session: Session(session.0 - 1),
network: n,
});
// all validator rewards should automatically be staked
assert_eq!(
ValidatorSets::<Test>::total_allocated_stake(n).unwrap().0,
*current_stake.get(&n).unwrap() + validator_rewards
);
// all pool rewards should be available in the pool account
if network_pool_rewards != 0 {
for c in n.coins() {
let pool_reward = u64::try_from(
u128::from(network_pool_rewards).saturating_mul(u128::from(vpc[c])) /
u128::from(vpn[&n]),
)
.unwrap();
let acc = Dex::<Test>::get_pool_account(*c);
assert_eq!(
Coins::<Test>::balance(acc, Coin::Serai).0,
current_pool_coins[&c] + pool_reward
)
}
}
}
block_number += <<Test as pallet_babe::Config>::EpochDuration as Get<u64>>::get();
System::set_block_number(block_number);
}
});
}