From 9d50fe7f9c86f668d7ddb12d2c03ee0a81486823 Mon Sep 17 00:00:00 2001 From: akildemir Date: Thu, 18 Jul 2024 16:35:12 +0300 Subject: [PATCH] tidy up --- substrate/abi/src/genesis_liquidity.rs | 2 +- .../client/src/serai/genesis_liquidity.rs | 14 +- substrate/client/tests/genesis_liquidity.rs | 210 +++++++++--------- substrate/genesis-liquidity/pallet/src/lib.rs | 41 ++-- .../genesis-liquidity/primitives/src/lib.rs | 10 +- substrate/runtime/src/abi.rs | 8 +- 6 files changed, 144 insertions(+), 141 deletions(-) diff --git a/substrate/abi/src/genesis_liquidity.rs b/substrate/abi/src/genesis_liquidity.rs index 2047bc2b9..461284141 100644 --- a/substrate/abi/src/genesis_liquidity.rs +++ b/substrate/abi/src/genesis_liquidity.rs @@ -7,7 +7,7 @@ use primitives::*; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Call { remove_coin_liquidity { balance: Balance }, - oraclize_values { prices: Prices, signature: Signature }, + oraclize_values { values: Values, signature: Signature }, } #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] diff --git a/substrate/client/src/serai/genesis_liquidity.rs b/substrate/client/src/serai/genesis_liquidity.rs index 431ce90b0..04e80d745 100644 --- a/substrate/client/src/serai/genesis_liquidity.rs +++ b/substrate/client/src/serai/genesis_liquidity.rs @@ -1,5 +1,5 @@ pub use serai_abi::genesis_liquidity::primitives; -use primitives::Prices; +use primitives::{Values, LiquidityAmount}; use serai_abi::primitives::*; @@ -29,9 +29,9 @@ impl<'a> SeraiGenesisLiquidity<'a> { .await } - pub fn oraclize_values(prices: Prices, signature: Signature) -> Transaction { + pub fn oraclize_values(values: Values, signature: Signature) -> Transaction { Serai::unsigned(serai_abi::Call::GenesisLiquidity( - serai_abi::genesis_liquidity::Call::oraclize_values { prices, signature }, + serai_abi::genesis_liquidity::Call::oraclize_values { values, signature }, )) } @@ -45,7 +45,7 @@ impl<'a> SeraiGenesisLiquidity<'a> { &self, address: &SeraiAddress, coin: Coin, - ) -> Result<(Amount, Amount), SeraiError> { + ) -> Result { Ok( self .0 @@ -55,11 +55,11 @@ impl<'a> SeraiGenesisLiquidity<'a> { (coin, sp_core::hashing::blake2_128(&address.encode()), &address.0), ) .await? - .unwrap_or((Amount(0), Amount(0))), + .unwrap_or(LiquidityAmount::zero()), ) } - pub async fn supply(&self, coin: Coin) -> Result<(Amount, Amount), SeraiError> { - Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or((Amount(0), Amount(0)))) + pub async fn supply(&self, coin: Coin) -> Result { + Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(LiquidityAmount::zero())) } } diff --git a/substrate/client/tests/genesis_liquidity.rs b/substrate/client/tests/genesis_liquidity.rs index ae0f27eb6..17f7ee904 100644 --- a/substrate/client/tests/genesis_liquidity.rs +++ b/substrate/client/tests/genesis_liquidity.rs @@ -9,14 +9,14 @@ use schnorrkel::Schnorrkel; use serai_client::{ genesis_liquidity::{ - primitives::{GENESIS_LIQUIDITY_ACCOUNT, GENESIS_LP_SHARES}, + 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, Prices}, + genesis_liquidity::primitives::{oraclize_values_message, Values}, primitives::COINS, }; @@ -40,59 +40,53 @@ serai_test_fast_epoch!( ); 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 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; + // 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.. - tokio::time::timeout(tokio::time::Duration::from_secs(300), async { + tokio::time::timeout(tokio::time::Duration::from_secs(3 * 10 * 6), async { while serai.latest_finalized_block().await.unwrap().number() < 10 { tokio::time::sleep(Duration::from_secs(6)).await; } @@ -101,18 +95,22 @@ async fn test_genesis_liquidity(serai: Serai) { .unwrap(); // set prices - let prices = Prices { monero: 184100, ethereum: 4785000, dai: 1500 }; - set_prices(&serai, &prices).await; - - // wait little bit.. + 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); - 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)); @@ -124,60 +122,66 @@ 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; - 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).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).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_supply = serai.genesis_liquidity().supply(Coin::Bitcoin).await.unwrap(); - for (acc, amount) in btc_addresses { - let acc_liq_shares = - serai.genesis_liquidity().liquidity(&acc, Coin::Bitcoin).await.unwrap().0 .0; - - // 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 shares_ratio = (GENESIS_LP_SHARES * acc_liq_shares) / btc_liq_supply.0 .0; - let amounts_ratio = (GENESIS_LP_SHARES * amount.0) / u64::try_from(pool_btc).unwrap(); - assert_eq!(shares_ratio, amounts_ratio); + 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 each xmr liq provider got liq tokens proportional to their value - let xmr_liq_supply = serai.genesis_liquidity().supply(Coin::Monero).await.unwrap(); - for (acc, amount) in xmr_addresses { - let acc_liq_shares = - serai.genesis_liquidity().liquidity(&acc, Coin::Monero).await.unwrap().0 .0; + // 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 shares_ratio = (GENESIS_LP_SHARES * acc_liq_shares) / xmr_liq_supply.0 .0; - let amounts_ratio = (GENESIS_LP_SHARES * amount.0) / u64::try_from(pool_xmr).unwrap(); - assert_eq!(shares_ratio, amounts_ratio); + 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_prices(serai: &Serai, prices: &Prices) { +async fn set_values(serai: &Serai, values: &Values) { // prepare a Musig tx to set the initial prices let pair = insecure_pair_from_name("Alice"); let public = pair.public(); - // we publish the tx in set 2 - let set = ValidatorSet { session: Session(2), 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]>( @@ -196,11 +200,11 @@ async fn set_prices(serai: &Serai, prices: &Prices) { &Schnorrkel::new(b"substrate"), &HashMap::from([(threshold_keys.params().i(), threshold_keys.into())]), ), - &oraclize_values_message(&set, prices), + &oraclize_values_message(&set, values), ); - // set initial prices + // oraclize values let _ = - publish_tx(serai, &SeraiGenesisLiquidity::oraclize_values(*prices, Signature(sig.to_bytes()))) + publish_tx(serai, &SeraiGenesisLiquidity::oraclize_values(*values, Signature(sig.to_bytes()))) .await; } diff --git a/substrate/genesis-liquidity/pallet/src/lib.rs b/substrate/genesis-liquidity/pallet/src/lib.rs index 86f2a9081..8c052a451 100644 --- a/substrate/genesis-liquidity/pallet/src/lib.rs +++ b/substrate/genesis-liquidity/pallet/src/lib.rs @@ -16,10 +16,11 @@ pub mod pallet { use validator_sets_pallet::{Config as VsConfig, Pallet as ValidatorSets}; use serai_primitives::{Coin, COINS, *}; - use validator_sets_primitives::{ValidatorSet, Session, musig_key}; + use validator_sets_primitives::{ValidatorSet, musig_key}; pub use genesis_liquidity_primitives as primitives; use primitives::*; + // TODO: Have a more robust way of accessing LiquidityTokens pallet. /// LiquidityTokens Pallet as an instance of coins pallet. pub type LiquidityTokens = coins_pallet::Pallet; @@ -71,7 +72,7 @@ pub mod pallet { pub(crate) type Oracle = StorageMap<_, Identity, Coin, u64, OptionQuery>; #[pallet::storage] - pub(crate) type GenesisComplete = StorageValue<_, bool, OptionQuery>; + pub(crate) type GenesisComplete = StorageValue<_, (), OptionQuery>; #[pallet::hooks] impl Hooks> for Pallet { @@ -137,14 +138,9 @@ pub mod pallet { }; total_sri_distributed = total_sri_distributed.checked_add(sri_amount).unwrap(); - // we can't add 0 liquidity - if !(pool_amount > 0 && sri_amount > 0) { - continue; - } - // actually add the liquidity to dex let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into()); - Dex::::add_liquidity( + let Ok(()) = Dex::::add_liquidity( origin.into(), coin, u64::try_from(pool_amount).unwrap(), @@ -152,8 +148,9 @@ pub mod pallet { u64::try_from(pool_amount).unwrap(), sri_amount, GENESIS_LIQUIDITY_ACCOUNT.into(), - ) - .unwrap(); + ) else { + continue; + }; // let everyone know about the event Self::deposit_event(Event::GenesisLiquidityAddedToPool { @@ -169,7 +166,7 @@ pub mod pallet { assert_eq!(Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), coin), Amount(0)); } - GenesisComplete::::set(Some(true)); + GenesisComplete::::set(Some(())); } // we accept we reached economic security once we can mint smallest amount of a network's coin @@ -222,7 +219,8 @@ pub mod pallet { }, ) } else { - let first_amount = LiquidityAmount { shares: GENESIS_LP_SHARES, coins: balance.amount.0 }; + let first_amount = + LiquidityAmount { shares: INITIAL_GENESIS_LP_SHARES, coins: balance.amount.0 }; (first_amount, first_amount) }; @@ -301,7 +299,8 @@ pub mod pallet { Liquidity::::get(balance.coin, account).unwrap_or(LiquidityAmount::zero()); let total_shares = Supply::::get(balance.coin).unwrap_or(LiquidityAmount::zero()).shares; let user_liq_tokens = Self::mul_div(total_liq_tokens, shares, total_shares)?; - let amount_to_remove = Self::mul_div(user_liq_tokens, balance.amount.0, GENESIS_LP_SHARES)?; + let amount_to_remove = + Self::mul_div(user_liq_tokens, balance.amount.0, INITIAL_GENESIS_LP_SHARES)?; // remove liquidity from pool let prev_sri = Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai); @@ -364,7 +363,7 @@ pub mod pallet { }, ) } else { - if balance.amount.0 != GENESIS_LP_SHARES { + if balance.amount.0 != INITIAL_GENESIS_LP_SHARES { Err(Error::::CanOnlyRemoveFullAmount)?; } let existing = @@ -406,16 +405,16 @@ pub mod pallet { #[pallet::weight((0, DispatchClass::Operational))] // TODO pub fn oraclize_values( origin: OriginFor, - prices: Prices, + values: Values, _signature: Signature, ) -> DispatchResult { ensure_none(origin)?; // set the prices Oracle::::set(Coin::Bitcoin, Some(10u64.pow(8))); - Oracle::::set(Coin::Monero, Some(prices.monero)); - Oracle::::set(Coin::Ether, Some(prices.ethereum)); - Oracle::::set(Coin::Dai, Some(prices.dai)); + Oracle::::set(Coin::Monero, Some(values.monero)); + Oracle::::set(Coin::Ether, Some(values.ether)); + Oracle::::set(Coin::Dai, Some(values.dai)); Ok(()) } } @@ -426,7 +425,7 @@ pub mod pallet { fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity { match call { - Call::oraclize_values { ref prices, ref signature } => { + Call::oraclize_values { ref values, ref signature } => { let network = NetworkId::Serai; let Some(session) = ValidatorSets::::session(network) else { return Err(TransactionValidityError::from(InvalidTransaction::Custom(0))); @@ -444,14 +443,14 @@ pub mod pallet { Err(InvalidTransaction::Custom(1))?; } - // make sure signers settings the price at the end of the genesis period. + // make sure signers settings the value at the end of the genesis period. // we don't need this check for tests. #[cfg(not(feature = "fast-epoch"))] if >::block_number().saturated_into::() < MONTHS { Err(InvalidTransaction::Custom(2))?; } - if !musig_key(set, &signers).verify(&oraclize_values_message(&set, prices), signature) { + if !musig_key(set, &signers).verify(&oraclize_values_message(&set, values), signature) { Err(InvalidTransaction::BadProof)?; } diff --git a/substrate/genesis-liquidity/primitives/src/lib.rs b/substrate/genesis-liquidity/primitives/src/lib.rs index 0468fdca0..4f07dccef 100644 --- a/substrate/genesis-liquidity/primitives/src/lib.rs +++ b/substrate/genesis-liquidity/primitives/src/lib.rs @@ -18,7 +18,7 @@ use scale_info::TypeInfo; use serai_primitives::*; use validator_sets_primitives::ValidatorSet; -pub const GENESIS_LP_SHARES: u64 = 10_000; +pub const INITIAL_GENESIS_LP_SHARES: u64 = 10_000; // This is the account to hold and manage the genesis liquidity. pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"Genesis-liquidity-account"); @@ -27,9 +27,9 @@ pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"Genesis-liq #[cfg_attr(feature = "std", derive(Zeroize))] #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct Prices { +pub struct Values { pub monero: u64, - pub ethereum: u64, + pub ether: u64, pub dai: u64, } @@ -49,6 +49,6 @@ impl LiquidityAmount { } /// The message for the oraclize_values signature. -pub fn oraclize_values_message(set: &ValidatorSet, prices: &Prices) -> Vec { - (b"GenesisLiquidity-oraclize_values", set, prices).encode() +pub fn oraclize_values_message(set: &ValidatorSet, values: &Values) -> Vec { + (b"GenesisLiquidity-oraclize_values", set, values).encode() } diff --git a/substrate/runtime/src/abi.rs b/substrate/runtime/src/abi.rs index aa5f1a82e..b479036d2 100644 --- a/substrate/runtime/src/abi.rs +++ b/substrate/runtime/src/abi.rs @@ -93,9 +93,9 @@ impl From for RuntimeCall { 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 { prices, signature } => { + serai_abi::genesis_liquidity::Call::oraclize_values { values, signature } => { RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::oraclize_values { - prices, + values, signature, }) } @@ -276,8 +276,8 @@ impl TryInto for RuntimeCall { genesis_liquidity::Call::remove_coin_liquidity { balance } => { serai_abi::genesis_liquidity::Call::remove_coin_liquidity { balance } } - genesis_liquidity::Call::oraclize_values { prices, signature } => { - serai_abi::genesis_liquidity::Call::oraclize_values { prices, signature } + genesis_liquidity::Call::oraclize_values { values, signature } => { + serai_abi::genesis_liquidity::Call::oraclize_values { values, signature } } _ => Err(())?, }),