fix some pr comments

This commit is contained in:
akildemir
2024-07-30 14:26:24 +03:00
parent 3cb7386915
commit e1b4a9a610
23 changed files with 363 additions and 299 deletions

4
Cargo.lock generated
View File

@@ -8111,6 +8111,9 @@ dependencies = [
[[package]]
name = "serai-emissions-primitives"
version = "0.1.0"
dependencies = [
"serai-primitives",
]
[[package]]
name = "serai-env"
@@ -8197,7 +8200,6 @@ dependencies = [
"serai-dex-pallet",
"serai-emissions-pallet",
"serai-genesis-liquidity-pallet",
"serai-genesis-liquidity-primitives",
"serai-in-instructions-primitives",
"serai-primitives",
"serai-validator-sets-pallet",

View File

@@ -56,8 +56,6 @@ exceptions = [
{ allow = ["AGPL-3.0"], name = "serai-genesis-liquidity-pallet" },
{ allow = ["AGPL-3.0"], name = "serai-emissions-pallet" },
{ allow = ["AGPL-3.0"], name = "serai-genesis-liquidity-pallet" },
{ allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" },
{ allow = ["AGPL-3.0"], name = "serai-validator-sets-pallet" },

View File

@@ -1,16 +1 @@
pub use serai_emissions_primitives as primitives;
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Call {
// This call is just a place holder so that abi works as expected.
empty_call,
}
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Event {
empty_event,
}

View File

@@ -35,7 +35,7 @@ pub enum Call {
Dex(dex::Call),
ValidatorSets(validator_sets::Call),
GenesisLiquidity(genesis_liquidity::Call),
Emissions(emissions::Call),
Emissions,
InInstructions(in_instructions::Call),
Signals(signals::Call),
Babe(babe::Call),
@@ -58,7 +58,7 @@ pub enum Event {
Dex(dex::Event),
ValidatorSets(validator_sets::Event),
GenesisLiquidity(genesis_liquidity::Event),
Emissions(emissions::Event),
Emissions,
InInstructions(in_instructions::Event),
Signals(signals::Event),
Babe,

View File

@@ -61,7 +61,7 @@ impl<'a> SeraiDex<'a> {
}
/// Returns the reserves of `coin:SRI` pool.
pub async fn get_reserves(&self, coin: Coin) -> Result<Option<(u64, u64)>, SeraiError> {
pub async fn get_reserves(&self, coin: Coin) -> Result<Option<(Amount, Amount)>, SeraiError> {
self.0.runtime_api("DexApi_get_reserves", (coin, Coin::Serai)).await
}

View File

@@ -63,7 +63,8 @@ impl<'a> SeraiGenesisLiquidity<'a> {
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(LiquidityAmount::zero()))
}
pub async fn genesis_complete(&self) -> Result<Option<()>, SeraiError> {
self.0.storage(PALLET, "GenesisComplete", ()).await
pub async fn genesis_complete(&self) -> Result<bool, SeraiError> {
let result: Option<()> = self.0.storage(PALLET, "GenesisComplete", ()).await?;
Ok(result.is_some())
}
}

View File

@@ -383,7 +383,7 @@ impl<'a> TemporalSerai<'a> {
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: {}",
"different type than what is expected to be returned, raw value: {}",
hex::encode(result)
))
})

View File

@@ -1,4 +1,4 @@
use std::{time::Duration, collections::HashMap};
use std::collections::HashMap;
use rand_core::{RngCore, OsRng};
use zeroize::Zeroizing;
@@ -7,39 +7,30 @@ use ciphersuite::{Ciphersuite, Ristretto};
use frost::dkg::musig::musig;
use schnorrkel::Schnorrkel;
use serai_client::{
genesis_liquidity::{
primitives::{GENESIS_LIQUIDITY_ACCOUNT, INITIAL_GENESIS_LP_SHARES},
SeraiGenesisLiquidity,
},
validator_sets::primitives::{musig_context, Session, ValidatorSet},
};
use sp_core::{sr25519::Signature, Pair as PairTrait};
use serai_abi::{
genesis_liquidity::primitives::{oraclize_values_message, Values},
primitives::COINS,
};
use sp_core::{sr25519::Signature, Pair as PairTrait};
use serai_client::{
primitives::{
Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, insecure_pair_from_name, GENESIS_SRI,
},
validator_sets::primitives::{musig_context, Session, ValidatorSet},
in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch},
Serai,
primitives::{
Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, insecure_pair_from_name,
},
};
use serai_client::{Serai, SeraiGenesisLiquidity};
use crate::common::{in_instructions::provide_batch, tx::publish_tx};
#[allow(dead_code)]
pub async fn test_genesis_liquidity(serai: Serai) -> HashMap<NetworkId, u32> {
// all coins except the native
let coins = COINS.into_iter().filter(|c| *c != Coin::native()).collect::<Vec<_>>();
pub async fn set_up_genesis(
serai: &Serai,
coins: &[Coin],
values: &HashMap<Coin, u64>,
) -> (HashMap<Coin, Vec<(SeraiAddress, Amount)>>, HashMap<NetworkId, u32>) {
// make accounts with amounts
let mut accounts = HashMap::new();
for coin in coins.clone() {
for coin in coins {
// make 5 accounts per coin
let mut values = vec![];
for _ in 0 .. 5 {
@@ -47,18 +38,18 @@ pub async fn test_genesis_liquidity(serai: Serai) -> HashMap<NetworkId, u32> {
OsRng.fill_bytes(&mut address.0);
values.push((address, Amount(OsRng.next_u64() % 10u64.pow(coin.decimals()))));
}
accounts.insert(coin, values);
accounts.insert(*coin, values);
}
// send a batch per coin
let mut batch_ids: HashMap<NetworkId, u32> = HashMap::new();
for coin in coins.clone() {
for coin in coins {
// set up instructions
let instructions = accounts[&coin]
let instructions = accounts[coin]
.iter()
.map(|(addr, amount)| InInstructionWithBalance {
instruction: InInstruction::GenesisLiquidity(*addr),
balance: Balance { coin, amount: *amount },
balance: Balance { coin: *coin, amount: *amount },
})
.collect::<Vec<_>>();
@@ -76,103 +67,17 @@ pub async fn test_genesis_liquidity(serai: Serai) -> HashMap<NetworkId, u32> {
let batch =
Batch { network: coin.network(), id: batch_ids[&coin.network()], block, instructions };
provide_batch(&serai, batch).await;
provide_batch(serai, batch).await;
}
// 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;
let values_map = HashMap::from([
(Coin::Monero, values.monero),
(Coin::Ether, values.ether),
(Coin::Dai, values.dai),
]);
let values =
Values { monero: values[&Coin::Monero], ether: values[&Coin::Ether], dai: values[&Coin::Dai] };
set_values(serai, &values).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
// for the total sri minted at this time.
let serai = serai.as_of_latest_finalized_block().await.unwrap();
let sri = serai.coins().coin_supply(Coin::Serai).await.unwrap();
let endowed_amount: u64 = 1 << 60;
let total_sri = (6 * endowed_amount) + GENESIS_SRI;
assert_eq!(sri, Amount(total_sri));
// check genesis account has no coins, all transferred to pools.
for coin in COINS {
let amount = serai.coins().coin_balance(coin, GENESIS_LIQUIDITY_ACCOUNT).await.unwrap();
assert_eq!(amount.0, 0);
}
// check pools has proper liquidity
let mut pool_amounts = HashMap::new();
let mut total_value = 0u128;
for coin in coins.clone() {
let total_coin = accounts[&coin].iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0));
let value = if coin != Coin::Bitcoin {
(total_coin * u128::from(values_map[&coin])) / 10u128.pow(coin.decimals())
} else {
total_coin
};
total_value += value;
pool_amounts.insert(coin, (total_coin, value));
}
// check distributed SRI per pool
let mut total_sri_distributed = 0u128;
for coin in coins.clone() {
let sri = if coin == *COINS.last().unwrap() {
u128::from(GENESIS_SRI).checked_sub(total_sri_distributed).unwrap()
} else {
(pool_amounts[&coin].1 * u128::from(GENESIS_SRI)) / total_value
};
total_sri_distributed += sri;
let reserves = serai.dex().get_reserves(coin).await.unwrap().unwrap();
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
for coin in coins {
let liq_supply = serai.genesis_liquidity().supply(coin).await.unwrap();
for (acc, amount) in &accounts[&coin] {
let acc_liq_shares = serai.genesis_liquidity().liquidity(acc, coin).await.unwrap().shares;
// since we can't test the ratios directly(due to integer division giving 0)
// we test whether they give the same result when multiplied by another constant.
// Following test ensures the account in fact has the right amount of shares.
let mut shares_ratio = (INITIAL_GENESIS_LP_SHARES * acc_liq_shares) / liq_supply.shares;
let amounts_ratio =
(INITIAL_GENESIS_LP_SHARES * amount.0) / u64::try_from(pool_amounts[&coin].0).unwrap();
// we can tolerate 1 unit diff between them due to integer division.
if shares_ratio.abs_diff(amounts_ratio) == 1 {
shares_ratio = amounts_ratio;
}
assert_eq!(shares_ratio, amounts_ratio);
}
}
// TODO: test remove the liq before/after genesis ended.
batch_ids
(accounts, batch_ids)
}
#[allow(dead_code)]

View File

@@ -6,7 +6,7 @@ use serai_client::TemporalSerai;
use serai_abi::{
emissions::primitives::{INITIAL_REWARD_PER_BLOCK, SECURE_BY},
in_instructions::primitives::Batch,
primitives::{NETWORKS, BlockHash},
primitives::{NETWORKS, Coin, BlockHash, COINS, FAST_EPOCH_DURATION, TARGET_BLOCK_TIME},
validator_sets::primitives::Session,
};
@@ -16,7 +16,7 @@ use serai_client::{
};
mod common;
use common::{genesis_liquidity::test_genesis_liquidity, in_instructions::provide_batch};
use common::{genesis_liquidity::set_up_genesis, in_instructions::provide_batch};
serai_test_fast_epoch!(
emissions: (|serai: Serai| async move {
@@ -45,8 +45,23 @@ async fn send_batches(serai: &Serai, ids: &mut HashMap<NetworkId, u32>) {
}
async fn test_emissions(serai: Serai) {
// provide some genesis liquidity
let mut batch_ids = test_genesis_liquidity(serai.clone()).await;
// set up the genesis
let coins = COINS.into_iter().filter(|c| *c != Coin::native()).collect::<Vec<_>>();
let values = HashMap::from([(Coin::Monero, 184100), (Coin::Ether, 4785000), (Coin::Dai, 1500)]);
let (_, mut batch_ids) = set_up_genesis(&serai, &coins, &values).await;
// wait until genesis is complete
while !serai
.as_of_latest_finalized_block()
.await
.unwrap()
.genesis_liquidity()
.genesis_complete()
.await
.unwrap()
{
tokio::time::sleep(Duration::from_secs(1)).await;
}
for _ in 0 .. 3 {
// get current stakes
@@ -156,8 +171,10 @@ async fn wait_for_session_change(serai: &Serai) -> u32 {
.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 {
// lets wait double the epoch time.
tokio::time::timeout(
tokio::time::Duration::from_secs(FAST_EPOCH_DURATION * TARGET_BLOCK_TIME * 2),
async {
while serai
.as_of_latest_finalized_block()
.await
@@ -172,7 +189,8 @@ async fn wait_for_session_change(serai: &Serai) -> u32 {
{
tokio::time::sleep(Duration::from_secs(6)).await;
}
})
},
)
.await
.unwrap();

View File

@@ -1,10 +1,107 @@
use std::{time::Duration, collections::HashMap};
use serai_client::Serai;
use serai_abi::primitives::{Coin, COINS, Amount, GENESIS_SRI};
use serai_client::genesis_liquidity::primitives::{
GENESIS_LIQUIDITY_ACCOUNT, INITIAL_GENESIS_LP_SHARES,
};
mod common;
use common::genesis_liquidity::test_genesis_liquidity;
use common::genesis_liquidity::set_up_genesis;
serai_test_fast_epoch!(
genesis_liquidity: (|serai: Serai| async move {
test_genesis_liquidity(serai).await;
})
);
pub async fn test_genesis_liquidity(serai: Serai) {
// set up the genesis
let coins = COINS.into_iter().filter(|c| *c != Coin::native()).collect::<Vec<_>>();
let values = HashMap::from([(Coin::Monero, 184100), (Coin::Ether, 4785000), (Coin::Dai, 1500)]);
let (accounts, _) = set_up_genesis(&serai, &coins, &values).await;
// wait until genesis is complete
while !serai
.as_of_latest_finalized_block()
.await
.unwrap()
.genesis_liquidity()
.genesis_complete()
.await
.unwrap()
{
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
// for the total sri minted at this time.
let serai = serai.as_of_latest_finalized_block().await.unwrap();
let sri = serai.coins().coin_supply(Coin::Serai).await.unwrap();
let endowed_amount: u64 = 1 << 60;
let total_sri = (6 * endowed_amount) + GENESIS_SRI;
assert_eq!(sri, Amount(total_sri));
// check genesis account has no coins, all transferred to pools.
for coin in COINS {
let amount = serai.coins().coin_balance(coin, GENESIS_LIQUIDITY_ACCOUNT).await.unwrap();
assert_eq!(amount.0, 0);
}
// check pools has proper liquidity
let mut pool_amounts = HashMap::new();
let mut total_value = 0u128;
for coin in coins.clone() {
let total_coin = accounts[&coin].iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0));
let value = if coin != Coin::Bitcoin {
(total_coin * u128::from(values[&coin])) / 10u128.pow(coin.decimals())
} else {
total_coin
};
total_value += value;
pool_amounts.insert(coin, (total_coin, value));
}
// check distributed SRI per pool
let mut total_sri_distributed = 0u128;
for coin in coins.clone() {
let sri = if coin == *COINS.last().unwrap() {
u128::from(GENESIS_SRI).checked_sub(total_sri_distributed).unwrap()
} else {
(pool_amounts[&coin].1 * u128::from(GENESIS_SRI)) / total_value
};
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
}
// check each liquidity provider got liquidity tokens proportional to their value
for coin in coins {
let liq_supply = serai.genesis_liquidity().supply(coin).await.unwrap();
for (acc, amount) in &accounts[&coin] {
let acc_liq_shares = serai.genesis_liquidity().liquidity(acc, coin).await.unwrap().shares;
// since we can't test the ratios directly(due to integer division giving 0)
// we test whether they give the same result when multiplied by another constant.
// Following test ensures the account in fact has the right amount of shares.
let mut shares_ratio = (INITIAL_GENESIS_LP_SHARES * acc_liq_shares) / liq_supply.shares;
let amounts_ratio =
(INITIAL_GENESIS_LP_SHARES * amount.0) / u64::try_from(pool_amounts[&coin].0).unwrap();
// we can tolerate 1 unit diff between them due to integer division.
if shares_ratio.abs_diff(amounts_ratio) == 1 {
shares_ratio = amounts_ratio;
}
assert_eq!(shares_ratio, amounts_ratio);
}
}
// TODO: test remove the liq before/after genesis ended.
}

View File

@@ -6,7 +6,9 @@ use sp_core::{
};
use serai_client::{
primitives::{NETWORKS, NetworkId, BlockHash, insecure_pair_from_name},
primitives::{
NETWORKS, NetworkId, BlockHash, insecure_pair_from_name, FAST_EPOCH_DURATION, TARGET_BLOCK_TIME,
},
validator_sets::{
primitives::{Session, ValidatorSet, KeyPair},
ValidatorSetsEvent,
@@ -326,8 +328,10 @@ async fn verify_session_and_active_validators(
session: u32,
participants: &[Public],
) {
// 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 {
// wait until the active session.
let block = tokio::time::timeout(
core::time::Duration::from_secs(FAST_EPOCH_DURATION * TARGET_BLOCK_TIME * 2),
async move {
loop {
let mut block = serai.latest_finalized_block_hash().await.unwrap();
if session_for_block(serai, block, network).await < session {
@@ -341,7 +345,8 @@ async fn verify_session_and_active_validators(
assert_eq!(session_for_block(serai, block, network).await, session);
break block;
}
})
},
)
.await
.unwrap();
let serai_for_block = serai.as_of(block);
@@ -358,7 +363,7 @@ async fn verify_session_and_active_validators(
// make sure finalization continues as usual after the changes
let current_finalized_block = serai.latest_finalized_block().await.unwrap().header.number;
tokio::time::timeout(core::time::Duration::from_secs(60), async move {
tokio::time::timeout(core::time::Duration::from_secs(TARGET_BLOCK_TIME * 10), async move {
let mut finalized_block = serai.latest_finalized_block().await.unwrap().header.number;
while finalized_block <= current_finalized_block + 2 {
tokio::time::sleep(core::time::Duration::from_secs(6)).await;

View File

@@ -194,10 +194,10 @@ pub mod pallet {
#[pallet::getter(fn security_oracle_value)]
pub type SecurityOracleValue<T: Config> = StorageMap<_, Identity, Coin, Amount, OptionQuery>;
/// Current length of the `SpotPrices` map.
/// Total swap volume of a given pool in terms of SRI.
#[pallet::storage]
#[pallet::getter(fn swap_volume)]
pub type SwapVolume<T: Config> = StorageMap<_, Identity, Coin, u64, OptionQuery>;
pub type SwapVolume<T: Config> = StorageMap<_, Identity, PoolId, u64, OptionQuery>;
impl<T: Config> Pallet<T> {
fn restore_median(
@@ -866,23 +866,20 @@ pub mod pallet {
&to,
Balance { coin: *coin2, amount: Amount(*amount_out) },
)?;
// update the volume
let swap_volume = if *coin1 == Coin::Serai {
amounts.get(i as usize).ok_or(Error::<T>::CorrespondenceError)?
} else {
amount_out
};
let existing = SwapVolume::<T>::get(pool_id).unwrap_or(0);
let new_volume = existing.saturating_add(*swap_volume);
SwapVolume::<T>::set(pool_id, Some(new_volume));
}
i += 1;
}
// update the volume, SRI amount is always at index 1 if the path len is 2 or 3.
// path len 1 is not allowed and 3 is already the maximum.
let swap_volume = amounts.get(1).ok_or(Error::<T>::CorrespondenceError)?;
let existing = SwapVolume::<T>::get(coin1).unwrap_or(0);
let new_volume = existing.saturating_add(*swap_volume);
SwapVolume::<T>::set(coin1, Some(new_volume));
// if we did 2 pools, update the volume for second coin as well.
if u32::try_from(path.len()).unwrap() == T::MaxSwapPathLength::get() {
let existing = SwapVolume::<T>::get(path.last().unwrap()).unwrap_or(0);
let new_volume = existing.saturating_add(*swap_volume);
SwapVolume::<T>::set(path.last().unwrap(), Some(new_volume));
}
Self::deposit_event(Event::SwapExecuted {
who: sender,
send_to,

View File

@@ -18,7 +18,6 @@ ignored = ["scale", "scale-info"]
[lints]
workspace = true
[dependencies]
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2", default-features = false, features = ["derive"] }

View File

@@ -4,7 +4,7 @@
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_system::pallet_prelude::*;
use frame_system::{pallet_prelude::*, RawOrigin};
use frame_support::{pallet_prelude::*, sp_runtime::SaturatedConversion};
use sp_std::{vec, vec::Vec, ops::Mul, collections::btree_map::BTreeMap};
@@ -18,7 +18,8 @@ pub mod pallet {
use serai_primitives::*;
use validator_sets_primitives::{MAX_KEY_SHARES_PER_SET, Session};
use emissions_primitives::*;
pub use emissions_primitives as primitives;
use primitives::*;
#[pallet::config]
pub trait Config:
@@ -79,7 +80,7 @@ pub mod pallet {
#[pallet::storage]
#[pallet::getter(fn last_swap_volume)]
pub(crate) type LastSwapVolume<T: Config> = StorageMap<_, Identity, NetworkId, u64, OptionQuery>;
pub(crate) type LastSwapVolume<T: Config> = StorageMap<_, Identity, Coin, u64, OptionQuery>;
#[pallet::storage]
pub(crate) type GenesisCompleteBlock<T: Config> = StorageValue<_, u64, OptionQuery>;
@@ -194,32 +195,9 @@ pub mod pallet {
block_count * REWARD_PER_BLOCK
};
// get swap volumes
let mut volume_per_network: BTreeMap<NetworkId, 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::<T>::swap_volume(c).unwrap_or(0);
volume_per_network.insert(
c.network(),
(*volume_per_network.get(&c.network()).unwrap_or(&0)).saturating_add(current_volume),
);
}
// map current volumes to epoch volumes
let mut total_volume = 0u64;
for (n, vol) in &mut volume_per_network {
let last_volume = Self::last_swap_volume(n).unwrap_or(0);
let vol_this_epoch = vol.saturating_sub(last_volume);
// update the current volume
LastSwapVolume::<T>::set(n, Some(*vol));
total_volume = total_volume.saturating_add(vol_this_epoch);
*vol = vol_this_epoch;
}
// map epoch ec-security-distance/volume to rewards
let rewards_per_network = if pre_ec_security {
let (rewards_per_network, volume_per_network, volume_per_coin) = if pre_ec_security {
(
distances
.into_iter()
.map(|(n, distance)| {
@@ -231,35 +209,66 @@ pub mod pallet {
.unwrap();
(n, reward)
})
.collect::<BTreeMap<NetworkId, u64>>()
.collect::<BTreeMap<NetworkId, u64>>(),
None,
None,
)
} else {
// get swap volumes
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::<T>::swap_volume(c).unwrap_or(0);
let last_volume = Self::last_swap_volume(c).unwrap_or(0);
let vol_this_epoch = current_volume.saturating_sub(last_volume);
// update the current volume
LastSwapVolume::<T>::set(c, Some(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_network
.into_iter()
.iter()
.map(|(n, vol)| {
// 20% of the reward goes to the Serai network and rest is distributed among others
// based on swap-volume.
let reward = if n == NetworkId::Serai {
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(vol)) / u128::from(total_volume),
u128::from(reward).saturating_mul(u128::from(*vol)) / u128::from(total_volume),
)
.unwrap()
} else {
0
}
};
(n, reward)
(*n, reward)
})
.collect::<BTreeMap<NetworkId, u64>>()
.collect::<BTreeMap<NetworkId, u64>>(),
Some(volume_per_network),
Some(volume_per_coin),
)
};
// distribute the rewards within the network
for (n, reward) in rewards_per_network {
let (validators_reward, pool_reward) = if n == NetworkId::Serai {
let (validators_reward, network_pool_reward) = if n == NetworkId::Serai {
(reward, 0)
} else {
// calculate pool vs validator share
@@ -271,23 +280,29 @@ pub mod pallet {
let total = DESIRED_DISTRIBUTION.saturating_add(distribution);
let validators_reward = DESIRED_DISTRIBUTION.saturating_mul(reward) / total;
let pool_reward = reward.saturating_sub(validators_reward);
(validators_reward, pool_reward)
let network_pool_reward = reward.saturating_sub(validators_reward);
(validators_reward, network_pool_reward)
};
// distribute validators rewards
if Self::distribute_to_validators(n, validators_reward).is_err() {
// TODO: log the failure
continue;
}
Self::distribute_to_validators(n, validators_reward);
// send the rest to the pool
let coin_count = u64::try_from(n.coins().len()).unwrap();
if network_pool_reward != 0 {
// these should be available to unwrap if we have a network_pool_reward. Because that
// means we had an unused capacity hence in a post-ec era.
let vpn = volume_per_network.as_ref().unwrap();
let vpc = volume_per_coin.as_ref().unwrap();
for c in n.coins() {
// assumes reward is equally distributed between network coins.
let pool_reward = u64::try_from(
u128::from(network_pool_reward).saturating_mul(u128::from(vpc[c])) /
u128::from(vpn[&n]),
)
.unwrap();
if Coins::<T>::mint(
Dex::<T>::get_pool_account(*c),
Balance { coin: Coin::Serai, amount: Amount(pool_reward / coin_count) },
Balance { coin: Coin::Serai, amount: Amount(pool_reward) },
)
.is_err()
{
@@ -296,6 +311,7 @@ pub mod pallet {
}
}
}
}
Self::update_participants();
Weight::zero() // TODO
@@ -334,15 +350,15 @@ pub mod pallet {
false
}
fn distribute_to_validators(n: NetworkId, reward: u64) -> DispatchResult {
// distribute among network's set based on
// Distribute the reward among network's set based on
// -> (key shares * stake per share) + ((stake % stake per share) / 2)
fn distribute_to_validators(n: NetworkId, reward: u64) {
let stake_per_share = ValidatorSets::<T>::allocation_per_key_share(n).unwrap().0;
let mut scores = vec![];
let mut total_score = 0u64;
for (p, amount) in Self::participants(n).unwrap() {
let remainder = amount % stake_per_share;
let score = (amount - remainder) + (remainder / 2);
let score = amount - (remainder / 2);
total_score = total_score.saturating_add(score);
scores.push((p, score));
@@ -355,11 +371,16 @@ pub mod pallet {
)
.unwrap();
Coins::<T>::mint(p, Balance { coin: Coin::Serai, amount: Amount(p_reward) })?;
ValidatorSets::<T>::deposit_stake(n, p, Amount(p_reward))?;
if Coins::<T>::mint(p, Balance { coin: Coin::Serai, amount: Amount(p_reward) }).is_err() {
// TODO: log the failure
continue;
}
Ok(())
if ValidatorSets::<T>::deposit_stake(n, p, Amount(p_reward)).is_err() {
// TODO: log the failure
continue;
}
}
}
pub fn swap_to_staked_sri(
@@ -372,9 +393,36 @@ pub mod pallet {
Err(Error::<T>::NetworkHasEconomicSecurity)?;
}
// calculate how much SRI the balance makes
let value =
Dex::<T>::security_oracle_value(balance.coin).ok_or(Error::<T>::NoValueForCoin)?;
// swap half of the liquidity for SRI to form PoL.
let half = balance.amount.0 / 2;
let path = BoundedVec::try_from(vec![balance.coin, Coin::Serai]).unwrap();
let origin = RawOrigin::Signed(POL_ACCOUNT.into());
Dex::<T>::swap_exact_tokens_for_tokens(
origin.clone().into(),
path,
half,
1, // minimum out, so we accept whatever we get.
POL_ACCOUNT.into(),
)?;
// get how much we got for our swap
let sri_amount = Coins::<T>::balance(POL_ACCOUNT.into(), Coin::Serai).0;
// add liquidity
Dex::<T>::add_liquidity(
origin.clone().into(),
balance.coin,
half,
sri_amount,
1,
1,
POL_ACCOUNT.into(),
)?;
// use last block spot price to calculate how much SRI the balance makes.
let last_block = <frame_system::Pallet<T>>::block_number() - 1u32.into();
let value = Dex::<T>::spot_price_for_block(last_block, balance.coin)
.ok_or(Error::<T>::NoValueForCoin)?;
// TODO: may panic? It might be best for this math ops to return the result as is instead of
// doing an unwrap so that it can be properly dealt with.
let sri_amount = balance.amount.mul(value);
@@ -383,6 +431,7 @@ pub mod pallet {
Coins::<T>::mint(to, Balance { coin: Coin::Serai, amount: sri_amount })?;
// TODO: deposit_stake lets staking less than per key share. Should we allow that here?
ValidatorSets::<T>::deposit_stake(network, to, sri_amount)?;
Ok(())
}

View File

@@ -16,7 +16,8 @@ rustdoc-args = ["--cfg", "docsrs"]
workspace = true
[dependencies]
serai-primitives = { path = "../../primitives", default-features = false }
[features]
std = []
std = ["serai-primitives/std"]
default = ["std"]

View File

@@ -2,6 +2,11 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
use serai_primitives::{SeraiAddress, system_address};
// Protocol owned liquidity account.
pub const POL_ACCOUNT: SeraiAddress = system_address(b"PoL-account");
/// Amount of blocks in 30 days for 6s per block.
const BLOCKS_PER_MONTH: u64 = 10 * 60 * 24 * 30;

View File

@@ -1,6 +1,6 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[allow(clippy::cast_possible_truncation, clippy::no_effect_underscore_binding)]
#[allow(clippy::cast_possible_truncation, clippy::no_effect_underscore_binding, clippy::empty_docs)]
#[frame_support::pallet]
pub mod pallet {
use super::*;

View File

@@ -33,7 +33,6 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur
serai-primitives = { path = "../../primitives", default-features = false }
in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../primitives", default-features = false }
genesis-liquidity-primitives = { package = "serai-genesis-liquidity-primitives", path = "../../genesis-liquidity/primitives", default-features = false }
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false }
@@ -57,7 +56,6 @@ std = [
"serai-primitives/std",
"in-instructions-primitives/std",
"genesis-liquidity-primitives/std",
"coins-pallet/std",
"dex-pallet/std",

View File

@@ -19,7 +19,6 @@ pub mod pallet {
use sp_core::sr25519::Public;
use serai_primitives::{Coin, Amount, Balance};
use genesis_liquidity_primitives::GENESIS_LIQUIDITY_ACCOUNT;
use frame_support::pallet_prelude::*;
use frame_system::{pallet_prelude::*, RawOrigin};
@@ -34,8 +33,10 @@ pub mod pallet {
Config as ValidatorSetsConfig, Pallet as ValidatorSets,
};
use genesis_liquidity_pallet::{Pallet as GenesisLiq, Config as GenesisLiqConfig};
use emissions_pallet::{Pallet as Emissions, Config as EmissionsConfig};
use genesis_liquidity_pallet::{
Pallet as GenesisLiq, Config as GenesisLiqConfig, primitives::GENESIS_LIQUIDITY_ACCOUNT,
};
use emissions_pallet::{Pallet as Emissions, Config as EmissionsConfig, primitives::POL_ACCOUNT};
use super::*;
@@ -216,6 +217,7 @@ pub mod pallet {
GenesisLiq::<T>::add_coin_liquidity(address.into(), instruction.balance)?;
}
InInstruction::SwapToStakedSRI(address, network) => {
Coins::<T>::mint(POL_ACCOUNT.into(), instruction.balance)?;
Emissions::<T>::swap_to_staked_sri(address.into(), network, instruction.balance)?;
}
}

View File

@@ -27,3 +27,6 @@ pub const ARBITRAGE_TIME: u16 = (2 * HOURS) as u16;
///
/// We additionally +1 so there is a true median.
pub const MEDIAN_PRICE_WINDOW_LENGTH: u16 = (2 * ARBITRAGE_TIME) + 1;
/// Amount of blocks per epoch in the fast-epoch feature that is used in tests.
pub const FAST_EPOCH_DURATION: u64 = 2 * MINUTES;

View File

@@ -138,7 +138,7 @@ impl From<Call> for RuntimeCall {
})
}
},
Call::Emissions(_) => todo!(), // TODO
Call::Emissions => todo!(), // TODO
Call::InInstructions(ii) => match ii {
serai_abi::in_instructions::Call::execute_batch { batch } => {
RuntimeCall::InInstructions(in_instructions::Call::execute_batch { batch })

View File

@@ -52,7 +52,7 @@ use sp_runtime::{
#[allow(unused_imports)]
use primitives::{
NetworkId, PublicKey, AccountLookup, SubstrateAmount, Coin, NETWORKS, MEDIAN_PRICE_WINDOW_LENGTH,
HOURS, DAYS, MINUTES, TARGET_BLOCK_TIME, BLOCK_SIZE,
HOURS, DAYS, MINUTES, TARGET_BLOCK_TIME, BLOCK_SIZE, FAST_EPOCH_DURATION,
};
use support::{
@@ -283,7 +283,7 @@ pub type ReportLongevity = <Runtime as pallet_babe::Config>::EpochDuration;
impl babe::Config for Runtime {
#[cfg(feature = "fast-epoch")]
type EpochDuration = ConstU64<{ 2 * MINUTES }>;
type EpochDuration = ConstU64<{ FAST_EPOCH_DURATION }>;
#[cfg(not(feature = "fast-epoch"))]
type EpochDuration = ConstU64<{ 4 * 7 * DAYS }>;

View File

@@ -832,7 +832,6 @@ pub mod pallet {
total_required
}
// TODO: make the increase_allocation public instead?
pub fn deposit_stake(
network: NetworkId,
account: T::AccountId,