From 8eb7e66a2373cabedf219c65642f30d8bc4bc5c8 Mon Sep 17 00:00:00 2001 From: akildemir Date: Fri, 26 Jul 2024 18:33:51 +0300 Subject: [PATCH] fix tests --- substrate/client/src/serai/dex.rs | 19 +-- .../client/src/serai/genesis_liquidity.rs | 4 + substrate/client/src/serai/mod.rs | 33 ++-- substrate/client/src/serai/validator_sets.rs | 10 +- .../client/tests/common/genesis_liquidity.rs | 45 +++--- .../client/tests/common/in_instructions.rs | 12 +- substrate/client/tests/dex.rs | 32 +--- substrate/client/tests/emissions.rs | 124 +++++++++++---- substrate/client/tests/validator_sets.rs | 4 +- substrate/dex/pallet/src/tests.rs | 8 - substrate/emissions/pallet/Cargo.toml | 2 +- substrate/emissions/pallet/src/lib.rs | 145 +++++++++++------- substrate/emissions/primitives/src/lib.rs | 8 +- substrate/runtime/src/lib.rs | 2 +- substrate/validator-sets/pallet/src/lib.rs | 12 ++ 15 files changed, 270 insertions(+), 190 deletions(-) diff --git a/substrate/client/src/serai/dex.rs b/substrate/client/src/serai/dex.rs index d20c4e2c5..a30db6da9 100644 --- a/substrate/client/src/serai/dex.rs +++ b/substrate/client/src/serai/dex.rs @@ -1,9 +1,7 @@ use sp_core::bounded_vec::BoundedVec; use serai_abi::primitives::{SeraiAddress, Amount, Coin}; -use scale::{decode_from_bytes, Encode}; - -use crate::{Serai, SeraiError, TemporalSerai}; +use crate::{SeraiError, TemporalSerai}; pub type DexEvent = serai_abi::dex::Event; @@ -63,19 +61,8 @@ impl<'a> SeraiDex<'a> { } /// Returns the reserves of `coin:SRI` pool. - pub async fn get_reserves(&self, coin: Coin) -> Result, SeraiError> { - let reserves = self - .0 - .serai - .call( - "state_call", - ["DexApi_get_reserves".to_string(), hex::encode((coin, Coin::Serai).encode())], - ) - .await?; - let bytes = Serai::hex_decode(reserves)?; - let result = decode_from_bytes::>(bytes.into()) - .map_err(|e| SeraiError::ErrorInResponse(e.to_string()))?; - Ok(result.map(|amounts| (Amount(amounts.0), Amount(amounts.1)))) + pub async fn get_reserves(&self, coin: Coin) -> Result, SeraiError> { + self.0.runtime_api("DexApi_get_reserves", (coin, Coin::Serai)).await } pub async fn oracle_value(&self, coin: Coin) -> Result, SeraiError> { diff --git a/substrate/client/src/serai/genesis_liquidity.rs b/substrate/client/src/serai/genesis_liquidity.rs index 04e80d745..1a6de2213 100644 --- a/substrate/client/src/serai/genesis_liquidity.rs +++ b/substrate/client/src/serai/genesis_liquidity.rs @@ -62,4 +62,8 @@ impl<'a> SeraiGenesisLiquidity<'a> { pub async fn supply(&self, coin: Coin) -> Result { Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(LiquidityAmount::zero())) } + + pub async fn genesis_complete(&self) -> Result, SeraiError> { + self.0.storage(PALLET, "GenesisComplete", ()).await + } } diff --git a/substrate/client/src/serai/mod.rs b/substrate/client/src/serai/mod.rs index dfd14779e..258b1c073 100644 --- a/substrate/client/src/serai/mod.rs +++ b/substrate/client/src/serai/mod.rs @@ -198,17 +198,6 @@ impl Serai { Ok(()) } - // TODO: move this into substrate/client/src/validator_sets.rs - async fn active_network_validators(&self, network: NetworkId) -> Result, SeraiError> { - let validators: String = self - .call("state_call", ["SeraiRuntimeApi_validators".to_string(), hex::encode(network.encode())]) - .await?; - let bytes = Self::hex_decode(validators)?; - let r = Vec::::decode(&mut bytes.as_slice()) - .map_err(|e| SeraiError::ErrorInResponse(e.to_string()))?; - Ok(r) - } - pub async fn latest_finalized_block_hash(&self) -> Result<[u8; 32], SeraiError> { let hash: String = self.call("chain_getFinalizedHead", ()).await?; Self::hex_decode(hash)?.try_into().map_err(|_| { @@ -378,6 +367,28 @@ impl<'a> TemporalSerai<'a> { })?)) } + async fn runtime_api( + &self, + method: &'static str, + params: P, + ) -> Result { + let result: String = self + .serai + .call( + "state_call", + [method.to_string(), hex::encode(params.encode()), hex::encode(self.block)], + ) + .await?; + + let bytes = Serai::hex_decode(result.clone())?; + R::decode(&mut bytes.as_slice()).map_err(|_| { + SeraiError::InvalidRuntime(format!( + "different type than what is expected returned, raw value: {}", + hex::encode(result) + )) + }) + } + pub fn coins(&'a self) -> SeraiCoins<'a> { SeraiCoins(self) } diff --git a/substrate/client/src/serai/validator_sets.rs b/substrate/client/src/serai/validator_sets.rs index 959f8ee60..ec67bae0f 100644 --- a/substrate/client/src/serai/validator_sets.rs +++ b/substrate/client/src/serai/validator_sets.rs @@ -163,7 +163,7 @@ impl<'a> SeraiValidatorSets<'a> { &self, network: NetworkId, ) -> Result, SeraiError> { - self.0.serai.active_network_validators(network).await + self.0.runtime_api("SeraiRuntimeApi_validators", network).await } // TODO: Store these separately since we almost never need both at once? @@ -178,6 +178,14 @@ impl<'a> SeraiValidatorSets<'a> { self.0.storage(PALLET, "PendingSlashReport", network).await } + pub async fn session_begin_block( + &self, + network: NetworkId, + session: Session, + ) -> Result, SeraiError> { + self.0.storage(PALLET, "SessionBeginBlock", (network, session)).await + } + pub fn set_keys( network: NetworkId, removed_participants: sp_runtime::BoundedVec< diff --git a/substrate/client/tests/common/genesis_liquidity.rs b/substrate/client/tests/common/genesis_liquidity.rs index 990771c26..abaa98290 100644 --- a/substrate/client/tests/common/genesis_liquidity.rs +++ b/substrate/client/tests/common/genesis_liquidity.rs @@ -33,7 +33,7 @@ use serai_client::{ use crate::common::{in_instructions::provide_batch, tx::publish_tx}; #[allow(dead_code)] -pub async fn test_genesis_liquidity(serai: Serai) { +pub async fn test_genesis_liquidity(serai: Serai) -> HashMap { // all coins except the native let coins = COINS.into_iter().filter(|c| *c != Coin::native()).collect::>(); @@ -79,21 +79,8 @@ pub async fn test_genesis_liquidity(serai: Serai) { provide_batch(&serai, batch).await; } - // wait until genesis ends - let genesis_blocks = 10; // TODO - let block_time = 6; // TODO - tokio::time::timeout( - tokio::time::Duration::from_secs(3 * (genesis_blocks * block_time)), - async { - while serai.latest_finalized_block().await.unwrap().number() < 10 { - tokio::time::sleep(Duration::from_secs(6)).await; - } - }, - ) - .await - .unwrap(); - - // set values relative to each other + // set values relative to each other. We can do that without checking for genesis period blocks + // since we are running in test(fast-epoch) mode. // TODO: Random values here let values = Values { monero: 184100, ether: 4785000, dai: 1500 }; set_values(&serai, &values).await; @@ -103,8 +90,19 @@ pub async fn test_genesis_liquidity(serai: Serai) { (Coin::Dai, values.dai), ]); - // wait a little bit.. - tokio::time::sleep(Duration::from_secs(12)).await; + // wait until genesis is complete + while serai + .as_of_latest_finalized_block() + .await + .unwrap() + .genesis_liquidity() + .genesis_complete() + .await + .unwrap() + .is_none() + { + tokio::time::sleep(Duration::from_secs(1)).await; + } // check total SRI supply is +100M // there are 6 endowed accounts in dev-net. Take this into consideration when checking @@ -147,8 +145,8 @@ pub async fn test_genesis_liquidity(serai: Serai) { total_sri_distributed += sri; let reserves = serai.dex().get_reserves(coin).await.unwrap().unwrap(); - assert_eq!(u128::from(reserves.0 .0), pool_amounts[&coin].0); // coin side - assert_eq!(u128::from(reserves.1 .0), sri); // SRI side + assert_eq!(u128::from(reserves.0), pool_amounts[&coin].0); // coin side + assert_eq!(u128::from(reserves.1), sri); // SRI side } // check each liquidity provider got liquidity tokens proportional to their value @@ -172,8 +170,9 @@ pub async fn test_genesis_liquidity(serai: Serai) { assert_eq!(shares_ratio, amounts_ratio); } } - // TODO: test remove the liq before/after genesis ended. + + batch_ids } #[allow(dead_code)] @@ -181,8 +180,8 @@ async fn set_values(serai: &Serai, values: &Values) { // prepare a Musig tx to oraclize the relative values let pair = insecure_pair_from_name("Alice"); let public = pair.public(); - // we publish the tx in set 4 - let set = ValidatorSet { session: Session(4), network: NetworkId::Serai }; + // we publish the tx in set 1 + let set = ValidatorSet { session: Session(1), network: NetworkId::Serai }; let public_key = ::read_G::<&[u8]>(&mut public.0.as_ref()).unwrap(); let secret_key = ::read_F::<&[u8]>( diff --git a/substrate/client/tests/common/in_instructions.rs b/substrate/client/tests/common/in_instructions.rs index e335244a2..103940abf 100644 --- a/substrate/client/tests/common/in_instructions.rs +++ b/substrate/client/tests/common/in_instructions.rs @@ -10,7 +10,7 @@ use sp_core::Pair; use serai_client::{ primitives::{insecure_pair_from_name, BlockHash, NetworkId, Balance, SeraiAddress}, - validator_sets::primitives::{Session, ValidatorSet, KeyPair}, + validator_sets::primitives::{ValidatorSet, KeyPair}, in_instructions::{ primitives::{Batch, SignedBatch, batch_message, InInstruction, InInstructionWithBalance}, InInstructionsEvent, @@ -22,12 +22,12 @@ use crate::common::{tx::publish_tx, validator_sets::set_keys}; #[allow(dead_code)] pub async fn provide_batch(serai: &Serai, batch: Batch) -> [u8; 32] { - // TODO: Get the latest session - let set = ValidatorSet { session: Session(0), network: batch.network }; + let serai_latest = serai.as_of_latest_finalized_block().await.unwrap(); + let session = serai_latest.validator_sets().session(batch.network).await.unwrap().unwrap(); + let set = ValidatorSet { session, network: batch.network }; + let pair = insecure_pair_from_name(&format!("ValidatorSet {set:?}")); - let keys = if let Some(keys) = - serai.as_of_latest_finalized_block().await.unwrap().validator_sets().keys(set).await.unwrap() - { + let keys = if let Some(keys) = serai_latest.validator_sets().keys(set).await.unwrap() { keys } else { let keys = KeyPair(pair.public(), vec![].try_into().unwrap()); diff --git a/substrate/client/tests/dex.rs b/substrate/client/tests/dex.rs index da0270ff7..d02d52602 100644 --- a/substrate/client/tests/dex.rs +++ b/substrate/client/tests/dex.rs @@ -1,14 +1,13 @@ use rand_core::{RngCore, OsRng}; -use scale::Encode; -use sp_core::{Pair as PairTrait, bounded_vec::BoundedVec, hashing::blake2_256}; +use sp_core::{Pair as PairTrait, bounded_vec::BoundedVec}; use serai_abi::in_instructions::primitives::DexCall; use serai_client::{ primitives::{ Amount, NetworkId, Coin, Balance, BlockHash, insecure_pair_from_name, ExternalAddress, - SeraiAddress, PublicKey, + SeraiAddress, }, in_instructions::primitives::{ InInstruction, InInstructionWithBalance, Batch, IN_INSTRUCTION_EXECUTOR, OutAddress, @@ -28,33 +27,6 @@ use common::{ // TODO: Modularize common code // TODO: Check Transfer events serai_test!( - create_pool: (|serai: Serai| async move { - let block = serai.finalized_block_by_number(0).await.unwrap().unwrap().hash(); - let events = serai.as_of(block).dex().events().await.unwrap(); - - assert_eq!( - events, - vec![ - DexEvent::PoolCreated { - pool_id: Coin::Bitcoin, - pool_account: PublicKey::from_raw(blake2_256(&Coin::Bitcoin.encode())).into(), - }, - DexEvent::PoolCreated { - pool_id: Coin::Ether, - pool_account: PublicKey::from_raw(blake2_256(&Coin::Ether.encode())).into(), - }, - DexEvent::PoolCreated { - pool_id: Coin::Dai, - pool_account: PublicKey::from_raw(blake2_256(&Coin::Dai.encode())).into(), - }, - DexEvent::PoolCreated { - pool_id: Coin::Monero, - pool_account: PublicKey::from_raw(blake2_256(&Coin::Monero.encode())).into(), - }, - ] - ); - }) - add_liquidity: (|serai: Serai| async move { let coin = Coin::Monero; let pair = insecure_pair_from_name("Ferdie"); diff --git a/substrate/client/tests/emissions.rs b/substrate/client/tests/emissions.rs index 9b5f568f8..e42469b90 100644 --- a/substrate/client/tests/emissions.rs +++ b/substrate/client/tests/emissions.rs @@ -1,10 +1,13 @@ use std::{time::Duration, collections::HashMap}; +use rand_core::{RngCore, OsRng}; use serai_client::TemporalSerai; use serai_abi::{ emissions::primitives::{INITIAL_REWARD_PER_BLOCK, SECURE_BY}, - primitives::{Coin, COINS, NETWORKS}, + in_instructions::primitives::Batch, + primitives::{NETWORKS, BlockHash}, + validator_sets::primitives::Session, }; use serai_client::{ @@ -13,7 +16,7 @@ use serai_client::{ }; mod common; -use common::genesis_liquidity::test_genesis_liquidity; +use common::{genesis_liquidity::test_genesis_liquidity, in_instructions::provide_batch}; serai_test_fast_epoch!( emissions: (|serai: Serai| async move { @@ -21,14 +24,35 @@ serai_test_fast_epoch!( }) ); +async fn send_batches(serai: &Serai, ids: &mut HashMap) { + for network in NETWORKS { + if network != NetworkId::Serai { + // set up batch id + ids + .entry(network) + .and_modify(|v| { + *v += 1; + }) + .or_insert(0); + + // set up block hash + let mut block = BlockHash([0; 32]); + OsRng.fill_bytes(&mut block.0); + + provide_batch(serai, Batch { network, id: ids[&network], block, instructions: vec![] }).await; + } + } +} + async fn test_emissions(serai: Serai) { // provide some genesis liquidity - test_genesis_liquidity(serai.clone()).await; + let mut batch_ids = test_genesis_liquidity(serai.clone()).await; - let mut last_epoch_start = 0; - for i in 1 .. 3 { + for _ in 0 .. 3 { + // get current stakes let mut current_stake = HashMap::new(); for n in NETWORKS { + // TODO: investigate why serai network TAS isn't visible at session 0. let stake = serai .as_of_latest_finalized_block() .await @@ -42,21 +66,25 @@ async fn test_emissions(serai: Serai) { current_stake.insert(n, stake); } - // wait until we have at least 1 session - wait_for_session(&serai, i).await; + // wait for a session change + let current_session = wait_for_session_change(&serai).await; - // get distances to ec security + // get last block let last_block = serai.latest_finalized_block().await.unwrap(); let serai_latest = serai.as_of(last_block.hash()); + let change_block_number = last_block.number(); + + // get distances to ec security & block count of the previous session let (distances, total_distance) = get_distances(&serai_latest, ¤t_stake).await; + let block_count = get_session_blocks(&serai_latest, current_session - 1).await; // calculate how much reward in this session - let block_count = last_block.number() - last_epoch_start; - let reward_this_epoch = if i == 1 { - // last block number should be the block count since we are in the first block of session 1. + // TODO: genesis is complete at block 24 in current tests. Initial period is just double that. + // See the emissions pallet to further read on this. We also assume we are in pre-ec era. + let reward_this_epoch = if change_block_number < 24 * 3 { block_count * INITIAL_REWARD_PER_BLOCK } else { - let blocks_until = SECURE_BY - last_block.number(); + let blocks_until = SECURE_BY - change_block_number; let block_reward = total_distance / blocks_until; block_count * block_reward }; @@ -73,8 +101,14 @@ async fn test_emissions(serai: Serai) { }) .collect::>(); + // retire the prev-set so that TotalAllocatedStake updated. + send_batches(&serai, &mut batch_ids).await; + for (n, reward) in reward_per_network { - let stake = serai_latest + let stake = serai + .as_of_latest_finalized_block() + .await + .unwrap() .validator_sets() .total_allocated_stake(n) .await @@ -85,10 +119,9 @@ async fn test_emissions(serai: Serai) { // all reward should automatically staked for the network since we are in initial period. assert_eq!(stake, *current_stake.get(&n).unwrap() + reward); } + // TODO: check stake per address? // TODO: check post ec security era - - last_epoch_start = last_block.number(); } } @@ -99,7 +132,7 @@ async fn required_stake(serai: &TemporalSerai<'_>, balance: Balance) -> u64 { // See dex-pallet for the reasoning on these let coin_decimals = balance.coin.decimals().max(5); - let accuracy_increase = u128::from(u64::pow(10, coin_decimals)); + let accuracy_increase = u128::from(10u64.pow(coin_decimals)); let total_coin_value = u64::try_from(u128::from(balance.amount.0) * u128::from(sri_per_coin.0) / accuracy_increase) @@ -110,9 +143,21 @@ async fn required_stake(serai: &TemporalSerai<'_>, balance: Balance) -> u64 { required_stake.saturating_add(total_coin_value.saturating_div(5)) } -async fn wait_for_session(serai: &Serai, session: u32) { - // Epoch time is half an hour with the fast epoch feature, so lets wait double that. - tokio::time::timeout(tokio::time::Duration::from_secs(60 * 6), async { +async fn wait_for_session_change(serai: &Serai) -> u32 { + let current_session = serai + .as_of_latest_finalized_block() + .await + .unwrap() + .validator_sets() + .session(NetworkId::Serai) + .await + .unwrap() + .unwrap() + .0; + let next_session = current_session + 1; + + // Epoch time is 2 mins with the fast epoch feature, so lets wait double that. + tokio::time::timeout(tokio::time::Duration::from_secs(60 * 4), async { while serai .as_of_latest_finalized_block() .await @@ -123,13 +168,15 @@ async fn wait_for_session(serai: &Serai, session: u32) { .unwrap() .unwrap() .0 < - session + next_session { tokio::time::sleep(Duration::from_secs(6)).await; } }) .await .unwrap(); + + next_session } async fn get_distances( @@ -140,14 +187,18 @@ async fn get_distances( // we can check the supply to see how much coin hence liability we have. let mut distances: HashMap = HashMap::new(); let mut total_distance = 0; - for coin in COINS { - if coin == Coin::Serai { + for n in NETWORKS { + if n == NetworkId::Serai { continue; } - let amount = serai.coins().coin_supply(coin).await.unwrap(); - let required = required_stake(serai, Balance { coin, amount }).await; - let mut current = *current_stake.get(&coin.network()).unwrap(); + let mut required = 0; + for c in n.coins() { + let amount = serai.coins().coin_supply(*c).await.unwrap(); + required += required_stake(serai, Balance { coin: *c, amount }).await; + } + + let mut current = *current_stake.get(&n).unwrap(); if current > required { current = required; } @@ -155,10 +206,7 @@ async fn get_distances( let distance = required - current; total_distance += distance; - distances.insert( - coin.network(), - distances.get(&coin.network()).unwrap_or(&0).saturating_add(distance), - ); + distances.insert(n, distance); } // add serai network portion(20%) @@ -168,3 +216,21 @@ async fn get_distances( (distances, total_distance) } + +async fn get_session_blocks(serai: &TemporalSerai<'_>, session: u32) -> u64 { + let begin_block = serai + .validator_sets() + .session_begin_block(NetworkId::Serai, Session(session)) + .await + .unwrap() + .unwrap(); + + let next_begin_block = serai + .validator_sets() + .session_begin_block(NetworkId::Serai, Session(session + 1)) + .await + .unwrap() + .unwrap(); + + next_begin_block.saturating_sub(begin_block) +} diff --git a/substrate/client/tests/validator_sets.rs b/substrate/client/tests/validator_sets.rs index 8aa8174f4..c80c65a93 100644 --- a/substrate/client/tests/validator_sets.rs +++ b/substrate/client/tests/validator_sets.rs @@ -326,8 +326,8 @@ async fn verify_session_and_active_validators( session: u32, participants: &[Public], ) { - // wait until the active session. This wait should be max 30 secs since the epoch time. - let block = tokio::time::timeout(core::time::Duration::from_secs(2 * 60), async move { + // wait until the active session. This wait should be max 2 mins since the epoch time. + let block = tokio::time::timeout(core::time::Duration::from_secs(5 * 60), async move { loop { let mut block = serai.latest_finalized_block_hash().await.unwrap(); if session_for_block(serai, block, network).await < session { diff --git a/substrate/dex/pallet/src/tests.rs b/substrate/dex/pallet/src/tests.rs index b00141997..ee714471f 100644 --- a/substrate/dex/pallet/src/tests.rs +++ b/substrate/dex/pallet/src/tests.rs @@ -1155,16 +1155,8 @@ fn can_not_swap_same_coin() { new_test_ext().execute_with(|| { let user = system_address(b"user1").into(); let coin1 = Coin::Dai; - assert_ok!(CoinsPallet::::mint(user, Balance { coin: coin1, amount: Amount(1000) })); - let liquidity1 = 1000; - let liquidity2 = 20; - assert_noop!( - Dex::add_liquidity(RuntimeOrigin::signed(user), coin1, liquidity2, liquidity1, 1, 1, user,), - Error::::PoolNotFound - ); - let exchange_amount = 10; assert_noop!( Dex::swap_exact_tokens_for_tokens( diff --git a/substrate/emissions/pallet/Cargo.toml b/substrate/emissions/pallet/Cargo.toml index ca4f23a18..a03b92902 100644 --- a/substrate/emissions/pallet/Cargo.toml +++ b/substrate/emissions/pallet/Cargo.toml @@ -57,5 +57,5 @@ std = [ "serai-primitives/std", "emissions-primitives/std", ] - +try-runtime = [] # TODO default = ["std"] \ No newline at end of file diff --git a/substrate/emissions/pallet/src/lib.rs b/substrate/emissions/pallet/src/lib.rs index cdd704d8a..e97a6a53f 100644 --- a/substrate/emissions/pallet/src/lib.rs +++ b/substrate/emissions/pallet/src/lib.rs @@ -68,10 +68,6 @@ pub mod pallet { OptionQuery, >; - #[pallet::storage] - #[pallet::getter(fn session_begin_block)] - pub(crate) type SessionBeginBlock = StorageMap<_, Identity, u32, u64, ValueQuery>; - #[pallet::storage] #[pallet::getter(fn session)] pub type CurrentSession = StorageMap<_, Identity, NetworkId, u32, ValueQuery>; @@ -100,46 +96,61 @@ pub mod pallet { CurrentSession::::set(id, 0); EconomicSecurityReached::::set(id, false); } - - SessionBeginBlock::::set(0, 0); } } #[pallet::hooks] impl Hooks> for Pallet { - fn on_finalize(n: BlockNumberFor) { - let genesis_ended = GenesisLiquidity::::genesis_complete().is_some(); - if GenesisCompleteBlock::::get().is_none() && genesis_ended { + fn on_initialize(n: BlockNumberFor) -> Weight { + if GenesisCompleteBlock::::get().is_none() && + GenesisLiquidity::::genesis_complete().is_some() + { GenesisCompleteBlock::::set(Some(n.saturated_into::())); } + // we wait 1 extra block after genesis ended to see the changes. We only need this extra + // block in dev&test networks where we start the chain with accounts that already has some + // staked SRI. So when we check for ec-security pre-genesis we look like we are economically + // secure. The reason for this although we only check for it once the genesis is complete(so + // if the genesis complete we shouldn't be economically secure because the funds are not + // enough) is because ValidatorSets pallet runs before the genesis pallet in runtime. + // So ValidatorSets pallet sees the old state until next block. + let gcb = GenesisCompleteBlock::::get(); + let genesis_ended = gcb.is_some() && (n.saturated_into::() > gcb.unwrap()); + // we accept we reached economic security once we can mint smallest amount of a network's coin for coin in COINS { - let check = !Self::economic_security_reached(coin.network()) && genesis_ended; + let check = genesis_ended && !Self::economic_security_reached(coin.network()); if check && ::AllowMint::is_allowed(&Balance { coin, amount: Amount(1) }) { EconomicSecurityReached::::set(coin.network(), true); } } - // check wif we got a new session + // check if we got a new session let mut session_changed = false; - let session = ValidatorSets::::session(NetworkId::Serai).unwrap_or(Session(0)).0; - if session > Self::session(NetworkId::Serai) { + let session = ValidatorSets::::session(NetworkId::Serai).unwrap_or(Session(0)); + if session.0 > Self::session(NetworkId::Serai) { session_changed = true; - CurrentSession::::set(NetworkId::Serai, session); + CurrentSession::::set(NetworkId::Serai, session.0); + } + + // update participants per session before the genesis and after the genesis + // we update them after reward distribution. + if !genesis_ended && session_changed { + Self::update_participants(); } // emissions start only after genesis period and happens once per session. // so we don't do anything before that time. if !(genesis_ended && session_changed) { - return; + return Weight::zero(); // TODO } // figure out the amount of blocks in the last session. Session is at least 1 // if we come here. - let current_block = n.saturated_into::(); - let block_count = current_block - Self::session_begin_block(session - 1); + let block_count = ValidatorSets::::session_begin_block(NetworkId::Serai, session) - + ValidatorSets::::session_begin_block(NetworkId::Serai, Session(session.0 - 1)); // get total reward for this epoch let pre_ec_security = Self::pre_ec_security(); @@ -208,34 +219,43 @@ pub mod pallet { } // map epoch ec-security-distance/volume to rewards - let rewards_per_network = distances - .into_iter() - .map(|(n, distance)| { - let reward = if pre_ec_security { + let rewards_per_network = if pre_ec_security { + distances + .into_iter() + .map(|(n, distance)| { // calculate how much each network gets based on distance to ec-security - u64::try_from( + let reward = u64::try_from( u128::from(reward_this_epoch).saturating_mul(u128::from(distance)) / u128::from(total_distance), ) - .unwrap() - } else { + .unwrap(); + (n, reward) + }) + .collect::>() + } else { + volume_per_network + .into_iter() + .map(|(n, vol)| { // 20% of the reward goes to the Serai network and rest is distributed among others // based on swap-volume. - if n == NetworkId::Serai { + let reward = if n == NetworkId::Serai { reward_this_epoch / 5 } else { let reward = reward_this_epoch - (reward_this_epoch / 5); - u64::try_from( - u128::from(reward) - .saturating_mul(u128::from(*volume_per_network.get(&n).unwrap_or(&0))) / - u128::from(total_volume), - ) - .unwrap() - } - }; - (n, reward) - }) - .collect::>(); + // 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(vol)) / u128::from(total_volume), + ) + .unwrap() + } else { + 0 + } + }; + (n, reward) + }) + .collect::>() + }; // distribute the rewards within the network for (n, reward) in rewards_per_network { @@ -251,7 +271,7 @@ pub mod pallet { let total = DESIRED_DISTRIBUTION.saturating_add(distribution); let validators_reward = DESIRED_DISTRIBUTION.saturating_mul(reward) / total; - let pool_reward = total - validators_reward; + let pool_reward = reward.saturating_sub(validators_reward); (validators_reward, pool_reward) }; @@ -277,27 +297,8 @@ pub mod pallet { } } - // set the begin block and participants - SessionBeginBlock::::set(session, current_block); - for n in NETWORKS { - // TODO: `participants_for_latest_decided_set` returns keys with key shares but we - // store keys with actual stake amounts. Pr https://github.com/serai-dex/serai/pull/518 - // supposed to change that and so this pr relies and that pr. - let participants = ValidatorSets::::participants_for_latest_decided_set(n) - .unwrap() - .into_iter() - .map(|(key, shares)| { - let amount = match n { - NetworkId::Serai => shares * 50_000 * 10_u64.pow(8), - NetworkId::Bitcoin | NetworkId::Ethereum => shares * 1_000_000 * 10_u64.pow(8), - NetworkId::Monero => shares * 100_000 * 10_u64.pow(8), - }; - (key, amount) - }) - .collect::>(); - - Participants::::set(n, Some(participants.try_into().unwrap())); - } + Self::update_participants(); + Weight::zero() // TODO } } @@ -308,6 +309,12 @@ pub mod pallet { } fn initial_period(n: BlockNumberFor) -> bool { + // TODO: we should wait for exactly 2 months according to paper. This waits double the time + // it took until genesis complete since we assume it will be done in a month. We know genesis + // period blocks is a month but there will be delay until oracilization is done and genesis + // completed and emissions start happening. If we wait exactly 2 months and the delay is big + // enough we might not be able to distribute all funds we want to in this period. + // In the current case we will distribute more than we want to. What to do? let genesis_complete_block = GenesisCompleteBlock::::get(); genesis_complete_block.is_some() && (n.saturated_into::() < (3 * genesis_complete_block.unwrap())) @@ -378,6 +385,28 @@ pub mod pallet { ValidatorSets::::deposit_stake(network, to, sri_amount)?; Ok(()) } + + fn update_participants() { + for n in NETWORKS { + // TODO: `participants_for_latest_decided_set` returns keys with key shares but we + // store keys with actual stake amounts. Pr https://github.com/serai-dex/serai/pull/518 + // supposed to change that and so this pr relies and that pr. + let participants = ValidatorSets::::participants_for_latest_decided_set(n) + .unwrap() + .into_iter() + .map(|(key, shares)| { + let amount = match n { + NetworkId::Serai => shares * 50_000 * 10_u64.pow(8), + NetworkId::Bitcoin | NetworkId::Ethereum => shares * 1_000_000 * 10_u64.pow(8), + NetworkId::Monero => shares * 100_000 * 10_u64.pow(8), + }; + (key, amount) + }) + .collect::>(); + + Participants::::set(n, Some(participants.try_into().unwrap())); + } + } } } diff --git a/substrate/emissions/primitives/src/lib.rs b/substrate/emissions/primitives/src/lib.rs index 74d7f0e00..ce9ee2056 100644 --- a/substrate/emissions/primitives/src/lib.rs +++ b/substrate/emissions/primitives/src/lib.rs @@ -3,13 +3,13 @@ #![cfg_attr(not(feature = "std"), no_std)] /// Amount of blocks in 30 days for 6s per block. -const BLOCKS_PER_MONTH: u32 = 10 * 60 * 24 * 30; +const BLOCKS_PER_MONTH: u64 = 10 * 60 * 24 * 30; /// INITIAL_REWARD = 100,000 SRI / BLOCKS_PER_DAY for 60 days -pub const INITIAL_REWARD_PER_BLOCK: u64 = 100_000 * 10u64.pow(8) / ((BLOCKS_PER_MONTH as u64) / 30); +pub const INITIAL_REWARD_PER_BLOCK: u64 = 100_000 * 10u64.pow(8) / (BLOCKS_PER_MONTH / 30); /// REWARD = 20M SRI / BLOCKS_PER_YEAR -pub const REWARD_PER_BLOCK: u64 = 20_000_000 * 10u64.pow(8) / ((BLOCKS_PER_MONTH as u64) * 12); +pub const REWARD_PER_BLOCK: u64 = 20_000_000 * 10u64.pow(8) / (BLOCKS_PER_MONTH * 12); /// 20% of all stake desired to be for Serai network(2/10) pub const SERAI_VALIDATORS_DESIRED_PERCENTAGE: u64 = 2; @@ -21,4 +21,4 @@ pub const DESIRED_DISTRIBUTION: u64 = 1_000; pub const ACCURACY_MULTIPLIER: u64 = 10_000; /// The block to target for economic security -pub const SECURE_BY: u64 = (BLOCKS_PER_MONTH as u64) * 12; +pub const SECURE_BY: u64 = BLOCKS_PER_MONTH * 12; diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index ca67df3d9..78689f803 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -283,7 +283,7 @@ pub type ReportLongevity = ::EpochDuration; impl babe::Config for Runtime { #[cfg(feature = "fast-epoch")] - type EpochDuration = ConstU64<{ MINUTES / 2 }>; // 30 seconds + type EpochDuration = ConstU64<{ 2 * MINUTES }>; #[cfg(not(feature = "fast-epoch"))] type EpochDuration = ConstU64<{ 4 * 7 * DAYS }>; diff --git a/substrate/validator-sets/pallet/src/lib.rs b/substrate/validator-sets/pallet/src/lib.rs index 97e0fdb1a..0da87a18e 100644 --- a/substrate/validator-sets/pallet/src/lib.rs +++ b/substrate/validator-sets/pallet/src/lib.rs @@ -15,6 +15,7 @@ use sp_staking::offence::{ReportOffence, Offence, OffenceError}; use frame_system::{pallet_prelude::*, RawOrigin}; use frame_support::{ pallet_prelude::*, + sp_runtime::SaturatedConversion, traits::{DisabledValidators, KeyOwnerProofSystem, FindAuthor}, BoundedVec, WeakBoundedVec, StoragePrefixedMap, }; @@ -309,6 +310,12 @@ pub mod pallet { #[pallet::storage] pub type SeraiDisabledIndices = StorageMap<_, Identity, u32, Public, OptionQuery>; + /// Mapping from session to block number + #[pallet::storage] + #[pallet::getter(fn session_begin_block)] + pub type SessionBeginBlock = + StorageDoubleMap<_, Identity, NetworkId, Identity, Session, u64, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -391,6 +398,11 @@ pub mod pallet { Pallet::::deposit_event(Event::NewSet { set }); Participants::::set(network, Some(participants.try_into().unwrap())); + SessionBeginBlock::::set( + network, + session, + >::block_number().saturated_into::(), + ); } }