From eb04f873d52c78d4e8c5f630e7569bf88a64838a Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 2 Dec 2025 16:24:17 -0500 Subject: [PATCH] Stub the `genesis-liquidity` pallet --- Cargo.lock | 9 +- substrate/abi/src/genesis_liquidity.rs | 42 +- substrate/dex/Cargo.toml | 7 +- substrate/dex/src/lib.rs | 4 + substrate/genesis-liquidity/Cargo.toml | 58 +- substrate/genesis-liquidity/LICENSE | 2 +- substrate/genesis-liquidity/README.md | 3 + substrate/genesis-liquidity/src/lib.rs | 500 +++--------------- .../src/{genesis.rs => genesis_liquidity.rs} | 4 + substrate/primitives/src/lib.rs | 4 +- substrate/runtime/Cargo.toml | 4 + substrate/runtime/src/wasm/mod.rs | 22 +- 12 files changed, 180 insertions(+), 479 deletions(-) create mode 100644 substrate/genesis-liquidity/README.md rename substrate/primitives/src/{genesis.rs => genesis_liquidity.rs} (82%) diff --git a/Cargo.lock b/Cargo.lock index 2c0bbc0a..f4fb2868 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8386,7 +8386,6 @@ dependencies = [ "serai-core-pallet", "sp-core", "sp-io", - "substrate-median", ] [[package]] @@ -8509,14 +8508,11 @@ dependencies = [ "frame-support", "frame-system", "parity-scale-codec", + "serai-abi", "serai-coins-pallet", + "serai-core-pallet", "serai-dex-pallet", - "serai-economic-security-pallet", - "serai-primitives", - "serai-validator-sets-pallet", - "sp-application-crypto", "sp-core", - "sp-std", ] [[package]] @@ -9028,6 +9024,7 @@ dependencies = [ "serai-coins-pallet", "serai-core-pallet", "serai-dex-pallet", + "serai-genesis-liquidity-pallet", "serai-signals-pallet", "serai-validator-sets-pallet", "sp-api", diff --git a/substrate/abi/src/genesis_liquidity.rs b/substrate/abi/src/genesis_liquidity.rs index fd737d2f..0cca07e2 100644 --- a/substrate/abi/src/genesis_liquidity.rs +++ b/substrate/abi/src/genesis_liquidity.rs @@ -1,23 +1,38 @@ use borsh::{BorshSerialize, BorshDeserialize}; use serai_primitives::{ - crypto::Signature, address::SeraiAddress, balance::ExternalBalance, genesis::GenesisValues, + crypto::Signature, address::SeraiAddress, coin::ExternalCoin, balance::ExternalBalance, + genesis_liquidity::GenesisValues, }; +/// The address used for to hold genesis liquidity for a pool. +pub fn address(coin: ExternalCoin) -> SeraiAddress { + SeraiAddress::system(borsh::to_vec(&(b"GenesisLiquidity", coin)).unwrap()) +} + /// A call to the genesis liquidity. #[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)] pub enum Call { - /// Oraclize the value of non-Bitcoin external coins relative to Bitcoin. + /// Oraclize the values of the coins available on genesis, relative to BTC. + /// + /// This will trigger the addition of the liquidity into the pools and their initialization. oraclize_values { /// The values of the non-Bitcoin external coins. values: GenesisValues, /// The signature by the genesis validators for these values. signature: Signature, }, - /// Remove liquidity. - remove_liquidity { + /// Transfer genesis liquidity. + transfer_genesis_liquidity { + /// The account to transfer the liquidity to. + to: SeraiAddress, + /// The genesis liquidity to transfer. + genesis_liquidity: ExternalBalance, + }, + /// Remove genesis liquidity. + remove_genesis_liquidity { /// The genesis liquidity to remove. - balance: ExternalBalance, + genesis_liquidity: ExternalBalance, }, } @@ -25,7 +40,7 @@ impl Call { pub(crate) fn is_signed(&self) -> bool { match self { Call::oraclize_values { .. } => false, - Call::remove_liquidity { .. } => true, + Call::transfer_genesis_liquidity { .. } | Call::remove_genesis_liquidity { .. } => true, } } } @@ -38,13 +53,22 @@ pub enum Event { /// The recipient of the genesis liquidity. recipient: SeraiAddress, /// The coins added as genesis liquidity. - balance: ExternalBalance, + genesis_liquidity: ExternalBalance, + }, + /// Genesis liquidity added. + GenesisLiquidityTransferred { + /// The address transferred from. + from: SeraiAddress, + /// The address transferred to. + to: SeraiAddress, + /// The genesis liquidity transferred. + genesis_liquidity: ExternalBalance, }, /// Genesis liquidity removed. GenesisLiquidityRemoved { /// The account which removed the genesis liquidity. - origin: SeraiAddress, + from: SeraiAddress, /// The amount of genesis liquidity removed. - balance: ExternalBalance, + genesis_liquidity: ExternalBalance, }, } diff --git a/substrate/dex/Cargo.toml b/substrate/dex/Cargo.toml index f490cde0..272fd4e2 100644 --- a/substrate/dex/Cargo.toml +++ b/substrate/dex/Cargo.toml @@ -12,6 +12,9 @@ rust-version = "1.85" all-features = true rustdoc-args = ["--cfg", "docsrs"] +[package.metadata.cargo-machete] +ignored = ["scale"] + [lints] workspace = true @@ -23,8 +26,6 @@ sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-fea frame-system = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false } frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false } -substrate-median = { path = "../median", default-features = false } - serai-abi = { path = "../abi", default-features = false, features = ["substrate"] } serai-core-pallet = { path = "../core", default-features = false } serai-coins-pallet = { path = "../coins", default-features = false } @@ -45,8 +46,6 @@ std = [ "frame-system/std", "frame-support/std", - "substrate-median/std", - "serai-abi/std", "serai-core-pallet/std", "serai-coins-pallet/std", diff --git a/substrate/dex/src/lib.rs b/substrate/dex/src/lib.rs index dc433bd1..f8e0a372 100644 --- a/substrate/dex/src/lib.rs +++ b/substrate/dex/src/lib.rs @@ -78,6 +78,10 @@ mod pallet { } } + /// The minimum amount of liquidity allowed to be initially added. + /// + /// This should be sufficiently low it isn't inaccessible, yet sufficiently high that future + /// additions can be reasonably grained when their share of the new supply is calculated. const MINIMUM_LIQUIDITY: u64 = 1 << 16; #[pallet::call] diff --git a/substrate/genesis-liquidity/Cargo.toml b/substrate/genesis-liquidity/Cargo.toml index c491f557..ad91e29d 100644 --- a/substrate/genesis-liquidity/Cargo.toml +++ b/substrate/genesis-liquidity/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "serai-genesis-liquidity-pallet" version = "0.1.0" -description = "Genesis liquidity pallet for Serai" +description = "Genesis Liquidity pallet for Serai" license = "AGPL-3.0-only" repository = "https://github.com/serai-dex/serai/tree/develop/substrate/genesis-liquidity" -authors = ["Akil Demir "] +authors = ["Luke Parker "] edition = "2021" rust-version = "1.85" @@ -21,40 +21,48 @@ workspace = true [dependencies] scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false } + frame-system = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false } frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false } -sp-std = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false } -sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false } -sp-application-crypto = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false } - -dex-pallet = { package = "serai-dex-pallet", path = "../dex", default-features = false } -coins-pallet = { package = "serai-coins-pallet", path = "../coins", default-features = false } -validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets", default-features = false } - -economic-security-pallet = { package = "serai-economic-security-pallet", path = "../economic-security", default-features = false } - -serai-primitives = { path = "../primitives", default-features = false } +serai-abi = { path = "../abi", default-features = false, features = ["substrate"] } +serai-core-pallet = { path = "../core", default-features = false } +serai-coins-pallet = { path = "../coins", default-features = false } +serai-dex-pallet = { path = "../dex", default-features = false } [features] std = [ "scale/std", + "sp-core/std", + "frame-system/std", "frame-support/std", - "sp-std/std", - "sp-core/std", - "sp-application-crypto/std", - - "coins-pallet/std", - "dex-pallet/std", - "validator-sets-pallet/std", - - "economic-security-pallet/std", - - "serai-primitives/std", + "serai-abi/std", + "serai-core-pallet/std", + "serai-coins-pallet/std", + "serai-dex-pallet/std", +] + +try-runtime = [ + "frame-system/try-runtime", + "frame-support/try-runtime", + + "serai-abi/try-runtime", + "serai-core-pallet/try-runtime", + "serai-coins-pallet/try-runtime", + "serai-dex-pallet/try-runtime", +] + +runtime-benchmarks = [ + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", + + "serai-core-pallet/runtime-benchmarks", + "serai-coins-pallet/runtime-benchmarks", + "serai-dex-pallet/runtime-benchmarks", ] -try-runtime = [] # TODO default = ["std"] diff --git a/substrate/genesis-liquidity/LICENSE b/substrate/genesis-liquidity/LICENSE index c65849a7..621233a9 100644 --- a/substrate/genesis-liquidity/LICENSE +++ b/substrate/genesis-liquidity/LICENSE @@ -1,6 +1,6 @@ AGPL-3.0-only license -Copyright (c) 2024-2025 Luke Parker +Copyright (c) 2023-2025 Luke Parker This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License Version 3 as diff --git a/substrate/genesis-liquidity/README.md b/substrate/genesis-liquidity/README.md new file mode 100644 index 00000000..8950e66c --- /dev/null +++ b/substrate/genesis-liquidity/README.md @@ -0,0 +1,3 @@ +# Genesis Liquidity Pallet + +Pallet implementing the Serai protocol's genesis liquidity. diff --git a/substrate/genesis-liquidity/src/lib.rs b/substrate/genesis-liquidity/src/lib.rs index 2fd24589..21ef3e75 100644 --- a/substrate/genesis-liquidity/src/lib.rs +++ b/substrate/genesis-liquidity/src/lib.rs @@ -1,464 +1,102 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![doc = include_str!("../README.md")] +#![deny(missing_docs)] +#![cfg_attr(not(any(feature = "std", test)), no_std)] -#[allow( - unreachable_patterns, - clippy::cast_possible_truncation, - clippy::no_effect_underscore_binding, - clippy::empty_docs -)] +extern crate alloc; + +#[expect(clippy::cast_possible_truncation)] #[frame_support::pallet] -pub mod pallet { +mod pallet { + use frame_system::pallet_prelude::*; + use frame_support::pallet_prelude::*; + + use serai_abi::{ + primitives::{prelude::*, crypto::Signature, genesis_liquidity::GenesisValues}, + genesis_liquidity::Event, + }; + + use serai_core_pallet::Pallet as Core; + type Coins = serai_coins_pallet::Pallet; + type LiquidityTokens = + serai_coins_pallet::Pallet; + use super::*; - use frame_system::{pallet_prelude::*, RawOrigin}; - use frame_support::{pallet_prelude::*, sp_runtime::SaturatedConversion}; - - use sp_std::{vec, vec::Vec}; - use sp_core::sr25519::Signature; - use sp_application_crypto::RuntimePublic; - - use dex_pallet::{Pallet as Dex, Config as DexConfig}; - use coins_pallet::{Config as CoinsConfig, Pallet as Coins}; - use validator_sets_pallet::{Config as VsConfig, Pallet as ValidatorSets}; - - use economic_security_pallet::{Config as EconomicSecurityConfig, Pallet as EconomicSecurity}; - - use serai_primitives::*; - 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; + /// The configuration of this pallet. #[pallet::config] pub trait Config: frame_system::Config - + VsConfig - + DexConfig - + EconomicSecurityConfig - + CoinsConfig - + coins_pallet::Config + + serai_core_pallet::Config + + serai_coins_pallet::Config + + serai_coins_pallet::Config + + serai_dex_pallet::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; } + /// An error incurred. #[pallet::error] - pub enum Error { - GenesisPeriodEnded, - AmountOverflowed, - NotEnoughLiquidity, - CanOnlyRemoveFullAmount, - } - - #[pallet::event] - #[pallet::generate_deposit(fn deposit_event)] - pub enum Event { - GenesisLiquidityAdded { by: SeraiAddress, balance: ExternalBalance }, - GenesisLiquidityRemoved { by: SeraiAddress, balance: ExternalBalance }, - GenesisLiquidityAddedToPool { coin: ExternalBalance, sri: Amount }, - } + pub enum Error {} + /// The Pallet struct. #[pallet::pallet] - pub struct Pallet(PhantomData); - - /// Keeps shares and the amount of coins per account. - #[pallet::storage] - #[pallet::getter(fn liquidity)] - pub(crate) type Liquidity = StorageDoubleMap< - _, - Identity, - ExternalCoin, - Blake2_128Concat, - PublicKey, - LiquidityAmount, - OptionQuery, - >; - - /// Keeps the total shares and the total amount of coins per coin. - #[pallet::storage] - #[pallet::getter(fn supply)] - pub(crate) type Supply = - StorageMap<_, Identity, ExternalCoin, LiquidityAmount, OptionQuery>; - - #[pallet::storage] - pub(crate) type Oracle = StorageMap<_, Identity, ExternalCoin, u64, OptionQuery>; - - #[pallet::storage] - #[pallet::getter(fn genesis_complete_block)] - pub(crate) type GenesisCompleteBlock = StorageValue<_, u64, OptionQuery>; - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(n: BlockNumberFor) -> Weight { - #[cfg(feature = "fast-epoch")] - let final_block = 10u64; - - #[cfg(not(feature = "fast-epoch"))] - let final_block = MONTHS; - - // Distribute the genesis sri to pools after a month - if (n.saturated_into::() >= final_block) && - Self::oraclization_is_done() && - GenesisCompleteBlock::::get().is_none() - { - // mint the SRI - Coins::::mint( - GENESIS_LIQUIDITY_ACCOUNT.into(), - Balance { coin: Coin::Serai, amount: Amount(GENESIS_SRI) }, - ) - .unwrap(); - - // get pool & total values - let mut pool_values = vec![]; - let mut total_value: u128 = 0; - for coin in EXTERNAL_COINS { - // initial coin value in terms of btc - let Some(value) = Oracle::::get(coin) else { - continue; - }; - - let pool_amount = - u128::from(Supply::::get(coin).unwrap_or(LiquidityAmount::zero()).coins); - let pool_value = pool_amount - .checked_mul(value.into()) - .unwrap() - .checked_div(10u128.pow(coin.decimals())) - .unwrap(); - total_value = total_value.checked_add(pool_value).unwrap(); - pool_values.push((coin, pool_amount, pool_value)); - } - - // add the liquidity per pool - let mut total_sri_distributed = 0; - let pool_values_len = pool_values.len(); - for (i, (coin, pool_amount, pool_value)) in pool_values.into_iter().enumerate() { - // whatever sri left for the last coin should be ~= it's ratio - let sri_amount = if i == (pool_values_len - 1) { - GENESIS_SRI.checked_sub(total_sri_distributed).unwrap() - } else { - u64::try_from( - u128::from(GENESIS_SRI) - .checked_mul(pool_value) - .unwrap() - .checked_div(total_value) - .unwrap(), - ) - .unwrap() - }; - total_sri_distributed = total_sri_distributed.checked_add(sri_amount).unwrap(); - - // actually add the liquidity to dex - let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into()); - let Ok(()) = Dex::::add_liquidity( - origin.into(), - coin, - u64::try_from(pool_amount).unwrap(), - sri_amount, - u64::try_from(pool_amount).unwrap(), - sri_amount, - GENESIS_LIQUIDITY_ACCOUNT.into(), - ) else { - continue; - }; - - // let everyone know about the event - Self::deposit_event(Event::GenesisLiquidityAddedToPool { - coin: ExternalBalance { coin, amount: Amount(u64::try_from(pool_amount).unwrap()) }, - sri: Amount(sri_amount), - }); - } - assert_eq!(total_sri_distributed, GENESIS_SRI); - - // we shouldn't have left any coin in genesis account at this moment, including SRI. - // All transferred to the pools. - for coin in COINS { - assert_eq!(Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), coin), Amount(0)); - } - - GenesisCompleteBlock::::set(Some(n.saturated_into::())); - } - - Weight::zero() // TODO - } - } + pub struct Pallet(_); impl Pallet { - /// Add genesis liquidity for the given account. All accounts that provide liquidity - /// will receive the genesis SRI according to their liquidity ratio. - pub fn add_coin_liquidity(account: PublicKey, balance: ExternalBalance) -> DispatchResult { - // check we are still in genesis period - if Self::genesis_ended() { - Err(Error::::GenesisPeriodEnded)?; - } - - // calculate new shares & supply - let (new_liquidity, new_supply) = if let Some(supply) = Supply::::get(balance.coin) { - // calculate amount of shares for this amount - let shares = Self::mul_div(supply.shares, balance.amount.0, supply.coins)?; - - // get new shares for this account - let existing = - Liquidity::::get(balance.coin, account).unwrap_or(LiquidityAmount::zero()); - ( - LiquidityAmount { - shares: existing.shares.checked_add(shares).ok_or(Error::::AmountOverflowed)?, - coins: existing - .coins - .checked_add(balance.amount.0) - .ok_or(Error::::AmountOverflowed)?, - }, - LiquidityAmount { - shares: supply.shares.checked_add(shares).ok_or(Error::::AmountOverflowed)?, - coins: supply - .coins - .checked_add(balance.amount.0) - .ok_or(Error::::AmountOverflowed)?, - }, - ) - } else { - let first_amount = - LiquidityAmount { shares: INITIAL_GENESIS_LP_SHARES, coins: balance.amount.0 }; - (first_amount, first_amount) - }; - - // save - Liquidity::::set(balance.coin, account, Some(new_liquidity)); - Supply::::set(balance.coin, Some(new_supply)); - Self::deposit_event(Event::GenesisLiquidityAdded { by: account.into(), balance }); - Ok(()) + fn emit_event(event: Event) { + Core::::emit_event(event) } + } - /// Returns the number of blocks since the all networks reached economic security first time. - /// If networks is yet to be reached that threshold, None is returned. - fn blocks_since_ec_security() -> Option { - let mut min = u64::MAX; - for n in EXTERNAL_NETWORKS { - let ec_security_block = - EconomicSecurity::::economic_security_block(n)?.saturated_into::(); - let current = >::block_number().saturated_into::(); - let diff = current.saturating_sub(ec_security_block); - min = diff.min(min); - } - Some(min) - } + /// The minimum amount of liquidity allowed to be initially added. + /// + /// This should be sufficiently low it isn't inaccessible, yet sufficiently high that future + /// additions can be reasonably grained when their share of the new supply is calculated. + /// + /// This constant is duplicated with `serai-dex-pallet` intentionally as while they have the same + /// value, they are distinct constants and don't require being equivalent. + const MINIMUM_LIQUIDITY: u64 = 1 << 16; - fn genesis_ended() -> bool { - Self::oraclization_is_done() && - >::block_number().saturated_into::() >= MONTHS - } - - fn oraclization_is_done() -> bool { - for c in EXTERNAL_COINS { - if Oracle::::get(c).is_none() { - return false; - } - } - - true - } - - fn mul_div(a: u64, b: u64, c: u64) -> Result> { - let a = u128::from(a); - let b = u128::from(b); - let c = u128::from(c); - - let result = a - .checked_mul(b) - .ok_or(Error::::AmountOverflowed)? - .checked_div(c) - .ok_or(Error::::AmountOverflowed)?; - - result.try_into().map_err(|_| Error::::AmountOverflowed) + impl Pallet { + /// Add liquidity on behalf of the specified address. + pub fn add_liquidity(to: SeraiAddress, balance: ExternalBalance) -> Result<(), Error> { + todo!("TODO") } } #[pallet::call] impl Pallet { - /// Remove the provided genesis liquidity for an account. + /// Oraclize the values of the coins available on genesis, relative to BTC. + /// + /// This will trigger the addition of the liquidity into the pools and their initialization. #[pallet::call_index(0)] - #[pallet::weight((0, DispatchClass::Operational))] // TODO - pub fn remove_coin_liquidity(origin: OriginFor, balance: ExternalBalance) -> DispatchResult { - let account = ensure_signed(origin)?; - let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into()); - let supply = Supply::::get(balance.coin).ok_or(Error::::NotEnoughLiquidity)?; - - // check we are still in genesis period - let (new_liquidity, new_supply) = if Self::genesis_ended() { - // see how much liq tokens we have - let total_liq_tokens = - LiquidityTokens::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai).0; - - // get how much user wants to remove - let LiquidityAmount { shares, coins } = - 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, INITIAL_GENESIS_LP_SHARES)?; - - // remove liquidity from pool - let prev_sri = Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai); - let prev_coin = Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin.into()); - Dex::::remove_liquidity( - origin.clone().into(), - balance.coin, - amount_to_remove, - 1, - 1, - GENESIS_LIQUIDITY_ACCOUNT.into(), - )?; - let current_sri = Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai); - let current_coin = - Coins::::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin.into()); - - // burn the SRI if necessary - // TODO: take into consideration movement between pools. - let mut sri: u64 = current_sri.0.saturating_sub(prev_sri.0); - let distance_to_full_pay = - GENESIS_SRI_TRICKLE_FEED.saturating_sub(Self::blocks_since_ec_security().unwrap_or(0)); - let burn_sri_amount = u64::try_from( - u128::from(sri) - .checked_mul(u128::from(distance_to_full_pay)) - .ok_or(Error::::AmountOverflowed)? - .checked_div(u128::from(GENESIS_SRI_TRICKLE_FEED)) - .ok_or(Error::::AmountOverflowed)?, - ) - .map_err(|_| Error::::AmountOverflowed)?; - Coins::::burn( - origin.clone().into(), - Balance { coin: Coin::Serai, amount: Amount(burn_sri_amount) }, - )?; - sri = sri.checked_sub(burn_sri_amount).ok_or(Error::::AmountOverflowed)?; - - // transfer to owner - let coin_out = current_coin.0.saturating_sub(prev_coin.0); - Coins::::transfer( - origin.clone().into(), - account, - Balance { coin: balance.coin.into(), amount: Amount(coin_out) }, - )?; - Coins::::transfer( - origin.into(), - account, - Balance { coin: Coin::Serai, amount: Amount(sri) }, - )?; - - // return new amounts - ( - LiquidityAmount { - shares: shares.checked_sub(amount_to_remove).ok_or(Error::::AmountOverflowed)?, - coins: coins.checked_sub(coin_out).ok_or(Error::::AmountOverflowed)?, - }, - LiquidityAmount { - shares: supply - .shares - .checked_sub(amount_to_remove) - .ok_or(Error::::AmountOverflowed)?, - coins: supply.coins.checked_sub(coin_out).ok_or(Error::::AmountOverflowed)?, - }, - ) - } else { - if balance.amount.0 != INITIAL_GENESIS_LP_SHARES { - Err(Error::::CanOnlyRemoveFullAmount)?; - } - let existing = - Liquidity::::get(balance.coin, account).ok_or(Error::::NotEnoughLiquidity)?; - - // transfer to the user - Coins::::transfer( - origin.into(), - account, - Balance { coin: balance.coin.into(), amount: Amount(existing.coins) }, - )?; - - ( - LiquidityAmount::zero(), - LiquidityAmount { - shares: supply - .shares - .checked_sub(existing.shares) - .ok_or(Error::::AmountOverflowed)?, - coins: supply.coins.checked_sub(existing.coins).ok_or(Error::::AmountOverflowed)?, - }, - ) - }; - - // save - if new_liquidity == LiquidityAmount::zero() { - Liquidity::::set(balance.coin, account, None); - } else { - Liquidity::::set(balance.coin, account, Some(new_liquidity)); - } - Supply::::set(balance.coin, Some(new_supply)); - - Self::deposit_event(Event::GenesisLiquidityRemoved { by: account.into(), balance }); - Ok(()) - } - - /// A call to submit the initial coin values in terms of BTC. - #[pallet::call_index(1)] - #[pallet::weight((0, DispatchClass::Operational))] // TODO + #[pallet::weight((0, DispatchClass::Normal))] // TODO pub fn oraclize_values( origin: OriginFor, - values: Values, - _signature: Signature, + values: GenesisValues, + signature: Signature, ) -> DispatchResult { - ensure_none(origin)?; - - // set their relative values - Oracle::::set(ExternalCoin::Bitcoin, Some(10u64.pow(ExternalCoin::Bitcoin.decimals()))); - Oracle::::set(ExternalCoin::Monero, Some(values.monero)); - Oracle::::set(ExternalCoin::Ether, Some(values.ether)); - Oracle::::set(ExternalCoin::Dai, Some(values.dai)); - Ok(()) + todo!("TODO") } - } - #[pallet::validate_unsigned] - impl ValidateUnsigned for Pallet { - type Call = Call; + /// Transfer genesis liquidity. + #[pallet::call_index(1)] + #[pallet::weight((0, DispatchClass::Normal))] // TODO + pub fn transfer_genesis_liquidity( + origin: OriginFor, + to: SeraiAddress, + genesis_liquidity: ExternalBalance, + ) -> DispatchResult { + todo!("TODO") + } - fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity { - match call { - 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))); - }; - - let set = ValidatorSet { network, session }; - let signers = ValidatorSets::::participants_for_latest_decided_set(network) - .expect("no participant in the current set") - .into_iter() - .map(|(p, _)| p) - .collect::>(); - - // check this didn't get called before - if Self::oraclization_is_done() { - Err(InvalidTransaction::Custom(1))?; - } - - // 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, values), signature) { - Err(InvalidTransaction::BadProof)?; - } - - ValidTransaction::with_tag_prefix("GenesisLiquidity") - .and_provides((0, set)) - .longevity(u64::MAX) - .propagate(true) - .build() - } - Call::remove_coin_liquidity { .. } => Err(InvalidTransaction::Call)?, - Call::__Ignore(_, _) => unreachable!(), - } + /// Remove genesis liquidity. + #[pallet::call_index(2)] + #[pallet::weight((0, DispatchClass::Normal))] // TODO + pub fn remove_genesis_liquidity( + origin: OriginFor, + genesis_liquidity: ExternalBalance, + ) -> DispatchResult { + todo!("TODO") } } } diff --git a/substrate/primitives/src/genesis.rs b/substrate/primitives/src/genesis_liquidity.rs similarity index 82% rename from substrate/primitives/src/genesis.rs rename to substrate/primitives/src/genesis_liquidity.rs index 4ff4e3da..2a5f8191 100644 --- a/substrate/primitives/src/genesis.rs +++ b/substrate/primitives/src/genesis_liquidity.rs @@ -7,6 +7,10 @@ use crate::balance::Amount; /// The value of non-Bitcoin externals coins present at genesis, relative to Bitcoin. #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)] +#[cfg_attr( + feature = "non_canonical_scale_derivations", + derive(scale::Encode, scale::Decode, scale::MaxEncodedLen, scale::DecodeWithMemTracking) +)] pub struct GenesisValues { /// The value of Ether, relative to Bitcoin. pub ether: Amount, diff --git a/substrate/primitives/src/lib.rs b/substrate/primitives/src/lib.rs index 30b1b87f..af4b47d6 100644 --- a/substrate/primitives/src/lib.rs +++ b/substrate/primitives/src/lib.rs @@ -29,8 +29,8 @@ pub mod coin; /// The `Amount`, `ExternalBalance`, and `Balance` types. pub mod balance; -/// Types for genesis. -pub mod genesis; +/// Types for the genesis liquidity functionality. +pub mod genesis_liquidity; /// Types for identifying networks and their properties. pub mod network_id; diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index 921f0d30..7a7b2a85 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -54,6 +54,7 @@ serai-coins-pallet = { path = "../coins", default-features = false } serai-validator-sets-pallet = { path = "../validator-sets", default-features = false } serai-signals-pallet = { path = "../signals", default-features = false } serai-dex-pallet = { path = "../dex", default-features = false } +serai-genesis-liquidity-pallet = { path = "../genesis-liquidity", default-features = false } [build-dependencies] substrate-wasm-builder = { git = "https://github.com/serai-dex/patch-polkadot-sdk" } @@ -90,6 +91,7 @@ std = [ "serai-validator-sets-pallet/std", "serai-signals-pallet/std", "serai-dex-pallet/std", + "serai-genesis-liquidity-pallet/std", ] try-runtime = [ @@ -110,6 +112,7 @@ try-runtime = [ "serai-validator-sets-pallet/try-runtime", "serai-signals-pallet/try-runtime", "serai-dex-pallet/try-runtime", + "serai-genesis-liquidity-pallet/try-runtime", ] runtime-benchmarks = [ @@ -127,6 +130,7 @@ runtime-benchmarks = [ "serai-validator-sets-pallet/runtime-benchmarks", "serai-signals-pallet/runtime-benchmarks", "serai-dex-pallet/runtime-benchmarks", + "serai-genesis-liquidity-pallet/runtime-benchmarks", ] default = ["std"] diff --git a/substrate/runtime/src/wasm/mod.rs b/substrate/runtime/src/wasm/mod.rs index a3d12445..d862bcc7 100644 --- a/substrate/runtime/src/wasm/mod.rs +++ b/substrate/runtime/src/wasm/mod.rs @@ -96,6 +96,9 @@ mod runtime { #[runtime::pallet_index(6)] pub type Dex = serai_dex_pallet::Pallet; + #[runtime::pallet_index(7)] + pub type GenesisLiquidity = serai_genesis_liquidity_pallet::Pallet; + #[runtime::pallet_index(0xfd)] #[runtime::disable_inherent] pub type Timestamp = pallet_timestamp::Pallet; @@ -174,6 +177,7 @@ impl serai_coins_pallet::Config for Runtime { type AllowMint = serai_coins_pallet::AlwaysAllowMint; } impl serai_dex_pallet::Config for Runtime {} +impl serai_genesis_liquidity_pallet::Config for Runtime {} impl pallet_timestamp::Config for Runtime { type Moment = u64; @@ -329,7 +333,23 @@ impl From for RuntimeCall { serai_abi::Call::GenesisLiquidity(call) => { use serai_abi::genesis_liquidity::Call; match call { - Call::oraclize_values { .. } | Call::remove_liquidity { .. } => todo!("TODO"), + Call::oraclize_values { values, signature } => { + RuntimeCall::GenesisLiquidity(serai_genesis_liquidity_pallet::Call::oraclize_values { + values, + signature, + }) + } + Call::transfer_genesis_liquidity { to, genesis_liquidity } => { + RuntimeCall::GenesisLiquidity( + serai_genesis_liquidity_pallet::Call::transfer_genesis_liquidity { + to, + genesis_liquidity, + }, + ) + } + Call::remove_genesis_liquidity { genesis_liquidity } => RuntimeCall::GenesisLiquidity( + serai_genesis_liquidity_pallet::Call::remove_genesis_liquidity { genesis_liquidity }, + ), } } serai_abi::Call::InInstructions(call) => {