mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-12 14:09:25 +00:00
Compare commits
21 Commits
next-polka
...
ef703843a6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef703843a6 | ||
|
|
c14923facb | ||
|
|
f75fe67493 | ||
|
|
28edc6615e | ||
|
|
ad70bce9f0 | ||
|
|
929d0bf48c | ||
|
|
04fcb2bba3 | ||
|
|
90a5232bbd | ||
|
|
20cf4c930c | ||
|
|
7ecbfde936 | ||
|
|
926ddd09db | ||
|
|
826f9986e4 | ||
|
|
7041dbeb0b | ||
|
|
df774d153c | ||
|
|
8be0538eba | ||
|
|
c32040240c | ||
|
|
207f2bd28a | ||
|
|
2b32fe90ca | ||
|
|
b9df73e418 | ||
|
|
da51543588 | ||
|
|
c1bcb0f6c7 |
36
Cargo.lock
generated
36
Cargo.lock
generated
@@ -7756,6 +7756,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",
|
||||||
@@ -7953,6 +7954,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"
|
||||||
@@ -7963,6 +7997,7 @@ dependencies = [
|
|||||||
"scale-info",
|
"scale-info",
|
||||||
"serai-coins-pallet",
|
"serai-coins-pallet",
|
||||||
"serai-dex-pallet",
|
"serai-dex-pallet",
|
||||||
|
"serai-genesis-liquidity-pallet",
|
||||||
"serai-in-instructions-primitives",
|
"serai-in-instructions-primitives",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-validator-sets-pallet",
|
"serai-validator-sets-pallet",
|
||||||
@@ -8232,6 +8267,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 },
|
||||||
|
set_initial_price { prices: Prices, 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::Prices;
|
||||||
|
|
||||||
|
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 set_initial_price(prices: Prices, signature: Signature) -> Transaction {
|
||||||
|
Serai::unsigned(serai_abi::Call::GenesisLiquidity(
|
||||||
|
serai_abi::genesis_liquidity::Call::set_initial_price { prices, 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<(Amount, Amount), SeraiError> {
|
||||||
|
Ok(
|
||||||
|
self
|
||||||
|
.0
|
||||||
|
.storage(
|
||||||
|
PALLET,
|
||||||
|
"Liquidity",
|
||||||
|
(coin, sp_core::hashing::blake2_128(&address.encode()), &address.0),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.unwrap_or((Amount(0), Amount(0))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn supply(&self, coin: Coin) -> Result<(Amount, Amount), SeraiError> {
|
||||||
|
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or((Amount(0), Amount(0))))
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
207
substrate/client/tests/genesis_liquidity.rs
Normal file
207
substrate/client/tests/genesis_liquidity.rs
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
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, GENESIS_LP_SHARES},
|
||||||
|
SeraiGenesisLiquidity,
|
||||||
|
},
|
||||||
|
validator_sets::primitives::{musig_context, Session, ValidatorSet},
|
||||||
|
};
|
||||||
|
|
||||||
|
use serai_abi::{
|
||||||
|
genesis_liquidity::primitives::{set_initial_price_message, Prices},
|
||||||
|
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) {
|
||||||
|
// amounts
|
||||||
|
let amounts = vec![
|
||||||
|
Amount(5_53246991),
|
||||||
|
Amount(3_14562819),
|
||||||
|
Amount(9_33648912),
|
||||||
|
Amount(150_873639000000),
|
||||||
|
Amount(248_665228000000),
|
||||||
|
Amount(451_765529000000),
|
||||||
|
];
|
||||||
|
|
||||||
|
// addresses
|
||||||
|
let mut btc_addresses = vec![];
|
||||||
|
let mut xmr_addresses = vec![];
|
||||||
|
let addr_count = amounts.len();
|
||||||
|
for (i, amount) in amounts.into_iter().enumerate() {
|
||||||
|
let mut address = SeraiAddress::new([0; 32]);
|
||||||
|
OsRng.fill_bytes(&mut address.0);
|
||||||
|
if i < addr_count / 2 {
|
||||||
|
btc_addresses.push((address, amount));
|
||||||
|
} else {
|
||||||
|
xmr_addresses.push((address, amount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// btc batch
|
||||||
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
|
let btc_ins = btc_addresses
|
||||||
|
.iter()
|
||||||
|
.map(|(addr, amount)| InInstructionWithBalance {
|
||||||
|
instruction: InInstruction::GenesisLiquidity(*addr),
|
||||||
|
balance: Balance { coin: Coin::Bitcoin, amount: *amount },
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let batch =
|
||||||
|
Batch { network: NetworkId::Bitcoin, id: 0, block: block_hash, instructions: btc_ins };
|
||||||
|
provide_batch(&serai, batch).await;
|
||||||
|
|
||||||
|
// xmr batch
|
||||||
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
|
let xmr_ins = xmr_addresses
|
||||||
|
.iter()
|
||||||
|
.map(|(addr, amount)| InInstructionWithBalance {
|
||||||
|
instruction: InInstruction::GenesisLiquidity(*addr),
|
||||||
|
balance: Balance { coin: Coin::Monero, amount: *amount },
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let batch = Batch { network: NetworkId::Monero, id: 0, block: block_hash, instructions: xmr_ins };
|
||||||
|
provide_batch(&serai, batch).await;
|
||||||
|
|
||||||
|
// wait until genesis ends..
|
||||||
|
tokio::time::timeout(tokio::time::Duration::from_secs(300), async {
|
||||||
|
while serai.latest_finalized_block().await.unwrap().number() < 10 {
|
||||||
|
tokio::time::sleep(Duration::from_secs(6)).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// set prices
|
||||||
|
let prices = Prices { bitcoin: 10u64.pow(8), monero: 184100, ethereum: 4785000, dai: 1500 };
|
||||||
|
set_prices(&serai, &prices).await;
|
||||||
|
|
||||||
|
// wait little bit..
|
||||||
|
tokio::time::sleep(Duration::from_secs(12)).await;
|
||||||
|
|
||||||
|
// check total SRI supply is +100M
|
||||||
|
let last_block = serai.latest_finalized_block().await.unwrap().hash();
|
||||||
|
let serai = serai.as_of(last_block);
|
||||||
|
let sri = serai.coins().coin_supply(Coin::Serai).await.unwrap();
|
||||||
|
// there are 6 endowed accounts in dev-net. Take this into consideration when checking
|
||||||
|
// for the total sri minted at this time.
|
||||||
|
let 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 pool_btc = btc_addresses.iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0));
|
||||||
|
let pool_xmr = xmr_addresses.iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0));
|
||||||
|
|
||||||
|
let pool_btc_value = (pool_btc * u128::from(prices.bitcoin)) / 10u128.pow(8);
|
||||||
|
let pool_xmr_value = (pool_xmr * u128::from(prices.monero)) / 10u128.pow(12);
|
||||||
|
let total_value = pool_btc_value + pool_xmr_value;
|
||||||
|
|
||||||
|
// calculated distributed SRI. We know that xmr is at the end of COINS array
|
||||||
|
// so it will be approximated to roof instead of floor after integer division.
|
||||||
|
let btc_sri = (pool_btc_value * u128::from(GENESIS_SRI)) / total_value;
|
||||||
|
let xmr_sri = ((pool_xmr_value * u128::from(GENESIS_SRI)) / total_value) + 1;
|
||||||
|
|
||||||
|
let btc_reserves = serai.dex().get_reserves(Coin::Bitcoin).await.unwrap().unwrap();
|
||||||
|
assert_eq!(u128::from(btc_reserves.0 .0), pool_btc);
|
||||||
|
assert_eq!(u128::from(btc_reserves.1 .0), btc_sri);
|
||||||
|
|
||||||
|
let xmr_reserves = serai.dex().get_reserves(Coin::Monero).await.unwrap().unwrap();
|
||||||
|
assert_eq!(u128::from(xmr_reserves.0 .0), pool_xmr);
|
||||||
|
assert_eq!(u128::from(xmr_reserves.1 .0), xmr_sri);
|
||||||
|
|
||||||
|
// check each btc liq provider got liq tokens proportional to their value
|
||||||
|
let btc_liq_supply = serai.genesis_liquidity().supply(Coin::Bitcoin).await.unwrap();
|
||||||
|
for (acc, amount) in btc_addresses {
|
||||||
|
let acc_liq_shares =
|
||||||
|
serai.genesis_liquidity().liquidity(&acc, Coin::Bitcoin).await.unwrap().0 .0;
|
||||||
|
|
||||||
|
// since we can't test the ratios directly(due to integer division giving 0)
|
||||||
|
// we test whether they give the same result when multiplied by another constant.
|
||||||
|
// Following test ensures the account in fact has the right amount of shares.
|
||||||
|
let shares_ratio = (GENESIS_LP_SHARES * acc_liq_shares) / btc_liq_supply.0 .0;
|
||||||
|
let amounts_ratio = (GENESIS_LP_SHARES * amount.0) / u64::try_from(pool_btc).unwrap();
|
||||||
|
assert_eq!(shares_ratio, amounts_ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check each xmr liq provider got liq tokens proportional to their value
|
||||||
|
let xmr_liq_supply = serai.genesis_liquidity().supply(Coin::Monero).await.unwrap();
|
||||||
|
for (acc, amount) in xmr_addresses {
|
||||||
|
let acc_liq_shares =
|
||||||
|
serai.genesis_liquidity().liquidity(&acc, Coin::Monero).await.unwrap().0 .0;
|
||||||
|
|
||||||
|
let shares_ratio = (GENESIS_LP_SHARES * acc_liq_shares) / xmr_liq_supply.0 .0;
|
||||||
|
let amounts_ratio = (GENESIS_LP_SHARES * amount.0) / u64::try_from(pool_xmr).unwrap();
|
||||||
|
assert_eq!(shares_ratio, amounts_ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: test remove the liq before/after genesis ended.
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_prices(serai: &Serai, prices: &Prices) {
|
||||||
|
// prepare a Musig tx to set the initial prices
|
||||||
|
let pair = insecure_pair_from_name("Alice");
|
||||||
|
let public = pair.public();
|
||||||
|
let set = ValidatorSet { session: Session(0), 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())]),
|
||||||
|
),
|
||||||
|
&set_initial_price_message(&set, prices),
|
||||||
|
);
|
||||||
|
|
||||||
|
// set initial prices
|
||||||
|
let _ = publish_tx(
|
||||||
|
serai,
|
||||||
|
&SeraiGenesisLiquidity::set_initial_price(*prices, 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>;
|
||||||
|
|||||||
64
substrate/genesis-liquidity/pallet/Cargo.toml
Normal file
64
substrate/genesis-liquidity/pallet/Cargo.toml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
[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/>.
|
||||||
444
substrate/genesis-liquidity/pallet/src/lib.rs
Normal file
444
substrate/genesis-liquidity/pallet/src/lib.rs
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
#![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, Session, musig_key};
|
||||||
|
pub use genesis_liquidity_primitives as primitives;
|
||||||
|
use primitives::*;
|
||||||
|
|
||||||
|
/// 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, coin2: Balance },
|
||||||
|
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,
|
||||||
|
(SubstrateAmount, SubstrateAmount),
|
||||||
|
OptionQuery,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Keeps the total shares and the total amount of coins per coin.
|
||||||
|
#[pallet::storage]
|
||||||
|
pub(crate) type Supply<T: Config> =
|
||||||
|
StorageMap<_, Identity, Coin, (SubstrateAmount, SubstrateAmount), 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<_, bool, OptionQuery>;
|
||||||
|
|
||||||
|
#[pallet::hooks]
|
||||||
|
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||||
|
fn on_finalize(n: BlockNumberFor<T>) {
|
||||||
|
#[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((0, 0)).1);
|
||||||
|
let pool_value = pool_amount.saturating_mul(value.into()) / 10u128.pow(coin.decimals());
|
||||||
|
total_value = total_value.saturating_add(pool_value);
|
||||||
|
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 - total_sri_distributed
|
||||||
|
} else {
|
||||||
|
u64::try_from(u128::from(GENESIS_SRI).saturating_mul(pool_value) / total_value).unwrap()
|
||||||
|
};
|
||||||
|
total_sri_distributed += sri_amount;
|
||||||
|
|
||||||
|
// we can't add 0 liquidity
|
||||||
|
if !(pool_amount > 0 && sri_amount > 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// actually add the liquidity to dex
|
||||||
|
let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into());
|
||||||
|
Dex::<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(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// let everyone know about the event
|
||||||
|
Self::deposit_event(Event::GenesisLiquidityAddedToPool {
|
||||||
|
coin1: Balance { coin, amount: Amount(u64::try_from(pool_amount).unwrap()) },
|
||||||
|
coin2: Balance { coin: Coin::Serai, amount: 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(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// we accept we reached economic security once we can mint smallest amount of a network's coin
|
||||||
|
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() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mint the coins
|
||||||
|
Coins::<T>::mint(GENESIS_LIQUIDITY_ACCOUNT.into(), balance)?;
|
||||||
|
|
||||||
|
// calculate new shares & supply
|
||||||
|
let (new_shares, new_supply) = if let Some(supply) = Supply::<T>::get(balance.coin) {
|
||||||
|
// calculate amount of shares for this amount
|
||||||
|
let shares = Self::mul_div(supply.0, balance.amount.0, supply.1)?;
|
||||||
|
|
||||||
|
// get new shares for this account
|
||||||
|
let existing = Liquidity::<T>::get(balance.coin, account).unwrap_or((0, 0));
|
||||||
|
(
|
||||||
|
(
|
||||||
|
existing.0.checked_add(shares).ok_or(Error::<T>::AmountOverflowed)?,
|
||||||
|
existing.1.checked_add(balance.amount.0).ok_or(Error::<T>::AmountOverflowed)?,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
supply.0.checked_add(shares).ok_or(Error::<T>::AmountOverflowed)?,
|
||||||
|
supply.1.checked_add(balance.amount.0).ok_or(Error::<T>::AmountOverflowed)?,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let first_amounts = (GENESIS_LP_SHARES, balance.amount.0);
|
||||||
|
(first_amounts, first_amounts)
|
||||||
|
};
|
||||||
|
|
||||||
|
// save
|
||||||
|
Liquidity::<T>::set(balance.coin, account, Some(new_shares));
|
||||||
|
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_shares, 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 (user_shares, user_coins) =
|
||||||
|
Liquidity::<T>::get(balance.coin, account).unwrap_or((0, 0));
|
||||||
|
let total_shares = Supply::<T>::get(balance.coin).unwrap_or((0, 0)).0;
|
||||||
|
let user_liq_tokens = Self::mul_div(total_liq_tokens, user_shares, total_shares)?;
|
||||||
|
let amount_to_remove = Self::mul_div(user_liq_tokens, balance.amount.0, 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 = 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 = sri.saturating_mul(distance_to_full_pay) / GENESIS_SRI_TRICKLE_FEED;
|
||||||
|
Coins::<T>::burn(
|
||||||
|
origin.clone().into(),
|
||||||
|
Balance { coin: Coin::Serai, amount: Amount(burn_sri_amount) },
|
||||||
|
)?;
|
||||||
|
sri -= burn_sri_amount;
|
||||||
|
|
||||||
|
// transfer to owner
|
||||||
|
let coin_out = current_coin.0 - 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
|
||||||
|
(
|
||||||
|
(
|
||||||
|
user_shares.checked_sub(amount_to_remove).ok_or(Error::<T>::AmountOverflowed)?,
|
||||||
|
user_coins.checked_sub(coin_out).ok_or(Error::<T>::AmountOverflowed)?,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
supply.0.checked_sub(amount_to_remove).ok_or(Error::<T>::AmountOverflowed)?,
|
||||||
|
supply.1.checked_sub(coin_out).ok_or(Error::<T>::AmountOverflowed)?,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if balance.amount.0 != 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.1) },
|
||||||
|
)?;
|
||||||
|
|
||||||
|
(
|
||||||
|
(0, 0),
|
||||||
|
(
|
||||||
|
supply.0.checked_sub(existing.0).ok_or(Error::<T>::AmountOverflowed)?,
|
||||||
|
supply.1.checked_sub(existing.1).ok_or(Error::<T>::AmountOverflowed)?,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// save
|
||||||
|
if new_shares.0 == 0 {
|
||||||
|
Liquidity::<T>::set(balance.coin, account, None);
|
||||||
|
} else {
|
||||||
|
Liquidity::<T>::set(balance.coin, account, Some(new_shares));
|
||||||
|
}
|
||||||
|
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 set_initial_price(
|
||||||
|
origin: OriginFor<T>,
|
||||||
|
prices: Prices,
|
||||||
|
_signature: Signature,
|
||||||
|
) -> DispatchResult {
|
||||||
|
ensure_none(origin)?;
|
||||||
|
|
||||||
|
// set the prices
|
||||||
|
Oracle::<T>::set(Coin::Bitcoin, Some(prices.bitcoin));
|
||||||
|
Oracle::<T>::set(Coin::Monero, Some(prices.monero));
|
||||||
|
Oracle::<T>::set(Coin::Ether, Some(prices.ethereum));
|
||||||
|
Oracle::<T>::set(Coin::Dai, Some(prices.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::set_initial_price { ref prices, ref signature } => {
|
||||||
|
// TODO: if this is supposed to be called after a month, serai set won't still be
|
||||||
|
// in the session 0? Ideally this should pull the session from Vs pallet?
|
||||||
|
let set = ValidatorSet { network: NetworkId::Serai, session: Session(0) };
|
||||||
|
let signers = ValidatorSets::<T>::participants_for_latest_decided_set(NetworkId::Serai)
|
||||||
|
.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(0))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure signers settings the price 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(1))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !musig_key(set, &signers).verify(&set_initial_price_message(&set, prices), 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.
|
||||||
40
substrate/genesis-liquidity/primitives/src/lib.rs
Normal file
40
substrate/genesis-liquidity/primitives/src/lib.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#![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 GENESIS_LP_SHARES: u64 = 10_000;
|
||||||
|
|
||||||
|
// This is the account to hold and manage the genesis liquidity.
|
||||||
|
pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"Genesis-liquidity-account");
|
||||||
|
|
||||||
|
#[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 Prices {
|
||||||
|
pub bitcoin: u64,
|
||||||
|
pub monero: u64,
|
||||||
|
pub ethereum: u64,
|
||||||
|
pub dai: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The message for the set_initial_price signature.
|
||||||
|
pub fn set_initial_price_message(set: &ValidatorSet, prices: &Prices) -> Vec<u8> {
|
||||||
|
(b"GenesisLiquidity-set_initial_price", set, prices).encode()
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ in-instructions-primitives = { package = "serai-in-instructions-primitives", pat
|
|||||||
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 = [
|
||||||
@@ -58,6 +59,7 @@ 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"]
|
||||||
|
|
||||||
|
|||||||
@@ -33,10 +33,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 +204,9 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
InInstruction::GenesisLiquidity(address) => {
|
||||||
|
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::set_initial_price { prices, signature } => {
|
||||||
|
RuntimeCall::GenesisLiquidity(genesis_liquidity::Call::set_initial_price {
|
||||||
|
prices,
|
||||||
|
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::set_initial_price { prices, signature } => {
|
||||||
|
serai_abi::genesis_liquidity::Call::set_initial_price { prices, 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user