diff --git a/Cargo.lock b/Cargo.lock index 7bf972439..d17379fc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8100,7 +8100,7 @@ dependencies = [ "serai-coins-pallet", "serai-dex-pallet", "serai-emissions-primitives", - "serai-genesis-liquidity-primitives", + "serai-genesis-liquidity-pallet", "serai-primitives", "serai-validator-sets-pallet", "serai-validator-sets-primitives", diff --git a/substrate/abi/Cargo.toml b/substrate/abi/Cargo.toml index c2947aaab..072f7460e 100644 --- a/substrate/abi/Cargo.toml +++ b/substrate/abi/Cargo.toml @@ -34,6 +34,7 @@ serai-primitives = { path = "../primitives", version = "0.1", default-features = serai-coins-primitives = { path = "../coins/primitives", version = "0.1", default-features = false } serai-validator-sets-primitives = { path = "../validator-sets/primitives", version = "0.1", default-features = false } serai-genesis-liquidity-primitives = { path = "../genesis-liquidity/primitives", version = "0.1", default-features = false } +serai-emissions-primitives = { path = "../emissions/primitives", version = "0.1", default-features = false } serai-in-instructions-primitives = { path = "../in-instructions/primitives", version = "0.1", default-features = false } serai-signals-primitives = { path = "../signals/primitives", version = "0.1", default-features = false } @@ -57,6 +58,7 @@ std = [ "serai-coins-primitives/std", "serai-validator-sets-primitives/std", "serai-genesis-liquidity-primitives/std", + "serai-emissions-primitives/std", "serai-in-instructions-primitives/std", "serai-signals-primitives/std", ] diff --git a/substrate/abi/src/lib.rs b/substrate/abi/src/lib.rs index e193b51cd..452202cf6 100644 --- a/substrate/abi/src/lib.rs +++ b/substrate/abi/src/lib.rs @@ -33,7 +33,6 @@ pub enum Call { Coins(coins::Call), LiquidityTokens(liquidity_tokens::Call), Dex(dex::Call), - GenesisLiquidity(genesis_liquidity::Call), ValidatorSets(validator_sets::Call), GenesisLiquidity(genesis_liquidity::Call), Emissions(emissions::Call), @@ -57,7 +56,6 @@ pub enum Event { Coins(coins::Event), LiquidityTokens(liquidity_tokens::Event), Dex(dex::Event), - GenesisLiquidity(genesis_liquidity::Event), ValidatorSets(validator_sets::Event), GenesisLiquidity(genesis_liquidity::Event), Emissions(emissions::Event), diff --git a/substrate/client/src/serai/dex.rs b/substrate/client/src/serai/dex.rs index 046fdb8fd..d20c4e2c5 100644 --- a/substrate/client/src/serai/dex.rs +++ b/substrate/client/src/serai/dex.rs @@ -77,4 +77,8 @@ impl<'a> SeraiDex<'a> { .map_err(|e| SeraiError::ErrorInResponse(e.to_string()))?; Ok(result.map(|amounts| (Amount(amounts.0), Amount(amounts.1)))) } + + pub async fn oracle_value(&self, coin: Coin) -> Result, SeraiError> { + self.0.storage(PALLET, "SecurityOracleValue", coin).await + } } diff --git a/substrate/client/src/serai/mod.rs b/substrate/client/src/serai/mod.rs index 53dc7b679..dfd14779e 100644 --- a/substrate/client/src/serai/mod.rs +++ b/substrate/client/src/serai/mod.rs @@ -1,4 +1,3 @@ -use hex::FromHexError; use thiserror::Error; use async_lock::RwLock; @@ -87,14 +86,6 @@ impl<'a> Clone for TemporalSerai<'a> { } } -pub fn hex_decode(str: String) -> Result, FromHexError> { - if let Some(stripped) = str.strip_prefix("0x") { - hex::decode(stripped) - } else { - hex::decode(str) - } -} - impl Serai { pub async fn call( &self, @@ -147,11 +138,19 @@ impl Serai { } } + fn hex_decode(str: String) -> Result, SeraiError> { + (if let Some(stripped) = str.strip_prefix("0x") { + hex::decode(stripped) + } else { + hex::decode(str) + }) + .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string())) + } + pub async fn block_hash(&self, number: u64) -> Result, SeraiError> { let hash: Option = self.call("chain_getBlockHash", [number]).await?; let Some(hash) = hash else { return Ok(None) }; - hex_decode(hash) - .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))? + Self::hex_decode(hash)? .try_into() .map_err(|_| SeraiError::InvalidNode("didn't respond to getBlockHash with hash".to_string())) .map(Some) @@ -212,12 +211,9 @@ impl Serai { pub async fn latest_finalized_block_hash(&self) -> Result<[u8; 32], SeraiError> { let hash: String = self.call("chain_getFinalizedHead", ()).await?; - hex_decode(hash) - .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))? - .try_into() - .map_err(|_| { - SeraiError::InvalidNode("didn't respond to getFinalizedHead with hash".to_string()) - }) + Self::hex_decode(hash)?.try_into().map_err(|_| { + SeraiError::InvalidNode("didn't respond to getFinalizedHead with hash".to_string()) + }) } pub async fn header(&self, hash: [u8; 32]) -> Result, SeraiError> { @@ -227,7 +223,7 @@ impl Serai { pub async fn block(&self, hash: [u8; 32]) -> Result, SeraiError> { let block: Option = self.call("chain_getBlockBin", [hex::encode(hash)]).await?; let Some(block) = block else { return Ok(None) }; - let Ok(bytes) = hex_decode(block) else { + let Ok(bytes) = Self::hex_decode(block) else { Err(SeraiError::InvalidNode("didn't return a hex-encoded block".to_string()))? }; let Ok(block) = Block::decode(&mut bytes.as_slice()) else { @@ -373,8 +369,7 @@ impl<'a> TemporalSerai<'a> { let res: Option = self.serai.call("state_getStorage", [hex::encode(full_key), hex::encode(self.block)]).await?; let Some(res) = res else { return Ok(None) }; - let res = hex_decode(res) - .map_err(|_| SeraiError::InvalidNode("expected hex from node wasn't hex".to_string()))?; + let res = Serai::hex_decode(res)?; Ok(Some(R::decode(&mut res.as_slice()).map_err(|_| { SeraiError::InvalidRuntime(format!( "different type present at storage location, raw value: {}", diff --git a/substrate/client/tests/common/genesis_liquidity.rs b/substrate/client/tests/common/genesis_liquidity.rs index 33699377a..990771c26 100644 --- a/substrate/client/tests/common/genesis_liquidity.rs +++ b/substrate/client/tests/common/genesis_liquidity.rs @@ -9,14 +9,14 @@ use schnorrkel::Schnorrkel; use serai_client::{ genesis_liquidity::{ - primitives::{GENESIS_LIQUIDITY_ACCOUNT, GENESIS_SRI}, + primitives::{GENESIS_LIQUIDITY_ACCOUNT, INITIAL_GENESIS_LP_SHARES}, SeraiGenesisLiquidity, }, validator_sets::primitives::{musig_context, Session, ValidatorSet}, }; use serai_abi::{ - genesis_liquidity::primitives::{set_initial_price_message, Prices}, + genesis_liquidity::primitives::{oraclize_values_message, Values}, primitives::COINS, }; @@ -24,7 +24,7 @@ use sp_core::{sr25519::Signature, Pair as PairTrait}; use serai_client::{ primitives::{ - Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, insecure_pair_from_name, + Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, insecure_pair_from_name, GENESIS_SRI, }, in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch}, Serai, @@ -34,79 +34,83 @@ use crate::common::{in_instructions::provide_batch, tx::publish_tx}; #[allow(dead_code)] pub async fn test_genesis_liquidity(serai: Serai) { - // amounts - let amounts = vec![ - Amount(5_53246991), - Amount(3_14562819), - Amount(9_33648912), - Amount(150_873639000000), - Amount(248_665228000000), - Amount(451_765529000000), - ]; - - // addresses - let mut btc_addresses = vec![]; - let mut xmr_addresses = vec![]; - let addr_count = amounts.len(); - for (i, amount) in amounts.into_iter().enumerate() { - let mut address = SeraiAddress::new([0; 32]); - OsRng.fill_bytes(&mut address.0); - if i < addr_count / 2 { - btc_addresses.push((address, amount)); - } else { - xmr_addresses.push((address, amount)); + // all coins except the native + let coins = COINS.into_iter().filter(|c| *c != Coin::native()).collect::>(); + + // make accounts with amounts + let mut accounts = HashMap::new(); + for coin in coins.clone() { + // make 5 accounts per coin + let mut values = vec![]; + for _ in 0 .. 5 { + let mut address = SeraiAddress::new([0; 32]); + OsRng.fill_bytes(&mut address.0); + values.push((address, Amount(OsRng.next_u64() % 10u64.pow(coin.decimals())))); } + accounts.insert(coin, values); } - btc_addresses.sort_by(|a1, a2| a1.0.cmp(&a2.0)); - xmr_addresses.sort_by(|a1, a2| a1.0.cmp(&a2.0)); - - // btc batch - let mut block_hash = BlockHash([0; 32]); - OsRng.fill_bytes(&mut block_hash.0); - let btc_ins = btc_addresses - .iter() - .map(|(addr, amount)| InInstructionWithBalance { - instruction: InInstruction::GenesisLiquidity(*addr), - balance: Balance { coin: Coin::Bitcoin, amount: *amount }, - }) - .collect::>(); - let batch = - Batch { network: NetworkId::Bitcoin, id: 0, block: block_hash, instructions: btc_ins }; - provide_batch(&serai, batch).await; - - // xmr batch - let mut block_hash = BlockHash([0; 32]); - OsRng.fill_bytes(&mut block_hash.0); - let xmr_ins = xmr_addresses - .iter() - .map(|(addr, amount)| InInstructionWithBalance { - instruction: InInstruction::GenesisLiquidity(*addr), - balance: Balance { coin: Coin::Monero, amount: *amount }, - }) - .collect::>(); - let batch = Batch { network: NetworkId::Monero, id: 0, block: block_hash, instructions: xmr_ins }; - provide_batch(&serai, batch).await; - - // set prices - let prices = Prices { bitcoin: 10u64.pow(8), monero: 184100, ethereum: 4785000, dai: 1500 }; - set_prices(&serai, &prices).await; - - // wait until genesis ends.. - tokio::time::timeout(tokio::time::Duration::from_secs(300), async { - while serai.latest_finalized_block().await.unwrap().number() < 25 { - tokio::time::sleep(Duration::from_secs(6)).await; - } - }) + + // send a batch per coin + let mut batch_ids: HashMap = HashMap::new(); + for coin in coins.clone() { + // set up instructions + let instructions = accounts[&coin] + .iter() + .map(|(addr, amount)| InInstructionWithBalance { + instruction: InInstruction::GenesisLiquidity(*addr), + balance: Balance { coin, amount: *amount }, + }) + .collect::>(); + + // set up bloch hash + let mut block = BlockHash([0; 32]); + OsRng.fill_bytes(&mut block.0); + + // set up batch id + batch_ids + .entry(coin.network()) + .and_modify(|v| { + *v += 1; + }) + .or_insert(0); + + let batch = + Batch { network: coin.network(), id: batch_ids[&coin.network()], block, instructions }; + 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 + // 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), + ]); + + // wait a little bit.. + tokio::time::sleep(Duration::from_secs(12)).await; + // check total SRI supply is +100M - let last_block = serai.latest_finalized_block().await.unwrap().hash(); - let serai = serai.as_of(last_block); - // Check balance instead of supply - let sri = serai.coins().coin_supply(Coin::Serai).await.unwrap(); // 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)); @@ -118,85 +122,67 @@ pub async fn test_genesis_liquidity(serai: Serai) { } // check pools has proper liquidity - let pool_btc = btc_addresses.iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0)); - let pool_xmr = xmr_addresses.iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0)); - - let pool_btc_value = (pool_btc * u128::from(prices.bitcoin)) / 10u128.pow(8); - let pool_xmr_value = (pool_xmr * u128::from(prices.monero)) / 10u128.pow(12); - let total_value = pool_btc_value + pool_xmr_value; - - // calculated distributed SRI. We know that xmr is at the end of COINS array - // so it will be approximated to roof instead of floor after integer division. - let btc_sri = (pool_btc_value * u128::from(GENESIS_SRI)) / total_value; - let xmr_sri = ((pool_xmr_value * u128::from(GENESIS_SRI)) / total_value) + 1; - - let btc_reserves = serai.dex().get_reserves(Coin::Bitcoin, Coin::Serai).await.unwrap().unwrap(); - assert_eq!(u128::from(btc_reserves.0 .0), pool_btc); - assert_eq!(u128::from(btc_reserves.1 .0), btc_sri); - - let xmr_reserves = serai.dex().get_reserves(Coin::Monero, Coin::Serai).await.unwrap().unwrap(); - assert_eq!(u128::from(xmr_reserves.0 .0), pool_xmr); - assert_eq!(u128::from(xmr_reserves.1 .0), xmr_sri); - - // check each btc liq provider got liq tokens proportional to their value - let btc_liq_token_supply = u128::from( - serai - .liquidity_tokens() - .token_balance(Coin::Bitcoin, GENESIS_LIQUIDITY_ACCOUNT) - .await - .unwrap() - .0, - ); - let mut total_tokens_this_coin: u128 = 0; - for (i, (addr, amount)) in btc_addresses.iter().enumerate() { - let addr_value = (u128::from(amount.0) * u128::from(prices.bitcoin)) / 10u128.pow(8); - let addr_liq_tokens = if i == btc_addresses.len() - 1 { - btc_liq_token_supply - total_tokens_this_coin + 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 { - (addr_value * btc_liq_token_supply) / pool_btc_value + total_coin }; - let addr_actual_token_amount = - serai.genesis_liquidity().liquidity_tokens(addr, Coin::Bitcoin).await.unwrap(); - - assert_eq!(addr_liq_tokens, addr_actual_token_amount.0.into()); - total_tokens_this_coin += addr_liq_tokens; + total_value += value; + pool_amounts.insert(coin, (total_coin, value)); } - // check each xmr liq provider got liq tokens proportional to their value - let xmr_liq_token_supply = u128::from( - serai - .liquidity_tokens() - .token_balance(Coin::Monero, GENESIS_LIQUIDITY_ACCOUNT) - .await - .unwrap() - .0, - ); - total_tokens_this_coin = 0; - for (i, (addr, amount)) in xmr_addresses.iter().enumerate() { - let addr_value = (u128::from(amount.0) * u128::from(prices.monero)) / 10u128.pow(12); - let addr_liq_tokens = if i == xmr_addresses.len() - 1 { - xmr_liq_token_supply - total_tokens_this_coin + // 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 { - (addr_value * xmr_liq_token_supply) / pool_xmr_value + (pool_amounts[&coin].1 * u128::from(GENESIS_SRI)) / total_value }; + total_sri_distributed += sri; - let addr_actual_token_amount = - serai.genesis_liquidity().liquidity_tokens(addr, Coin::Monero).await.unwrap(); + 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!(addr_liq_tokens, addr_actual_token_amount.0.into()); - total_tokens_this_coin += addr_liq_tokens; + // 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: remove the liq before/after genesis ended. + // TODO: test remove the liq before/after genesis ended. } #[allow(dead_code)] -async fn set_prices(serai: &Serai, prices: &Prices) { - // prepare a Musig tx to set the initial prices +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(); - let set = ValidatorSet { session: Session(0), network: NetworkId::Serai }; + // we publish the tx in set 4 + let set = ValidatorSet { session: Session(4), network: NetworkId::Serai }; let public_key = ::read_G::<&[u8]>(&mut public.0.as_ref()).unwrap(); let secret_key = ::read_F::<&[u8]>( @@ -215,13 +201,11 @@ async fn set_prices(serai: &Serai, prices: &Prices) { &Schnorrkel::new(b"substrate"), &HashMap::from([(threshold_keys.params().i(), threshold_keys.into())]), ), - &set_initial_price_message(&set, prices), + &oraclize_values_message(&set, values), ); - // set initial prices - let _ = publish_tx( - serai, - &SeraiGenesisLiquidity::set_initial_price(*prices, Signature(sig.to_bytes())), - ) - .await; + // oraclize values + let _ = + publish_tx(serai, &SeraiGenesisLiquidity::oraclize_values(*values, Signature(sig.to_bytes()))) + .await; } diff --git a/substrate/client/tests/genesis_liquidity.rs b/substrate/client/tests/genesis_liquidity.rs index 8edc87d3d..c61d6e70f 100644 --- a/substrate/client/tests/genesis_liquidity.rs +++ b/substrate/client/tests/genesis_liquidity.rs @@ -1,216 +1,10 @@ -use std::{time::Duration, collections::HashMap}; - -use rand_core::{RngCore, OsRng}; -use zeroize::Zeroizing; - -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 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, - }, - in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch}, - Serai, -}; +use serai_client::Serai; mod common; -use common::{in_instructions::provide_batch, tx::publish_tx}; +use common::genesis_liquidity::test_genesis_liquidity; serai_test_fast_epoch!( genesis_liquidity: (|serai: Serai| async move { test_genesis_liquidity(serai).await; }) ); - -async fn test_genesis_liquidity(serai: Serai) { - // all coins except the native - let coins = COINS.into_iter().filter(|c| *c != Coin::native()).collect::>(); - - // make accounts with amounts - let mut accounts = HashMap::new(); - for coin in coins.clone() { - // make 5 accounts per coin - let mut values = vec![]; - for _ in 0 .. 5 { - let mut address = SeraiAddress::new([0; 32]); - OsRng.fill_bytes(&mut address.0); - values.push((address, Amount(OsRng.next_u64() % 10u64.pow(coin.decimals())))); - } - accounts.insert(coin, values); - } - - // send a batch per coin - let mut batch_ids: HashMap = HashMap::new(); - for coin in coins.clone() { - // set up instructions - let instructions = accounts[&coin] - .iter() - .map(|(addr, amount)| InInstructionWithBalance { - instruction: InInstruction::GenesisLiquidity(*addr), - balance: Balance { coin, amount: *amount }, - }) - .collect::>(); - - // set up bloch hash - let mut block = BlockHash([0; 32]); - OsRng.fill_bytes(&mut block.0); - - // set up batch id - batch_ids - .entry(coin.network()) - .and_modify(|v| { - *v += 1; - }) - .or_insert(0); - - let batch = - Batch { network: coin.network(), id: batch_ids[&coin.network()], block, instructions }; - 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 - // 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), - ]); - - // wait a little bit.. - tokio::time::sleep(Duration::from_secs(12)).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 .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. -} - -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 }; - - let public_key = ::read_G::<&[u8]>(&mut public.0.as_ref()).unwrap(); - let secret_key = ::read_F::<&[u8]>( - &mut pair.as_ref().secret.to_bytes()[.. 32].as_ref(), - ) - .unwrap(); - - assert_eq!(Ristretto::generator() * secret_key, public_key); - let threshold_keys = - musig::(&musig_context(set), &Zeroizing::new(secret_key), &[public_key]).unwrap(); - - let sig = frost::tests::sign_without_caching( - &mut OsRng, - frost::tests::algorithm_machines( - &mut OsRng, - &Schnorrkel::new(b"substrate"), - &HashMap::from([(threshold_keys.params().i(), threshold_keys.into())]), - ), - &oraclize_values_message(&set, values), - ); - - // oraclize values - let _ = - publish_tx(serai, &SeraiGenesisLiquidity::oraclize_values(*values, Signature(sig.to_bytes()))) - .await; -} diff --git a/substrate/emissions/pallet/Cargo.toml b/substrate/emissions/pallet/Cargo.toml index 49e3277d0..ca4f23a18 100644 --- a/substrate/emissions/pallet/Cargo.toml +++ b/substrate/emissions/pallet/Cargo.toml @@ -32,10 +32,10 @@ sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false } validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false } dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false } +genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../../genesis-liquidity/pallet", default-features = false } serai-primitives = { path = "../../primitives", default-features = false } validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../validator-sets/primitives", default-features = false } -genesis-liquidity-primitives = { package = "serai-genesis-liquidity-primitives", path = "../../genesis-liquidity/primitives", default-features = false } emissions-primitives = { package = "serai-emissions-primitives", path = "../primitives", default-features = false } [features] @@ -52,11 +52,10 @@ std = [ "coins-pallet/std", "validator-sets-pallet/std", "dex-pallet/std", + "genesis-liquidity-pallet/std", "serai-primitives/std", "emissions-primitives/std", - "genesis-liquidity-primitives/std", ] -fast-epoch = ["genesis-liquidity-primitives/fast-epoch"] 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 9b2ecca7a..cdd704d8a 100644 --- a/substrate/emissions/pallet/src/lib.rs +++ b/substrate/emissions/pallet/src/lib.rs @@ -14,15 +14,19 @@ pub mod pallet { use dex_pallet::{Config as DexConfig, Pallet as Dex}; use validator_sets_pallet::{Pallet as ValidatorSets, Config as ValidatorSetsConfig}; + use genesis_liquidity_pallet::{Pallet as GenesisLiquidity, Config as GenesisLiquidityConfig}; - use serai_primitives::{NetworkId, NETWORKS, *}; + use serai_primitives::*; use validator_sets_primitives::{MAX_KEY_SHARES_PER_SET, Session}; - use genesis_liquidity_primitives::GENESIS_PERIOD_BLOCKS; use emissions_primitives::*; #[pallet::config] pub trait Config: - frame_system::Config + ValidatorSetsConfig + CoinsConfig + DexConfig + frame_system::Config + + ValidatorSetsConfig + + CoinsConfig + + DexConfig + + GenesisLiquidityConfig { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } @@ -81,6 +85,9 @@ pub mod pallet { #[pallet::getter(fn last_swap_volume)] pub(crate) type LastSwapVolume = StorageMap<_, Identity, NetworkId, u64, OptionQuery>; + #[pallet::storage] + pub(crate) type GenesisCompleteBlock = StorageValue<_, u64, OptionQuery>; + #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { @@ -101,8 +108,10 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_finalize(n: BlockNumberFor) { - // wait 1 extra block to actually see genesis changes - let genesis_ended = n >= (GENESIS_PERIOD_BLOCKS + 1).into(); + let genesis_ended = GenesisLiquidity::::genesis_complete().is_some(); + if GenesisCompleteBlock::::get().is_none() && genesis_ended { + GenesisCompleteBlock::::set(Some(n.saturated_into::())); + } // we accept we reached economic security once we can mint smallest amount of a network's coin for coin in COINS { @@ -299,7 +308,9 @@ pub mod pallet { } fn initial_period(n: BlockNumberFor) -> bool { - n >= GENESIS_PERIOD_BLOCKS.into() && n < (3 * GENESIS_PERIOD_BLOCKS).into() + let genesis_complete_block = GenesisCompleteBlock::::get(); + genesis_complete_block.is_some() && + (n.saturated_into::() < (3 * genesis_complete_block.unwrap())) } /// Returns true if any of the external networks haven't reached economic security yet. diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index 6ff849619..c719090d7 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -72,6 +72,7 @@ pub mod pallet { pub(crate) type Oracle = StorageMap<_, Identity, Coin, u64, OptionQuery>; #[pallet::storage] + #[pallet::getter(fn genesis_complete)] pub(crate) type GenesisComplete = StorageValue<_, (), OptionQuery>; #[pallet::hooks] diff --git a/substrate/genesis-liquidity/primitives/Cargo.toml b/substrate/genesis-liquidity/primitives/Cargo.toml index 3b0b9d3b5..e795ff24b 100644 --- a/substrate/genesis-liquidity/primitives/Cargo.toml +++ b/substrate/genesis-liquidity/primitives/Cargo.toml @@ -42,5 +42,4 @@ std = [ "sp-std/std" ] -fast-epoch = [] default = ["std"] diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index 40feda618..e67674cc5 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -8,7 +8,6 @@ use sc_service::ChainType; use serai_runtime::{ primitives::*, WASM_BINARY, BABE_GENESIS_EPOCH_CONFIG, RuntimeGenesisConfig, SystemConfig, CoinsConfig, ValidatorSetsConfig, SignalsConfig, BabeConfig, GrandpaConfig, EmissionsConfig, - GenesisLiquidityConfig, }; pub type ChainSpec = sc_service::GenericChainSpec; @@ -59,7 +58,6 @@ fn devnet_genesis( .collect(), participants: validators.clone(), }, - genesis_liquidity: GenesisLiquidityConfig { participants: validators.clone() }, emissions: EmissionsConfig { networks: serai_runtime::primitives::NETWORKS .iter() @@ -118,7 +116,6 @@ fn testnet_genesis(wasm_binary: &[u8], validators: Vec<&'static str>) -> Runtime .collect(), participants: validators.clone(), }, - genesis_liquidity: GenesisLiquidityConfig { participants: validators.clone() }, emissions: EmissionsConfig { networks: serai_runtime::primitives::NETWORKS .iter() diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index 6baee37e0..c2eee13d1 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -131,7 +131,7 @@ std = [ "pallet-transaction-payment-rpc-runtime-api/std", ] -fast-epoch = ["genesis-liquidity-pallet/fast-epoch", "emissions-pallet/fast-epoch"] +fast-epoch = ["genesis-liquidity-pallet/fast-epoch"] runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", diff --git a/substrate/runtime/src/abi.rs b/substrate/runtime/src/abi.rs index b479036d2..41e60599f 100644 --- a/substrate/runtime/src/abi.rs +++ b/substrate/runtime/src/abi.rs @@ -89,17 +89,6 @@ impl From for RuntimeCall { send_to: send_to.into(), }), }, - Call::GenesisLiquidity(gl) => match gl { - serai_abi::genesis_liquidity::Call::remove_coin_liquidity { balance } => { - RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::remove_coin_liquidity { balance }) - } - serai_abi::genesis_liquidity::Call::oraclize_values { values, signature } => { - RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::oraclize_values { - values, - signature, - }) - } - }, Call::ValidatorSets(vs) => match vs { serai_abi::validator_sets::Call::set_keys { network, @@ -138,6 +127,18 @@ impl From for RuntimeCall { RuntimeCall::ValidatorSets(validator_sets::Call::claim_deallocation { network, session }) } }, + Call::GenesisLiquidity(gl) => match gl { + serai_abi::genesis_liquidity::Call::remove_coin_liquidity { balance } => { + RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::remove_coin_liquidity { balance }) + } + serai_abi::genesis_liquidity::Call::oraclize_values { values, signature } => { + RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::oraclize_values { + values, + signature, + }) + } + }, + 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 }) diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index dde797d92..ca67df3d9 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<{ HOURS / 2 }>; // 30 minutes + type EpochDuration = ConstU64<{ MINUTES / 2 }>; // 30 seconds #[cfg(not(feature = "fast-epoch"))] type EpochDuration = ConstU64<{ 4 * 7 * DAYS }>; @@ -331,7 +331,6 @@ construct_runtime!( Coins: coins, LiquidityTokens: coins::::{Pallet, Call, Storage, Event}, Dex: dex, - GenesisLiquidity: genesis_liquidity, ValidatorSets: validator_sets, GenesisLiquidity: genesis_liquidity,