mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Implement genesis liquidity protocol (#545)
* add genesis liquidity implementation * add missing deposit event * fix CI issues * minor fixes * make math safer * fix fmt * make remove liquidity an authorized call * implement setting initial values for coins * add genesis liquidity test & misc fixes * updato develop latest * fix rotation test * Finish merging develop * Remove accidentally committed ETH files * fix pr comments * further bug fixes * fix last pr comments * tidy up * Misc --------- Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
This commit is contained in:
63
substrate/genesis-liquidity/pallet/Cargo.toml
Normal file
63
substrate/genesis-liquidity/pallet/Cargo.toml
Normal file
@@ -0,0 +1,63 @@
|
||||
[package]
|
||||
name = "serai-genesis-liquidity-pallet"
|
||||
version = "0.1.0"
|
||||
description = "Genesis liquidity pallet for Serai"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/genesis-liquidity/pallet"
|
||||
authors = ["Akil Demir <aeg_asd@hotmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.77"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["scale", "scale-info"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false }
|
||||
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 }
|
||||
|
||||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
genesis-liquidity-primitives = { package = "serai-genesis-liquidity-primitives", path = "../primitives", default-features = false }
|
||||
validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../validator-sets/primitives", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"scale/std",
|
||||
"scale-info/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",
|
||||
|
||||
"serai-primitives/std",
|
||||
"genesis-liquidity-primitives/std",
|
||||
"validator-sets-primitives/std",
|
||||
]
|
||||
try-runtime = [] # TODO
|
||||
fast-epoch = []
|
||||
|
||||
default = ["std"]
|
||||
15
substrate/genesis-liquidity/pallet/LICENSE
Normal file
15
substrate/genesis-liquidity/pallet/LICENSE
Normal file
@@ -0,0 +1,15 @@
|
||||
AGPL-3.0-only license
|
||||
|
||||
Copyright (c) 2024 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
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
470
substrate/genesis-liquidity/pallet/src/lib.rs
Normal file
470
substrate/genesis-liquidity/pallet/src/lib.rs
Normal file
@@ -0,0 +1,470 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[allow(clippy::cast_possible_truncation, clippy::no_effect_underscore_binding)]
|
||||
#[frame_support::pallet]
|
||||
pub mod 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, AllowMint};
|
||||
use validator_sets_pallet::{Config as VsConfig, Pallet as ValidatorSets};
|
||||
|
||||
use serai_primitives::{Coin, COINS, *};
|
||||
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<T> = coins_pallet::Pallet<T, coins_pallet::Instance1>;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config:
|
||||
frame_system::Config
|
||||
+ VsConfig
|
||||
+ DexConfig
|
||||
+ CoinsConfig
|
||||
+ coins_pallet::Config<coins_pallet::Instance1>
|
||||
{
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
GenesisPeriodEnded,
|
||||
AmountOverflowed,
|
||||
NotEnoughLiquidity,
|
||||
CanOnlyRemoveFullAmount,
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
GenesisLiquidityAdded { by: SeraiAddress, balance: Balance },
|
||||
GenesisLiquidityRemoved { by: SeraiAddress, balance: Balance },
|
||||
GenesisLiquidityAddedToPool { coin1: Balance, sri: Amount },
|
||||
EconomicSecurityReached { network: NetworkId },
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(PhantomData<T>);
|
||||
|
||||
/// Keeps shares and the amount of coins per account.
|
||||
#[pallet::storage]
|
||||
pub(crate) type Liquidity<T: Config> =
|
||||
StorageDoubleMap<_, Identity, Coin, Blake2_128Concat, PublicKey, LiquidityAmount, OptionQuery>;
|
||||
|
||||
/// Keeps the total shares and the total amount of coins per coin.
|
||||
#[pallet::storage]
|
||||
pub(crate) type Supply<T: Config> = StorageMap<_, Identity, Coin, LiquidityAmount, OptionQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub(crate) type EconomicSecurityReached<T: Config> =
|
||||
StorageMap<_, Identity, NetworkId, BlockNumberFor<T>, OptionQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub(crate) type Oracle<T: Config> = StorageMap<_, Identity, Coin, u64, OptionQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub(crate) type GenesisComplete<T: Config> = StorageValue<_, (), OptionQuery>;
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_initialize(n: BlockNumberFor<T>) -> 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::<u64>() >= final_block) &&
|
||||
Self::oraclization_is_done() &&
|
||||
GenesisComplete::<T>::get().is_none()
|
||||
{
|
||||
// mint the SRI
|
||||
Coins::<T>::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 COINS {
|
||||
if coin == Coin::Serai {
|
||||
continue;
|
||||
}
|
||||
|
||||
// initial coin value in terms of btc
|
||||
let Some(value) = Oracle::<T>::get(coin) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let pool_amount =
|
||||
u128::from(Supply::<T>::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::<T>::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 {
|
||||
coin1: Balance { 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::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), coin), Amount(0));
|
||||
}
|
||||
|
||||
GenesisComplete::<T>::set(Some(()));
|
||||
}
|
||||
|
||||
// we accept we reached economic security once we can mint smallest amount of a network's coin
|
||||
// TODO: move EconomicSecurity to a separate pallet
|
||||
for coin in COINS {
|
||||
let existing = EconomicSecurityReached::<T>::get(coin.network());
|
||||
if existing.is_none() &&
|
||||
<T as CoinsConfig>::AllowMint::is_allowed(&Balance { coin, amount: Amount(1) })
|
||||
{
|
||||
EconomicSecurityReached::<T>::set(coin.network(), Some(n));
|
||||
Self::deposit_event(Event::EconomicSecurityReached { network: coin.network() });
|
||||
}
|
||||
}
|
||||
|
||||
Weight::zero() // TODO
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// 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: Balance) -> DispatchResult {
|
||||
// check we are still in genesis period
|
||||
if Self::genesis_ended() {
|
||||
Err(Error::<T>::GenesisPeriodEnded)?;
|
||||
}
|
||||
|
||||
// calculate new shares & supply
|
||||
let (new_liquidity, new_supply) = if let Some(supply) = Supply::<T>::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::<T>::get(balance.coin, account).unwrap_or(LiquidityAmount::zero());
|
||||
(
|
||||
LiquidityAmount {
|
||||
shares: existing.shares.checked_add(shares).ok_or(Error::<T>::AmountOverflowed)?,
|
||||
coins: existing
|
||||
.coins
|
||||
.checked_add(balance.amount.0)
|
||||
.ok_or(Error::<T>::AmountOverflowed)?,
|
||||
},
|
||||
LiquidityAmount {
|
||||
shares: supply.shares.checked_add(shares).ok_or(Error::<T>::AmountOverflowed)?,
|
||||
coins: supply
|
||||
.coins
|
||||
.checked_add(balance.amount.0)
|
||||
.ok_or(Error::<T>::AmountOverflowed)?,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
let first_amount =
|
||||
LiquidityAmount { shares: INITIAL_GENESIS_LP_SHARES, coins: balance.amount.0 };
|
||||
(first_amount, first_amount)
|
||||
};
|
||||
|
||||
// save
|
||||
Liquidity::<T>::set(balance.coin, account, Some(new_liquidity));
|
||||
Supply::<T>::set(balance.coin, Some(new_supply));
|
||||
Self::deposit_event(Event::GenesisLiquidityAdded { by: account.into(), balance });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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<u64> {
|
||||
let mut min = u64::MAX;
|
||||
for n in NETWORKS {
|
||||
let ec_security_block = EconomicSecurityReached::<T>::get(n)?.saturated_into::<u64>();
|
||||
let current = <frame_system::Pallet<T>>::block_number().saturated_into::<u64>();
|
||||
let diff = current.saturating_sub(ec_security_block);
|
||||
min = diff.min(min);
|
||||
}
|
||||
Some(min)
|
||||
}
|
||||
|
||||
fn genesis_ended() -> bool {
|
||||
Self::oraclization_is_done() &&
|
||||
<frame_system::Pallet<T>>::block_number().saturated_into::<u64>() >= MONTHS
|
||||
}
|
||||
|
||||
fn oraclization_is_done() -> bool {
|
||||
for c in COINS {
|
||||
if c == Coin::Serai {
|
||||
continue;
|
||||
}
|
||||
|
||||
if Oracle::<T>::get(c).is_none() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn mul_div(a: u64, b: u64, c: u64) -> Result<u64, Error<T>> {
|
||||
let a = u128::from(a);
|
||||
let b = u128::from(b);
|
||||
let c = u128::from(c);
|
||||
|
||||
let result = a
|
||||
.checked_mul(b)
|
||||
.ok_or(Error::<T>::AmountOverflowed)?
|
||||
.checked_div(c)
|
||||
.ok_or(Error::<T>::AmountOverflowed)?;
|
||||
|
||||
result.try_into().map_err(|_| Error::<T>::AmountOverflowed)
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Remove the provided genesis liquidity for an account.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
||||
pub fn remove_coin_liquidity(origin: OriginFor<T>, balance: Balance) -> DispatchResult {
|
||||
let account = ensure_signed(origin)?;
|
||||
let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into());
|
||||
let supply = Supply::<T>::get(balance.coin).ok_or(Error::<T>::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::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai).0;
|
||||
|
||||
// get how much user wants to remove
|
||||
let LiquidityAmount { shares, coins } =
|
||||
Liquidity::<T>::get(balance.coin, account).unwrap_or(LiquidityAmount::zero());
|
||||
let total_shares = Supply::<T>::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::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai);
|
||||
let prev_coin = Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin);
|
||||
Dex::<T>::remove_liquidity(
|
||||
origin.clone().into(),
|
||||
balance.coin,
|
||||
amount_to_remove,
|
||||
1,
|
||||
1,
|
||||
GENESIS_LIQUIDITY_ACCOUNT.into(),
|
||||
)?;
|
||||
let current_sri = Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai);
|
||||
let current_coin = Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin);
|
||||
|
||||
// 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::<T>::AmountOverflowed)?
|
||||
.checked_div(u128::from(GENESIS_SRI_TRICKLE_FEED))
|
||||
.ok_or(Error::<T>::AmountOverflowed)?,
|
||||
)
|
||||
.map_err(|_| Error::<T>::AmountOverflowed)?;
|
||||
Coins::<T>::burn(
|
||||
origin.clone().into(),
|
||||
Balance { coin: Coin::Serai, amount: Amount(burn_sri_amount) },
|
||||
)?;
|
||||
sri = sri.checked_sub(burn_sri_amount).ok_or(Error::<T>::AmountOverflowed)?;
|
||||
|
||||
// transfer to owner
|
||||
let coin_out = current_coin.0.saturating_sub(prev_coin.0);
|
||||
Coins::<T>::transfer(
|
||||
origin.clone().into(),
|
||||
account,
|
||||
Balance { coin: balance.coin, amount: Amount(coin_out) },
|
||||
)?;
|
||||
Coins::<T>::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::<T>::AmountOverflowed)?,
|
||||
coins: coins.checked_sub(coin_out).ok_or(Error::<T>::AmountOverflowed)?,
|
||||
},
|
||||
LiquidityAmount {
|
||||
shares: supply
|
||||
.shares
|
||||
.checked_sub(amount_to_remove)
|
||||
.ok_or(Error::<T>::AmountOverflowed)?,
|
||||
coins: supply.coins.checked_sub(coin_out).ok_or(Error::<T>::AmountOverflowed)?,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
if balance.amount.0 != INITIAL_GENESIS_LP_SHARES {
|
||||
Err(Error::<T>::CanOnlyRemoveFullAmount)?;
|
||||
}
|
||||
let existing =
|
||||
Liquidity::<T>::get(balance.coin, account).ok_or(Error::<T>::NotEnoughLiquidity)?;
|
||||
|
||||
// transfer to the user
|
||||
Coins::<T>::transfer(
|
||||
origin.into(),
|
||||
account,
|
||||
Balance { coin: balance.coin, amount: Amount(existing.coins) },
|
||||
)?;
|
||||
|
||||
(
|
||||
LiquidityAmount::zero(),
|
||||
LiquidityAmount {
|
||||
shares: supply
|
||||
.shares
|
||||
.checked_sub(existing.shares)
|
||||
.ok_or(Error::<T>::AmountOverflowed)?,
|
||||
coins: supply.coins.checked_sub(existing.coins).ok_or(Error::<T>::AmountOverflowed)?,
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
// save
|
||||
if new_liquidity == LiquidityAmount::zero() {
|
||||
Liquidity::<T>::set(balance.coin, account, None);
|
||||
} else {
|
||||
Liquidity::<T>::set(balance.coin, account, Some(new_liquidity));
|
||||
}
|
||||
Supply::<T>::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
|
||||
pub fn oraclize_values(
|
||||
origin: OriginFor<T>,
|
||||
values: Values,
|
||||
_signature: Signature,
|
||||
) -> DispatchResult {
|
||||
ensure_none(origin)?;
|
||||
|
||||
// set their relative values
|
||||
Oracle::<T>::set(Coin::Bitcoin, Some(10u64.pow(Coin::Bitcoin.decimals())));
|
||||
Oracle::<T>::set(Coin::Monero, Some(values.monero));
|
||||
Oracle::<T>::set(Coin::Ether, Some(values.ether));
|
||||
Oracle::<T>::set(Coin::Dai, Some(values.dai));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::validate_unsigned]
|
||||
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
||||
type Call = Call<T>;
|
||||
|
||||
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::<T>::session(network) else {
|
||||
return Err(TransactionValidityError::from(InvalidTransaction::Custom(0)));
|
||||
};
|
||||
|
||||
let set = ValidatorSet { network, session };
|
||||
let signers = ValidatorSets::<T>::participants_for_latest_decided_set(network)
|
||||
.expect("no participant in the current set")
|
||||
.into_iter()
|
||||
.map(|(p, _)| p)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// 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 <frame_system::Pallet<T>>::block_number().saturated_into::<u64>() < 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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use pallet::*;
|
||||
45
substrate/genesis-liquidity/primitives/Cargo.toml
Normal file
45
substrate/genesis-liquidity/primitives/Cargo.toml
Normal file
@@ -0,0 +1,45 @@
|
||||
[package]
|
||||
name = "serai-genesis-liquidity-primitives"
|
||||
version = "0.1.0"
|
||||
description = "Serai genesis liquidity primitives"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/genesis-liquidity/primitives"
|
||||
authors = ["Akil Demir <aeg_asd@hotmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.77"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
zeroize = { version = "^1.5", features = ["derive"], optional = true }
|
||||
|
||||
borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"], optional = true }
|
||||
serde = { version = "1", default-features = false, features = ["derive", "alloc"], optional = true }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", 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 }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"zeroize",
|
||||
"scale/std",
|
||||
"borsh?/std",
|
||||
"serde?/std",
|
||||
"scale-info/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
"validator-sets-primitives/std",
|
||||
|
||||
"sp-std/std"
|
||||
]
|
||||
default = ["std"]
|
||||
21
substrate/genesis-liquidity/primitives/LICENSE
Normal file
21
substrate/genesis-liquidity/primitives/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Luke Parker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
54
substrate/genesis-liquidity/primitives/src/lib.rs
Normal file
54
substrate/genesis-liquidity/primitives/src/lib.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
use serai_primitives::*;
|
||||
use validator_sets_primitives::ValidatorSet;
|
||||
|
||||
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"GenesisLiquidity-account");
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Values {
|
||||
pub monero: u64,
|
||||
pub ether: u64,
|
||||
pub dai: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LiquidityAmount {
|
||||
pub shares: u64,
|
||||
pub coins: u64,
|
||||
}
|
||||
|
||||
impl LiquidityAmount {
|
||||
pub fn zero() -> Self {
|
||||
LiquidityAmount { shares: 0, coins: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
/// The message for the oraclize_values signature.
|
||||
pub fn oraclize_values_message(set: &ValidatorSet, values: &Values) -> Vec<u8> {
|
||||
(b"GenesisLiquidity-oraclize_values", set, values).encode()
|
||||
}
|
||||
Reference in New Issue
Block a user