mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-22 19:49:22 +00:00
Implement genesis liquidity protocol (#545)
Some checks failed
Full Stack Tests / build (push) Waiting to run
Lint / fmt (push) Waiting to run
Lint / machete (push) Waiting to run
Lint / clippy (macos-13) (push) Waiting to run
Lint / clippy (macos-14) (push) Waiting to run
Lint / clippy (ubuntu-latest) (push) Waiting to run
Lint / clippy (windows-latest) (push) Waiting to run
Lint / deny (push) Waiting to run
Reproducible Runtime / build (push) Has been cancelled
Tests / test-infra (push) Has been cancelled
Tests / test-substrate (push) Has been cancelled
Tests / test-serai-client (push) Has been cancelled
Some checks failed
Full Stack Tests / build (push) Waiting to run
Lint / fmt (push) Waiting to run
Lint / machete (push) Waiting to run
Lint / clippy (macos-13) (push) Waiting to run
Lint / clippy (macos-14) (push) Waiting to run
Lint / clippy (ubuntu-latest) (push) Waiting to run
Lint / clippy (windows-latest) (push) Waiting to run
Lint / deny (push) Waiting to run
Reproducible Runtime / build (push) Has been cancelled
Tests / test-infra (push) Has been cancelled
Tests / test-substrate (push) Has been cancelled
Tests / test-serai-client (push) Has been cancelled
* 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:
parent
2ccb0cd90d
commit
1493f49416
28 changed files with 1287 additions and 43 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -7929,6 +7929,7 @@ dependencies = [
|
|||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serai-coins-primitives",
|
||||
"serai-genesis-liquidity-primitives",
|
||||
"serai-in-instructions-primitives",
|
||||
"serai-primitives",
|
||||
"serai-signals-primitives",
|
||||
|
@ -8127,6 +8128,39 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serai-genesis-liquidity-pallet"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serai-coins-pallet",
|
||||
"serai-dex-pallet",
|
||||
"serai-genesis-liquidity-primitives",
|
||||
"serai-primitives",
|
||||
"serai-validator-sets-pallet",
|
||||
"serai-validator-sets-primitives",
|
||||
"sp-application-crypto",
|
||||
"sp-core",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serai-genesis-liquidity-primitives"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"borsh",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serai-primitives",
|
||||
"serai-validator-sets-primitives",
|
||||
"serde",
|
||||
"sp-std",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serai-in-instructions-pallet"
|
||||
version = "0.1.0"
|
||||
|
@ -8137,6 +8171,8 @@ dependencies = [
|
|||
"scale-info",
|
||||
"serai-coins-pallet",
|
||||
"serai-dex-pallet",
|
||||
"serai-genesis-liquidity-pallet",
|
||||
"serai-genesis-liquidity-primitives",
|
||||
"serai-in-instructions-primitives",
|
||||
"serai-primitives",
|
||||
"serai-validator-sets-pallet",
|
||||
|
@ -8406,6 +8442,7 @@ dependencies = [
|
|||
"serai-abi",
|
||||
"serai-coins-pallet",
|
||||
"serai-dex-pallet",
|
||||
"serai-genesis-liquidity-pallet",
|
||||
"serai-in-instructions-pallet",
|
||||
"serai-primitives",
|
||||
"serai-signals-pallet",
|
||||
|
|
|
@ -53,6 +53,8 @@ exceptions = [
|
|||
{ allow = ["AGPL-3.0"], name = "serai-coins-pallet" },
|
||||
{ allow = ["AGPL-3.0"], name = "serai-dex-pallet" },
|
||||
|
||||
{ allow = ["AGPL-3.0"], name = "serai-genesis-liquidity-pallet" },
|
||||
|
||||
{ allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" },
|
||||
|
||||
{ allow = ["AGPL-3.0"], name = "serai-validator-sets-pallet" },
|
||||
|
|
|
@ -33,6 +33,7 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur
|
|||
serai-primitives = { path = "../primitives", version = "0.1", default-features = false }
|
||||
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-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 }
|
||||
|
||||
|
@ -55,6 +56,7 @@ std = [
|
|||
"serai-primitives/std",
|
||||
"serai-coins-primitives/std",
|
||||
"serai-validator-sets-primitives/std",
|
||||
"serai-genesis-liquidity-primitives/std",
|
||||
"serai-in-instructions-primitives/std",
|
||||
"serai-signals-primitives/std",
|
||||
]
|
||||
|
@ -63,6 +65,7 @@ borsh = [
|
|||
"serai-primitives/borsh",
|
||||
"serai-coins-primitives/borsh",
|
||||
"serai-validator-sets-primitives/borsh",
|
||||
"serai-genesis-liquidity-primitives/borsh",
|
||||
"serai-in-instructions-primitives/borsh",
|
||||
"serai-signals-primitives/borsh",
|
||||
]
|
||||
|
@ -71,6 +74,7 @@ serde = [
|
|||
"serai-primitives/serde",
|
||||
"serai-coins-primitives/serde",
|
||||
"serai-validator-sets-primitives/serde",
|
||||
"serai-genesis-liquidity-primitives/serde",
|
||||
"serai-in-instructions-primitives/serde",
|
||||
"serai-signals-primitives/serde",
|
||||
]
|
||||
|
|
|
@ -13,15 +13,6 @@ pub enum Call {
|
|||
burn_with_instruction { instruction: OutInstructionWithBalance },
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
pub enum LiquidityTokensCall {
|
||||
transfer { to: SeraiAddress, balance: Balance },
|
||||
burn { balance: Balance },
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
|
|
21
substrate/abi/src/genesis_liquidity.rs
Normal file
21
substrate/abi/src/genesis_liquidity.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
pub use serai_genesis_liquidity_primitives as primitives;
|
||||
|
||||
use serai_primitives::*;
|
||||
use primitives::*;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Call {
|
||||
remove_coin_liquidity { balance: Balance },
|
||||
oraclize_values { values: Values, signature: Signature },
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Event {
|
||||
GenesisLiquidityAdded { by: SeraiAddress, balance: Balance },
|
||||
GenesisLiquidityRemoved { by: SeraiAddress, balance: Balance },
|
||||
GenesisLiquidityAddedToPool { coin1: Balance, coin2: Balance },
|
||||
EconomicSecurityReached { network: NetworkId },
|
||||
}
|
|
@ -12,12 +12,15 @@ pub mod system;
|
|||
pub mod timestamp;
|
||||
|
||||
pub mod coins;
|
||||
pub mod liquidity_tokens;
|
||||
pub mod dex;
|
||||
|
||||
pub mod validator_sets;
|
||||
pub mod in_instructions;
|
||||
pub mod signals;
|
||||
|
||||
pub mod genesis_liquidity;
|
||||
|
||||
pub mod babe;
|
||||
pub mod grandpa;
|
||||
|
||||
|
@ -27,8 +30,9 @@ pub mod tx;
|
|||
pub enum Call {
|
||||
Timestamp(timestamp::Call),
|
||||
Coins(coins::Call),
|
||||
LiquidityTokens(coins::LiquidityTokensCall),
|
||||
LiquidityTokens(liquidity_tokens::Call),
|
||||
Dex(dex::Call),
|
||||
GenesisLiquidity(genesis_liquidity::Call),
|
||||
ValidatorSets(validator_sets::Call),
|
||||
InInstructions(in_instructions::Call),
|
||||
Signals(signals::Call),
|
||||
|
@ -48,8 +52,9 @@ pub enum Event {
|
|||
Timestamp,
|
||||
TransactionPayment(TransactionPaymentEvent),
|
||||
Coins(coins::Event),
|
||||
LiquidityTokens(coins::Event),
|
||||
LiquidityTokens(liquidity_tokens::Event),
|
||||
Dex(dex::Event),
|
||||
GenesisLiquidity(genesis_liquidity::Event),
|
||||
ValidatorSets(validator_sets::Event),
|
||||
InInstructions(in_instructions::Event),
|
||||
Signals(signals::Event),
|
||||
|
|
18
substrate/abi/src/liquidity_tokens.rs
Normal file
18
substrate/abi/src/liquidity_tokens.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use serai_primitives::{Balance, SeraiAddress};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Call {
|
||||
burn { balance: Balance },
|
||||
transfer { to: SeraiAddress, balance: Balance },
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Event {
|
||||
Mint { to: SeraiAddress, balance: Balance },
|
||||
Burn { from: SeraiAddress, balance: Balance },
|
||||
Transfer { from: SeraiAddress, to: SeraiAddress, balance: Balance },
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
use sp_core::bounded_vec::BoundedVec;
|
||||
use serai_abi::primitives::{SeraiAddress, Amount, Coin};
|
||||
|
||||
use crate::{SeraiError, TemporalSerai};
|
||||
use scale::{decode_from_bytes, Encode};
|
||||
|
||||
use crate::{Serai, SeraiError, TemporalSerai};
|
||||
|
||||
pub type DexEvent = serai_abi::dex::Event;
|
||||
|
||||
|
@ -57,4 +59,20 @@ impl<'a> SeraiDex<'a> {
|
|||
send_to: address,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the reserves of `coin:SRI` pool.
|
||||
pub async fn get_reserves(&self, coin: Coin) -> Result<Option<(Amount, Amount)>, SeraiError> {
|
||||
let reserves = self
|
||||
.0
|
||||
.serai
|
||||
.call(
|
||||
"state_call",
|
||||
["DexApi_get_reserves".to_string(), hex::encode((coin, Coin::Serai).encode())],
|
||||
)
|
||||
.await?;
|
||||
let bytes = Serai::hex_decode(reserves)?;
|
||||
let result = decode_from_bytes::<Option<(u64, u64)>>(bytes.into())
|
||||
.map_err(|e| SeraiError::ErrorInResponse(e.to_string()))?;
|
||||
Ok(result.map(|amounts| (Amount(amounts.0), Amount(amounts.1))))
|
||||
}
|
||||
}
|
||||
|
|
65
substrate/client/src/serai/genesis_liquidity.rs
Normal file
65
substrate/client/src/serai/genesis_liquidity.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
pub use serai_abi::genesis_liquidity::primitives;
|
||||
use primitives::{Values, LiquidityAmount};
|
||||
|
||||
use serai_abi::primitives::*;
|
||||
|
||||
use sp_core::sr25519::Signature;
|
||||
|
||||
use scale::Encode;
|
||||
|
||||
use crate::{Serai, SeraiError, TemporalSerai, Transaction};
|
||||
|
||||
pub type GenesisLiquidityEvent = serai_abi::genesis_liquidity::Event;
|
||||
|
||||
const PALLET: &str = "GenesisLiquidity";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SeraiGenesisLiquidity<'a>(pub(crate) &'a TemporalSerai<'a>);
|
||||
impl<'a> SeraiGenesisLiquidity<'a> {
|
||||
pub async fn events(&self) -> Result<Vec<GenesisLiquidityEvent>, SeraiError> {
|
||||
self
|
||||
.0
|
||||
.events(|event| {
|
||||
if let serai_abi::Event::GenesisLiquidity(event) = event {
|
||||
Some(event.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn oraclize_values(values: Values, signature: Signature) -> Transaction {
|
||||
Serai::unsigned(serai_abi::Call::GenesisLiquidity(
|
||||
serai_abi::genesis_liquidity::Call::oraclize_values { values, signature },
|
||||
))
|
||||
}
|
||||
|
||||
pub fn remove_coin_liquidity(balance: Balance) -> serai_abi::Call {
|
||||
serai_abi::Call::GenesisLiquidity(serai_abi::genesis_liquidity::Call::remove_coin_liquidity {
|
||||
balance,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn liquidity(
|
||||
&self,
|
||||
address: &SeraiAddress,
|
||||
coin: Coin,
|
||||
) -> Result<LiquidityAmount, SeraiError> {
|
||||
Ok(
|
||||
self
|
||||
.0
|
||||
.storage(
|
||||
PALLET,
|
||||
"Liquidity",
|
||||
(coin, sp_core::hashing::blake2_128(&address.encode()), &address.0),
|
||||
)
|
||||
.await?
|
||||
.unwrap_or(LiquidityAmount::zero()),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn supply(&self, coin: Coin) -> Result<LiquidityAmount, SeraiError> {
|
||||
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(LiquidityAmount::zero()))
|
||||
}
|
||||
}
|
41
substrate/client/src/serai/liquidity_tokens.rs
Normal file
41
substrate/client/src/serai/liquidity_tokens.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use scale::Encode;
|
||||
|
||||
use serai_abi::primitives::{SeraiAddress, Amount, Coin, Balance};
|
||||
|
||||
use crate::{TemporalSerai, SeraiError};
|
||||
|
||||
const PALLET: &str = "LiquidityTokens";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SeraiLiquidityTokens<'a>(pub(crate) &'a TemporalSerai<'a>);
|
||||
impl<'a> SeraiLiquidityTokens<'a> {
|
||||
pub async fn token_supply(&self, coin: Coin) -> Result<Amount, SeraiError> {
|
||||
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(Amount(0)))
|
||||
}
|
||||
|
||||
pub async fn token_balance(
|
||||
&self,
|
||||
coin: Coin,
|
||||
address: SeraiAddress,
|
||||
) -> Result<Amount, SeraiError> {
|
||||
Ok(
|
||||
self
|
||||
.0
|
||||
.storage(
|
||||
PALLET,
|
||||
"Balances",
|
||||
(sp_core::hashing::blake2_128(&address.encode()), &address.0, coin),
|
||||
)
|
||||
.await?
|
||||
.unwrap_or(Amount(0)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn transfer(to: SeraiAddress, balance: Balance) -> serai_abi::Call {
|
||||
serai_abi::Call::LiquidityTokens(serai_abi::liquidity_tokens::Call::transfer { to, balance })
|
||||
}
|
||||
|
||||
pub fn burn(balance: Balance) -> serai_abi::Call {
|
||||
serai_abi::Call::LiquidityTokens(serai_abi::liquidity_tokens::Call::burn { balance })
|
||||
}
|
||||
}
|
|
@ -26,6 +26,10 @@ pub mod in_instructions;
|
|||
pub use in_instructions::SeraiInInstructions;
|
||||
pub mod validator_sets;
|
||||
pub use validator_sets::SeraiValidatorSets;
|
||||
pub mod genesis_liquidity;
|
||||
pub use genesis_liquidity::SeraiGenesisLiquidity;
|
||||
pub mod liquidity_tokens;
|
||||
pub use liquidity_tokens::SeraiLiquidityTokens;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode)]
|
||||
pub struct Block {
|
||||
|
@ -194,6 +198,7 @@ impl Serai {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: move this into substrate/client/src/validator_sets.rs
|
||||
async fn active_network_validators(&self, network: NetworkId) -> Result<Vec<Public>, SeraiError> {
|
||||
let validators: String = self
|
||||
.call("state_call", ["SeraiRuntimeApi_validators".to_string(), hex::encode(network.encode())])
|
||||
|
@ -388,4 +393,12 @@ impl<'a> TemporalSerai<'a> {
|
|||
pub fn validator_sets(&'a self) -> SeraiValidatorSets<'a> {
|
||||
SeraiValidatorSets(self)
|
||||
}
|
||||
|
||||
pub fn genesis_liquidity(&'a self) -> SeraiGenesisLiquidity {
|
||||
SeraiGenesisLiquidity(self)
|
||||
}
|
||||
|
||||
pub fn liquidity_tokens(&'a self) -> SeraiLiquidityTokens {
|
||||
SeraiLiquidityTokens(self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,3 +66,67 @@ macro_rules! serai_test {
|
|||
)*
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! serai_test_fast_epoch {
|
||||
($($name: ident: $test: expr)*) => {
|
||||
$(
|
||||
#[tokio::test]
|
||||
async fn $name() {
|
||||
use std::collections::HashMap;
|
||||
use dockertest::{
|
||||
PullPolicy, StartPolicy, LogOptions, LogAction, LogPolicy, LogSource, Image,
|
||||
TestBodySpecification, DockerTest,
|
||||
};
|
||||
|
||||
serai_docker_tests::build("serai-fast-epoch".to_string());
|
||||
|
||||
let handle = concat!("serai_client-serai_node-", stringify!($name));
|
||||
|
||||
let composition = TestBodySpecification::with_image(
|
||||
Image::with_repository("serai-dev-serai-fast-epoch").pull_policy(PullPolicy::Never),
|
||||
)
|
||||
.replace_cmd(vec![
|
||||
"serai-node".to_string(),
|
||||
"--dev".to_string(),
|
||||
"--unsafe-rpc-external".to_string(),
|
||||
"--rpc-cors".to_string(),
|
||||
"all".to_string(),
|
||||
])
|
||||
.replace_env(
|
||||
HashMap::from([
|
||||
("RUST_LOG".to_string(), "runtime=debug".to_string()),
|
||||
("KEY".to_string(), " ".to_string()),
|
||||
])
|
||||
)
|
||||
.set_publish_all_ports(true)
|
||||
.set_handle(handle)
|
||||
.set_start_policy(StartPolicy::Strict)
|
||||
.set_log_options(Some(LogOptions {
|
||||
action: LogAction::Forward,
|
||||
policy: LogPolicy::Always,
|
||||
source: LogSource::Both,
|
||||
}));
|
||||
|
||||
let mut test = DockerTest::new().with_network(dockertest::Network::Isolated);
|
||||
test.provide_container(composition);
|
||||
test.run_async(|ops| async move {
|
||||
// Sleep until the Substrate RPC starts
|
||||
let serai_rpc = ops.handle(handle).host_port(9944).unwrap();
|
||||
let serai_rpc = format!("http://{}:{}", serai_rpc.0, serai_rpc.1);
|
||||
// Bound execution to 60 seconds
|
||||
for _ in 0 .. 60 {
|
||||
tokio::time::sleep(core::time::Duration::from_secs(1)).await;
|
||||
let Ok(client) = Serai::new(serai_rpc.clone()).await else { continue };
|
||||
if client.latest_finalized_block_hash().await.is_err() {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#[allow(clippy::redundant_closure_call)]
|
||||
$test(Serai::new(serai_rpc).await.unwrap()).await;
|
||||
}).await;
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
|
216
substrate/client/tests/genesis_liquidity.rs
Normal file
216
substrate/client/tests/genesis_liquidity.rs
Normal file
|
@ -0,0 +1,216 @@
|
|||
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,
|
||||
};
|
||||
|
||||
mod common;
|
||||
use common::{in_instructions::provide_batch, tx::publish_tx};
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
// 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<NetworkId, u32> = 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::<Vec<_>>();
|
||||
|
||||
// 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 = <Ristretto as Ciphersuite>::read_G::<&[u8]>(&mut public.0.as_ref()).unwrap();
|
||||
let secret_key = <Ristretto as Ciphersuite>::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::<Ristretto>(&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;
|
||||
}
|
|
@ -25,7 +25,7 @@ pub use coins_pallet as coins;
|
|||
|
||||
use coins::Pallet as CoinsPallet;
|
||||
|
||||
use serai_primitives::*;
|
||||
use serai_primitives::{Balance, COINS, PublicKey, system_address, Amount};
|
||||
|
||||
type LiquidityTokens<T> = coins_pallet::Pallet<T, coins::Instance1>;
|
||||
type LiquidityTokensError<T> = coins_pallet::Error<T, coins::Instance1>;
|
||||
|
|
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()
|
||||
}
|
|
@ -33,10 +33,12 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur
|
|||
|
||||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../primitives", default-features = false }
|
||||
genesis-liquidity-primitives = { package = "serai-genesis-liquidity-primitives", path = "../../genesis-liquidity/primitives", default-features = false }
|
||||
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||
dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false }
|
||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false }
|
||||
genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../../genesis-liquidity/pallet", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
|
@ -54,10 +56,12 @@ std = [
|
|||
|
||||
"serai-primitives/std",
|
||||
"in-instructions-primitives/std",
|
||||
"genesis-liquidity-primitives/std",
|
||||
|
||||
"coins-pallet/std",
|
||||
"dex-pallet/std",
|
||||
"validator-sets-pallet/std",
|
||||
"genesis-liquidity-pallet/std",
|
||||
]
|
||||
default = ["std"]
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ pub mod pallet {
|
|||
use sp_core::sr25519::Public;
|
||||
|
||||
use serai_primitives::{Coin, Amount, Balance};
|
||||
use genesis_liquidity_primitives::GENESIS_LIQUIDITY_ACCOUNT;
|
||||
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::{pallet_prelude::*, RawOrigin};
|
||||
|
@ -33,10 +34,14 @@ pub mod pallet {
|
|||
Config as ValidatorSetsConfig, Pallet as ValidatorSets,
|
||||
};
|
||||
|
||||
use genesis_liquidity_pallet::{Pallet as GenesisLiq, Config as GenesisLiqConfig};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + CoinsConfig + DexConfig + ValidatorSetsConfig {
|
||||
pub trait Config:
|
||||
frame_system::Config + CoinsConfig + DexConfig + ValidatorSetsConfig + GenesisLiqConfig
|
||||
{
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
}
|
||||
|
||||
|
@ -200,6 +205,10 @@ pub mod pallet {
|
|||
}
|
||||
}
|
||||
}
|
||||
InInstruction::GenesisLiquidity(address) => {
|
||||
Coins::<T>::mint(GENESIS_LIQUIDITY_ACCOUNT.into(), instruction.balance)?;
|
||||
GenesisLiq::<T>::add_coin_liquidity(address.into(), instruction.balance)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ pub enum DexCall {
|
|||
pub enum InInstruction {
|
||||
Transfer(SeraiAddress),
|
||||
Dex(DexCall),
|
||||
GenesisLiquidity(SeraiAddress),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)]
|
||||
|
|
29
substrate/primitives/src/constants.rs
Normal file
29
substrate/primitives/src/constants.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use crate::BlockNumber;
|
||||
|
||||
// 1 MB
|
||||
pub const BLOCK_SIZE: u32 = 1024 * 1024;
|
||||
// 6 seconds
|
||||
pub const TARGET_BLOCK_TIME: u64 = 6;
|
||||
|
||||
/// Measured in blocks.
|
||||
pub const MINUTES: BlockNumber = 60 / TARGET_BLOCK_TIME;
|
||||
pub const HOURS: BlockNumber = MINUTES * 60;
|
||||
pub const DAYS: BlockNumber = HOURS * 24;
|
||||
pub const WEEKS: BlockNumber = DAYS * 7;
|
||||
pub const MONTHS: BlockNumber = WEEKS * 4;
|
||||
|
||||
/// 6 months of blocks
|
||||
pub const GENESIS_SRI_TRICKLE_FEED: u64 = MONTHS * 6;
|
||||
|
||||
// 100 Million SRI
|
||||
pub const GENESIS_SRI: u64 = 100_000_000 * 10_u64.pow(8);
|
||||
|
||||
/// This needs to be long enough for arbitrage to occur and make holding any fake price up
|
||||
/// sufficiently unrealistic.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
pub const ARBITRAGE_TIME: u16 = (2 * HOURS) as u16;
|
||||
|
||||
/// Since we use the median price, double the window length.
|
||||
///
|
||||
/// We additionally +1 so there is a true median.
|
||||
pub const MEDIAN_PRICE_WINDOW_LENGTH: u16 = (2 * ARBITRAGE_TIME) + 1;
|
|
@ -37,6 +37,9 @@ pub use balance::*;
|
|||
mod account;
|
||||
pub use account::*;
|
||||
|
||||
mod constants;
|
||||
pub use constants::*;
|
||||
|
||||
pub type BlockNumber = u64;
|
||||
pub type Header = sp_runtime::generic::Header<BlockNumber, sp_runtime::traits::BlakeTwo256>;
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ coins-pallet = { package = "serai-coins-pallet", path = "../coins/pallet", defau
|
|||
dex-pallet = { package = "serai-dex-pallet", path = "../dex/pallet", default-features = false }
|
||||
|
||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets/pallet", default-features = false }
|
||||
genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../genesis-liquidity/pallet", default-features = false }
|
||||
|
||||
in-instructions-pallet = { package = "serai-in-instructions-pallet", path = "../in-instructions/pallet", default-features = false }
|
||||
|
||||
|
@ -115,6 +116,7 @@ std = [
|
|||
"dex-pallet/std",
|
||||
|
||||
"validator-sets-pallet/std",
|
||||
"genesis-liquidity-pallet/std",
|
||||
|
||||
"in-instructions-pallet/std",
|
||||
|
||||
|
@ -127,7 +129,7 @@ std = [
|
|||
"pallet-transaction-payment-rpc-runtime-api/std",
|
||||
]
|
||||
|
||||
fast-epoch = []
|
||||
fast-epoch = ["genesis-liquidity-pallet/fast-epoch"]
|
||||
|
||||
runtime-benchmarks = [
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
|
|
|
@ -7,7 +7,7 @@ use serai_abi::Call;
|
|||
use crate::{
|
||||
Vec,
|
||||
primitives::{PublicKey, SeraiAddress},
|
||||
timestamp, coins, dex,
|
||||
timestamp, coins, dex, genesis_liquidity,
|
||||
validator_sets::{self, MembershipProof},
|
||||
in_instructions, signals, babe, grandpa, RuntimeCall,
|
||||
};
|
||||
|
@ -30,10 +30,10 @@ impl From<Call> for RuntimeCall {
|
|||
}
|
||||
},
|
||||
Call::LiquidityTokens(lt) => match lt {
|
||||
serai_abi::coins::LiquidityTokensCall::transfer { to, balance } => {
|
||||
serai_abi::liquidity_tokens::Call::transfer { to, balance } => {
|
||||
RuntimeCall::LiquidityTokens(coins::Call::transfer { to: to.into(), balance })
|
||||
}
|
||||
serai_abi::coins::LiquidityTokensCall::burn { balance } => {
|
||||
serai_abi::liquidity_tokens::Call::burn { balance } => {
|
||||
RuntimeCall::LiquidityTokens(coins::Call::burn { balance })
|
||||
}
|
||||
},
|
||||
|
@ -89,6 +89,17 @@ impl From<Call> 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,
|
||||
|
@ -209,9 +220,9 @@ impl TryInto<Call> for RuntimeCall {
|
|||
}),
|
||||
RuntimeCall::LiquidityTokens(call) => Call::LiquidityTokens(match call {
|
||||
coins::Call::transfer { to, balance } => {
|
||||
serai_abi::coins::LiquidityTokensCall::transfer { to: to.into(), balance }
|
||||
serai_abi::liquidity_tokens::Call::transfer { to: to.into(), balance }
|
||||
}
|
||||
coins::Call::burn { balance } => serai_abi::coins::LiquidityTokensCall::burn { balance },
|
||||
coins::Call::burn { balance } => serai_abi::liquidity_tokens::Call::burn { balance },
|
||||
_ => Err(())?,
|
||||
}),
|
||||
RuntimeCall::Dex(call) => Call::Dex(match call {
|
||||
|
@ -261,6 +272,15 @@ impl TryInto<Call> for RuntimeCall {
|
|||
}
|
||||
_ => Err(())?,
|
||||
}),
|
||||
RuntimeCall::GenesisLiquidity(call) => Call::GenesisLiquidity(match call {
|
||||
genesis_liquidity::Call::remove_coin_liquidity { balance } => {
|
||||
serai_abi::genesis_liquidity::Call::remove_coin_liquidity { balance }
|
||||
}
|
||||
genesis_liquidity::Call::oraclize_values { values, signature } => {
|
||||
serai_abi::genesis_liquidity::Call::oraclize_values { values, signature }
|
||||
}
|
||||
_ => Err(())?,
|
||||
}),
|
||||
RuntimeCall::ValidatorSets(call) => Call::ValidatorSets(match call {
|
||||
validator_sets::Call::set_keys { network, removed_participants, key_pair, signature } => {
|
||||
serai_abi::validator_sets::Call::set_keys {
|
||||
|
|
|
@ -11,7 +11,6 @@ use core::marker::PhantomData;
|
|||
// Re-export all components
|
||||
pub use serai_primitives as primitives;
|
||||
pub use primitives::{BlockNumber, Header};
|
||||
use primitives::{NetworkId, NETWORKS};
|
||||
|
||||
pub use frame_system as system;
|
||||
pub use frame_support as support;
|
||||
|
@ -32,6 +31,8 @@ pub use signals_pallet as signals;
|
|||
pub use pallet_babe as babe;
|
||||
pub use pallet_grandpa as grandpa;
|
||||
|
||||
pub use genesis_liquidity_pallet as genesis_liquidity;
|
||||
|
||||
// Actually used by the runtime
|
||||
use sp_core::OpaqueMetadata;
|
||||
use sp_std::prelude::*;
|
||||
|
@ -47,7 +48,11 @@ use sp_runtime::{
|
|||
BoundedVec, Perbill, ApplyExtrinsicResult,
|
||||
};
|
||||
|
||||
use primitives::{PublicKey, AccountLookup, SubstrateAmount};
|
||||
#[allow(unused_imports)]
|
||||
use primitives::{
|
||||
NetworkId, PublicKey, AccountLookup, SubstrateAmount, Coin, NETWORKS, MEDIAN_PRICE_WINDOW_LENGTH,
|
||||
HOURS, DAYS, MINUTES, TARGET_BLOCK_TIME, BLOCK_SIZE,
|
||||
};
|
||||
|
||||
use support::{
|
||||
traits::{ConstU8, ConstU16, ConstU32, ConstU64, Contains},
|
||||
|
@ -114,28 +119,7 @@ pub fn native_version() -> NativeVersion {
|
|||
NativeVersion { runtime_version: VERSION, can_author_with: Default::default() }
|
||||
}
|
||||
|
||||
// 1 MB
|
||||
pub const BLOCK_SIZE: u32 = 1024 * 1024;
|
||||
// 6 seconds
|
||||
pub const TARGET_BLOCK_TIME: u64 = 6;
|
||||
|
||||
/// Measured in blocks.
|
||||
pub const MINUTES: BlockNumber = 60 / TARGET_BLOCK_TIME;
|
||||
pub const HOURS: BlockNumber = MINUTES * 60;
|
||||
pub const DAYS: BlockNumber = HOURS * 24;
|
||||
|
||||
pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4);
|
||||
|
||||
/// This needs to be long enough for arbitrage to occur and make holding any fake price up
|
||||
/// sufficiently unrealistic.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
pub const ARBITRAGE_TIME: u16 = (2 * HOURS) as u16;
|
||||
|
||||
/// Since we use the median price, double the window length.
|
||||
///
|
||||
/// We additionally +1 so there is a true median.
|
||||
pub const MEDIAN_PRICE_WINDOW_LENGTH: u16 = (2 * ARBITRAGE_TIME) + 1;
|
||||
|
||||
pub const BABE_GENESIS_EPOCH_CONFIG: sp_consensus_babe::BabeEpochConfiguration =
|
||||
sp_consensus_babe::BabeEpochConfiguration {
|
||||
c: PRIMARY_PROBABILITY,
|
||||
|
@ -264,6 +248,10 @@ impl in_instructions::Config for Runtime {
|
|||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
impl genesis_liquidity::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
// for publishing equivocation evidences.
|
||||
impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime
|
||||
where
|
||||
|
@ -338,6 +326,7 @@ construct_runtime!(
|
|||
Coins: coins,
|
||||
LiquidityTokens: coins::<Instance1>::{Pallet, Call, Storage, Event<T>},
|
||||
Dex: dex,
|
||||
GenesisLiquidity: genesis_liquidity,
|
||||
|
||||
ValidatorSets: validator_sets,
|
||||
|
||||
|
@ -604,4 +593,28 @@ sp_api::impl_runtime_apis! {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl dex::DexApi<Block> for Runtime {
|
||||
fn quote_price_exact_tokens_for_tokens(
|
||||
asset1: Coin,
|
||||
asset2: Coin,
|
||||
amount: SubstrateAmount,
|
||||
include_fee: bool
|
||||
) -> Option<SubstrateAmount> {
|
||||
Dex::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee)
|
||||
}
|
||||
|
||||
fn quote_price_tokens_for_exact_tokens(
|
||||
asset1: Coin,
|
||||
asset2: Coin,
|
||||
amount: SubstrateAmount,
|
||||
include_fee: bool
|
||||
) -> Option<SubstrateAmount> {
|
||||
Dex::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee)
|
||||
}
|
||||
|
||||
fn get_reserves(asset1: Coin, asset2: Coin) -> Option<(SubstrateAmount, SubstrateAmount)> {
|
||||
Dex::get_reserves(&asset1, &asset2).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue