mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-16 17:07:35 +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",
|
"parity-scale-codec",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
"serai-coins-primitives",
|
"serai-coins-primitives",
|
||||||
|
"serai-genesis-liquidity-primitives",
|
||||||
"serai-in-instructions-primitives",
|
"serai-in-instructions-primitives",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-signals-primitives",
|
"serai-signals-primitives",
|
||||||
|
@ -8127,6 +8128,39 @@ dependencies = [
|
||||||
"zeroize",
|
"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]]
|
[[package]]
|
||||||
name = "serai-in-instructions-pallet"
|
name = "serai-in-instructions-pallet"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -8137,6 +8171,8 @@ dependencies = [
|
||||||
"scale-info",
|
"scale-info",
|
||||||
"serai-coins-pallet",
|
"serai-coins-pallet",
|
||||||
"serai-dex-pallet",
|
"serai-dex-pallet",
|
||||||
|
"serai-genesis-liquidity-pallet",
|
||||||
|
"serai-genesis-liquidity-primitives",
|
||||||
"serai-in-instructions-primitives",
|
"serai-in-instructions-primitives",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-validator-sets-pallet",
|
"serai-validator-sets-pallet",
|
||||||
|
@ -8406,6 +8442,7 @@ dependencies = [
|
||||||
"serai-abi",
|
"serai-abi",
|
||||||
"serai-coins-pallet",
|
"serai-coins-pallet",
|
||||||
"serai-dex-pallet",
|
"serai-dex-pallet",
|
||||||
|
"serai-genesis-liquidity-pallet",
|
||||||
"serai-in-instructions-pallet",
|
"serai-in-instructions-pallet",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-signals-pallet",
|
"serai-signals-pallet",
|
||||||
|
|
|
@ -53,6 +53,8 @@ exceptions = [
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-coins-pallet" },
|
{ allow = ["AGPL-3.0"], name = "serai-coins-pallet" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-dex-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-in-instructions-pallet" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-validator-sets-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-primitives = { path = "../primitives", version = "0.1", default-features = false }
|
||||||
serai-coins-primitives = { path = "../coins/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-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-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 }
|
serai-signals-primitives = { path = "../signals/primitives", version = "0.1", default-features = false }
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ std = [
|
||||||
"serai-primitives/std",
|
"serai-primitives/std",
|
||||||
"serai-coins-primitives/std",
|
"serai-coins-primitives/std",
|
||||||
"serai-validator-sets-primitives/std",
|
"serai-validator-sets-primitives/std",
|
||||||
|
"serai-genesis-liquidity-primitives/std",
|
||||||
"serai-in-instructions-primitives/std",
|
"serai-in-instructions-primitives/std",
|
||||||
"serai-signals-primitives/std",
|
"serai-signals-primitives/std",
|
||||||
]
|
]
|
||||||
|
@ -63,6 +65,7 @@ borsh = [
|
||||||
"serai-primitives/borsh",
|
"serai-primitives/borsh",
|
||||||
"serai-coins-primitives/borsh",
|
"serai-coins-primitives/borsh",
|
||||||
"serai-validator-sets-primitives/borsh",
|
"serai-validator-sets-primitives/borsh",
|
||||||
|
"serai-genesis-liquidity-primitives/borsh",
|
||||||
"serai-in-instructions-primitives/borsh",
|
"serai-in-instructions-primitives/borsh",
|
||||||
"serai-signals-primitives/borsh",
|
"serai-signals-primitives/borsh",
|
||||||
]
|
]
|
||||||
|
@ -71,6 +74,7 @@ serde = [
|
||||||
"serai-primitives/serde",
|
"serai-primitives/serde",
|
||||||
"serai-coins-primitives/serde",
|
"serai-coins-primitives/serde",
|
||||||
"serai-validator-sets-primitives/serde",
|
"serai-validator-sets-primitives/serde",
|
||||||
|
"serai-genesis-liquidity-primitives/serde",
|
||||||
"serai-in-instructions-primitives/serde",
|
"serai-in-instructions-primitives/serde",
|
||||||
"serai-signals-primitives/serde",
|
"serai-signals-primitives/serde",
|
||||||
]
|
]
|
||||||
|
|
|
@ -13,15 +13,6 @@ pub enum Call {
|
||||||
burn_with_instruction { instruction: OutInstructionWithBalance },
|
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)]
|
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[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 timestamp;
|
||||||
|
|
||||||
pub mod coins;
|
pub mod coins;
|
||||||
|
pub mod liquidity_tokens;
|
||||||
pub mod dex;
|
pub mod dex;
|
||||||
|
|
||||||
pub mod validator_sets;
|
pub mod validator_sets;
|
||||||
pub mod in_instructions;
|
pub mod in_instructions;
|
||||||
pub mod signals;
|
pub mod signals;
|
||||||
|
|
||||||
|
pub mod genesis_liquidity;
|
||||||
|
|
||||||
pub mod babe;
|
pub mod babe;
|
||||||
pub mod grandpa;
|
pub mod grandpa;
|
||||||
|
|
||||||
|
@ -27,8 +30,9 @@ pub mod tx;
|
||||||
pub enum Call {
|
pub enum Call {
|
||||||
Timestamp(timestamp::Call),
|
Timestamp(timestamp::Call),
|
||||||
Coins(coins::Call),
|
Coins(coins::Call),
|
||||||
LiquidityTokens(coins::LiquidityTokensCall),
|
LiquidityTokens(liquidity_tokens::Call),
|
||||||
Dex(dex::Call),
|
Dex(dex::Call),
|
||||||
|
GenesisLiquidity(genesis_liquidity::Call),
|
||||||
ValidatorSets(validator_sets::Call),
|
ValidatorSets(validator_sets::Call),
|
||||||
InInstructions(in_instructions::Call),
|
InInstructions(in_instructions::Call),
|
||||||
Signals(signals::Call),
|
Signals(signals::Call),
|
||||||
|
@ -48,8 +52,9 @@ pub enum Event {
|
||||||
Timestamp,
|
Timestamp,
|
||||||
TransactionPayment(TransactionPaymentEvent),
|
TransactionPayment(TransactionPaymentEvent),
|
||||||
Coins(coins::Event),
|
Coins(coins::Event),
|
||||||
LiquidityTokens(coins::Event),
|
LiquidityTokens(liquidity_tokens::Event),
|
||||||
Dex(dex::Event),
|
Dex(dex::Event),
|
||||||
|
GenesisLiquidity(genesis_liquidity::Event),
|
||||||
ValidatorSets(validator_sets::Event),
|
ValidatorSets(validator_sets::Event),
|
||||||
InInstructions(in_instructions::Event),
|
InInstructions(in_instructions::Event),
|
||||||
Signals(signals::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 sp_core::bounded_vec::BoundedVec;
|
||||||
use serai_abi::primitives::{SeraiAddress, Amount, Coin};
|
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;
|
pub type DexEvent = serai_abi::dex::Event;
|
||||||
|
|
||||||
|
@ -57,4 +59,20 @@ impl<'a> SeraiDex<'a> {
|
||||||
send_to: address,
|
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 use in_instructions::SeraiInInstructions;
|
||||||
pub mod validator_sets;
|
pub mod validator_sets;
|
||||||
pub use validator_sets::SeraiValidatorSets;
|
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)]
|
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode)]
|
||||||
pub struct Block {
|
pub struct Block {
|
||||||
|
@ -194,6 +198,7 @@ impl Serai {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move this into substrate/client/src/validator_sets.rs
|
||||||
async fn active_network_validators(&self, network: NetworkId) -> Result<Vec<Public>, SeraiError> {
|
async fn active_network_validators(&self, network: NetworkId) -> Result<Vec<Public>, SeraiError> {
|
||||||
let validators: String = self
|
let validators: String = self
|
||||||
.call("state_call", ["SeraiRuntimeApi_validators".to_string(), hex::encode(network.encode())])
|
.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> {
|
pub fn validator_sets(&'a self) -> SeraiValidatorSets<'a> {
|
||||||
SeraiValidatorSets(self)
|
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 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 LiquidityTokens<T> = coins_pallet::Pallet<T, coins::Instance1>;
|
||||||
type LiquidityTokensError<T> = coins_pallet::Error<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 }
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
in-instructions-primitives = { package = "serai-in-instructions-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 }
|
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||||
dex-pallet = { package = "serai-dex-pallet", path = "../../dex/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 }
|
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]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
|
@ -54,10 +56,12 @@ std = [
|
||||||
|
|
||||||
"serai-primitives/std",
|
"serai-primitives/std",
|
||||||
"in-instructions-primitives/std",
|
"in-instructions-primitives/std",
|
||||||
|
"genesis-liquidity-primitives/std",
|
||||||
|
|
||||||
"coins-pallet/std",
|
"coins-pallet/std",
|
||||||
"dex-pallet/std",
|
"dex-pallet/std",
|
||||||
"validator-sets-pallet/std",
|
"validator-sets-pallet/std",
|
||||||
|
"genesis-liquidity-pallet/std",
|
||||||
]
|
]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ pub mod pallet {
|
||||||
use sp_core::sr25519::Public;
|
use sp_core::sr25519::Public;
|
||||||
|
|
||||||
use serai_primitives::{Coin, Amount, Balance};
|
use serai_primitives::{Coin, Amount, Balance};
|
||||||
|
use genesis_liquidity_primitives::GENESIS_LIQUIDITY_ACCOUNT;
|
||||||
|
|
||||||
use frame_support::pallet_prelude::*;
|
use frame_support::pallet_prelude::*;
|
||||||
use frame_system::{pallet_prelude::*, RawOrigin};
|
use frame_system::{pallet_prelude::*, RawOrigin};
|
||||||
|
@ -33,10 +34,14 @@ pub mod pallet {
|
||||||
Config as ValidatorSetsConfig, Pallet as ValidatorSets,
|
Config as ValidatorSetsConfig, Pallet as ValidatorSets,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use genesis_liquidity_pallet::{Pallet as GenesisLiq, Config as GenesisLiqConfig};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[pallet::config]
|
#[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>;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ pub enum DexCall {
|
||||||
pub enum InInstruction {
|
pub enum InInstruction {
|
||||||
Transfer(SeraiAddress),
|
Transfer(SeraiAddress),
|
||||||
Dex(DexCall),
|
Dex(DexCall),
|
||||||
|
GenesisLiquidity(SeraiAddress),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)]
|
#[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;
|
mod account;
|
||||||
pub use account::*;
|
pub use account::*;
|
||||||
|
|
||||||
|
mod constants;
|
||||||
|
pub use constants::*;
|
||||||
|
|
||||||
pub type BlockNumber = u64;
|
pub type BlockNumber = u64;
|
||||||
pub type Header = sp_runtime::generic::Header<BlockNumber, sp_runtime::traits::BlakeTwo256>;
|
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 }
|
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 }
|
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 }
|
in-instructions-pallet = { package = "serai-in-instructions-pallet", path = "../in-instructions/pallet", default-features = false }
|
||||||
|
|
||||||
|
@ -115,6 +116,7 @@ std = [
|
||||||
"dex-pallet/std",
|
"dex-pallet/std",
|
||||||
|
|
||||||
"validator-sets-pallet/std",
|
"validator-sets-pallet/std",
|
||||||
|
"genesis-liquidity-pallet/std",
|
||||||
|
|
||||||
"in-instructions-pallet/std",
|
"in-instructions-pallet/std",
|
||||||
|
|
||||||
|
@ -127,7 +129,7 @@ std = [
|
||||||
"pallet-transaction-payment-rpc-runtime-api/std",
|
"pallet-transaction-payment-rpc-runtime-api/std",
|
||||||
]
|
]
|
||||||
|
|
||||||
fast-epoch = []
|
fast-epoch = ["genesis-liquidity-pallet/fast-epoch"]
|
||||||
|
|
||||||
runtime-benchmarks = [
|
runtime-benchmarks = [
|
||||||
"sp-runtime/runtime-benchmarks",
|
"sp-runtime/runtime-benchmarks",
|
||||||
|
|
|
@ -7,7 +7,7 @@ use serai_abi::Call;
|
||||||
use crate::{
|
use crate::{
|
||||||
Vec,
|
Vec,
|
||||||
primitives::{PublicKey, SeraiAddress},
|
primitives::{PublicKey, SeraiAddress},
|
||||||
timestamp, coins, dex,
|
timestamp, coins, dex, genesis_liquidity,
|
||||||
validator_sets::{self, MembershipProof},
|
validator_sets::{self, MembershipProof},
|
||||||
in_instructions, signals, babe, grandpa, RuntimeCall,
|
in_instructions, signals, babe, grandpa, RuntimeCall,
|
||||||
};
|
};
|
||||||
|
@ -30,10 +30,10 @@ impl From<Call> for RuntimeCall {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Call::LiquidityTokens(lt) => match lt {
|
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 })
|
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 })
|
RuntimeCall::LiquidityTokens(coins::Call::burn { balance })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -89,6 +89,17 @@ impl From<Call> for RuntimeCall {
|
||||||
send_to: send_to.into(),
|
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 {
|
Call::ValidatorSets(vs) => match vs {
|
||||||
serai_abi::validator_sets::Call::set_keys {
|
serai_abi::validator_sets::Call::set_keys {
|
||||||
network,
|
network,
|
||||||
|
@ -209,9 +220,9 @@ impl TryInto<Call> for RuntimeCall {
|
||||||
}),
|
}),
|
||||||
RuntimeCall::LiquidityTokens(call) => Call::LiquidityTokens(match call {
|
RuntimeCall::LiquidityTokens(call) => Call::LiquidityTokens(match call {
|
||||||
coins::Call::transfer { to, balance } => {
|
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(())?,
|
_ => Err(())?,
|
||||||
}),
|
}),
|
||||||
RuntimeCall::Dex(call) => Call::Dex(match call {
|
RuntimeCall::Dex(call) => Call::Dex(match call {
|
||||||
|
@ -261,6 +272,15 @@ impl TryInto<Call> for RuntimeCall {
|
||||||
}
|
}
|
||||||
_ => Err(())?,
|
_ => 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 {
|
RuntimeCall::ValidatorSets(call) => Call::ValidatorSets(match call {
|
||||||
validator_sets::Call::set_keys { network, removed_participants, key_pair, signature } => {
|
validator_sets::Call::set_keys { network, removed_participants, key_pair, signature } => {
|
||||||
serai_abi::validator_sets::Call::set_keys {
|
serai_abi::validator_sets::Call::set_keys {
|
||||||
|
|
|
@ -11,7 +11,6 @@ use core::marker::PhantomData;
|
||||||
// Re-export all components
|
// Re-export all components
|
||||||
pub use serai_primitives as primitives;
|
pub use serai_primitives as primitives;
|
||||||
pub use primitives::{BlockNumber, Header};
|
pub use primitives::{BlockNumber, Header};
|
||||||
use primitives::{NetworkId, NETWORKS};
|
|
||||||
|
|
||||||
pub use frame_system as system;
|
pub use frame_system as system;
|
||||||
pub use frame_support as support;
|
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_babe as babe;
|
||||||
pub use pallet_grandpa as grandpa;
|
pub use pallet_grandpa as grandpa;
|
||||||
|
|
||||||
|
pub use genesis_liquidity_pallet as genesis_liquidity;
|
||||||
|
|
||||||
// Actually used by the runtime
|
// Actually used by the runtime
|
||||||
use sp_core::OpaqueMetadata;
|
use sp_core::OpaqueMetadata;
|
||||||
use sp_std::prelude::*;
|
use sp_std::prelude::*;
|
||||||
|
@ -47,7 +48,11 @@ use sp_runtime::{
|
||||||
BoundedVec, Perbill, ApplyExtrinsicResult,
|
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::{
|
use support::{
|
||||||
traits::{ConstU8, ConstU16, ConstU32, ConstU64, Contains},
|
traits::{ConstU8, ConstU16, ConstU32, ConstU64, Contains},
|
||||||
|
@ -114,28 +119,7 @@ pub fn native_version() -> NativeVersion {
|
||||||
NativeVersion { runtime_version: VERSION, can_author_with: Default::default() }
|
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);
|
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 =
|
pub const BABE_GENESIS_EPOCH_CONFIG: sp_consensus_babe::BabeEpochConfiguration =
|
||||||
sp_consensus_babe::BabeEpochConfiguration {
|
sp_consensus_babe::BabeEpochConfiguration {
|
||||||
c: PRIMARY_PROBABILITY,
|
c: PRIMARY_PROBABILITY,
|
||||||
|
@ -264,6 +248,10 @@ impl in_instructions::Config for Runtime {
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl genesis_liquidity::Config for Runtime {
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
}
|
||||||
|
|
||||||
// for publishing equivocation evidences.
|
// for publishing equivocation evidences.
|
||||||
impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime
|
impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime
|
||||||
where
|
where
|
||||||
|
@ -338,6 +326,7 @@ construct_runtime!(
|
||||||
Coins: coins,
|
Coins: coins,
|
||||||
LiquidityTokens: coins::<Instance1>::{Pallet, Call, Storage, Event<T>},
|
LiquidityTokens: coins::<Instance1>::{Pallet, Call, Storage, Event<T>},
|
||||||
Dex: dex,
|
Dex: dex,
|
||||||
|
GenesisLiquidity: genesis_liquidity,
|
||||||
|
|
||||||
ValidatorSets: validator_sets,
|
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