mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-14 23:19:24 +00:00
fix some pr comments
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -8111,6 +8111,9 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "serai-emissions-primitives"
|
name = "serai-emissions-primitives"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serai-primitives",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serai-env"
|
name = "serai-env"
|
||||||
@@ -8197,7 +8200,6 @@ dependencies = [
|
|||||||
"serai-dex-pallet",
|
"serai-dex-pallet",
|
||||||
"serai-emissions-pallet",
|
"serai-emissions-pallet",
|
||||||
"serai-genesis-liquidity-pallet",
|
"serai-genesis-liquidity-pallet",
|
||||||
"serai-genesis-liquidity-primitives",
|
|
||||||
"serai-in-instructions-primitives",
|
"serai-in-instructions-primitives",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-validator-sets-pallet",
|
"serai-validator-sets-pallet",
|
||||||
|
|||||||
@@ -56,8 +56,6 @@ exceptions = [
|
|||||||
{ allow = ["AGPL-3.0"], name = "serai-genesis-liquidity-pallet" },
|
{ allow = ["AGPL-3.0"], name = "serai-genesis-liquidity-pallet" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-emissions-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-in-instructions-pallet" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-validator-sets-pallet" },
|
{ allow = ["AGPL-3.0"], name = "serai-validator-sets-pallet" },
|
||||||
|
|||||||
@@ -1,16 +1 @@
|
|||||||
pub use serai_emissions_primitives as primitives;
|
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,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub enum Call {
|
|||||||
Dex(dex::Call),
|
Dex(dex::Call),
|
||||||
ValidatorSets(validator_sets::Call),
|
ValidatorSets(validator_sets::Call),
|
||||||
GenesisLiquidity(genesis_liquidity::Call),
|
GenesisLiquidity(genesis_liquidity::Call),
|
||||||
Emissions(emissions::Call),
|
Emissions,
|
||||||
InInstructions(in_instructions::Call),
|
InInstructions(in_instructions::Call),
|
||||||
Signals(signals::Call),
|
Signals(signals::Call),
|
||||||
Babe(babe::Call),
|
Babe(babe::Call),
|
||||||
@@ -58,7 +58,7 @@ pub enum Event {
|
|||||||
Dex(dex::Event),
|
Dex(dex::Event),
|
||||||
ValidatorSets(validator_sets::Event),
|
ValidatorSets(validator_sets::Event),
|
||||||
GenesisLiquidity(genesis_liquidity::Event),
|
GenesisLiquidity(genesis_liquidity::Event),
|
||||||
Emissions(emissions::Event),
|
Emissions,
|
||||||
InInstructions(in_instructions::Event),
|
InInstructions(in_instructions::Event),
|
||||||
Signals(signals::Event),
|
Signals(signals::Event),
|
||||||
Babe,
|
Babe,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ impl<'a> SeraiDex<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the reserves of `coin:SRI` pool.
|
/// 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
|
self.0.runtime_api("DexApi_get_reserves", (coin, Coin::Serai)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ impl<'a> SeraiGenesisLiquidity<'a> {
|
|||||||
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(LiquidityAmount::zero()))
|
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(LiquidityAmount::zero()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn genesis_complete(&self) -> Result<Option<()>, SeraiError> {
|
pub async fn genesis_complete(&self) -> Result<bool, SeraiError> {
|
||||||
self.0.storage(PALLET, "GenesisComplete", ()).await
|
let result: Option<()> = self.0.storage(PALLET, "GenesisComplete", ()).await?;
|
||||||
|
Ok(result.is_some())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -383,7 +383,7 @@ impl<'a> TemporalSerai<'a> {
|
|||||||
let bytes = Serai::hex_decode(result.clone())?;
|
let bytes = Serai::hex_decode(result.clone())?;
|
||||||
R::decode(&mut bytes.as_slice()).map_err(|_| {
|
R::decode(&mut bytes.as_slice()).map_err(|_| {
|
||||||
SeraiError::InvalidRuntime(format!(
|
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)
|
hex::encode(result)
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{time::Duration, collections::HashMap};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use rand_core::{RngCore, OsRng};
|
use rand_core::{RngCore, OsRng};
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
@@ -7,39 +7,30 @@ use ciphersuite::{Ciphersuite, Ristretto};
|
|||||||
use frost::dkg::musig::musig;
|
use frost::dkg::musig::musig;
|
||||||
use schnorrkel::Schnorrkel;
|
use schnorrkel::Schnorrkel;
|
||||||
|
|
||||||
use serai_client::{
|
use sp_core::{sr25519::Signature, Pair as PairTrait};
|
||||||
genesis_liquidity::{
|
|
||||||
primitives::{GENESIS_LIQUIDITY_ACCOUNT, INITIAL_GENESIS_LP_SHARES},
|
|
||||||
SeraiGenesisLiquidity,
|
|
||||||
},
|
|
||||||
validator_sets::primitives::{musig_context, Session, ValidatorSet},
|
|
||||||
};
|
|
||||||
|
|
||||||
use serai_abi::{
|
use serai_abi::{
|
||||||
genesis_liquidity::primitives::{oraclize_values_message, Values},
|
genesis_liquidity::primitives::{oraclize_values_message, Values},
|
||||||
primitives::COINS,
|
validator_sets::primitives::{musig_context, Session, ValidatorSet},
|
||||||
};
|
|
||||||
|
|
||||||
use sp_core::{sr25519::Signature, Pair as PairTrait};
|
|
||||||
|
|
||||||
use serai_client::{
|
|
||||||
primitives::{
|
|
||||||
Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, insecure_pair_from_name, GENESIS_SRI,
|
|
||||||
},
|
|
||||||
in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch},
|
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};
|
use crate::common::{in_instructions::provide_batch, tx::publish_tx};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn test_genesis_liquidity(serai: Serai) -> HashMap<NetworkId, u32> {
|
pub async fn set_up_genesis(
|
||||||
// all coins except the native
|
serai: &Serai,
|
||||||
let coins = COINS.into_iter().filter(|c| *c != Coin::native()).collect::<Vec<_>>();
|
coins: &[Coin],
|
||||||
|
values: &HashMap<Coin, u64>,
|
||||||
|
) -> (HashMap<Coin, Vec<(SeraiAddress, Amount)>>, HashMap<NetworkId, u32>) {
|
||||||
// make accounts with amounts
|
// make accounts with amounts
|
||||||
let mut accounts = HashMap::new();
|
let mut accounts = HashMap::new();
|
||||||
for coin in coins.clone() {
|
for coin in coins {
|
||||||
// make 5 accounts per coin
|
// make 5 accounts per coin
|
||||||
let mut values = vec![];
|
let mut values = vec![];
|
||||||
for _ in 0 .. 5 {
|
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);
|
OsRng.fill_bytes(&mut address.0);
|
||||||
values.push((address, Amount(OsRng.next_u64() % 10u64.pow(coin.decimals()))));
|
values.push((address, Amount(OsRng.next_u64() % 10u64.pow(coin.decimals()))));
|
||||||
}
|
}
|
||||||
accounts.insert(coin, values);
|
accounts.insert(*coin, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send a batch per coin
|
// send a batch per coin
|
||||||
let mut batch_ids: HashMap<NetworkId, u32> = HashMap::new();
|
let mut batch_ids: HashMap<NetworkId, u32> = HashMap::new();
|
||||||
for coin in coins.clone() {
|
for coin in coins {
|
||||||
// set up instructions
|
// set up instructions
|
||||||
let instructions = accounts[&coin]
|
let instructions = accounts[coin]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(addr, amount)| InInstructionWithBalance {
|
.map(|(addr, amount)| InInstructionWithBalance {
|
||||||
instruction: InInstruction::GenesisLiquidity(*addr),
|
instruction: InInstruction::GenesisLiquidity(*addr),
|
||||||
balance: Balance { coin, amount: *amount },
|
balance: Balance { coin: *coin, amount: *amount },
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@@ -76,103 +67,17 @@ pub async fn test_genesis_liquidity(serai: Serai) -> HashMap<NetworkId, u32> {
|
|||||||
|
|
||||||
let batch =
|
let batch =
|
||||||
Batch { network: coin.network(), id: batch_ids[&coin.network()], block, instructions };
|
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
|
// 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.
|
// since we are running in test(fast-epoch) mode.
|
||||||
// TODO: Random values here
|
// TODO: Random values here
|
||||||
let values = Values { monero: 184100, ether: 4785000, dai: 1500 };
|
let values =
|
||||||
set_values(&serai, &values).await;
|
Values { monero: values[&Coin::Monero], ether: values[&Coin::Ether], dai: values[&Coin::Dai] };
|
||||||
let values_map = HashMap::from([
|
set_values(serai, &values).await;
|
||||||
(Coin::Monero, values.monero),
|
|
||||||
(Coin::Ether, values.ether),
|
|
||||||
(Coin::Dai, values.dai),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// wait until genesis is complete
|
(accounts, batch_ids)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use serai_client::TemporalSerai;
|
|||||||
use serai_abi::{
|
use serai_abi::{
|
||||||
emissions::primitives::{INITIAL_REWARD_PER_BLOCK, SECURE_BY},
|
emissions::primitives::{INITIAL_REWARD_PER_BLOCK, SECURE_BY},
|
||||||
in_instructions::primitives::Batch,
|
in_instructions::primitives::Batch,
|
||||||
primitives::{NETWORKS, BlockHash},
|
primitives::{NETWORKS, Coin, BlockHash, COINS, FAST_EPOCH_DURATION, TARGET_BLOCK_TIME},
|
||||||
validator_sets::primitives::Session,
|
validator_sets::primitives::Session,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ use serai_client::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
mod common;
|
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!(
|
serai_test_fast_epoch!(
|
||||||
emissions: (|serai: Serai| async move {
|
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) {
|
async fn test_emissions(serai: Serai) {
|
||||||
// provide some genesis liquidity
|
// set up the genesis
|
||||||
let mut batch_ids = test_genesis_liquidity(serai.clone()).await;
|
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 {
|
for _ in 0 .. 3 {
|
||||||
// get current stakes
|
// get current stakes
|
||||||
@@ -156,23 +171,26 @@ async fn wait_for_session_change(serai: &Serai) -> u32 {
|
|||||||
.0;
|
.0;
|
||||||
let next_session = current_session + 1;
|
let next_session = current_session + 1;
|
||||||
|
|
||||||
// Epoch time is 2 mins with the fast epoch feature, so lets wait double that.
|
// lets wait double the epoch time.
|
||||||
tokio::time::timeout(tokio::time::Duration::from_secs(60 * 4), async {
|
tokio::time::timeout(
|
||||||
while serai
|
tokio::time::Duration::from_secs(FAST_EPOCH_DURATION * TARGET_BLOCK_TIME * 2),
|
||||||
.as_of_latest_finalized_block()
|
async {
|
||||||
.await
|
while serai
|
||||||
.unwrap()
|
.as_of_latest_finalized_block()
|
||||||
.validator_sets()
|
.await
|
||||||
.session(NetworkId::Serai)
|
.unwrap()
|
||||||
.await
|
.validator_sets()
|
||||||
.unwrap()
|
.session(NetworkId::Serai)
|
||||||
.unwrap()
|
.await
|
||||||
.0 <
|
.unwrap()
|
||||||
next_session
|
.unwrap()
|
||||||
{
|
.0 <
|
||||||
tokio::time::sleep(Duration::from_secs(6)).await;
|
next_session
|
||||||
}
|
{
|
||||||
})
|
tokio::time::sleep(Duration::from_secs(6)).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,107 @@
|
|||||||
|
use std::{time::Duration, collections::HashMap};
|
||||||
|
|
||||||
use serai_client::Serai;
|
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;
|
mod common;
|
||||||
use common::genesis_liquidity::test_genesis_liquidity;
|
use common::genesis_liquidity::set_up_genesis;
|
||||||
|
|
||||||
serai_test_fast_epoch!(
|
serai_test_fast_epoch!(
|
||||||
genesis_liquidity: (|serai: Serai| async move {
|
genesis_liquidity: (|serai: Serai| async move {
|
||||||
test_genesis_liquidity(serai).await;
|
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.
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ use sp_core::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use serai_client::{
|
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::{
|
validator_sets::{
|
||||||
primitives::{Session, ValidatorSet, KeyPair},
|
primitives::{Session, ValidatorSet, KeyPair},
|
||||||
ValidatorSetsEvent,
|
ValidatorSetsEvent,
|
||||||
@@ -326,22 +328,25 @@ async fn verify_session_and_active_validators(
|
|||||||
session: u32,
|
session: u32,
|
||||||
participants: &[Public],
|
participants: &[Public],
|
||||||
) {
|
) {
|
||||||
// wait until the active session. This wait should be max 2 mins since the epoch time.
|
// wait until the active session.
|
||||||
let block = tokio::time::timeout(core::time::Duration::from_secs(5 * 60), async move {
|
let block = tokio::time::timeout(
|
||||||
loop {
|
core::time::Duration::from_secs(FAST_EPOCH_DURATION * TARGET_BLOCK_TIME * 2),
|
||||||
let mut block = serai.latest_finalized_block_hash().await.unwrap();
|
async move {
|
||||||
if session_for_block(serai, block, network).await < session {
|
loop {
|
||||||
// Sleep a block
|
let mut block = serai.latest_finalized_block_hash().await.unwrap();
|
||||||
tokio::time::sleep(core::time::Duration::from_secs(6)).await;
|
if session_for_block(serai, block, network).await < session {
|
||||||
continue;
|
// Sleep a block
|
||||||
|
tokio::time::sleep(core::time::Duration::from_secs(6)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
while session_for_block(serai, block, network).await > session {
|
||||||
|
block = serai.block(block).await.unwrap().unwrap().header.parent_hash.0;
|
||||||
|
}
|
||||||
|
assert_eq!(session_for_block(serai, block, network).await, session);
|
||||||
|
break block;
|
||||||
}
|
}
|
||||||
while session_for_block(serai, block, network).await > session {
|
},
|
||||||
block = serai.block(block).await.unwrap().unwrap().header.parent_hash.0;
|
)
|
||||||
}
|
|
||||||
assert_eq!(session_for_block(serai, block, network).await, session);
|
|
||||||
break block;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let serai_for_block = serai.as_of(block);
|
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
|
// make sure finalization continues as usual after the changes
|
||||||
let current_finalized_block = serai.latest_finalized_block().await.unwrap().header.number;
|
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;
|
let mut finalized_block = serai.latest_finalized_block().await.unwrap().header.number;
|
||||||
while finalized_block <= current_finalized_block + 2 {
|
while finalized_block <= current_finalized_block + 2 {
|
||||||
tokio::time::sleep(core::time::Duration::from_secs(6)).await;
|
tokio::time::sleep(core::time::Duration::from_secs(6)).await;
|
||||||
|
|||||||
@@ -194,10 +194,10 @@ pub mod pallet {
|
|||||||
#[pallet::getter(fn security_oracle_value)]
|
#[pallet::getter(fn security_oracle_value)]
|
||||||
pub type SecurityOracleValue<T: Config> = StorageMap<_, Identity, Coin, Amount, OptionQuery>;
|
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::storage]
|
||||||
#[pallet::getter(fn swap_volume)]
|
#[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> {
|
impl<T: Config> Pallet<T> {
|
||||||
fn restore_median(
|
fn restore_median(
|
||||||
@@ -866,23 +866,20 @@ pub mod pallet {
|
|||||||
&to,
|
&to,
|
||||||
Balance { coin: *coin2, amount: Amount(*amount_out) },
|
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;
|
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 {
|
Self::deposit_event(Event::SwapExecuted {
|
||||||
who: sender,
|
who: sender,
|
||||||
send_to,
|
send_to,
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ ignored = ["scale", "scale-info"]
|
|||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#[frame_support::pallet]
|
#[frame_support::pallet]
|
||||||
pub mod pallet {
|
pub mod pallet {
|
||||||
use super::*;
|
use super::*;
|
||||||
use frame_system::pallet_prelude::*;
|
use frame_system::{pallet_prelude::*, RawOrigin};
|
||||||
use frame_support::{pallet_prelude::*, sp_runtime::SaturatedConversion};
|
use frame_support::{pallet_prelude::*, sp_runtime::SaturatedConversion};
|
||||||
|
|
||||||
use sp_std::{vec, vec::Vec, ops::Mul, collections::btree_map::BTreeMap};
|
use sp_std::{vec, vec::Vec, ops::Mul, collections::btree_map::BTreeMap};
|
||||||
@@ -18,7 +18,8 @@ pub mod pallet {
|
|||||||
|
|
||||||
use serai_primitives::*;
|
use serai_primitives::*;
|
||||||
use validator_sets_primitives::{MAX_KEY_SHARES_PER_SET, Session};
|
use validator_sets_primitives::{MAX_KEY_SHARES_PER_SET, Session};
|
||||||
use emissions_primitives::*;
|
pub use emissions_primitives as primitives;
|
||||||
|
use primitives::*;
|
||||||
|
|
||||||
#[pallet::config]
|
#[pallet::config]
|
||||||
pub trait Config:
|
pub trait Config:
|
||||||
@@ -79,7 +80,7 @@ pub mod pallet {
|
|||||||
|
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
#[pallet::getter(fn last_swap_volume)]
|
#[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]
|
#[pallet::storage]
|
||||||
pub(crate) type GenesisCompleteBlock<T: Config> = StorageValue<_, u64, OptionQuery>;
|
pub(crate) type GenesisCompleteBlock<T: Config> = StorageValue<_, u64, OptionQuery>;
|
||||||
@@ -194,72 +195,80 @@ pub mod pallet {
|
|||||||
block_count * REWARD_PER_BLOCK
|
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
|
// 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()
|
distances
|
||||||
.map(|(n, distance)| {
|
.into_iter()
|
||||||
// calculate how much each network gets based on distance to ec-security
|
.map(|(n, distance)| {
|
||||||
let reward = u64::try_from(
|
// calculate how much each network gets based on distance to ec-security
|
||||||
u128::from(reward_this_epoch).saturating_mul(u128::from(distance)) /
|
let reward = u64::try_from(
|
||||||
u128::from(total_distance),
|
u128::from(reward_this_epoch).saturating_mul(u128::from(distance)) /
|
||||||
)
|
u128::from(total_distance),
|
||||||
.unwrap();
|
)
|
||||||
(n, reward)
|
.unwrap();
|
||||||
})
|
(n, reward)
|
||||||
.collect::<BTreeMap<NetworkId, u64>>()
|
})
|
||||||
|
.collect::<BTreeMap<NetworkId, u64>>(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
volume_per_network
|
// get swap volumes
|
||||||
.into_iter()
|
let mut volume_per_coin: BTreeMap<Coin, u64> = BTreeMap::new();
|
||||||
.map(|(n, vol)| {
|
for c in COINS {
|
||||||
// 20% of the reward goes to the Serai network and rest is distributed among others
|
// this should return 0 for SRI and so it shouldn't affect the total volume.
|
||||||
// based on swap-volume.
|
let current_volume = Dex::<T>::swap_volume(c).unwrap_or(0);
|
||||||
let reward = if n == NetworkId::Serai {
|
let last_volume = Self::last_swap_volume(c).unwrap_or(0);
|
||||||
reward_this_epoch / 5
|
let vol_this_epoch = current_volume.saturating_sub(last_volume);
|
||||||
} else {
|
|
||||||
let reward = reward_this_epoch - (reward_this_epoch / 5);
|
// update the current volume
|
||||||
// TODO: It is highly unlikely but what to do in case of 0 total volume?
|
LastSwapVolume::<T>::set(c, Some(current_volume));
|
||||||
if total_volume != 0 {
|
volume_per_coin.insert(c, vol_this_epoch);
|
||||||
u64::try_from(
|
}
|
||||||
u128::from(reward).saturating_mul(u128::from(vol)) / u128::from(total_volume),
|
|
||||||
)
|
// aggregate per network
|
||||||
.unwrap()
|
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
|
||||||
|
.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 {
|
||||||
|
reward_this_epoch / 5
|
||||||
} else {
|
} else {
|
||||||
0
|
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 {
|
||||||
(n, reward)
|
u64::try_from(
|
||||||
})
|
u128::from(reward).saturating_mul(u128::from(*vol)) / u128::from(total_volume),
|
||||||
.collect::<BTreeMap<NetworkId, u64>>()
|
)
|
||||||
|
.unwrap()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(*n, reward)
|
||||||
|
})
|
||||||
|
.collect::<BTreeMap<NetworkId, u64>>(),
|
||||||
|
Some(volume_per_network),
|
||||||
|
Some(volume_per_coin),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// distribute the rewards within the network
|
// distribute the rewards within the network
|
||||||
for (n, reward) in rewards_per_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)
|
(reward, 0)
|
||||||
} else {
|
} else {
|
||||||
// calculate pool vs validator share
|
// calculate pool vs validator share
|
||||||
@@ -271,28 +280,35 @@ pub mod pallet {
|
|||||||
let total = DESIRED_DISTRIBUTION.saturating_add(distribution);
|
let total = DESIRED_DISTRIBUTION.saturating_add(distribution);
|
||||||
|
|
||||||
let validators_reward = DESIRED_DISTRIBUTION.saturating_mul(reward) / total;
|
let validators_reward = DESIRED_DISTRIBUTION.saturating_mul(reward) / total;
|
||||||
let pool_reward = reward.saturating_sub(validators_reward);
|
let network_pool_reward = reward.saturating_sub(validators_reward);
|
||||||
(validators_reward, pool_reward)
|
(validators_reward, network_pool_reward)
|
||||||
};
|
};
|
||||||
|
|
||||||
// distribute validators rewards
|
// distribute validators rewards
|
||||||
if Self::distribute_to_validators(n, validators_reward).is_err() {
|
Self::distribute_to_validators(n, validators_reward);
|
||||||
// TODO: log the failure
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// send the rest to the pool
|
// send the rest to the pool
|
||||||
let coin_count = u64::try_from(n.coins().len()).unwrap();
|
if network_pool_reward != 0 {
|
||||||
for c in n.coins() {
|
// these should be available to unwrap if we have a network_pool_reward. Because that
|
||||||
// assumes reward is equally distributed between network coins.
|
// means we had an unused capacity hence in a post-ec era.
|
||||||
if Coins::<T>::mint(
|
let vpn = volume_per_network.as_ref().unwrap();
|
||||||
Dex::<T>::get_pool_account(*c),
|
let vpc = volume_per_coin.as_ref().unwrap();
|
||||||
Balance { coin: Coin::Serai, amount: Amount(pool_reward / coin_count) },
|
for c in n.coins() {
|
||||||
)
|
let pool_reward = u64::try_from(
|
||||||
.is_err()
|
u128::from(network_pool_reward).saturating_mul(u128::from(vpc[c])) /
|
||||||
{
|
u128::from(vpn[&n]),
|
||||||
// TODO: log the failure
|
)
|
||||||
continue;
|
.unwrap();
|
||||||
|
|
||||||
|
if Coins::<T>::mint(
|
||||||
|
Dex::<T>::get_pool_account(*c),
|
||||||
|
Balance { coin: Coin::Serai, amount: Amount(pool_reward) },
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
// TODO: log the failure
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,15 +350,15 @@ pub mod pallet {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn distribute_to_validators(n: NetworkId, reward: u64) -> DispatchResult {
|
// Distribute the reward among network's set based on
|
||||||
// distribute among network's set based on
|
// -> (key shares * stake per share) + ((stake % stake per share) / 2)
|
||||||
// -> (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 stake_per_share = ValidatorSets::<T>::allocation_per_key_share(n).unwrap().0;
|
||||||
let mut scores = vec![];
|
let mut scores = vec![];
|
||||||
let mut total_score = 0u64;
|
let mut total_score = 0u64;
|
||||||
for (p, amount) in Self::participants(n).unwrap() {
|
for (p, amount) in Self::participants(n).unwrap() {
|
||||||
let remainder = amount % stake_per_share;
|
let remainder = amount % stake_per_share;
|
||||||
let score = (amount - remainder) + (remainder / 2);
|
let score = amount - (remainder / 2);
|
||||||
|
|
||||||
total_score = total_score.saturating_add(score);
|
total_score = total_score.saturating_add(score);
|
||||||
scores.push((p, score));
|
scores.push((p, score));
|
||||||
@@ -355,11 +371,16 @@ pub mod pallet {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Coins::<T>::mint(p, Balance { coin: Coin::Serai, amount: Amount(p_reward) })?;
|
if Coins::<T>::mint(p, Balance { coin: Coin::Serai, amount: Amount(p_reward) }).is_err() {
|
||||||
ValidatorSets::<T>::deposit_stake(n, p, Amount(p_reward))?;
|
// 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(
|
pub fn swap_to_staked_sri(
|
||||||
@@ -372,9 +393,36 @@ pub mod pallet {
|
|||||||
Err(Error::<T>::NetworkHasEconomicSecurity)?;
|
Err(Error::<T>::NetworkHasEconomicSecurity)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate how much SRI the balance makes
|
// swap half of the liquidity for SRI to form PoL.
|
||||||
let value =
|
let half = balance.amount.0 / 2;
|
||||||
Dex::<T>::security_oracle_value(balance.coin).ok_or(Error::<T>::NoValueForCoin)?;
|
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
|
// 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.
|
// doing an unwrap so that it can be properly dealt with.
|
||||||
let sri_amount = balance.amount.mul(value);
|
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 })?;
|
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?
|
// TODO: deposit_stake lets staking less than per key share. Should we allow that here?
|
||||||
ValidatorSets::<T>::deposit_stake(network, to, sri_amount)?;
|
ValidatorSets::<T>::deposit_stake(network, to, sri_amount)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = []
|
std = ["serai-primitives/std"]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![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.
|
/// Amount of blocks in 30 days for 6s per block.
|
||||||
const BLOCKS_PER_MONTH: u64 = 10 * 60 * 24 * 30;
|
const BLOCKS_PER_MONTH: u64 = 10 * 60 * 24 * 30;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![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]
|
#[frame_support::pallet]
|
||||||
pub mod pallet {
|
pub mod pallet {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur
|
|||||||
|
|
||||||
serai-primitives = { path = "../../primitives", default-features = false }
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
in-instructions-primitives = { package = "serai-in-instructions-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 }
|
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||||
dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false }
|
dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false }
|
||||||
@@ -57,7 +56,6 @@ std = [
|
|||||||
|
|
||||||
"serai-primitives/std",
|
"serai-primitives/std",
|
||||||
"in-instructions-primitives/std",
|
"in-instructions-primitives/std",
|
||||||
"genesis-liquidity-primitives/std",
|
|
||||||
|
|
||||||
"coins-pallet/std",
|
"coins-pallet/std",
|
||||||
"dex-pallet/std",
|
"dex-pallet/std",
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ pub mod pallet {
|
|||||||
use sp_core::sr25519::Public;
|
use sp_core::sr25519::Public;
|
||||||
|
|
||||||
use serai_primitives::{Coin, Amount, Balance};
|
use serai_primitives::{Coin, Amount, Balance};
|
||||||
use genesis_liquidity_primitives::GENESIS_LIQUIDITY_ACCOUNT;
|
|
||||||
|
|
||||||
use frame_support::pallet_prelude::*;
|
use frame_support::pallet_prelude::*;
|
||||||
use frame_system::{pallet_prelude::*, RawOrigin};
|
use frame_system::{pallet_prelude::*, RawOrigin};
|
||||||
@@ -34,8 +33,10 @@ pub mod pallet {
|
|||||||
Config as ValidatorSetsConfig, Pallet as ValidatorSets,
|
Config as ValidatorSetsConfig, Pallet as ValidatorSets,
|
||||||
};
|
};
|
||||||
|
|
||||||
use genesis_liquidity_pallet::{Pallet as GenesisLiq, Config as GenesisLiqConfig};
|
use genesis_liquidity_pallet::{
|
||||||
use emissions_pallet::{Pallet as Emissions, Config as EmissionsConfig};
|
Pallet as GenesisLiq, Config as GenesisLiqConfig, primitives::GENESIS_LIQUIDITY_ACCOUNT,
|
||||||
|
};
|
||||||
|
use emissions_pallet::{Pallet as Emissions, Config as EmissionsConfig, primitives::POL_ACCOUNT};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -216,6 +217,7 @@ pub mod pallet {
|
|||||||
GenesisLiq::<T>::add_coin_liquidity(address.into(), instruction.balance)?;
|
GenesisLiq::<T>::add_coin_liquidity(address.into(), instruction.balance)?;
|
||||||
}
|
}
|
||||||
InInstruction::SwapToStakedSRI(address, network) => {
|
InInstruction::SwapToStakedSRI(address, network) => {
|
||||||
|
Coins::<T>::mint(POL_ACCOUNT.into(), instruction.balance)?;
|
||||||
Emissions::<T>::swap_to_staked_sri(address.into(), network, instruction.balance)?;
|
Emissions::<T>::swap_to_staked_sri(address.into(), network, instruction.balance)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,3 +27,6 @@ pub const ARBITRAGE_TIME: u16 = (2 * HOURS) as u16;
|
|||||||
///
|
///
|
||||||
/// We additionally +1 so there is a true median.
|
/// We additionally +1 so there is a true median.
|
||||||
pub const MEDIAN_PRICE_WINDOW_LENGTH: u16 = (2 * ARBITRAGE_TIME) + 1;
|
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;
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ impl From<Call> for RuntimeCall {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Call::Emissions(_) => todo!(), // TODO
|
Call::Emissions => todo!(), // TODO
|
||||||
Call::InInstructions(ii) => match ii {
|
Call::InInstructions(ii) => match ii {
|
||||||
serai_abi::in_instructions::Call::execute_batch { batch } => {
|
serai_abi::in_instructions::Call::execute_batch { batch } => {
|
||||||
RuntimeCall::InInstructions(in_instructions::Call::execute_batch { batch })
|
RuntimeCall::InInstructions(in_instructions::Call::execute_batch { batch })
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ use sp_runtime::{
|
|||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use primitives::{
|
use primitives::{
|
||||||
NetworkId, PublicKey, AccountLookup, SubstrateAmount, Coin, NETWORKS, MEDIAN_PRICE_WINDOW_LENGTH,
|
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::{
|
use support::{
|
||||||
@@ -283,7 +283,7 @@ pub type ReportLongevity = <Runtime as pallet_babe::Config>::EpochDuration;
|
|||||||
|
|
||||||
impl babe::Config for Runtime {
|
impl babe::Config for Runtime {
|
||||||
#[cfg(feature = "fast-epoch")]
|
#[cfg(feature = "fast-epoch")]
|
||||||
type EpochDuration = ConstU64<{ 2 * MINUTES }>;
|
type EpochDuration = ConstU64<{ FAST_EPOCH_DURATION }>;
|
||||||
|
|
||||||
#[cfg(not(feature = "fast-epoch"))]
|
#[cfg(not(feature = "fast-epoch"))]
|
||||||
type EpochDuration = ConstU64<{ 4 * 7 * DAYS }>;
|
type EpochDuration = ConstU64<{ 4 * 7 * DAYS }>;
|
||||||
|
|||||||
@@ -832,7 +832,6 @@ pub mod pallet {
|
|||||||
total_required
|
total_required
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make the increase_allocation public instead?
|
|
||||||
pub fn deposit_stake(
|
pub fn deposit_stake(
|
||||||
network: NetworkId,
|
network: NetworkId,
|
||||||
account: T::AccountId,
|
account: T::AccountId,
|
||||||
|
|||||||
Reference in New Issue
Block a user