mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Merge branch 'develop' into next
This is an initial resolution of conflicts which does not work.
This commit is contained in:
@@ -2,7 +2,7 @@ use sp_runtime::BoundedVec;
|
||||
|
||||
use serai_primitives::*;
|
||||
|
||||
type PoolId = Coin;
|
||||
type PoolId = ExternalCoin;
|
||||
type MaxSwapPathLength = sp_core::ConstU32<3>;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
@@ -10,7 +10,7 @@ type MaxSwapPathLength = sp_core::ConstU32<3>;
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
pub enum Call {
|
||||
add_liquidity {
|
||||
coin: Coin,
|
||||
coin: ExternalCoin,
|
||||
coin_desired: SubstrateAmount,
|
||||
sri_desired: SubstrateAmount,
|
||||
coin_min: SubstrateAmount,
|
||||
@@ -18,7 +18,7 @@ pub enum Call {
|
||||
mint_to: SeraiAddress,
|
||||
},
|
||||
remove_liquidity {
|
||||
coin: Coin,
|
||||
coin: ExternalCoin,
|
||||
lp_token_burn: SubstrateAmount,
|
||||
coin_min_receive: SubstrateAmount,
|
||||
sri_min_receive: SubstrateAmount,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use serai_primitives::NetworkId;
|
||||
use serai_primitives::ExternalNetworkId;
|
||||
|
||||
#[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 {
|
||||
EconomicSecurityReached { network: NetworkId },
|
||||
EconomicSecurityReached { network: ExternalNetworkId },
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ 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 },
|
||||
remove_coin_liquidity { balance: ExternalBalance },
|
||||
oraclize_values { values: Values, signature: Signature },
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ pub enum Call {
|
||||
#[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 },
|
||||
GenesisLiquidityAdded { by: SeraiAddress, balance: ExternalBalance },
|
||||
GenesisLiquidityRemoved { by: SeraiAddress, balance: ExternalBalance },
|
||||
GenesisLiquidityAddedToPool { coin: ExternalBalance, sri: Amount },
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ pub enum Call {
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
pub enum Event {
|
||||
Batch {
|
||||
network: NetworkId,
|
||||
network: ExternalNetworkId,
|
||||
publishing_session: Session,
|
||||
id: u32,
|
||||
external_network_block_hash: BlockHash,
|
||||
@@ -25,6 +25,6 @@ pub enum Event {
|
||||
in_instruction_results: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
|
||||
},
|
||||
Halt {
|
||||
network: NetworkId,
|
||||
network: ExternalNetworkId,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use serai_validator_sets_primitives::*;
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
pub enum Call {
|
||||
set_keys {
|
||||
network: NetworkId,
|
||||
network: ExternalNetworkId,
|
||||
key_pair: KeyPair,
|
||||
signature_participants: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
|
||||
signature: Signature,
|
||||
@@ -20,7 +20,7 @@ pub enum Call {
|
||||
key: BoundedVec<u8, ConstU32<{ MAX_KEY_LEN }>>,
|
||||
},
|
||||
report_slashes {
|
||||
network: NetworkId,
|
||||
network: ExternalNetworkId,
|
||||
slashes: SlashReport,
|
||||
signature: Signature,
|
||||
},
|
||||
@@ -51,7 +51,7 @@ pub enum Event {
|
||||
removed: SeraiAddress,
|
||||
},
|
||||
KeyGen {
|
||||
set: ValidatorSet,
|
||||
set: ExternalValidatorSet,
|
||||
key_pair: KeyPair,
|
||||
},
|
||||
AcceptedHandover {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use sp_core::bounded_vec::BoundedVec;
|
||||
use serai_abi::primitives::{SeraiAddress, Amount, Coin};
|
||||
use serai_abi::primitives::{Amount, Coin, ExternalCoin, SeraiAddress};
|
||||
|
||||
use crate::{SeraiError, TemporalSerai};
|
||||
|
||||
@@ -20,7 +20,7 @@ impl SeraiDex<'_> {
|
||||
}
|
||||
|
||||
pub fn add_liquidity(
|
||||
coin: Coin,
|
||||
coin: ExternalCoin,
|
||||
coin_amount: Amount,
|
||||
sri_amount: Amount,
|
||||
min_coin_amount: Amount,
|
||||
@@ -61,11 +61,14 @@ impl SeraiDex<'_> {
|
||||
}
|
||||
|
||||
/// Returns the reserves of `coin:SRI` pool.
|
||||
pub async fn get_reserves(&self, coin: Coin) -> Result<Option<(Amount, Amount)>, SeraiError> {
|
||||
self.0.runtime_api("DexApi_get_reserves", (coin, Coin::Serai)).await
|
||||
pub async fn get_reserves(
|
||||
&self,
|
||||
coin: ExternalCoin,
|
||||
) -> Result<Option<(Amount, Amount)>, SeraiError> {
|
||||
self.0.runtime_api("DexApi_get_reserves", (Coin::from(coin), Coin::Serai)).await
|
||||
}
|
||||
|
||||
pub async fn oracle_value(&self, coin: Coin) -> Result<Option<Amount>, SeraiError> {
|
||||
pub async fn oracle_value(&self, coin: ExternalCoin) -> Result<Option<Amount>, SeraiError> {
|
||||
self.0.storage(PALLET, "SecurityOracleValue", coin).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ impl SeraiGenesisLiquidity<'_> {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn remove_coin_liquidity(balance: Balance) -> serai_abi::Call {
|
||||
pub fn remove_coin_liquidity(balance: ExternalBalance) -> serai_abi::Call {
|
||||
serai_abi::Call::GenesisLiquidity(serai_abi::genesis_liquidity::Call::remove_coin_liquidity {
|
||||
balance,
|
||||
})
|
||||
@@ -44,7 +44,7 @@ impl SeraiGenesisLiquidity<'_> {
|
||||
pub async fn liquidity(
|
||||
&self,
|
||||
address: &SeraiAddress,
|
||||
coin: Coin,
|
||||
coin: ExternalCoin,
|
||||
) -> Result<LiquidityAmount, SeraiError> {
|
||||
Ok(
|
||||
self
|
||||
@@ -59,7 +59,7 @@ impl SeraiGenesisLiquidity<'_> {
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn supply(&self, coin: Coin) -> Result<LiquidityAmount, SeraiError> {
|
||||
pub async fn supply(&self, coin: ExternalCoin) -> Result<LiquidityAmount, SeraiError> {
|
||||
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(LiquidityAmount::zero()))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
pub use serai_abi::in_instructions::primitives;
|
||||
use primitives::SignedBatch;
|
||||
|
||||
use crate::{primitives::NetworkId, Transaction, SeraiError, Serai, TemporalSerai};
|
||||
use crate::{
|
||||
primitives::{BlockHash, ExternalNetworkId},
|
||||
Transaction, SeraiError, Serai, TemporalSerai,
|
||||
};
|
||||
|
||||
pub type InInstructionsEvent = serai_abi::in_instructions::Event;
|
||||
|
||||
@@ -9,10 +12,9 @@ const PALLET: &str = "InInstructions";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SeraiInInstructions<'a>(pub(crate) &'a TemporalSerai<'a>);
|
||||
impl SeraiInInstructions<'_> {
|
||||
pub async fn last_batch_for_network(
|
||||
&self,
|
||||
network: NetworkId,
|
||||
network: ExternalNetworkId,
|
||||
) -> Result<Option<u32>, SeraiError> {
|
||||
self.0.storage(PALLET, "LastBatch", network).await
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use scale::Encode;
|
||||
|
||||
use serai_abi::primitives::{SeraiAddress, Amount, Coin, Balance};
|
||||
use serai_abi::primitives::{Amount, ExternalBalance, ExternalCoin, SeraiAddress};
|
||||
|
||||
use crate::{TemporalSerai, SeraiError};
|
||||
|
||||
@@ -9,13 +9,13 @@ const PALLET: &str = "LiquidityTokens";
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SeraiLiquidityTokens<'a>(pub(crate) &'a TemporalSerai<'a>);
|
||||
impl SeraiLiquidityTokens<'_> {
|
||||
pub async fn token_supply(&self, coin: Coin) -> Result<Amount, SeraiError> {
|
||||
pub async fn token_supply(&self, coin: ExternalCoin) -> Result<Amount, SeraiError> {
|
||||
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(Amount(0)))
|
||||
}
|
||||
|
||||
pub async fn token_balance(
|
||||
&self,
|
||||
coin: Coin,
|
||||
coin: ExternalCoin,
|
||||
address: SeraiAddress,
|
||||
) -> Result<Amount, SeraiError> {
|
||||
Ok(
|
||||
@@ -31,11 +31,16 @@ impl SeraiLiquidityTokens<'_> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn transfer(to: SeraiAddress, balance: Balance) -> serai_abi::Call {
|
||||
serai_abi::Call::LiquidityTokens(serai_abi::liquidity_tokens::Call::transfer { to, balance })
|
||||
pub fn transfer(to: SeraiAddress, balance: ExternalBalance) -> serai_abi::Call {
|
||||
serai_abi::Call::LiquidityTokens(serai_abi::liquidity_tokens::Call::transfer {
|
||||
to,
|
||||
balance: balance.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn burn(balance: Balance) -> serai_abi::Call {
|
||||
serai_abi::Call::LiquidityTokens(serai_abi::liquidity_tokens::Call::burn { balance })
|
||||
pub fn burn(balance: ExternalBalance) -> serai_abi::Call {
|
||||
serai_abi::Call::LiquidityTokens(serai_abi::liquidity_tokens::Call::burn {
|
||||
balance: balance.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ use scale::Encode;
|
||||
use sp_core::sr25519::{Public, Signature};
|
||||
use sp_runtime::BoundedVec;
|
||||
|
||||
use serai_abi::primitives::Amount;
|
||||
use serai_abi::{primitives::Amount, validator_sets::primitives::ExternalValidatorSet};
|
||||
pub use serai_abi::validator_sets::primitives;
|
||||
use primitives::{MAX_KEY_LEN, Session, ValidatorSet, KeyPair, SlashReport};
|
||||
|
||||
use crate::{
|
||||
primitives::{EmbeddedEllipticCurve, NetworkId},
|
||||
primitives::{NetworkId, ExternalNetworkId, EmbeddedEllipticCurve, SeraiAddress},
|
||||
Transaction, Serai, TemporalSerai, SeraiError,
|
||||
};
|
||||
|
||||
@@ -183,13 +183,13 @@ impl SeraiValidatorSets<'_> {
|
||||
}
|
||||
|
||||
// TODO: Store these separately since we almost never need both at once?
|
||||
pub async fn keys(&self, set: ValidatorSet) -> Result<Option<KeyPair>, SeraiError> {
|
||||
pub async fn keys(&self, set: ExternalValidatorSet) -> Result<Option<KeyPair>, SeraiError> {
|
||||
self.0.storage(PALLET, "Keys", (sp_core::hashing::twox_64(&set.encode()), set)).await
|
||||
}
|
||||
|
||||
pub async fn key_pending_slash_report(
|
||||
&self,
|
||||
network: NetworkId,
|
||||
network: ExternalNetworkId,
|
||||
) -> Result<Option<Public>, SeraiError> {
|
||||
self.0.storage(PALLET, "PendingSlashReport", network).await
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use blake2::{
|
||||
use scale::Encode;
|
||||
|
||||
use serai_client::{
|
||||
primitives::{BlockHash, NetworkId, Coin, Amount, Balance, SeraiAddress},
|
||||
primitives::{BlockHash, NetworkId, ExternalCoin, Amount, ExternalBalance, SeraiAddress},
|
||||
coins::CoinsEvent,
|
||||
validator_sets::primitives::Session,
|
||||
in_instructions::{
|
||||
@@ -23,15 +23,15 @@ use common::in_instructions::provide_batch;
|
||||
|
||||
serai_test!(
|
||||
publish_batch: (|serai: Serai| async move {
|
||||
let network = NetworkId::Bitcoin;
|
||||
let id = 0;
|
||||
|
||||
let mut address = SeraiAddress::new([0; 32]);
|
||||
OsRng.fill_bytes(&mut address.0);
|
||||
|
||||
let coin = Coin::Bitcoin;
|
||||
let coin = ExternalCoin::Bitcoin;
|
||||
let network = coin.network();
|
||||
let amount = Amount(OsRng.next_u64().saturating_add(1));
|
||||
let balance = Balance { coin, amount };
|
||||
let balance = ExternalBalance { coin, amount };
|
||||
|
||||
let mut external_network_block_hash = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut external_network_block_hash.0);
|
||||
@@ -68,9 +68,9 @@ serai_test!(
|
||||
let serai = serai.coins();
|
||||
assert_eq!(
|
||||
serai.mint_events().await.unwrap(),
|
||||
vec![CoinsEvent::Mint { to: address, balance }]
|
||||
vec![CoinsEvent::Mint { to: address, balance: balance.into() }]
|
||||
);
|
||||
assert_eq!(serai.coin_supply(coin).await.unwrap(), amount);
|
||||
assert_eq!(serai.coin_balance(coin, address).await.unwrap(), amount);
|
||||
assert_eq!(serai.coin_supply(coin.into()).await.unwrap(), amount);
|
||||
assert_eq!(serai.coin_balance(coin.into(), address).await.unwrap(), amount);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ use sp_core::Pair;
|
||||
|
||||
use serai_client::{
|
||||
primitives::{
|
||||
BlockHash, NetworkId, Coin, Amount, Balance, SeraiAddress, ExternalAddress,
|
||||
BlockHash, ExternalNetworkId, ExternalCoin, Amount, ExternalBalance, SeraiAddress, ExternalAddress,
|
||||
insecure_pair_from_name,
|
||||
},
|
||||
coins::{
|
||||
@@ -31,9 +31,7 @@ use common::{tx::publish_tx, in_instructions::provide_batch};
|
||||
|
||||
serai_test!(
|
||||
burn: (|serai: Serai| async move {
|
||||
let network = NetworkId::Bitcoin;
|
||||
let id = 0;
|
||||
|
||||
let mut block_hash = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut block_hash.0);
|
||||
|
||||
@@ -41,9 +39,10 @@ serai_test!(
|
||||
let public = pair.public();
|
||||
let address = SeraiAddress::from(public);
|
||||
|
||||
let coin = Coin::Bitcoin;
|
||||
let coin = ExternalCoin::Bitcoin;
|
||||
let network = coin.network();
|
||||
let amount = Amount(OsRng.next_u64().saturating_add(1));
|
||||
let balance = Balance { coin, amount };
|
||||
let balance = ExternalBalance { coin, amount };
|
||||
|
||||
let batch = Batch {
|
||||
network,
|
||||
@@ -72,12 +71,12 @@ serai_test!(
|
||||
}]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
serai.coins().mint_events().await.unwrap(),
|
||||
vec![CoinsEvent::Mint { to: address, balance }]
|
||||
);
|
||||
assert_eq!(serai.coins().coin_supply(coin).await.unwrap(), amount);
|
||||
assert_eq!(serai.coins().coin_balance(coin, address).await.unwrap(), amount);
|
||||
assert_eq!(
|
||||
serai.coins().mint_events().await.unwrap(),
|
||||
vec![CoinsEvent::Mint { to: address, balance: balance.into() }]
|
||||
);
|
||||
assert_eq!(serai.coins().coin_supply(coin.into()).await.unwrap(), amount);
|
||||
assert_eq!(serai.coins().coin_balance(coin.into(), address).await.unwrap(), amount);
|
||||
|
||||
// Now burn it
|
||||
let mut rand_bytes = vec![0; 32];
|
||||
@@ -100,7 +99,7 @@ serai_test!(
|
||||
let serai = serai.coins();
|
||||
let events = serai.burn_with_instruction_events().await.unwrap();
|
||||
assert_eq!(events, vec![CoinsEvent::BurnWithInstruction { from: address, instruction }]);
|
||||
assert_eq!(serai.coin_supply(coin).await.unwrap(), Amount(0));
|
||||
assert_eq!(serai.coin_balance(coin, address).await.unwrap(), Amount(0));
|
||||
assert_eq!(serai.coin_supply(coin.into()).await.unwrap(), Amount(0));
|
||||
assert_eq!(serai.coin_balance(coin.into(), address).await.unwrap(), Amount(0));
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use serai_abi::primitives::{Coin, Amount};
|
||||
use serai_abi::primitives::{Amount, Coin, ExternalCoin};
|
||||
|
||||
use serai_client::{Serai, SeraiDex};
|
||||
use sp_core::{sr25519::Pair, Pair as PairTrait};
|
||||
@@ -8,7 +8,7 @@ use crate::common::tx::publish_tx;
|
||||
#[allow(dead_code)]
|
||||
pub async fn add_liquidity(
|
||||
serai: &Serai,
|
||||
coin: Coin,
|
||||
coin: ExternalCoin,
|
||||
coin_amount: Amount,
|
||||
sri_amount: Amount,
|
||||
nonce: u32,
|
||||
|
||||
@@ -11,7 +11,7 @@ use sp_core::{sr25519::Signature, Pair as PairTrait};
|
||||
|
||||
use serai_abi::{
|
||||
primitives::{
|
||||
BlockHash, NetworkId, Coin, Amount, Balance, SeraiAddress, insecure_pair_from_name,
|
||||
BlockHash, ExternalNetworkId, ExternalCoin, Amount, ExternalBalance, SeraiAddress, insecure_pair_from_name,
|
||||
},
|
||||
validator_sets::primitives::{musig_context, Session, ValidatorSet},
|
||||
genesis_liquidity::primitives::{oraclize_values_message, Values},
|
||||
@@ -25,12 +25,11 @@ use crate::common::{in_instructions::provide_batch, tx::publish_tx};
|
||||
#[allow(dead_code)]
|
||||
pub async fn set_up_genesis(
|
||||
serai: &Serai,
|
||||
coins: &[Coin],
|
||||
values: &HashMap<Coin, u64>,
|
||||
) -> (HashMap<Coin, Vec<(SeraiAddress, Amount)>>, HashMap<NetworkId, u32>) {
|
||||
values: &HashMap<ExternalCoin, u64>,
|
||||
) -> (HashMap<ExternalCoin, Vec<(SeraiAddress, Amount)>>, HashMap<ExternalNetworkId, u32>) {
|
||||
// make accounts with amounts
|
||||
let mut accounts = HashMap::new();
|
||||
for coin in coins {
|
||||
for coin in EXTERNAL_COINS {
|
||||
// make 5 accounts per coin
|
||||
let mut values = vec![];
|
||||
for _ in 0 .. 5 {
|
||||
@@ -38,18 +37,18 @@ pub async fn set_up_genesis(
|
||||
OsRng.fill_bytes(&mut address.0);
|
||||
values.push((address, Amount(OsRng.next_u64() % 10u64.pow(coin.decimals()))));
|
||||
}
|
||||
accounts.insert(*coin, values);
|
||||
accounts.insert(coin, values);
|
||||
}
|
||||
|
||||
// send a batch per coin
|
||||
let mut batch_ids: HashMap<NetworkId, u32> = HashMap::new();
|
||||
for coin in coins {
|
||||
let mut batch_ids: HashMap<ExternalNetworkId, u32> = HashMap::new();
|
||||
for coin in EXTERNAL_COINS {
|
||||
// set up instructions
|
||||
let instructions = accounts[coin]
|
||||
let instructions = accounts[&coin]
|
||||
.iter()
|
||||
.map(|(addr, amount)| InInstructionWithBalance {
|
||||
instruction: InInstruction::GenesisLiquidity(*addr),
|
||||
balance: Balance { coin: *coin, amount: *amount },
|
||||
balance: ExternalBalance { coin, amount: *amount },
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -77,8 +76,11 @@ pub async fn set_up_genesis(
|
||||
// set values relative to each other. We can do that without checking for genesis period blocks
|
||||
// since we are running in test(fast-epoch) mode.
|
||||
// TODO: Random values here
|
||||
let values =
|
||||
Values { monero: values[&Coin::Monero], ether: values[&Coin::Ether], dai: values[&Coin::Dai] };
|
||||
let values = Values {
|
||||
monero: values[&ExternalCoin::Monero],
|
||||
ether: values[&ExternalCoin::Ether],
|
||||
dai: values[&ExternalCoin::Dai],
|
||||
};
|
||||
set_values(serai, &values).await;
|
||||
|
||||
(accounts, batch_ids)
|
||||
|
||||
@@ -9,8 +9,8 @@ use scale::Encode;
|
||||
use sp_core::Pair;
|
||||
|
||||
use serai_client::{
|
||||
primitives::{BlockHash, NetworkId, Balance, SeraiAddress, insecure_pair_from_name},
|
||||
validator_sets::primitives::{ValidatorSet, KeyPair},
|
||||
primitives::{BlockHash, NetworkId, ExternalBalance, SeraiAddress, insecure_pair_from_name},
|
||||
validator_sets::primitives::{ExternalValidatorSet, KeyPair},
|
||||
in_instructions::{
|
||||
primitives::{Batch, SignedBatch, batch_message, InInstruction, InInstructionWithBalance},
|
||||
InInstructionsEvent,
|
||||
@@ -23,8 +23,8 @@ use crate::common::{tx::publish_tx, validator_sets::set_keys};
|
||||
#[allow(dead_code)]
|
||||
pub async fn provide_batch(serai: &Serai, batch: Batch) -> [u8; 32] {
|
||||
let serai_latest = serai.as_of_latest_finalized_block().await.unwrap();
|
||||
let session = serai_latest.validator_sets().session(batch.network).await.unwrap().unwrap();
|
||||
let set = ValidatorSet { session, network: batch.network };
|
||||
let session = serai_latest.validator_sets().session(batch.network.into()).await.unwrap().unwrap();
|
||||
let set = ExternalValidatorSet { session, network: batch.network };
|
||||
|
||||
let pair = insecure_pair_from_name(&format!("ValidatorSet {set:?}"));
|
||||
let keys = if let Some(keys) = serai_latest.validator_sets().keys(set).await.unwrap() {
|
||||
@@ -77,8 +77,7 @@ pub async fn provide_batch(serai: &Serai, batch: Batch) -> [u8; 32] {
|
||||
#[allow(dead_code)]
|
||||
pub async fn mint_coin(
|
||||
serai: &Serai,
|
||||
balance: Balance,
|
||||
network: NetworkId,
|
||||
balance: ExternalBalance,
|
||||
batch_id: u32,
|
||||
address: SeraiAddress,
|
||||
) -> [u8; 32] {
|
||||
@@ -86,7 +85,7 @@ pub async fn mint_coin(
|
||||
OsRng.fill_bytes(&mut block_hash.0);
|
||||
|
||||
let batch = Batch {
|
||||
network,
|
||||
network: balance.coin.network(),
|
||||
id: batch_id,
|
||||
external_network_block_hash: block_hash,
|
||||
instructions: vec![InInstructionWithBalance {
|
||||
|
||||
@@ -18,7 +18,7 @@ use schnorrkel::Schnorrkel;
|
||||
use serai_client::{
|
||||
primitives::EmbeddedEllipticCurve,
|
||||
validator_sets::{
|
||||
primitives::{MAX_KEY_LEN, ValidatorSet, KeyPair, musig_context, set_keys_message},
|
||||
primitives::{MAX_KEY_LEN, ExternalValidatorSet, KeyPair, musig_context, set_keys_message},
|
||||
ValidatorSetsEvent,
|
||||
},
|
||||
Amount, Serai, SeraiValidatorSets,
|
||||
@@ -29,7 +29,7 @@ use crate::common::tx::publish_tx;
|
||||
#[allow(dead_code)]
|
||||
pub async fn set_keys(
|
||||
serai: &Serai,
|
||||
set: ValidatorSet,
|
||||
set: ExternalValidatorSet,
|
||||
key_pair: KeyPair,
|
||||
pairs: &[Pair],
|
||||
) -> [u8; 32] {
|
||||
@@ -49,7 +49,8 @@ pub async fn set_keys(
|
||||
assert_eq!(Ristretto::generator() * secret_key, pub_keys[i]);
|
||||
|
||||
threshold_keys.push(
|
||||
musig::<Ristretto>(&musig_context(set), &Zeroizing::new(secret_key), &pub_keys).unwrap(),
|
||||
musig::<Ristretto>(&musig_context(set.into()), &Zeroizing::new(secret_key), &pub_keys)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,15 +28,14 @@ use common::{
|
||||
// TODO: Check Transfer events
|
||||
serai_test!(
|
||||
add_liquidity: (|serai: Serai| async move {
|
||||
let coin = Coin::Monero;
|
||||
let coin = ExternalCoin::Monero;
|
||||
let pair = insecure_pair_from_name("Ferdie");
|
||||
|
||||
// mint sriXMR in the account so that we can add liq.
|
||||
// Ferdie account is already pre-funded with SRI.
|
||||
mint_coin(
|
||||
&serai,
|
||||
Balance { coin, amount: Amount(100_000_000_000_000) },
|
||||
NetworkId::Monero,
|
||||
ExternalBalance { coin, amount: Amount(100_000_000_000_000) },
|
||||
0,
|
||||
pair.clone().public().into(),
|
||||
)
|
||||
@@ -61,7 +60,7 @@ serai_test!(
|
||||
vec![DexEvent::LiquidityAdded {
|
||||
who: pair.public().into(),
|
||||
mint_to: pair.public().into(),
|
||||
pool_id: Coin::Monero,
|
||||
pool_id: coin,
|
||||
coin_amount: coin_amount.0,
|
||||
sri_amount: sri_amount.0,
|
||||
lp_token_minted: 49_999999990000
|
||||
@@ -71,15 +70,14 @@ serai_test!(
|
||||
|
||||
// Tests coin -> SRI and SRI -> coin swaps.
|
||||
swap_coin_to_sri: (|serai: Serai| async move {
|
||||
let coin = Coin::Ether;
|
||||
let coin = ExternalCoin::Ether;
|
||||
let pair = insecure_pair_from_name("Ferdie");
|
||||
|
||||
// mint sriXMR in the account so that we can add liq.
|
||||
// Ferdie account is already pre-funded with SRI.
|
||||
mint_coin(
|
||||
&serai,
|
||||
Balance { coin, amount: Amount(100_000_000_000_000) },
|
||||
NetworkId::Ethereum,
|
||||
ExternalBalance { coin, amount: Amount(100_000_000_000_000) },
|
||||
0,
|
||||
pair.clone().public().into(),
|
||||
)
|
||||
@@ -96,14 +94,21 @@ serai_test!(
|
||||
|
||||
// now that we have our liquid pool, swap some coin to SRI.
|
||||
let mut amount_in = Amount(25_000_000_000_000);
|
||||
let mut block = common_swap(&serai, coin, Coin::Serai, amount_in, Amount(1), 1, pair.clone())
|
||||
let mut block = common_swap(
|
||||
&serai,
|
||||
coin.into(),
|
||||
Coin::Serai,
|
||||
amount_in,
|
||||
Amount(1),
|
||||
1,
|
||||
pair.clone())
|
||||
.await;
|
||||
|
||||
// get only the swap events
|
||||
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
||||
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||
|
||||
let mut path = BoundedVec::try_from(vec![coin, Coin::Serai]).unwrap();
|
||||
let mut path = BoundedVec::try_from(vec![coin.into(), Coin::Serai]).unwrap();
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![DexEvent::SwapExecuted {
|
||||
@@ -117,13 +122,21 @@ serai_test!(
|
||||
|
||||
// now swap some SRI to coin
|
||||
amount_in = Amount(10_000_000_000_000);
|
||||
block = common_swap(&serai, Coin::Serai, coin, amount_in, Amount(1), 2, pair.clone()).await;
|
||||
block = common_swap(
|
||||
&serai,
|
||||
Coin::Serai,
|
||||
coin.into(),
|
||||
amount_in,
|
||||
Amount(1),
|
||||
2,
|
||||
pair.clone()
|
||||
).await;
|
||||
|
||||
// get only the swap events
|
||||
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
||||
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||
|
||||
path = BoundedVec::try_from(vec![Coin::Serai, coin]).unwrap();
|
||||
path = BoundedVec::try_from(vec![Coin::Serai, coin.into()]).unwrap();
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![DexEvent::SwapExecuted {
|
||||
@@ -137,23 +150,21 @@ serai_test!(
|
||||
})
|
||||
|
||||
swap_coin_to_coin: (|serai: Serai| async move {
|
||||
let coin1 = Coin::Monero;
|
||||
let coin2 = Coin::Dai;
|
||||
let coin1 = ExternalCoin::Monero;
|
||||
let coin2 = ExternalCoin::Dai;
|
||||
let pair = insecure_pair_from_name("Ferdie");
|
||||
|
||||
// mint coins
|
||||
mint_coin(
|
||||
&serai,
|
||||
Balance { coin: coin1, amount: Amount(100_000_000_000_000) },
|
||||
NetworkId::Monero,
|
||||
ExternalBalance { coin: coin1, amount: Amount(100_000_000_000_000) },
|
||||
0,
|
||||
pair.clone().public().into(),
|
||||
)
|
||||
.await;
|
||||
mint_coin(
|
||||
&serai,
|
||||
Balance { coin: coin2, amount: Amount(100_000_000_000_000) },
|
||||
NetworkId::Ethereum,
|
||||
ExternalBalance { coin: coin2, amount: Amount(100_000_000_000_000) },
|
||||
0,
|
||||
pair.clone().public().into(),
|
||||
)
|
||||
@@ -177,13 +188,21 @@ serai_test!(
|
||||
|
||||
// swap coin1 -> coin2
|
||||
let amount_in = Amount(25_000_000_000_000);
|
||||
let block = common_swap(&serai, coin1, coin2, amount_in, Amount(1), 2, pair.clone()).await;
|
||||
let block = common_swap(
|
||||
&serai,
|
||||
coin1.into(),
|
||||
coin2.into(),
|
||||
amount_in,
|
||||
Amount(1),
|
||||
2,
|
||||
pair.clone()
|
||||
).await;
|
||||
|
||||
// get only the swap events
|
||||
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
||||
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||
|
||||
let path = BoundedVec::try_from(vec![coin1, Coin::Serai, coin2]).unwrap();
|
||||
let path = BoundedVec::try_from(vec![coin1.into(), Coin::Serai, coin2.into()]).unwrap();
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![DexEvent::SwapExecuted {
|
||||
@@ -197,7 +216,7 @@ serai_test!(
|
||||
})
|
||||
|
||||
add_liquidity_in_instructions: (|serai: Serai| async move {
|
||||
let coin = Coin::Bitcoin;
|
||||
let coin = ExternalCoin::Bitcoin;
|
||||
let pair = insecure_pair_from_name("Ferdie");
|
||||
let mut batch_id = 0;
|
||||
|
||||
@@ -205,8 +224,7 @@ serai_test!(
|
||||
// Ferdie account is already pre-funded with SRI.
|
||||
mint_coin(
|
||||
&serai,
|
||||
Balance { coin, amount: Amount(100_000_000_000_000) },
|
||||
NetworkId::Bitcoin,
|
||||
ExternalBalance { coin, amount: Amount(100_000_000_000_000) },
|
||||
batch_id,
|
||||
pair.clone().public().into(),
|
||||
)
|
||||
@@ -227,12 +245,12 @@ serai_test!(
|
||||
let mut block_hash = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut block_hash.0);
|
||||
let batch = Batch {
|
||||
network: NetworkId::Bitcoin,
|
||||
network: coin.network(),
|
||||
id: batch_id,
|
||||
external_network_block_hash: block_hash,
|
||||
instructions: vec![InInstructionWithBalance {
|
||||
instruction: InInstruction::Dex(DexCall::SwapAndAddLiquidity(pair.public().into())),
|
||||
balance: Balance { coin: Coin::Bitcoin, amount: Amount(20_000_000_000_000) },
|
||||
balance: ExternalBalance { coin, amount: Amount(20_000_000_000_000) },
|
||||
}],
|
||||
};
|
||||
|
||||
@@ -244,7 +262,7 @@ serai_test!(
|
||||
vec![DexEvent::LiquidityAdded {
|
||||
who: IN_INSTRUCTION_EXECUTOR,
|
||||
mint_to: pair.public().into(),
|
||||
pool_id: Coin::Bitcoin,
|
||||
pool_id: coin,
|
||||
coin_amount: 10_000_000_000_000, // half of sent amount
|
||||
sri_amount: 111_333_778_668,
|
||||
lp_token_minted: 1_054_092_553_383
|
||||
@@ -253,8 +271,8 @@ serai_test!(
|
||||
})
|
||||
|
||||
swap_in_instructions: (|serai: Serai| async move {
|
||||
let coin1 = Coin::Monero;
|
||||
let coin2 = Coin::Ether;
|
||||
let coin1 = ExternalCoin::Monero;
|
||||
let coin2 = ExternalCoin::Ether;
|
||||
let pair = insecure_pair_from_name("Ferdie");
|
||||
let mut coin1_batch_id = 0;
|
||||
let mut coin2_batch_id = 0;
|
||||
@@ -262,8 +280,7 @@ serai_test!(
|
||||
// mint coins
|
||||
mint_coin(
|
||||
&serai,
|
||||
Balance { coin: coin1, amount: Amount(10_000_000_000_000_000) },
|
||||
NetworkId::Monero,
|
||||
ExternalBalance { coin: coin1, amount: Amount(10_000_000_000_000_000) },
|
||||
coin1_batch_id,
|
||||
pair.clone().public().into(),
|
||||
)
|
||||
@@ -271,8 +288,7 @@ serai_test!(
|
||||
coin1_batch_id += 1;
|
||||
mint_coin(
|
||||
&serai,
|
||||
Balance { coin: coin2, amount: Amount(100_000_000_000_000) },
|
||||
NetworkId::Ethereum,
|
||||
ExternalBalance { coin: coin2, amount: Amount(100_000_000_000_000) },
|
||||
coin2_batch_id,
|
||||
pair.clone().public().into(),
|
||||
)
|
||||
@@ -305,18 +321,18 @@ serai_test!(
|
||||
let out_address = OutAddress::External(ExternalAddress::new(rand_bytes.clone()).unwrap());
|
||||
|
||||
// amount is the min out amount
|
||||
let out_balance = Balance { coin: coin2, amount: Amount(1) };
|
||||
let out_balance = Balance { coin: coin2.into(), amount: Amount(1) };
|
||||
|
||||
// now that we have our pools, we can try to swap
|
||||
let mut block_hash = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut block_hash.0);
|
||||
let batch = Batch {
|
||||
network: NetworkId::Monero,
|
||||
network: coin1.network(),
|
||||
id: coin1_batch_id,
|
||||
external_network_block_hash: block_hash,
|
||||
instructions: vec![InInstructionWithBalance {
|
||||
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address)),
|
||||
balance: Balance { coin: coin1, amount: Amount(200_000_000_000_000) },
|
||||
balance: ExternalBalance { coin: coin1, amount: Amount(200_000_000_000_000) },
|
||||
}],
|
||||
};
|
||||
|
||||
@@ -325,7 +341,7 @@ serai_test!(
|
||||
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
||||
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||
|
||||
let path = BoundedVec::try_from(vec![coin1, Coin::Serai, coin2]).unwrap();
|
||||
let path = BoundedVec::try_from(vec![coin1.into(), Coin::Serai, coin2.into()]).unwrap();
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![DexEvent::SwapExecuted {
|
||||
@@ -345,18 +361,18 @@ serai_test!(
|
||||
OutAddress::Serai(SeraiAddress::new(rand_bytes.clone().try_into().unwrap()));
|
||||
|
||||
// amount is the min out amount
|
||||
let out_balance = Balance { coin: coin1, amount: Amount(1) };
|
||||
let out_balance = Balance { coin: coin1.into(), amount: Amount(1) };
|
||||
|
||||
// now that we have our pools, we can try to swap
|
||||
let mut block_hash = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut block_hash.0);
|
||||
let batch = Batch {
|
||||
network: NetworkId::Ethereum,
|
||||
network: coin2.network(),
|
||||
id: coin2_batch_id,
|
||||
external_network_block_hash: block_hash,
|
||||
instructions: vec![InInstructionWithBalance {
|
||||
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())),
|
||||
balance: Balance { coin: coin2, amount: Amount(200_000_000_000) },
|
||||
balance: ExternalBalance { coin: coin2, amount: Amount(200_000_000_000) },
|
||||
}],
|
||||
};
|
||||
|
||||
@@ -364,7 +380,7 @@ serai_test!(
|
||||
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
||||
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||
|
||||
let path = BoundedVec::try_from(vec![coin2, Coin::Serai, coin1]).unwrap();
|
||||
let path = BoundedVec::try_from(vec![coin2.into(), Coin::Serai, coin1.into()]).unwrap();
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![DexEvent::SwapExecuted {
|
||||
@@ -389,12 +405,12 @@ serai_test!(
|
||||
let mut block_hash = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut block_hash.0);
|
||||
let batch = Batch {
|
||||
network: NetworkId::Monero,
|
||||
network: coin1.network(),
|
||||
id: coin1_batch_id,
|
||||
external_network_block_hash: block_hash,
|
||||
instructions: vec![InInstructionWithBalance {
|
||||
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())),
|
||||
balance: Balance { coin: coin1, amount: Amount(100_000_000_000_000) },
|
||||
balance: ExternalBalance { coin: coin1, amount: Amount(100_000_000_000_000) },
|
||||
}],
|
||||
};
|
||||
|
||||
@@ -402,7 +418,7 @@ serai_test!(
|
||||
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
||||
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||
|
||||
let path = BoundedVec::try_from(vec![coin1, Coin::Serai]).unwrap();
|
||||
let path = BoundedVec::try_from(vec![coin1.into(), Coin::Serai]).unwrap();
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![DexEvent::SwapExecuted {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use serai_client::{primitives::NetworkId, Serai};
|
||||
use serai_client::{primitives::ExternalNetworkId, Serai};
|
||||
|
||||
#[tokio::test]
|
||||
async fn dht() {
|
||||
@@ -44,7 +44,7 @@ async fn dht() {
|
||||
assert!(!Serai::new(serai_rpc.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
.p2p_validators(NetworkId::Bitcoin)
|
||||
.p2p_validators(ExternalNetworkId::Bitcoin.into())
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty());
|
||||
|
||||
@@ -13,10 +13,7 @@ use serai_abi::{
|
||||
in_instructions::primitives::Batch,
|
||||
};
|
||||
|
||||
use serai_client::{
|
||||
primitives::{Amount, NetworkId, Balance},
|
||||
Serai,
|
||||
};
|
||||
use serai_client::Serai;
|
||||
|
||||
mod common;
|
||||
use common::{genesis_liquidity::set_up_genesis, in_instructions::provide_batch};
|
||||
@@ -27,20 +24,19 @@ serai_test_fast_epoch!(
|
||||
})
|
||||
);
|
||||
|
||||
async fn send_batches(serai: &Serai, ids: &mut HashMap<NetworkId, u32>) {
|
||||
for network in NETWORKS {
|
||||
if network != NetworkId::Serai {
|
||||
// set up batch id
|
||||
ids
|
||||
.entry(network)
|
||||
.and_modify(|v| {
|
||||
*v += 1;
|
||||
})
|
||||
.or_insert(0);
|
||||
async fn send_batches(serai: &Serai, ids: &mut HashMap<ExternalNetworkId, u32>) {
|
||||
for network in EXTERNAL_NETWORKS {
|
||||
// set up batch id
|
||||
ids
|
||||
.entry(network)
|
||||
.and_modify(|v| {
|
||||
*v += 1;
|
||||
})
|
||||
.or_insert(0);
|
||||
|
||||
// set up block hash
|
||||
let mut block = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut block.0);
|
||||
// set up block hash
|
||||
let mut block = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut block.0);
|
||||
|
||||
provide_batch(
|
||||
serai,
|
||||
@@ -58,9 +54,12 @@ async fn send_batches(serai: &Serai, ids: &mut HashMap<NetworkId, u32>) {
|
||||
|
||||
async fn test_emissions(serai: Serai) {
|
||||
// set up the genesis
|
||||
let coins = COINS.into_iter().filter(|c| *c != Coin::native()).collect::<Vec<_>>();
|
||||
let values = HashMap::from([(Coin::Monero, 184100), (Coin::Ether, 4785000), (Coin::Dai, 1500)]);
|
||||
let (_, mut batch_ids) = set_up_genesis(&serai, &coins, &values).await;
|
||||
let values = HashMap::from([
|
||||
(ExternalCoin::Monero, 184100),
|
||||
(ExternalCoin::Ether, 4785000),
|
||||
(ExternalCoin::Dai, 1500),
|
||||
]);
|
||||
let (_, mut batch_ids) = set_up_genesis(&serai, &values).await;
|
||||
|
||||
// wait until genesis is complete
|
||||
let mut genesis_complete_block = None;
|
||||
@@ -153,7 +152,7 @@ async fn test_emissions(serai: Serai) {
|
||||
}
|
||||
|
||||
/// Returns the required stake in terms SRI for a given `Balance`.
|
||||
async fn required_stake(serai: &TemporalSerai<'_>, balance: Balance) -> u64 {
|
||||
async fn required_stake(serai: &TemporalSerai<'_>, balance: ExternalBalance) -> u64 {
|
||||
// This is inclusive to an increase in accuracy
|
||||
let sri_per_coin = serai.dex().oracle_value(balance.coin).await.unwrap().unwrap_or(Amount(0));
|
||||
|
||||
@@ -217,18 +216,14 @@ async fn get_distances(
|
||||
// we can check the supply to see how much coin hence liability we have.
|
||||
let mut distances: HashMap<NetworkId, u64> = HashMap::new();
|
||||
let mut total_distance = 0;
|
||||
for n in NETWORKS {
|
||||
if n == NetworkId::Serai {
|
||||
continue;
|
||||
}
|
||||
|
||||
for n in EXTERNAL_NETWORKS {
|
||||
let mut required = 0;
|
||||
for c in n.coins() {
|
||||
let amount = serai.coins().coin_supply(*c).await.unwrap();
|
||||
required += required_stake(serai, Balance { coin: *c, amount }).await;
|
||||
let amount = serai.coins().coin_supply(c.into()).await.unwrap();
|
||||
required += required_stake(serai, ExternalBalance { coin: c, amount }).await;
|
||||
}
|
||||
|
||||
let mut current = *current_stake.get(&n).unwrap();
|
||||
let mut current = *current_stake.get(&n.into()).unwrap();
|
||||
if current > required {
|
||||
current = required;
|
||||
}
|
||||
@@ -236,7 +231,7 @@ async fn get_distances(
|
||||
let distance = required - current;
|
||||
total_distance += distance;
|
||||
|
||||
distances.insert(n, distance);
|
||||
distances.insert(n.into(), distance);
|
||||
}
|
||||
|
||||
// add serai network portion(20%)
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{time::Duration, collections::HashMap};
|
||||
|
||||
use serai_client::Serai;
|
||||
|
||||
use serai_abi::primitives::{Coin, COINS, Amount, GENESIS_SRI};
|
||||
use serai_abi::primitives::{Amount, Coin, ExternalCoin, COINS, EXTERNAL_COINS, GENESIS_SRI};
|
||||
|
||||
use serai_client::genesis_liquidity::primitives::{
|
||||
GENESIS_LIQUIDITY_ACCOUNT, INITIAL_GENESIS_LP_SHARES,
|
||||
@@ -19,9 +19,12 @@ serai_test_fast_epoch!(
|
||||
|
||||
pub async fn test_genesis_liquidity(serai: Serai) {
|
||||
// set up the genesis
|
||||
let coins = COINS.into_iter().filter(|c| *c != Coin::native()).collect::<Vec<_>>();
|
||||
let values = HashMap::from([(Coin::Monero, 184100), (Coin::Ether, 4785000), (Coin::Dai, 1500)]);
|
||||
let (accounts, _) = set_up_genesis(&serai, &coins, &values).await;
|
||||
let values = HashMap::from([
|
||||
(ExternalCoin::Monero, 184100),
|
||||
(ExternalCoin::Ether, 4785000),
|
||||
(ExternalCoin::Dai, 1500),
|
||||
]);
|
||||
let (accounts, _) = set_up_genesis(&serai, &values).await;
|
||||
|
||||
// wait until genesis is complete
|
||||
while serai
|
||||
@@ -55,9 +58,9 @@ pub async fn test_genesis_liquidity(serai: Serai) {
|
||||
// check pools has proper liquidity
|
||||
let mut pool_amounts = HashMap::new();
|
||||
let mut total_value = 0u128;
|
||||
for coin in coins.clone() {
|
||||
for coin in EXTERNAL_COINS {
|
||||
let total_coin = accounts[&coin].iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0));
|
||||
let value = if coin != Coin::Bitcoin {
|
||||
let value = if coin != ExternalCoin::Bitcoin {
|
||||
(total_coin * u128::from(values[&coin])) / 10u128.pow(coin.decimals())
|
||||
} else {
|
||||
total_coin
|
||||
@@ -69,8 +72,8 @@ pub async fn test_genesis_liquidity(serai: Serai) {
|
||||
|
||||
// check distributed SRI per pool
|
||||
let mut total_sri_distributed = 0u128;
|
||||
for coin in coins.clone() {
|
||||
let sri = if coin == *COINS.last().unwrap() {
|
||||
for coin in EXTERNAL_COINS {
|
||||
let sri = if coin == *EXTERNAL_COINS.last().unwrap() {
|
||||
u128::from(GENESIS_SRI).checked_sub(total_sri_distributed).unwrap()
|
||||
} else {
|
||||
(pool_amounts[&coin].1 * u128::from(GENESIS_SRI)) / total_value
|
||||
@@ -83,7 +86,7 @@ pub async fn test_genesis_liquidity(serai: Serai) {
|
||||
}
|
||||
|
||||
// check each liquidity provider got liquidity tokens proportional to their value
|
||||
for coin in coins {
|
||||
for coin in EXTERNAL_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;
|
||||
|
||||
@@ -11,14 +11,14 @@ use serai_client::{
|
||||
insecure_pair_from_name,
|
||||
},
|
||||
validator_sets::{
|
||||
primitives::{Session, ValidatorSet, KeyPair},
|
||||
primitives::{Session, ValidatorSet, ExternalValidatorSet, KeyPair},
|
||||
ValidatorSetsEvent,
|
||||
},
|
||||
in_instructions::{
|
||||
primitives::{Batch, SignedBatch, batch_message},
|
||||
SeraiInInstructions,
|
||||
},
|
||||
Amount, Serai,
|
||||
Serai,
|
||||
};
|
||||
|
||||
mod common;
|
||||
@@ -59,8 +59,8 @@ async fn get_ordered_keys(serai: &Serai, network: NetworkId, accounts: &[Pair])
|
||||
|
||||
serai_test!(
|
||||
set_keys_test: (|serai: Serai| async move {
|
||||
let network = NetworkId::Bitcoin;
|
||||
let set = ValidatorSet { session: Session(0), network };
|
||||
let network = ExternalNetworkId::Bitcoin;
|
||||
let set = ExternalValidatorSet { session: Session(0), network };
|
||||
|
||||
let pair = insecure_pair_from_name("Alice");
|
||||
let public = pair.public();
|
||||
@@ -90,7 +90,7 @@ serai_test!(
|
||||
{
|
||||
let vs_serai = serai.as_of_latest_finalized_block().await.unwrap();
|
||||
let vs_serai = vs_serai.validator_sets();
|
||||
let participants = vs_serai.participants(set.network).await
|
||||
let participants = vs_serai.participants(set.network.into()).await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
@@ -198,9 +198,9 @@ async fn validator_set_rotation() {
|
||||
// amounts for single key share per network
|
||||
let key_shares = HashMap::from([
|
||||
(NetworkId::Serai, Amount(50_000 * 10_u64.pow(8))),
|
||||
(NetworkId::Bitcoin, Amount(1_000_000 * 10_u64.pow(8))),
|
||||
(NetworkId::Monero, Amount(100_000 * 10_u64.pow(8))),
|
||||
(NetworkId::Ethereum, Amount(1_000_000 * 10_u64.pow(8))),
|
||||
(NetworkId::External(ExternalNetworkId::Bitcoin), Amount(1_000_000 * 10_u64.pow(8))),
|
||||
(NetworkId::External(ExternalNetworkId::Monero), Amount(100_000 * 10_u64.pow(8))),
|
||||
(NetworkId::External(ExternalNetworkId::Ethereum), Amount(1_000_000 * 10_u64.pow(8))),
|
||||
]);
|
||||
|
||||
// genesis participants per network
|
||||
@@ -209,9 +209,9 @@ async fn validator_set_rotation() {
|
||||
accounts[.. 4].to_vec().iter().map(|pair| pair.public()).collect::<Vec<_>>();
|
||||
let mut participants = HashMap::from([
|
||||
(NetworkId::Serai, default_participants.clone()),
|
||||
(NetworkId::Bitcoin, default_participants.clone()),
|
||||
(NetworkId::Monero, default_participants.clone()),
|
||||
(NetworkId::Ethereum, default_participants),
|
||||
(NetworkId::External(ExternalNetworkId::Bitcoin), default_participants.clone()),
|
||||
(NetworkId::External(ExternalNetworkId::Monero), default_participants.clone()),
|
||||
(NetworkId::External(ExternalNetworkId::Ethereum), default_participants),
|
||||
]);
|
||||
|
||||
// test the set rotation
|
||||
@@ -265,7 +265,8 @@ async fn validator_set_rotation() {
|
||||
|
||||
// set the keys if it is an external set
|
||||
if network != NetworkId::Serai {
|
||||
let set = ValidatorSet { session: Session(0), network };
|
||||
let set =
|
||||
ExternalValidatorSet { session: Session(0), network: network.try_into().unwrap() };
|
||||
let key_pair = get_random_key_pair();
|
||||
let pairs = get_ordered_keys(&serai, network, &accounts).await;
|
||||
set_keys(&serai, set, key_pair, &pairs).await;
|
||||
@@ -293,7 +294,8 @@ async fn validator_set_rotation() {
|
||||
|
||||
if network != NetworkId::Serai {
|
||||
// set the keys if it is an external set
|
||||
let set = ValidatorSet { session: Session(1), network };
|
||||
let set =
|
||||
ExternalValidatorSet { session: Session(1), network: network.try_into().unwrap() };
|
||||
|
||||
// we need the whole substrate key pair to sign the batch
|
||||
let (substrate_pair, key_pair) = {
|
||||
@@ -312,7 +314,7 @@ async fn validator_set_rotation() {
|
||||
let mut block_hash = BlockHash([0; 32]);
|
||||
OsRng.fill_bytes(&mut block_hash.0);
|
||||
let batch =
|
||||
Batch { network, id: 0, external_network_block_hash: block_hash, instructions: vec![] };
|
||||
Batch { network: network.try_into().unwrap(), id: 0, external_network_block_hash: block_hash, instructions: vec![] };
|
||||
publish_tx(
|
||||
&serai,
|
||||
&SeraiInInstructions::execute_batch(SignedBatch {
|
||||
|
||||
@@ -34,6 +34,9 @@ pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", d
|
||||
serai-primitives = { path = "../../primitives", default-features = false, features = ["serde"] }
|
||||
coins-primitives = { package = "serai-coins-primitives", path = "../primitives", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false, features = ["std"] }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"frame-system/std",
|
||||
@@ -49,8 +52,12 @@ std = [
|
||||
"coins-primitives/std",
|
||||
]
|
||||
|
||||
# TODO
|
||||
try-runtime = []
|
||||
try-runtime = [
|
||||
"frame-system/try-runtime",
|
||||
"frame-support/try-runtime",
|
||||
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
|
||||
runtime-benchmarks = [
|
||||
"frame-system/runtime-benchmarks",
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use serai_primitives::{Coin, SubstrateAmount, Balance};
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use serai_primitives::{Balance, Coin, ExternalBalance, SubstrateAmount};
|
||||
|
||||
pub trait AllowMint {
|
||||
fn is_allowed(balance: &Balance) -> bool;
|
||||
fn is_allowed(balance: &ExternalBalance) -> bool;
|
||||
}
|
||||
|
||||
impl AllowMint for () {
|
||||
fn is_allowed(_: &Balance) -> bool {
|
||||
fn is_allowed(_: &ExternalBalance) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -161,7 +167,10 @@ pub mod pallet {
|
||||
pub fn mint(to: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||
// If the coin isn't Serai, which we're always allowed to mint, and the mint isn't explicitly
|
||||
// allowed, error
|
||||
if (balance.coin != Coin::Serai) && (!T::AllowMint::is_allowed(&balance)) {
|
||||
if !ExternalCoin::try_from(balance.coin)
|
||||
.map(|coin| T::AllowMint::is_allowed(&ExternalBalance { coin, amount: balance.amount }))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
Err(Error::<T, I>::MintNotAllowed)?;
|
||||
}
|
||||
|
||||
@@ -230,22 +239,18 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
/// Burn `balance` with `OutInstructionWithBalance` from the caller.
|
||||
/// Errors if called for SRI or Instance1 instance of this pallet.
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||
pub fn burn_with_instruction(
|
||||
origin: OriginFor<T>,
|
||||
instruction: OutInstructionWithBalance,
|
||||
) -> DispatchResult {
|
||||
if instruction.balance.coin == Coin::Serai {
|
||||
Err(Error::<T, I>::BurnWithInstructionNotAllowed)?;
|
||||
}
|
||||
if TypeId::of::<I>() == TypeId::of::<LiquidityTokensInstance>() {
|
||||
Err(Error::<T, I>::BurnWithInstructionNotAllowed)?;
|
||||
}
|
||||
|
||||
let from = ensure_signed(origin)?;
|
||||
Self::burn_internal(from, instruction.balance)?;
|
||||
Self::burn_internal(from, instruction.balance.into())?;
|
||||
Self::deposit_event(Event::BurnWithInstruction { from, instruction });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
70
substrate/coins/pallet/src/mock.rs
Normal file
70
substrate/coins/pallet/src/mock.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
//! Test environment for Coins pallet.
|
||||
|
||||
use super::*;
|
||||
|
||||
use frame_support::{
|
||||
construct_runtime,
|
||||
traits::{ConstU32, ConstU64},
|
||||
};
|
||||
|
||||
use sp_core::{H256, sr25519::Public};
|
||||
use sp_runtime::{
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
BuildStorage,
|
||||
};
|
||||
|
||||
use crate as coins;
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system,
|
||||
Coins: coins,
|
||||
}
|
||||
);
|
||||
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Nonce = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = Public;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ConstU64<250>;
|
||||
type DbWeight = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = ConstU32<16>;
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
|
||||
type AllowMint = ();
|
||||
}
|
||||
|
||||
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
crate::GenesisConfig::<Test> { accounts: vec![], _ignore: Default::default() }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(0));
|
||||
ext
|
||||
}
|
||||
129
substrate/coins/pallet/src/tests.rs
Normal file
129
substrate/coins/pallet/src/tests.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use crate::{mock::*, primitives::*};
|
||||
|
||||
use frame_system::RawOrigin;
|
||||
use sp_core::Pair;
|
||||
|
||||
use serai_primitives::*;
|
||||
|
||||
pub type CoinsEvent = crate::Event<Test, ()>;
|
||||
|
||||
#[test]
|
||||
fn mint() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// minting u64::MAX should work
|
||||
let coin = Coin::Serai;
|
||||
let to = insecure_pair_from_name("random1").public();
|
||||
let balance = Balance { coin, amount: Amount(u64::MAX) };
|
||||
|
||||
Coins::mint(to, balance).unwrap();
|
||||
assert_eq!(Coins::balance(to, coin), balance.amount);
|
||||
|
||||
// minting more should fail
|
||||
assert!(Coins::mint(to, Balance { coin, amount: Amount(1) }).is_err());
|
||||
|
||||
// supply now should be equal to sum of the accounts balance sum
|
||||
assert_eq!(Coins::supply(coin), balance.amount.0);
|
||||
|
||||
// test events
|
||||
let mint_events = System::events()
|
||||
.iter()
|
||||
.filter_map(|event| {
|
||||
if let RuntimeEvent::Coins(e) = &event.event {
|
||||
if matches!(e, CoinsEvent::Mint { .. }) {
|
||||
Some(e.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(mint_events, vec![CoinsEvent::Mint { to, balance }]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn burn_with_instruction() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// mint some coin
|
||||
let coin = Coin::External(ExternalCoin::Bitcoin);
|
||||
let to = insecure_pair_from_name("random1").public();
|
||||
let balance = Balance { coin, amount: Amount(10 * 10u64.pow(coin.decimals())) };
|
||||
|
||||
Coins::mint(to, balance).unwrap();
|
||||
assert_eq!(Coins::balance(to, coin), balance.amount);
|
||||
assert_eq!(Coins::supply(coin), balance.amount.0);
|
||||
|
||||
// we shouldn't be able to burn more than what we have
|
||||
let mut instruction = OutInstructionWithBalance {
|
||||
instruction: OutInstruction { address: ExternalAddress::new(vec![]).unwrap(), data: None },
|
||||
balance: ExternalBalance {
|
||||
coin: coin.try_into().unwrap(),
|
||||
amount: Amount(balance.amount.0 + 1),
|
||||
},
|
||||
};
|
||||
assert!(
|
||||
Coins::burn_with_instruction(RawOrigin::Signed(to).into(), instruction.clone()).is_err()
|
||||
);
|
||||
|
||||
// it should now work
|
||||
instruction.balance.amount = balance.amount;
|
||||
Coins::burn_with_instruction(RawOrigin::Signed(to).into(), instruction.clone()).unwrap();
|
||||
|
||||
// balance & supply now should be back to 0
|
||||
assert_eq!(Coins::balance(to, coin), Amount(0));
|
||||
assert_eq!(Coins::supply(coin), 0);
|
||||
|
||||
let burn_events = System::events()
|
||||
.iter()
|
||||
.filter_map(|event| {
|
||||
if let RuntimeEvent::Coins(e) = &event.event {
|
||||
if matches!(e, CoinsEvent::BurnWithInstruction { .. }) {
|
||||
Some(e.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(burn_events, vec![CoinsEvent::BurnWithInstruction { from: to, instruction }]);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// mint some coin
|
||||
let coin = Coin::External(ExternalCoin::Bitcoin);
|
||||
let from = insecure_pair_from_name("random1").public();
|
||||
let balance = Balance { coin, amount: Amount(10 * 10u64.pow(coin.decimals())) };
|
||||
|
||||
Coins::mint(from, balance).unwrap();
|
||||
assert_eq!(Coins::balance(from, coin), balance.amount);
|
||||
assert_eq!(Coins::supply(coin), balance.amount.0);
|
||||
|
||||
// we can't send more than what we have
|
||||
let to = insecure_pair_from_name("random2").public();
|
||||
assert!(Coins::transfer(
|
||||
RawOrigin::Signed(from).into(),
|
||||
to,
|
||||
Balance { coin, amount: Amount(balance.amount.0 + 1) }
|
||||
)
|
||||
.is_err());
|
||||
|
||||
// we can send it all
|
||||
Coins::transfer(RawOrigin::Signed(from).into(), to, balance).unwrap();
|
||||
|
||||
// check the balances
|
||||
assert_eq!(Coins::balance(from, coin), Amount(0));
|
||||
assert_eq!(Coins::balance(to, coin), balance.amount);
|
||||
|
||||
// supply shouldn't change
|
||||
assert_eq!(Coins::supply(coin), balance.amount.0);
|
||||
})
|
||||
}
|
||||
@@ -13,7 +13,7 @@ use serde::{Serialize, Deserialize};
|
||||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
use serai_primitives::{Balance, SeraiAddress, ExternalAddress, system_address};
|
||||
use serai_primitives::{ExternalBalance, SeraiAddress, ExternalAddress, system_address};
|
||||
|
||||
pub const FEE_ACCOUNT: SeraiAddress = system_address(b"Coins-fees");
|
||||
|
||||
@@ -32,7 +32,7 @@ pub struct OutInstruction {
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct OutInstructionWithBalance {
|
||||
pub instruction: OutInstruction,
|
||||
pub balance: Balance,
|
||||
pub balance: ExternalBalance,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
|
||||
@@ -38,7 +38,7 @@ type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup
|
||||
|
||||
type LiquidityTokens<T> = coins_pallet::Pallet<T, coins_pallet::Instance1>;
|
||||
|
||||
fn create_coin<T: Config>(coin: &Coin) -> (T::AccountId, AccountIdLookupOf<T>) {
|
||||
fn create_coin<T: Config>(coin: &ExternalCoin) -> (T::AccountId, AccountIdLookupOf<T>) {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let caller_lookup = T::Lookup::unlookup(caller);
|
||||
assert_ok!(Coins::<T>::mint(
|
||||
@@ -47,12 +47,14 @@ fn create_coin<T: Config>(coin: &Coin) -> (T::AccountId, AccountIdLookupOf<T>) {
|
||||
));
|
||||
assert_ok!(Coins::<T>::mint(
|
||||
caller,
|
||||
Balance { coin: *coin, amount: Amount(INITIAL_COIN_BALANCE) }
|
||||
Balance { coin: (*coin).into(), amount: Amount(INITIAL_COIN_BALANCE) }
|
||||
));
|
||||
(caller, caller_lookup)
|
||||
}
|
||||
|
||||
fn create_coin_and_pool<T: Config>(coin: &Coin) -> (Coin, T::AccountId, AccountIdLookupOf<T>) {
|
||||
fn create_coin_and_pool<T: Config>(
|
||||
coin: &ExternalCoin,
|
||||
) -> (ExternalCoin, T::AccountId, AccountIdLookupOf<T>) {
|
||||
let (caller, caller_lookup) = create_coin::<T>(coin);
|
||||
assert_ok!(Dex::<T>::create_pool(*coin));
|
||||
|
||||
@@ -62,7 +64,7 @@ fn create_coin_and_pool<T: Config>(coin: &Coin) -> (Coin, T::AccountId, AccountI
|
||||
benchmarks! {
|
||||
add_liquidity {
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Bitcoin;
|
||||
let coin2 = ExternalCoin::Bitcoin;
|
||||
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin2);
|
||||
let add_amount: u64 = 1000;
|
||||
}: _(
|
||||
@@ -75,13 +77,13 @@ benchmarks! {
|
||||
caller
|
||||
)
|
||||
verify {
|
||||
let pool_id = Dex::<T>::get_pool_id(coin1, coin2).unwrap();
|
||||
let pool_id = Dex::<T>::get_pool_id(coin1, coin2.into()).unwrap();
|
||||
let lp_minted = Dex::<T>::calc_lp_amount_for_zero_supply(
|
||||
add_amount,
|
||||
1000u64,
|
||||
).unwrap();
|
||||
assert_eq!(
|
||||
LiquidityTokens::<T>::balance(caller, lp_token).0,
|
||||
LiquidityTokens::<T>::balance(caller, lp_token.into()).0,
|
||||
lp_minted
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -91,7 +93,7 @@ benchmarks! {
|
||||
assert_eq!(
|
||||
Coins::<T>::balance(
|
||||
Dex::<T>::get_pool_account(pool_id),
|
||||
Coin::Bitcoin,
|
||||
ExternalCoin::Bitcoin.into(),
|
||||
).0,
|
||||
1000
|
||||
);
|
||||
@@ -99,7 +101,7 @@ benchmarks! {
|
||||
|
||||
remove_liquidity {
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Monero;
|
||||
let coin2 = ExternalCoin::Monero;
|
||||
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin2);
|
||||
let add_amount: u64 = 100;
|
||||
let lp_minted = Dex::<T>::calc_lp_amount_for_zero_supply(
|
||||
@@ -117,7 +119,7 @@ benchmarks! {
|
||||
0u64,
|
||||
caller,
|
||||
)?;
|
||||
let total_supply = LiquidityTokens::<T>::supply(lp_token);
|
||||
let total_supply = LiquidityTokens::<T>::supply(Coin::from(lp_token));
|
||||
}: _(
|
||||
SystemOrigin::Signed(caller),
|
||||
coin2,
|
||||
@@ -127,7 +129,7 @@ benchmarks! {
|
||||
caller
|
||||
)
|
||||
verify {
|
||||
let new_total_supply = LiquidityTokens::<T>::supply(lp_token);
|
||||
let new_total_supply = LiquidityTokens::<T>::supply(Coin::from(lp_token));
|
||||
assert_eq!(
|
||||
new_total_supply,
|
||||
total_supply - remove_lp_amount
|
||||
@@ -136,8 +138,8 @@ benchmarks! {
|
||||
|
||||
swap_exact_tokens_for_tokens {
|
||||
let native = Coin::native();
|
||||
let coin1 = Coin::Bitcoin;
|
||||
let coin2 = Coin::Ether;
|
||||
let coin1 = ExternalCoin::Bitcoin;
|
||||
let coin2 = ExternalCoin::Ether;
|
||||
let (_, caller, _) = create_coin_and_pool::<T>(&coin1);
|
||||
let (_, _) = create_coin::<T>(&coin2);
|
||||
|
||||
@@ -168,21 +170,21 @@ benchmarks! {
|
||||
caller,
|
||||
)?;
|
||||
|
||||
let path = vec![coin1, native, coin2];
|
||||
let path = vec![Coin::from(coin1), native, Coin::from(coin2)];
|
||||
let path = BoundedVec::<_, T::MaxSwapPathLength>::try_from(path).unwrap();
|
||||
let native_balance = Coins::<T>::balance(caller, native).0;
|
||||
let coin1_balance = Coins::<T>::balance(caller, Coin::Bitcoin).0;
|
||||
let coin1_balance = Coins::<T>::balance(caller, ExternalCoin::Bitcoin.into()).0;
|
||||
}: _(SystemOrigin::Signed(caller), path, swap_amount, 1u64, caller)
|
||||
verify {
|
||||
let ed_bump = 2u64;
|
||||
let new_coin1_balance = Coins::<T>::balance(caller, Coin::Bitcoin).0;
|
||||
let new_coin1_balance = Coins::<T>::balance(caller, ExternalCoin::Bitcoin.into()).0;
|
||||
assert_eq!(new_coin1_balance, coin1_balance - 100u64);
|
||||
}
|
||||
|
||||
swap_tokens_for_exact_tokens {
|
||||
let native = Coin::native();
|
||||
let coin1 = Coin::Bitcoin;
|
||||
let coin2 = Coin::Ether;
|
||||
let coin1 = ExternalCoin::Bitcoin;
|
||||
let coin2 = ExternalCoin::Ether;
|
||||
let (_, caller, _) = create_coin_and_pool::<T>(&coin1);
|
||||
let (_, _) = create_coin::<T>(&coin2);
|
||||
|
||||
@@ -208,10 +210,10 @@ benchmarks! {
|
||||
0u64,
|
||||
caller,
|
||||
)?;
|
||||
let path = vec![coin1, native, coin2];
|
||||
let path = vec![Coin::from(coin1), native, Coin::from(coin2)];
|
||||
|
||||
let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap();
|
||||
let coin2_balance = Coins::<T>::balance(caller, Coin::Ether).0;
|
||||
let coin2_balance = Coins::<T>::balance(caller, ExternalCoin::Ether.into()).0;
|
||||
}: _(
|
||||
SystemOrigin::Signed(caller),
|
||||
path.clone(),
|
||||
@@ -220,7 +222,7 @@ benchmarks! {
|
||||
caller
|
||||
)
|
||||
verify {
|
||||
let new_coin2_balance = Coins::<T>::balance(caller, Coin::Ether).0;
|
||||
let new_coin2_balance = Coins::<T>::balance(caller, ExternalCoin::Ether.into()).0;
|
||||
assert_eq!(new_coin2_balance, coin2_balance + 100u64);
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ mod tests;
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
use frame_support::ensure;
|
||||
use frame_support::{ensure, pallet_prelude::*, BoundedBTreeSet};
|
||||
use frame_system::{
|
||||
pallet_prelude::{BlockNumberFor, OriginFor},
|
||||
ensure_signed,
|
||||
@@ -86,9 +86,12 @@ use frame_system::{
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
use sp_runtime::{traits::TrailingZeroInput, DispatchError};
|
||||
use sp_runtime::{
|
||||
traits::{TrailingZeroInput, IntegerSquareRoot},
|
||||
DispatchError,
|
||||
};
|
||||
|
||||
use serai_primitives::{NetworkId, Coin, SubstrateAmount};
|
||||
use serai_primitives::*;
|
||||
|
||||
use sp_std::prelude::*;
|
||||
pub use types::*;
|
||||
@@ -103,20 +106,16 @@ pub use weights::WeightInfo;
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::{pallet_prelude::*, BoundedBTreeSet};
|
||||
|
||||
use sp_core::sr25519::Public;
|
||||
use sp_runtime::traits::IntegerSquareRoot;
|
||||
|
||||
use coins_pallet::{Pallet as CoinsPallet, Config as CoinsConfig};
|
||||
|
||||
use serai_primitives::{Coin, Amount, Balance, SubstrateAmount, reverse_lexicographic_order};
|
||||
|
||||
/// Pool ID.
|
||||
///
|
||||
/// The pool's `AccountId` is derived from this type. Any changes to the type may necessitate a
|
||||
/// migration.
|
||||
pub type PoolId = Coin;
|
||||
pub type PoolId = ExternalCoin;
|
||||
|
||||
/// LiquidityTokens Pallet as an instance of coins pallet.
|
||||
pub type LiquidityTokens<T> = coins_pallet::Pallet<T, coins_pallet::Instance1>;
|
||||
@@ -164,7 +163,7 @@ pub mod pallet {
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn spot_price_for_block)]
|
||||
pub type SpotPriceForBlock<T: Config> =
|
||||
StorageDoubleMap<_, Identity, BlockNumberFor<T>, Identity, Coin, Amount, OptionQuery>;
|
||||
StorageDoubleMap<_, Identity, BlockNumberFor<T>, Identity, ExternalCoin, Amount, OptionQuery>;
|
||||
|
||||
/// Moving window of prices from each block.
|
||||
///
|
||||
@@ -173,30 +172,32 @@ pub mod pallet {
|
||||
/// low to high.
|
||||
#[pallet::storage]
|
||||
pub type SpotPrices<T: Config> =
|
||||
StorageDoubleMap<_, Identity, Coin, Identity, [u8; 8], u16, OptionQuery>;
|
||||
StorageDoubleMap<_, Identity, ExternalCoin, Identity, [u8; 8], u16, OptionQuery>;
|
||||
|
||||
// SpotPrices, yet with keys stored in reverse lexicographic order.
|
||||
#[pallet::storage]
|
||||
pub type ReverseSpotPrices<T: Config> =
|
||||
StorageDoubleMap<_, Identity, Coin, Identity, [u8; 8], (), OptionQuery>;
|
||||
StorageDoubleMap<_, Identity, ExternalCoin, Identity, [u8; 8], (), OptionQuery>;
|
||||
|
||||
/// Current length of the `SpotPrices` map.
|
||||
#[pallet::storage]
|
||||
pub type SpotPricesLength<T: Config> = StorageMap<_, Identity, Coin, u16, OptionQuery>;
|
||||
pub type SpotPricesLength<T: Config> = StorageMap<_, Identity, ExternalCoin, u16, OptionQuery>;
|
||||
|
||||
/// Current position of the median within the `SpotPrices` map;
|
||||
#[pallet::storage]
|
||||
pub type CurrentMedianPosition<T: Config> = StorageMap<_, Identity, Coin, u16, OptionQuery>;
|
||||
pub type CurrentMedianPosition<T: Config> =
|
||||
StorageMap<_, Identity, ExternalCoin, u16, OptionQuery>;
|
||||
|
||||
/// Current median price of the prices in the `SpotPrices` map at any given time.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn median_price)]
|
||||
pub type MedianPrice<T: Config> = StorageMap<_, Identity, Coin, Amount, OptionQuery>;
|
||||
pub type MedianPrice<T: Config> = StorageMap<_, Identity, ExternalCoin, Amount, OptionQuery>;
|
||||
|
||||
/// The price used for evaluating economic security, which is the highest observed median price.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn security_oracle_value)]
|
||||
pub type SecurityOracleValue<T: Config> = StorageMap<_, Identity, Coin, Amount, OptionQuery>;
|
||||
pub type SecurityOracleValue<T: Config> =
|
||||
StorageMap<_, Identity, ExternalCoin, Amount, OptionQuery>;
|
||||
|
||||
/// Total swap volume of a given pool in terms of SRI.
|
||||
#[pallet::storage]
|
||||
@@ -205,7 +206,7 @@ pub mod pallet {
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
fn restore_median(
|
||||
coin: Coin,
|
||||
coin: ExternalCoin,
|
||||
mut current_median_pos: u16,
|
||||
mut current_median: Amount,
|
||||
length: u16,
|
||||
@@ -256,7 +257,7 @@ pub mod pallet {
|
||||
MedianPrice::<T>::set(coin, Some(current_median));
|
||||
}
|
||||
|
||||
pub(crate) fn insert_into_median(coin: Coin, amount: Amount) {
|
||||
pub(crate) fn insert_into_median(coin: ExternalCoin, amount: Amount) {
|
||||
let new_quantity_of_presences =
|
||||
SpotPrices::<T>::get(coin, amount.0.to_be_bytes()).unwrap_or(0) + 1;
|
||||
SpotPrices::<T>::set(coin, amount.0.to_be_bytes(), Some(new_quantity_of_presences));
|
||||
@@ -286,7 +287,7 @@ pub mod pallet {
|
||||
Self::restore_median(coin, current_median_pos, current_median, new_length);
|
||||
}
|
||||
|
||||
pub(crate) fn remove_from_median(coin: Coin, amount: Amount) {
|
||||
pub(crate) fn remove_from_median(coin: ExternalCoin, amount: Amount) {
|
||||
let mut current_median = MedianPrice::<T>::get(coin).unwrap();
|
||||
|
||||
let mut current_median_pos = CurrentMedianPosition::<T>::get(coin).unwrap();
|
||||
@@ -451,7 +452,7 @@ pub mod pallet {
|
||||
// insert the new price to our oracle window
|
||||
// The spot price for 1 coin, in atomic units, to SRI is used
|
||||
let sri_per_coin =
|
||||
if let Ok((sri_balance, coin_balance)) = Self::get_reserves(&Coin::native(), &coin) {
|
||||
if let Ok((sri_balance, coin_balance)) = Self::get_reserves(&Coin::Serai, &coin.into()) {
|
||||
// We use 1 coin to handle rounding errors which may occur with atomic units
|
||||
// If we used atomic units, any coin whose atomic unit is worth less than SRI's atomic
|
||||
// unit would cause a 'price' of 0
|
||||
@@ -493,9 +494,9 @@ pub mod pallet {
|
||||
/// (the id of which is returned in the `Event::PoolCreated` event).
|
||||
///
|
||||
/// Once a pool is created, someone may [`Pallet::add_liquidity`] to it.
|
||||
pub(crate) fn create_pool(coin: Coin) -> DispatchResult {
|
||||
pub(crate) fn create_pool(coin: ExternalCoin) -> DispatchResult {
|
||||
// get pool_id
|
||||
let pool_id = Self::get_pool_id(coin, Coin::Serai)?;
|
||||
let pool_id = Self::get_pool_id(coin.into(), Coin::native())?;
|
||||
ensure!(!Pools::<T>::contains_key(pool_id), Error::<T>::PoolExists);
|
||||
|
||||
let pool_account = Self::get_pool_account(pool_id);
|
||||
@@ -508,9 +509,11 @@ pub mod pallet {
|
||||
|
||||
/// A hook to be called whenever a network's session is rotated.
|
||||
pub fn on_new_session(network: NetworkId) {
|
||||
// reset the oracle value
|
||||
for coin in network.coins() {
|
||||
SecurityOracleValue::<T>::set(*coin, Self::median_price(coin));
|
||||
// Only track the price for non-SRI coins as this is SRI denominated
|
||||
if let NetworkId::External(n) = network {
|
||||
for coin in n.coins() {
|
||||
SecurityOracleValue::<T>::set(coin, Self::median_price(coin));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,7 +535,7 @@ pub mod pallet {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn add_liquidity(
|
||||
origin: OriginFor<T>,
|
||||
coin: Coin,
|
||||
coin: ExternalCoin,
|
||||
coin_desired: SubstrateAmount,
|
||||
sri_desired: SubstrateAmount,
|
||||
coin_min: SubstrateAmount,
|
||||
@@ -542,7 +545,7 @@ pub mod pallet {
|
||||
let sender = ensure_signed(origin)?;
|
||||
ensure!((sri_desired > 0) && (coin_desired > 0), Error::<T>::WrongDesiredAmount);
|
||||
|
||||
let pool_id = Self::get_pool_id(coin, Coin::Serai)?;
|
||||
let pool_id = Self::get_pool_id(coin.into(), Coin::native())?;
|
||||
|
||||
// create the pool if it doesn't exist. We can just attempt to do that because our checks
|
||||
// far enough to allow that.
|
||||
@@ -552,7 +555,7 @@ pub mod pallet {
|
||||
let pool_account = Self::get_pool_account(pool_id);
|
||||
|
||||
let sri_reserve = Self::get_balance(&pool_account, Coin::Serai);
|
||||
let coin_reserve = Self::get_balance(&pool_account, coin);
|
||||
let coin_reserve = Self::get_balance(&pool_account, coin.into());
|
||||
|
||||
let sri_amount: SubstrateAmount;
|
||||
let coin_amount: SubstrateAmount;
|
||||
@@ -583,16 +586,20 @@ pub mod pallet {
|
||||
&pool_account,
|
||||
Balance { coin: Coin::Serai, amount: Amount(sri_amount) },
|
||||
)?;
|
||||
Self::transfer(&sender, &pool_account, Balance { coin, amount: Amount(coin_amount) })?;
|
||||
Self::transfer(
|
||||
&sender,
|
||||
&pool_account,
|
||||
Balance { coin: coin.into(), amount: Amount(coin_amount) },
|
||||
)?;
|
||||
|
||||
let total_supply = LiquidityTokens::<T>::supply(coin);
|
||||
let total_supply = LiquidityTokens::<T>::supply(Coin::from(coin));
|
||||
|
||||
let lp_token_amount: SubstrateAmount;
|
||||
if total_supply == 0 {
|
||||
lp_token_amount = Self::calc_lp_amount_for_zero_supply(sri_amount, coin_amount)?;
|
||||
LiquidityTokens::<T>::mint(
|
||||
pool_account,
|
||||
Balance { coin, amount: Amount(T::MintMinLiquidity::get()) },
|
||||
Balance { coin: coin.into(), amount: Amount(T::MintMinLiquidity::get()) },
|
||||
)?;
|
||||
} else {
|
||||
let side1 = Self::mul_div(sri_amount, total_supply, sri_reserve)?;
|
||||
@@ -605,7 +612,10 @@ pub mod pallet {
|
||||
Error::<T>::InsufficientLiquidityMinted
|
||||
);
|
||||
|
||||
LiquidityTokens::<T>::mint(mint_to, Balance { coin, amount: Amount(lp_token_amount) })?;
|
||||
LiquidityTokens::<T>::mint(
|
||||
mint_to,
|
||||
Balance { coin: coin.into(), amount: Amount(lp_token_amount) },
|
||||
)?;
|
||||
|
||||
Self::deposit_event(Event::LiquidityAdded {
|
||||
who: sender,
|
||||
@@ -626,25 +636,24 @@ pub mod pallet {
|
||||
#[pallet::weight(T::WeightInfo::remove_liquidity())]
|
||||
pub fn remove_liquidity(
|
||||
origin: OriginFor<T>,
|
||||
coin: Coin,
|
||||
coin: ExternalCoin,
|
||||
lp_token_burn: SubstrateAmount,
|
||||
coin_min_receive: SubstrateAmount,
|
||||
sri_min_receive: SubstrateAmount,
|
||||
withdraw_to: T::AccountId,
|
||||
) -> DispatchResult {
|
||||
let sender = ensure_signed(origin.clone())?;
|
||||
ensure!(coin != Coin::Serai, Error::<T>::EqualCoins);
|
||||
|
||||
let pool_id = Self::get_pool_id(coin, Coin::Serai).unwrap();
|
||||
let pool_id = Self::get_pool_id(coin.into(), Coin::native()).unwrap();
|
||||
ensure!(lp_token_burn > 0, Error::<T>::ZeroLiquidity);
|
||||
|
||||
Pools::<T>::get(pool_id).as_ref().ok_or(Error::<T>::PoolNotFound)?;
|
||||
|
||||
let pool_account = Self::get_pool_account(pool_id);
|
||||
let sri_reserve = Self::get_balance(&pool_account, Coin::Serai);
|
||||
let coin_reserve = Self::get_balance(&pool_account, coin);
|
||||
let coin_reserve = Self::get_balance(&pool_account, coin.into());
|
||||
|
||||
let total_supply = LiquidityTokens::<T>::supply(coin);
|
||||
let total_supply = LiquidityTokens::<T>::supply(Coin::from(coin));
|
||||
let lp_redeem_amount = lp_token_burn;
|
||||
|
||||
let sri_amount = Self::mul_div(lp_redeem_amount, sri_reserve, total_supply)?;
|
||||
@@ -665,14 +674,21 @@ pub mod pallet {
|
||||
ensure!(coin_reserve_left >= 1, Error::<T>::ReserveLeftLessThanMinimum);
|
||||
|
||||
// burn the provided lp token amount that includes the fee
|
||||
LiquidityTokens::<T>::burn(origin, Balance { coin, amount: Amount(lp_token_burn) })?;
|
||||
LiquidityTokens::<T>::burn(
|
||||
origin,
|
||||
Balance { coin: coin.into(), amount: Amount(lp_token_burn) },
|
||||
)?;
|
||||
|
||||
Self::transfer(
|
||||
&pool_account,
|
||||
&withdraw_to,
|
||||
Balance { coin: Coin::Serai, amount: Amount(sri_amount) },
|
||||
)?;
|
||||
Self::transfer(&pool_account, &withdraw_to, Balance { coin, amount: Amount(coin_amount) })?;
|
||||
Self::transfer(
|
||||
&pool_account,
|
||||
&withdraw_to,
|
||||
Balance { coin: coin.into(), amount: Amount(coin_amount) },
|
||||
)?;
|
||||
|
||||
Self::deposit_event(Event::LiquidityRemoved {
|
||||
who: sender,
|
||||
@@ -920,11 +936,9 @@ pub mod pallet {
|
||||
pub fn get_pool_id(coin1: Coin, coin2: Coin) -> Result<PoolId, Error<T>> {
|
||||
ensure!((coin1 == Coin::Serai) || (coin2 == Coin::Serai), Error::<T>::PoolNotFound);
|
||||
ensure!(coin1 != coin2, Error::<T>::EqualCoins);
|
||||
if coin1 == Coin::Serai {
|
||||
Ok(coin2)
|
||||
} else {
|
||||
Ok(coin1)
|
||||
}
|
||||
ExternalCoin::try_from(coin1)
|
||||
.or_else(|()| ExternalCoin::try_from(coin2))
|
||||
.map_err(|()| Error::<T>::PoolNotFound)
|
||||
}
|
||||
|
||||
/// Returns the balance of each coin in the pool.
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
// It has been forked into a crate distributed under the AGPL 3.0.
|
||||
// Please check the current distribution for up-to-date copyright and licensing information.
|
||||
|
||||
use crate::{mock::*, *};
|
||||
use crate::{
|
||||
mock::{*, MEDIAN_PRICE_WINDOW_LENGTH},
|
||||
*,
|
||||
};
|
||||
use frame_support::{assert_noop, assert_ok};
|
||||
|
||||
pub use coins_pallet as coins;
|
||||
@@ -72,11 +75,13 @@ fn check_pool_accounts_dont_collide() {
|
||||
let mut map = HashSet::new();
|
||||
|
||||
for coin in coins() {
|
||||
let account = Dex::get_pool_account(coin);
|
||||
if map.contains(&account) {
|
||||
panic!("Collision at {coin:?}");
|
||||
if let Coin::External(c) = coin {
|
||||
let account = Dex::get_pool_account(c);
|
||||
if map.contains(&account) {
|
||||
panic!("Collision at {c:?}");
|
||||
}
|
||||
map.insert(account);
|
||||
}
|
||||
map.insert(account);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,11 +103,11 @@ fn can_create_pool() {
|
||||
let coin_account_deposit: u64 = 0;
|
||||
let user: PublicKey = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Monero;
|
||||
let coin2 = Coin::External(ExternalCoin::Monero);
|
||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(1000) }));
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
assert_eq!(balance(user, coin1), 1000 - coin_account_deposit);
|
||||
|
||||
@@ -111,15 +116,13 @@ fn can_create_pool() {
|
||||
[Event::<Test>::PoolCreated { pool_id, pool_account: Dex::get_pool_account(pool_id) }]
|
||||
);
|
||||
assert_eq!(pools(), vec![pool_id]);
|
||||
|
||||
assert_noop!(Dex::create_pool(coin1), Error::<Test>::EqualCoins);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_same_pool_twice_should_fail() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let coin = Coin::Dai;
|
||||
let coin = ExternalCoin::Dai;
|
||||
assert_ok!(Dex::create_pool(coin));
|
||||
assert_noop!(Dex::create_pool(coin), Error::<Test>::PoolExists);
|
||||
});
|
||||
@@ -129,13 +132,13 @@ fn create_same_pool_twice_should_fail() {
|
||||
fn different_pools_should_have_different_lp_tokens() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Bitcoin;
|
||||
let coin3 = Coin::Ether;
|
||||
let coin2 = Coin::External(ExternalCoin::Bitcoin);
|
||||
let coin3 = Coin::External(ExternalCoin::Ether);
|
||||
let pool_id_1_2 = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||
let pool_id_1_3 = Dex::get_pool_id(coin1, coin3).unwrap();
|
||||
|
||||
let lp_token2_1 = coin2;
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
let lp_token3_1 = coin3;
|
||||
|
||||
assert_eq!(
|
||||
@@ -146,7 +149,7 @@ fn different_pools_should_have_different_lp_tokens() {
|
||||
}]
|
||||
);
|
||||
|
||||
assert_ok!(Dex::create_pool(coin3));
|
||||
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
|
||||
assert_eq!(
|
||||
events(),
|
||||
[Event::<Test>::PoolCreated {
|
||||
@@ -164,13 +167,13 @@ fn can_add_liquidity() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Dai;
|
||||
let coin3 = Coin::Monero;
|
||||
let coin2 = Coin::External(ExternalCoin::Dai);
|
||||
let coin3 = Coin::External(ExternalCoin::Monero);
|
||||
|
||||
let lp_token1 = coin2;
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
let lp_token2 = coin3;
|
||||
assert_ok!(Dex::create_pool(coin3));
|
||||
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(
|
||||
user,
|
||||
@@ -179,7 +182,15 @@ fn can_add_liquidity() {
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin3, amount: Amount(1000) }));
|
||||
|
||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 10, 10000, 10, 10000, user,));
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2.try_into().unwrap(),
|
||||
10,
|
||||
10000,
|
||||
10,
|
||||
10000,
|
||||
user,
|
||||
));
|
||||
|
||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||
assert!(events().contains(&Event::<Test>::LiquidityAdded {
|
||||
@@ -198,7 +209,15 @@ fn can_add_liquidity() {
|
||||
assert_eq!(pool_balance(user, lp_token1), 216);
|
||||
|
||||
// try to pass the non-native - native coins, the result should be the same
|
||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin3, 10, 10000, 10, 10000, user,));
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin3.try_into().unwrap(),
|
||||
10,
|
||||
10000,
|
||||
10,
|
||||
10000,
|
||||
user,
|
||||
));
|
||||
|
||||
let pool_id = Dex::get_pool_id(coin1, coin3).unwrap();
|
||||
assert!(events().contains(&Event::<Test>::LiquidityAdded {
|
||||
@@ -223,12 +242,15 @@ fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Bitcoin;
|
||||
let coin2 = ExternalCoin::Bitcoin;
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(1000) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(
|
||||
user,
|
||||
Balance { coin: coin2.into(), amount: Amount(1000) }
|
||||
));
|
||||
|
||||
assert_noop!(
|
||||
Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 1, 1, 1, 1, user),
|
||||
@@ -242,11 +264,11 @@ fn add_tiny_liquidity_directly_to_pool_address() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Ether;
|
||||
let coin3 = Coin::Dai;
|
||||
let coin2 = Coin::External(ExternalCoin::Ether);
|
||||
let coin3 = Coin::External(ExternalCoin::Dai);
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin3));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000 * 2) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(10000) }));
|
||||
@@ -259,7 +281,15 @@ fn add_tiny_liquidity_directly_to_pool_address() {
|
||||
Balance { coin: coin1, amount: Amount(1000) }
|
||||
));
|
||||
|
||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 10, 10000, 10, 10000, user,));
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2.try_into().unwrap(),
|
||||
10,
|
||||
10000,
|
||||
10,
|
||||
10000,
|
||||
user,
|
||||
));
|
||||
|
||||
// check the same but for coin3 (non-native token)
|
||||
let pallet_account = Dex::get_pool_account(Dex::get_pool_id(coin1, coin3).unwrap());
|
||||
@@ -267,7 +297,15 @@ fn add_tiny_liquidity_directly_to_pool_address() {
|
||||
pallet_account,
|
||||
Balance { coin: coin2, amount: Amount(1) }
|
||||
));
|
||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin3, 10, 10000, 10, 10000, user,));
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin3.try_into().unwrap(),
|
||||
10,
|
||||
10000,
|
||||
10,
|
||||
10000,
|
||||
user,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -276,11 +314,11 @@ fn can_remove_liquidity() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Monero;
|
||||
let coin2 = Coin::External(ExternalCoin::Monero);
|
||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||
|
||||
let lp_token = coin2;
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(
|
||||
user,
|
||||
@@ -290,7 +328,7 @@ fn can_remove_liquidity() {
|
||||
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
100000,
|
||||
1000000000,
|
||||
100000,
|
||||
@@ -302,7 +340,7 @@ fn can_remove_liquidity() {
|
||||
|
||||
assert_ok!(Dex::remove_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
total_lp_received,
|
||||
0,
|
||||
0,
|
||||
@@ -334,15 +372,23 @@ fn can_not_redeem_more_lp_tokens_than_were_minted() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Dai;
|
||||
let coin2 = Coin::External(ExternalCoin::Dai);
|
||||
let lp_token = coin2;
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||
|
||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 10, 10000, 10, 10000, user,));
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2.try_into().unwrap(),
|
||||
10,
|
||||
10000,
|
||||
10,
|
||||
10000,
|
||||
user,
|
||||
));
|
||||
|
||||
// Only 216 lp_tokens_minted
|
||||
assert_eq!(pool_balance(user, lp_token), 216);
|
||||
@@ -350,7 +396,7 @@ fn can_not_redeem_more_lp_tokens_than_were_minted() {
|
||||
assert_noop!(
|
||||
Dex::remove_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
216 + 1, // Try and redeem 10 lp tokens while only 9 minted.
|
||||
0,
|
||||
0,
|
||||
@@ -366,14 +412,22 @@ fn can_quote_price() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Ether;
|
||||
let coin2 = Coin::External(ExternalCoin::Ether);
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(100000) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||
|
||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 200, 10000, 1, 1, user,));
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2.try_into().unwrap(),
|
||||
200,
|
||||
10000,
|
||||
1,
|
||||
1,
|
||||
user,
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
Dex::quote_price_exact_tokens_for_tokens(Coin::native(), coin2, 3000, false,),
|
||||
@@ -481,14 +535,22 @@ fn quote_price_exact_tokens_for_tokens_matches_execution() {
|
||||
let user = system_address(b"user1").into();
|
||||
let user2 = system_address(b"user2").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Bitcoin;
|
||||
let coin2 = Coin::External(ExternalCoin::Bitcoin);
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(100000) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||
|
||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 200, 10000, 1, 1, user,));
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2.try_into().unwrap(),
|
||||
200,
|
||||
10000,
|
||||
1,
|
||||
1,
|
||||
user,
|
||||
));
|
||||
|
||||
let amount = 1;
|
||||
let quoted_price = 49;
|
||||
@@ -518,14 +580,22 @@ fn quote_price_tokens_for_exact_tokens_matches_execution() {
|
||||
let user = system_address(b"user1").into();
|
||||
let user2 = system_address(b"user2").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Monero;
|
||||
let coin2 = Coin::External(ExternalCoin::Monero);
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(100000) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||
|
||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 200, 10000, 1, 1, user,));
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2.try_into().unwrap(),
|
||||
200,
|
||||
10000,
|
||||
1,
|
||||
1,
|
||||
user,
|
||||
));
|
||||
|
||||
let amount = 49;
|
||||
let quoted_price = 1;
|
||||
@@ -557,10 +627,10 @@ fn can_swap_with_native() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Ether;
|
||||
let coin2 = Coin::External(ExternalCoin::Ether);
|
||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||
@@ -570,7 +640,7 @@ fn can_swap_with_native() {
|
||||
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
liquidity2,
|
||||
liquidity1,
|
||||
1,
|
||||
@@ -602,8 +672,8 @@ fn can_swap_with_realistic_values() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let sri = Coin::native();
|
||||
let dai = Coin::Dai;
|
||||
assert_ok!(Dex::create_pool(dai));
|
||||
let dai = Coin::External(ExternalCoin::Dai);
|
||||
assert_ok!(Dex::create_pool(dai.try_into().unwrap()));
|
||||
|
||||
const UNIT: u64 = 1_000_000_000;
|
||||
|
||||
@@ -620,7 +690,7 @@ fn can_swap_with_realistic_values() {
|
||||
let liquidity_dai = 1_000_000 * UNIT;
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
dai,
|
||||
dai.try_into().unwrap(),
|
||||
liquidity_dai,
|
||||
liquidity_sri,
|
||||
1,
|
||||
@@ -653,9 +723,9 @@ fn can_not_swap_in_pool_with_no_liquidity_added_yet() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Monero;
|
||||
let coin2 = Coin::External(ExternalCoin::Monero);
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
// Check can't swap an empty pool
|
||||
assert_noop!(
|
||||
@@ -676,11 +746,11 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Bitcoin;
|
||||
let coin2 = Coin::External(ExternalCoin::Bitcoin);
|
||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||
let lp_token = coin2;
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||
@@ -690,7 +760,7 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() {
|
||||
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
liquidity2,
|
||||
liquidity1,
|
||||
1,
|
||||
@@ -714,7 +784,7 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() {
|
||||
|
||||
assert_ok!(Dex::remove_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
lp_token_minted,
|
||||
1,
|
||||
1,
|
||||
@@ -787,9 +857,9 @@ fn swap_should_not_work_if_too_much_slippage() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Ether;
|
||||
let coin2 = Coin::External(ExternalCoin::Ether);
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||
@@ -799,7 +869,7 @@ fn swap_should_not_work_if_too_much_slippage() {
|
||||
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
liquidity2,
|
||||
liquidity1,
|
||||
1,
|
||||
@@ -827,10 +897,10 @@ fn can_swap_tokens_for_exact_tokens() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Dai;
|
||||
let coin2 = Coin::External(ExternalCoin::Dai);
|
||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(20000) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||
@@ -844,7 +914,7 @@ fn can_swap_tokens_for_exact_tokens() {
|
||||
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
liquidity2,
|
||||
liquidity1,
|
||||
1,
|
||||
@@ -882,11 +952,11 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() {
|
||||
let user = system_address(b"user1").into();
|
||||
let user2 = system_address(b"user2").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Monero;
|
||||
let coin2 = Coin::External(ExternalCoin::Monero);
|
||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||
let lp_token = coin2;
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
let base1 = 10000;
|
||||
let base2 = 1000;
|
||||
@@ -903,7 +973,7 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() {
|
||||
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user2),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
liquidity2,
|
||||
liquidity1,
|
||||
1,
|
||||
@@ -947,7 +1017,7 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() {
|
||||
|
||||
assert_ok!(Dex::remove_liquidity(
|
||||
RuntimeOrigin::signed(user2),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
lp_token_minted,
|
||||
0,
|
||||
0,
|
||||
@@ -961,9 +1031,9 @@ fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Ether;
|
||||
let coin2 = Coin::External(ExternalCoin::Ether);
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(20000) }));
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||
@@ -973,7 +1043,7 @@ fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() {
|
||||
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
liquidity2,
|
||||
liquidity1,
|
||||
1,
|
||||
@@ -1001,11 +1071,11 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Dai;
|
||||
let coin3 = Coin::Monero;
|
||||
let coin2 = Coin::External(ExternalCoin::Dai);
|
||||
let coin3 = Coin::External(ExternalCoin::Monero);
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin3));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
|
||||
|
||||
let base1 = 10000;
|
||||
let base2 = 10000;
|
||||
@@ -1019,7 +1089,7 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() {
|
||||
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
liquidity2,
|
||||
liquidity1,
|
||||
1,
|
||||
@@ -1028,7 +1098,7 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() {
|
||||
));
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin3,
|
||||
coin3.try_into().unwrap(),
|
||||
liquidity3,
|
||||
liquidity1,
|
||||
1,
|
||||
@@ -1089,11 +1159,11 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Bitcoin;
|
||||
let coin3 = Coin::Ether;
|
||||
let coin2 = Coin::External(ExternalCoin::Bitcoin);
|
||||
let coin3 = Coin::External(ExternalCoin::Ether);
|
||||
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin3));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
|
||||
|
||||
let base1 = 10000;
|
||||
let base2 = 10000;
|
||||
@@ -1107,7 +1177,7 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() {
|
||||
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2,
|
||||
coin2.try_into().unwrap(),
|
||||
liquidity2,
|
||||
liquidity1,
|
||||
1,
|
||||
@@ -1116,7 +1186,7 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() {
|
||||
));
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin3,
|
||||
coin3.try_into().unwrap(),
|
||||
liquidity3,
|
||||
liquidity1,
|
||||
1,
|
||||
@@ -1154,7 +1224,7 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() {
|
||||
fn can_not_swap_same_coin() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = system_address(b"user1").into();
|
||||
let coin1 = Coin::Dai;
|
||||
let coin1 = Coin::External(ExternalCoin::Dai);
|
||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(1000) }));
|
||||
|
||||
let exchange_amount = 10;
|
||||
@@ -1188,10 +1258,10 @@ fn validate_pool_id_sorting() {
|
||||
// Serai < Bitcoin < Ether < Dai < Monero.
|
||||
// coin1 <= coin2 for this test to pass.
|
||||
let native = Coin::native();
|
||||
let coin1 = Coin::Bitcoin;
|
||||
let coin2 = Coin::Monero;
|
||||
assert_eq!(Dex::get_pool_id(native, coin2).unwrap(), coin2);
|
||||
assert_eq!(Dex::get_pool_id(coin2, native).unwrap(), coin2);
|
||||
let coin1 = Coin::External(ExternalCoin::Bitcoin);
|
||||
let coin2 = Coin::External(ExternalCoin::Monero);
|
||||
assert_eq!(Dex::get_pool_id(native, coin2).unwrap(), coin2.try_into().unwrap());
|
||||
assert_eq!(Dex::get_pool_id(coin2, native).unwrap(), coin2.try_into().unwrap());
|
||||
assert!(matches!(Dex::get_pool_id(native, native), Err(Error::<Test>::EqualCoins)));
|
||||
assert!(matches!(Dex::get_pool_id(coin2, coin1), Err(Error::<Test>::PoolNotFound)));
|
||||
assert!(coin2 > coin1);
|
||||
@@ -1216,7 +1286,7 @@ fn cannot_block_pool_creation() {
|
||||
|
||||
// The target pool the user wants to create is Native <=> Coin(2)
|
||||
let coin1 = Coin::native();
|
||||
let coin2 = Coin::Ether;
|
||||
let coin2 = Coin::External(ExternalCoin::Ether);
|
||||
|
||||
// Attacker computes the still non-existing pool account for the target pair
|
||||
let pool_account = Dex::get_pool_account(Dex::get_pool_id(coin2, coin1).unwrap());
|
||||
@@ -1238,7 +1308,7 @@ fn cannot_block_pool_creation() {
|
||||
}
|
||||
|
||||
// User can still create the pool
|
||||
assert_ok!(Dex::create_pool(coin2));
|
||||
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||
|
||||
// User has to transfer one Coin(2) token to the pool account (otherwise add_liquidity will
|
||||
// fail with `CoinTwoDepositDidNotMeetMinimum`), also transfer native token for the same error.
|
||||
@@ -1256,7 +1326,15 @@ fn cannot_block_pool_creation() {
|
||||
));
|
||||
|
||||
// add_liquidity shouldn't fail because of the number of consumers
|
||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 100, 9900, 10, 9900, user,));
|
||||
assert_ok!(Dex::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
coin2.try_into().unwrap(),
|
||||
100,
|
||||
9900,
|
||||
10,
|
||||
9900,
|
||||
user,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1281,7 +1359,7 @@ fn test_median_price() {
|
||||
prices.push(OsRng.next_u64());
|
||||
}
|
||||
}
|
||||
let coin = Coin::Bitcoin;
|
||||
let coin = ExternalCoin::Bitcoin;
|
||||
|
||||
assert!(prices.len() >= (2 * usize::from(MEDIAN_PRICE_WINDOW_LENGTH)));
|
||||
for i in 0 .. prices.len() {
|
||||
|
||||
@@ -24,7 +24,7 @@ pub mod pallet {
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
EconomicSecurityReached { network: NetworkId },
|
||||
EconomicSecurityReached { network: ExternalNetworkId },
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
@@ -33,17 +33,19 @@ pub mod pallet {
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn economic_security_block)]
|
||||
pub(crate) type EconomicSecurityBlock<T: Config> =
|
||||
StorageMap<_, Identity, NetworkId, BlockNumberFor<T>, OptionQuery>;
|
||||
StorageMap<_, Identity, ExternalNetworkId, BlockNumberFor<T>, OptionQuery>;
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_initialize(n: BlockNumberFor<T>) -> Weight {
|
||||
// we accept we reached economic security once we can mint smallest amount of a network's coin
|
||||
for coin in COINS {
|
||||
for coin in EXTERNAL_COINS {
|
||||
let existing = EconomicSecurityBlock::<T>::get(coin.network());
|
||||
// TODO: we don't need to check for oracle value if is_allowed returns false when there is
|
||||
// no coin value
|
||||
if existing.is_none() &&
|
||||
Dex::<T>::security_oracle_value(coin).is_some() &&
|
||||
<T as CoinsConfig>::AllowMint::is_allowed(&Balance { coin, amount: Amount(1) })
|
||||
<T as CoinsConfig>::AllowMint::is_allowed(&ExternalBalance { coin, amount: Amount(1) })
|
||||
{
|
||||
EconomicSecurityBlock::<T>::set(coin.network(), Some(n));
|
||||
Self::deposit_event(Event::EconomicSecurityReached { network: coin.network() });
|
||||
|
||||
@@ -84,7 +84,8 @@ pub mod pallet {
|
||||
pub type CurrentSession<T: Config> = StorageMap<_, Identity, NetworkId, u32, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub(crate) type LastSwapVolume<T: Config> = StorageMap<_, Identity, Coin, u64, OptionQuery>;
|
||||
pub(crate) type LastSwapVolume<T: Config> =
|
||||
StorageMap<_, Identity, ExternalCoin, u64, OptionQuery>;
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
@@ -136,19 +137,16 @@ pub mod pallet {
|
||||
let mut total_distance: u64 = 0;
|
||||
let reward_this_epoch = if pre_ec_security {
|
||||
// calculate distance to economic security per network
|
||||
for n in NETWORKS {
|
||||
if n == NetworkId::Serai {
|
||||
continue;
|
||||
}
|
||||
|
||||
for n in EXTERNAL_NETWORKS {
|
||||
let required = ValidatorSets::<T>::required_stake_for_network(n);
|
||||
let mut current = ValidatorSets::<T>::total_allocated_stake(n).unwrap_or(Amount(0)).0;
|
||||
let mut current =
|
||||
ValidatorSets::<T>::total_allocated_stake(NetworkId::from(n)).unwrap_or(Amount(0)).0;
|
||||
if current > required {
|
||||
current = required;
|
||||
}
|
||||
|
||||
let distance = required - current;
|
||||
distances.insert(n, distance);
|
||||
distances.insert(NetworkId::from(n), distance);
|
||||
total_distance = total_distance.saturating_add(distance);
|
||||
}
|
||||
|
||||
@@ -192,9 +190,8 @@ pub mod pallet {
|
||||
)
|
||||
} else {
|
||||
// get swap volumes
|
||||
let mut volume_per_coin: BTreeMap<Coin, u64> = BTreeMap::new();
|
||||
for c in COINS {
|
||||
// this should return 0 for SRI and so it shouldn't affect the total volume.
|
||||
let mut volume_per_coin: BTreeMap<ExternalCoin, u64> = BTreeMap::new();
|
||||
for c in EXTERNAL_COINS {
|
||||
let current_volume = Dex::<T>::swap_volume(c).unwrap_or(0);
|
||||
let last_volume = LastSwapVolume::<T>::get(c).unwrap_or(0);
|
||||
let vol_this_epoch = current_volume.saturating_sub(last_volume);
|
||||
@@ -209,11 +206,13 @@ pub mod pallet {
|
||||
let mut volume_per_network: BTreeMap<NetworkId, u64> = BTreeMap::new();
|
||||
for (c, vol) in &volume_per_coin {
|
||||
volume_per_network.insert(
|
||||
c.network(),
|
||||
(*volume_per_network.get(&c.network()).unwrap_or(&0)).saturating_add(*vol),
|
||||
c.network().into(),
|
||||
(*volume_per_network.get(&c.network().into()).unwrap_or(&0)).saturating_add(*vol),
|
||||
);
|
||||
total_volume = total_volume.saturating_add(*vol);
|
||||
}
|
||||
// we add the serai network now
|
||||
volume_per_network.insert(NetworkId::Serai, 0);
|
||||
|
||||
(
|
||||
volume_per_network
|
||||
@@ -245,12 +244,13 @@ pub mod pallet {
|
||||
|
||||
// distribute the rewards within the network
|
||||
for (n, reward) in rewards_per_network {
|
||||
let (validators_reward, network_pool_reward) = if n == NetworkId::Serai {
|
||||
(reward, 0)
|
||||
} else {
|
||||
let validators_reward = if let NetworkId::External(external_network) = n {
|
||||
// calculate pool vs validator share
|
||||
let capacity = ValidatorSets::<T>::total_allocated_stake(n).unwrap_or(Amount(0)).0;
|
||||
let required = ValidatorSets::<T>::required_stake_for_network(n);
|
||||
let capacity =
|
||||
ValidatorSets::<T>::total_allocated_stake(NetworkId::from(external_network))
|
||||
.unwrap_or(Amount(0))
|
||||
.0;
|
||||
let required = ValidatorSets::<T>::required_stake_for_network(external_network);
|
||||
let unused_capacity = capacity.saturating_sub(required);
|
||||
|
||||
let distribution = unused_capacity.saturating_mul(ACCURACY_MULTIPLIER) / capacity;
|
||||
@@ -258,41 +258,44 @@ pub mod pallet {
|
||||
|
||||
let validators_reward = DESIRED_DISTRIBUTION.saturating_mul(reward) / total;
|
||||
let network_pool_reward = reward.saturating_sub(validators_reward);
|
||||
(validators_reward, network_pool_reward)
|
||||
|
||||
// send the rest to the pool
|
||||
if network_pool_reward != 0 {
|
||||
// these should be available to unwrap if we have a network_pool_reward. Because that
|
||||
// means we had an unused capacity hence in a post-ec era.
|
||||
let vpn = volume_per_network.as_ref().unwrap();
|
||||
let vpc = volume_per_coin.as_ref().unwrap();
|
||||
for c in external_network.coins() {
|
||||
let pool_reward = u64::try_from(
|
||||
u128::from(network_pool_reward).saturating_mul(u128::from(vpc[&c])) /
|
||||
u128::from(vpn[&n]),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if Coins::<T>::mint(
|
||||
Dex::<T>::get_pool_account(c),
|
||||
Balance { coin: Coin::Serai, amount: Amount(pool_reward) },
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
// TODO: log the failure
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validators_reward
|
||||
} else {
|
||||
reward
|
||||
};
|
||||
|
||||
// distribute validators rewards
|
||||
Self::distribute_to_validators(n, validators_reward);
|
||||
|
||||
// send the rest to the pool
|
||||
if network_pool_reward != 0 {
|
||||
// these should be available to unwrap if we have a network_pool_reward. Because that
|
||||
// means we had an unused capacity hence in a post-ec era.
|
||||
let vpn = volume_per_network.as_ref().unwrap();
|
||||
let vpc = volume_per_coin.as_ref().unwrap();
|
||||
for c in n.coins() {
|
||||
let pool_reward = u64::try_from(
|
||||
u128::from(network_pool_reward).saturating_mul(u128::from(vpc[c])) /
|
||||
u128::from(vpn[&n]),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if Coins::<T>::mint(
|
||||
Dex::<T>::get_pool_account(*c),
|
||||
Balance { coin: Coin::Serai, amount: Amount(pool_reward) },
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
// TODO: log the failure
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: we have the past session participants here in the emissions pallet so that we can
|
||||
// distribute rewards to them in the next session. Ideally we should be able to fetch this
|
||||
// information from valiadtor sets pallet.
|
||||
// information from validator sets pallet.
|
||||
Self::update_participants();
|
||||
Weight::zero() // TODO
|
||||
}
|
||||
@@ -318,11 +321,7 @@ pub mod pallet {
|
||||
|
||||
/// Returns true if any of the external networks haven't reached economic security yet.
|
||||
fn pre_ec_security() -> bool {
|
||||
for n in NETWORKS {
|
||||
if n == NetworkId::Serai {
|
||||
continue;
|
||||
}
|
||||
|
||||
for n in EXTERNAL_NETWORKS {
|
||||
if EconomicSecurity::<T>::economic_security_block(n).is_none() {
|
||||
return true;
|
||||
}
|
||||
@@ -362,16 +361,30 @@ pub mod pallet {
|
||||
pub fn swap_to_staked_sri(
|
||||
to: PublicKey,
|
||||
network: NetworkId,
|
||||
balance: Balance,
|
||||
balance: ExternalBalance,
|
||||
) -> DispatchResult {
|
||||
// check the network didn't reach the economic security yet
|
||||
if EconomicSecurity::<T>::economic_security_block(network).is_some() {
|
||||
Err(Error::<T>::NetworkHasEconomicSecurity)?;
|
||||
if let NetworkId::External(n) = network {
|
||||
if EconomicSecurity::<T>::economic_security_block(n).is_some() {
|
||||
Err(Error::<T>::NetworkHasEconomicSecurity)?;
|
||||
}
|
||||
} else {
|
||||
// we target 20% of the network's stake to be behind the Serai network
|
||||
let mut total_stake = 0;
|
||||
for n in NETWORKS {
|
||||
total_stake += ValidatorSets::<T>::total_allocated_stake(n).unwrap_or(Amount(0)).0;
|
||||
}
|
||||
|
||||
let stake = ValidatorSets::<T>::total_allocated_stake(network).unwrap_or(Amount(0)).0;
|
||||
let desired_stake = total_stake / (100 / SERAI_VALIDATORS_DESIRED_PERCENTAGE);
|
||||
if stake >= desired_stake {
|
||||
Err(Error::<T>::NetworkHasEconomicSecurity)?;
|
||||
}
|
||||
}
|
||||
|
||||
// swap half of the liquidity for SRI to form PoL.
|
||||
let half = balance.amount.0 / 2;
|
||||
let path = BoundedVec::try_from(vec![balance.coin, Coin::Serai]).unwrap();
|
||||
let path = BoundedVec::try_from(vec![balance.coin.into(), Coin::Serai]).unwrap();
|
||||
let origin = RawOrigin::Signed(POL_ACCOUNT.into());
|
||||
Dex::<T>::swap_exact_tokens_for_tokens(
|
||||
origin.clone().into(),
|
||||
|
||||
@@ -54,9 +54,9 @@ pub mod pallet {
|
||||
#[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 },
|
||||
GenesisLiquidityAdded { by: SeraiAddress, balance: ExternalBalance },
|
||||
GenesisLiquidityRemoved { by: SeraiAddress, balance: ExternalBalance },
|
||||
GenesisLiquidityAddedToPool { coin: ExternalBalance, sri: Amount },
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
@@ -64,15 +64,23 @@ pub mod pallet {
|
||||
|
||||
/// 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>;
|
||||
pub(crate) type Liquidity<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Identity,
|
||||
ExternalCoin,
|
||||
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>;
|
||||
pub(crate) type Supply<T: Config> =
|
||||
StorageMap<_, Identity, ExternalCoin, LiquidityAmount, OptionQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub(crate) type Oracle<T: Config> = StorageMap<_, Identity, Coin, u64, OptionQuery>;
|
||||
pub(crate) type Oracle<T: Config> = StorageMap<_, Identity, ExternalCoin, u64, OptionQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn genesis_complete_block)]
|
||||
@@ -102,11 +110,7 @@ pub mod pallet {
|
||||
// get pool & total values
|
||||
let mut pool_values = vec![];
|
||||
let mut total_value: u128 = 0;
|
||||
for coin in COINS {
|
||||
if coin == Coin::Serai {
|
||||
continue;
|
||||
}
|
||||
|
||||
for coin in EXTERNAL_COINS {
|
||||
// initial coin value in terms of btc
|
||||
let Some(value) = Oracle::<T>::get(coin) else {
|
||||
continue;
|
||||
@@ -158,7 +162,7 @@ pub mod pallet {
|
||||
|
||||
// let everyone know about the event
|
||||
Self::deposit_event(Event::GenesisLiquidityAddedToPool {
|
||||
coin1: Balance { coin, amount: Amount(u64::try_from(pool_amount).unwrap()) },
|
||||
coin: ExternalBalance { coin, amount: Amount(u64::try_from(pool_amount).unwrap()) },
|
||||
sri: Amount(sri_amount),
|
||||
});
|
||||
}
|
||||
@@ -180,7 +184,7 @@ pub mod pallet {
|
||||
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 {
|
||||
pub fn add_coin_liquidity(account: PublicKey, balance: ExternalBalance) -> DispatchResult {
|
||||
// check we are still in genesis period
|
||||
if Self::genesis_ended() {
|
||||
Err(Error::<T>::GenesisPeriodEnded)?;
|
||||
@@ -227,7 +231,7 @@ pub mod pallet {
|
||||
/// 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 {
|
||||
for n in EXTERNAL_NETWORKS {
|
||||
let ec_security_block =
|
||||
EconomicSecurity::<T>::economic_security_block(n)?.saturated_into::<u64>();
|
||||
let current = <frame_system::Pallet<T>>::block_number().saturated_into::<u64>();
|
||||
@@ -243,11 +247,7 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
fn oraclization_is_done() -> bool {
|
||||
for c in COINS {
|
||||
if c == Coin::Serai {
|
||||
continue;
|
||||
}
|
||||
|
||||
for c in EXTERNAL_COINS {
|
||||
if Oracle::<T>::get(c).is_none() {
|
||||
return false;
|
||||
}
|
||||
@@ -276,7 +276,7 @@ pub mod pallet {
|
||||
/// 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 {
|
||||
pub fn remove_coin_liquidity(origin: OriginFor<T>, balance: ExternalBalance) -> 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)?;
|
||||
@@ -297,7 +297,7 @@ pub mod pallet {
|
||||
|
||||
// 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);
|
||||
let prev_coin = Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin.into());
|
||||
Dex::<T>::remove_liquidity(
|
||||
origin.clone().into(),
|
||||
balance.coin,
|
||||
@@ -307,7 +307,8 @@ pub mod pallet {
|
||||
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);
|
||||
let current_coin =
|
||||
Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin.into());
|
||||
|
||||
// burn the SRI if necessary
|
||||
// TODO: take into consideration movement between pools.
|
||||
@@ -333,7 +334,7 @@ pub mod pallet {
|
||||
Coins::<T>::transfer(
|
||||
origin.clone().into(),
|
||||
account,
|
||||
Balance { coin: balance.coin, amount: Amount(coin_out) },
|
||||
Balance { coin: balance.coin.into(), amount: Amount(coin_out) },
|
||||
)?;
|
||||
Coins::<T>::transfer(
|
||||
origin.into(),
|
||||
@@ -366,7 +367,7 @@ pub mod pallet {
|
||||
Coins::<T>::transfer(
|
||||
origin.into(),
|
||||
account,
|
||||
Balance { coin: balance.coin, amount: Amount(existing.coins) },
|
||||
Balance { coin: balance.coin.into(), amount: Amount(existing.coins) },
|
||||
)?;
|
||||
|
||||
(
|
||||
@@ -404,10 +405,10 @@ pub mod pallet {
|
||||
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));
|
||||
Oracle::<T>::set(ExternalCoin::Bitcoin, Some(10u64.pow(ExternalCoin::Bitcoin.decimals())));
|
||||
Oracle::<T>::set(ExternalCoin::Monero, Some(values.monero));
|
||||
Oracle::<T>::set(ExternalCoin::Ether, Some(values.ether));
|
||||
Oracle::<T>::set(ExternalCoin::Dai, Some(values.dai));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use sp_io::hashing::blake2_256;
|
||||
|
||||
use serai_primitives::{BlockHash, NetworkId};
|
||||
use serai_primitives::*;
|
||||
|
||||
pub use in_instructions_primitives as primitives;
|
||||
use primitives::*;
|
||||
@@ -23,8 +23,6 @@ pub mod pallet {
|
||||
use sp_runtime::traits::Zero;
|
||||
use sp_core::sr25519::Public;
|
||||
|
||||
use serai_primitives::{Coin, Amount, Balance};
|
||||
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::{pallet_prelude::*, RawOrigin};
|
||||
|
||||
@@ -34,7 +32,7 @@ pub mod pallet {
|
||||
};
|
||||
use dex_pallet::{Config as DexConfig, Pallet as Dex};
|
||||
use validator_sets_pallet::{
|
||||
primitives::{Session, ValidatorSet},
|
||||
primitives::{Session, ValidatorSet, ExternalValidatorSet},
|
||||
Config as ValidatorSetsConfig, Pallet as ValidatorSets,
|
||||
};
|
||||
|
||||
@@ -61,7 +59,7 @@ pub mod pallet {
|
||||
#[pallet::generate_deposit(fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
Batch {
|
||||
network: NetworkId,
|
||||
network: ExternalNetworkId,
|
||||
publishing_session: Session,
|
||||
id: u32,
|
||||
external_network_block_hash: BlockHash,
|
||||
@@ -85,17 +83,18 @@ pub mod pallet {
|
||||
// The ID of the last executed Batch for a network.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn batches)]
|
||||
pub(crate) type LastBatch<T: Config> = StorageMap<_, Identity, NetworkId, u32, OptionQuery>;
|
||||
pub(crate) type LastBatch<T: Config> =
|
||||
StorageMap<_, Identity, ExternalNetworkId, u32, OptionQuery>;
|
||||
|
||||
// The last Serai block in which this validator set included a batch
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn last_batch_block)]
|
||||
pub(crate) type LastBatchBlock<T: Config> =
|
||||
StorageMap<_, Identity, NetworkId, BlockNumberFor<T>, OptionQuery>;
|
||||
StorageMap<_, Identity, ExternalNetworkId, BlockNumberFor<T>, OptionQuery>;
|
||||
|
||||
// Halted networks.
|
||||
#[pallet::storage]
|
||||
pub(crate) type Halted<T: Config> = StorageMap<_, Identity, NetworkId, (), OptionQuery>;
|
||||
pub(crate) type Halted<T: Config> = StorageMap<_, Identity, ExternalNetworkId, (), OptionQuery>;
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
// Use a dedicated transaction layer when executing this InInstruction
|
||||
@@ -104,8 +103,7 @@ pub mod pallet {
|
||||
fn execute(instruction: &InInstructionWithBalance) -> Result<(), DispatchError> {
|
||||
match &instruction.instruction {
|
||||
InInstruction::Transfer(address) => {
|
||||
let address = *address;
|
||||
Coins::<T>::mint(address.into(), instruction.balance)?;
|
||||
Coins::<T>::mint(address.into(), instruction.balance.into())?;
|
||||
}
|
||||
InInstruction::Dex(call) => {
|
||||
// This will only be initiated by external chain transactions. That is why we only need
|
||||
@@ -118,11 +116,11 @@ pub mod pallet {
|
||||
let coin = instruction.balance.coin;
|
||||
|
||||
// mint the given coin on the account
|
||||
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance)?;
|
||||
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance.into())?;
|
||||
|
||||
// swap half of it for SRI
|
||||
let half = instruction.balance.amount.0 / 2;
|
||||
let path = BoundedVec::try_from(vec![coin, Coin::Serai]).unwrap();
|
||||
let path = BoundedVec::try_from(vec![coin.into(), Coin::Serai]).unwrap();
|
||||
Dex::<T>::swap_exact_tokens_for_tokens(
|
||||
origin.clone().into(),
|
||||
path,
|
||||
@@ -148,13 +146,13 @@ pub mod pallet {
|
||||
// TODO: minimums are set to 1 above to guarantee successful adding liq call.
|
||||
// Ideally we either get this info from user or send the leftovers back to user.
|
||||
// Let's send the leftovers back to user for now.
|
||||
let coin_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), coin);
|
||||
let coin_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), coin.into());
|
||||
let sri_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), Coin::Serai);
|
||||
if coin_balance != Amount(0) {
|
||||
Coins::<T>::transfer_internal(
|
||||
IN_INSTRUCTION_EXECUTOR.into(),
|
||||
address.into(),
|
||||
Balance { coin, amount: coin_balance },
|
||||
Balance { coin: coin.into(), amount: coin_balance },
|
||||
)?;
|
||||
}
|
||||
if sri_balance != Amount(0) {
|
||||
@@ -175,10 +173,10 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
// mint the given coin on our account
|
||||
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance)?;
|
||||
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance.into())?;
|
||||
|
||||
// get the path
|
||||
let mut path = vec![instruction.balance.coin, Coin::Serai];
|
||||
let mut path = vec![instruction.balance.coin.into(), Coin::Serai];
|
||||
if !native_coin {
|
||||
path.push(out_balance.coin);
|
||||
}
|
||||
@@ -212,7 +210,10 @@ pub mod pallet {
|
||||
instruction: OutInstruction {
|
||||
address: out_address.clone().as_external().unwrap(),
|
||||
},
|
||||
balance: Balance { coin: out_balance.coin, amount: coin_balance },
|
||||
balance: ExternalBalance {
|
||||
coin: out_balance.coin.try_into().unwrap(),
|
||||
amount: coin_balance,
|
||||
},
|
||||
};
|
||||
Coins::<T>::burn_with_instruction(origin.into(), instruction)?;
|
||||
}
|
||||
@@ -220,20 +221,18 @@ pub mod pallet {
|
||||
}
|
||||
}
|
||||
InInstruction::GenesisLiquidity(address) => {
|
||||
let address = *address;
|
||||
Coins::<T>::mint(GENESIS_LIQUIDITY_ACCOUNT.into(), instruction.balance)?;
|
||||
Coins::<T>::mint(GENESIS_LIQUIDITY_ACCOUNT.into(), instruction.balance.into())?;
|
||||
GenesisLiq::<T>::add_coin_liquidity(address.into(), instruction.balance)?;
|
||||
}
|
||||
InInstruction::SwapToStakedSRI(address, network) => {
|
||||
let address = *address;
|
||||
Coins::<T>::mint(POL_ACCOUNT.into(), instruction.balance)?;
|
||||
Emissions::<T>::swap_to_staked_sri(address.into(), *network, instruction.balance)?;
|
||||
Coins::<T>::mint(POL_ACCOUNT.into(), instruction.balance.into())?;
|
||||
Emissions::<T>::swap_to_staked_sri(address.into(), network, instruction.balance)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn halt(network: NetworkId) -> Result<(), DispatchError> {
|
||||
pub fn halt(network: ExternalNetworkId) -> Result<(), DispatchError> {
|
||||
Halted::<T>::set(network, Some(()));
|
||||
Self::deposit_event(Event::Halt { network });
|
||||
Ok(())
|
||||
@@ -241,13 +240,13 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
fn keys_for_network<T: Config>(
|
||||
network: NetworkId,
|
||||
network: ExternalNetworkId,
|
||||
) -> Result<(Session, Option<Public>, Option<Public>), InvalidTransaction> {
|
||||
// If there's no session set, and therefore no keys set, then this must be an invalid signature
|
||||
let Some(session) = ValidatorSets::<T>::session(network) else {
|
||||
let Some(session) = ValidatorSets::<T>::session(NetworkId::from(network)) else {
|
||||
Err(InvalidTransaction::BadProof)?
|
||||
};
|
||||
let mut set = ValidatorSet { session, network };
|
||||
let mut set = ExternalValidatorSet { network, session };
|
||||
let latest = ValidatorSets::<T>::keys(set).map(|keys| keys.0);
|
||||
let prior = if set.session.0 != 0 {
|
||||
set.session.0 -= 1;
|
||||
@@ -290,12 +289,7 @@ pub mod pallet {
|
||||
if batch.batch.encode().len() > MAX_BATCH_SIZE {
|
||||
Err(InvalidTransaction::ExhaustsResources)?;
|
||||
}
|
||||
|
||||
let network = batch.batch.network;
|
||||
// Don't allow the Serai set to publish `Batch`s as-if Serai itself was an external network
|
||||
if network == NetworkId::Serai {
|
||||
Err(InvalidTransaction::Custom(0))?;
|
||||
}
|
||||
|
||||
// verify the signature
|
||||
let (current_session, prior, current) = keys_for_network::<T>(network)?;
|
||||
@@ -325,7 +319,7 @@ pub mod pallet {
|
||||
// key is publishing `Batch`s. This should only happen once the current key has verified all
|
||||
// `Batch`s published by the prior key, meaning they are accepting the hand-over.
|
||||
if prior.is_some() && (!valid_by_prior) {
|
||||
ValidatorSets::<T>::retire_set(ValidatorSet { network, session: prior_session });
|
||||
ValidatorSets::<T>::retire_set(ValidatorSet { network: network.into(), session: prior_session });
|
||||
}
|
||||
|
||||
// check that this validator set isn't publishing a batch more than once per block
|
||||
|
||||
@@ -97,7 +97,7 @@ pub struct RefundableInInstruction {
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct InInstructionWithBalance {
|
||||
pub instruction: InInstruction,
|
||||
pub balance: Balance,
|
||||
pub balance: ExternalBalance,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, TypeInfo, RuntimeDebug)]
|
||||
@@ -105,7 +105,7 @@ pub struct InInstructionWithBalance {
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Batch {
|
||||
pub network: NetworkId,
|
||||
pub network: ExternalNetworkId,
|
||||
pub id: u32,
|
||||
pub external_network_block_hash: BlockHash,
|
||||
pub instructions: Vec<InInstructionWithBalance>,
|
||||
|
||||
@@ -9,7 +9,7 @@ use serde::{Serialize, Deserialize};
|
||||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
use serai_primitives::{Coin, Amount, SeraiAddress, ExternalAddress};
|
||||
use serai_primitives::{Amount, ExternalAddress, ExternalCoin, SeraiAddress};
|
||||
|
||||
use coins_primitives::OutInstruction;
|
||||
|
||||
@@ -25,7 +25,7 @@ pub enum Shorthand {
|
||||
Raw(RefundableInInstruction),
|
||||
Swap {
|
||||
origin: Option<ExternalAddress>,
|
||||
coin: Coin,
|
||||
coin: ExternalCoin,
|
||||
minimum: Amount,
|
||||
out: OutInstruction,
|
||||
},
|
||||
|
||||
@@ -55,6 +55,21 @@ fn devnet_genesis(
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let key_shares = NETWORKS
|
||||
.iter()
|
||||
.map(|network| match network {
|
||||
NetworkId::Serai => (NetworkId::Serai, Amount(50_000 * 10_u64.pow(8))),
|
||||
NetworkId::External(ExternalNetworkId::Bitcoin) => {
|
||||
(NetworkId::External(ExternalNetworkId::Bitcoin), Amount(1_000_000 * 10_u64.pow(8)))
|
||||
}
|
||||
NetworkId::External(ExternalNetworkId::Ethereum) => {
|
||||
(NetworkId::External(ExternalNetworkId::Ethereum), Amount(1_000_000 * 10_u64.pow(8)))
|
||||
}
|
||||
NetworkId::External(ExternalNetworkId::Monero) => {
|
||||
(NetworkId::External(ExternalNetworkId::Monero), Amount(100_000 * 10_u64.pow(8)))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
RuntimeGenesisConfig {
|
||||
system: SystemConfig { code: wasm_binary.to_vec(), _config: PhantomData },
|
||||
@@ -70,29 +85,10 @@ fn devnet_genesis(
|
||||
},
|
||||
|
||||
validator_sets: ValidatorSetsConfig {
|
||||
networks: serai_runtime::primitives::NETWORKS
|
||||
.iter()
|
||||
.map(|network| match network {
|
||||
NetworkId::Serai => (NetworkId::Serai, Amount(50_000 * 10_u64.pow(8))),
|
||||
NetworkId::Bitcoin => (NetworkId::Bitcoin, Amount(1_000_000 * 10_u64.pow(8))),
|
||||
NetworkId::Ethereum => (NetworkId::Ethereum, Amount(1_000_000 * 10_u64.pow(8))),
|
||||
NetworkId::Monero => (NetworkId::Monero, Amount(100_000 * 10_u64.pow(8))),
|
||||
})
|
||||
.collect(),
|
||||
networks: key_shares.clone(),
|
||||
participants: validators.clone(),
|
||||
},
|
||||
emissions: EmissionsConfig {
|
||||
networks: serai_runtime::primitives::NETWORKS
|
||||
.iter()
|
||||
.map(|network| match network {
|
||||
NetworkId::Serai => (NetworkId::Serai, Amount(50_000 * 10_u64.pow(8))),
|
||||
NetworkId::Bitcoin => (NetworkId::Bitcoin, Amount(1_000_000 * 10_u64.pow(8))),
|
||||
NetworkId::Ethereum => (NetworkId::Ethereum, Amount(1_000_000 * 10_u64.pow(8))),
|
||||
NetworkId::Monero => (NetworkId::Monero, Amount(100_000 * 10_u64.pow(8))),
|
||||
})
|
||||
.collect(),
|
||||
participants: validators.iter().map(|(validator, _)| *validator).collect(),
|
||||
},
|
||||
emissions: EmissionsConfig { networks: key_shares, participants: validators.clone() },
|
||||
signals: SignalsConfig::default(),
|
||||
babe: BabeConfig {
|
||||
authorities: validators.iter().map(|validator| (validator.0.into(), 1)).collect(),
|
||||
@@ -112,6 +108,21 @@ fn testnet_genesis(wasm_binary: &[u8], validators: Vec<&'static str>) -> Runtime
|
||||
.into_iter()
|
||||
.map(|validator| Public::decode(&mut hex::decode(validator).unwrap().as_slice()).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let key_shares = NETWORKS
|
||||
.iter()
|
||||
.map(|network| match network {
|
||||
NetworkId::Serai => (NetworkId::Serai, Amount(50_000 * 10_u64.pow(8))),
|
||||
NetworkId::External(ExternalNetworkId::Bitcoin) => {
|
||||
(NetworkId::External(ExternalNetworkId::Bitcoin), Amount(1_000_000 * 10_u64.pow(8)))
|
||||
}
|
||||
NetworkId::External(ExternalNetworkId::Ethereum) => {
|
||||
(NetworkId::External(ExternalNetworkId::Ethereum), Amount(1_000_000 * 10_u64.pow(8)))
|
||||
}
|
||||
NetworkId::External(ExternalNetworkId::Monero) => {
|
||||
(NetworkId::External(ExternalNetworkId::Monero), Amount(100_000 * 10_u64.pow(8)))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(validators.iter().collect::<HashSet<_>>().len(), validators.len());
|
||||
|
||||
@@ -129,29 +140,10 @@ fn testnet_genesis(wasm_binary: &[u8], validators: Vec<&'static str>) -> Runtime
|
||||
},
|
||||
|
||||
validator_sets: ValidatorSetsConfig {
|
||||
networks: serai_runtime::primitives::NETWORKS
|
||||
.iter()
|
||||
.map(|network| match network {
|
||||
NetworkId::Serai => (NetworkId::Serai, Amount(50_000 * 10_u64.pow(8))),
|
||||
NetworkId::Bitcoin => (NetworkId::Bitcoin, Amount(1_000_000 * 10_u64.pow(8))),
|
||||
NetworkId::Ethereum => (NetworkId::Ethereum, Amount(1_000_000 * 10_u64.pow(8))),
|
||||
NetworkId::Monero => (NetworkId::Monero, Amount(100_000 * 10_u64.pow(8))),
|
||||
})
|
||||
.collect(),
|
||||
participants: validators.clone(),
|
||||
},
|
||||
emissions: EmissionsConfig {
|
||||
networks: serai_runtime::primitives::NETWORKS
|
||||
.iter()
|
||||
.map(|network| match network {
|
||||
NetworkId::Serai => (NetworkId::Serai, Amount(50_000 * 10_u64.pow(8))),
|
||||
NetworkId::Bitcoin => (NetworkId::Bitcoin, Amount(1_000_000 * 10_u64.pow(8))),
|
||||
NetworkId::Ethereum => (NetworkId::Ethereum, Amount(1_000_000 * 10_u64.pow(8))),
|
||||
NetworkId::Monero => (NetworkId::Monero, Amount(100_000 * 10_u64.pow(8))),
|
||||
})
|
||||
.collect(),
|
||||
networks: key_shares.clone(),
|
||||
participants: validators.clone(),
|
||||
},
|
||||
emissions: EmissionsConfig { networks: key_shares, participants: validators.clone() },
|
||||
signals: SignalsConfig::default(),
|
||||
babe: BabeConfig {
|
||||
authorities: validators.iter().map(|validator| ((*validator).into(), 1)).collect(),
|
||||
|
||||
@@ -30,6 +30,7 @@ sp-application-crypto = { git = "https://github.com/serai-dex/substrate", defaul
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
@@ -37,7 +38,7 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur
|
||||
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
|
||||
|
||||
[features]
|
||||
std = ["zeroize", "ciphersuite/std", "scale/std", "borsh?/std", "serde?/std", "scale-info/std", "sp-core/std", "sp-runtime/std", "frame-support/std"]
|
||||
std = ["zeroize", "ciphersuite/std", "scale/std", "borsh?/std", "serde?/std", "scale-info/std", "sp-core/std", "sp-runtime/std", "sp-std/std", "frame-support/std"]
|
||||
borsh = ["dep:borsh"]
|
||||
serde = ["dep:serde"]
|
||||
default = ["std"]
|
||||
|
||||
@@ -29,6 +29,8 @@ pub type SubstrateAmount = u64;
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Amount(pub SubstrateAmount);
|
||||
|
||||
// TODO: these impl shouldn't panic and return error to be dealt with.
|
||||
// Otherwise we might have a panic that stops the network.
|
||||
impl Add for Amount {
|
||||
type Output = Amount;
|
||||
fn add(self, other: Amount) -> Amount {
|
||||
|
||||
@@ -11,7 +11,7 @@ use serde::{Serialize, Deserialize};
|
||||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
use crate::{Coin, Amount};
|
||||
use crate::{Amount, Coin, ExternalCoin};
|
||||
|
||||
/// The type used for balances (a Coin and Balance).
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
@@ -23,6 +23,34 @@ pub struct Balance {
|
||||
pub amount: Amount,
|
||||
}
|
||||
|
||||
/// The type used for balances (a Coin and Balance).
|
||||
#[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 ExternalBalance {
|
||||
pub coin: ExternalCoin,
|
||||
pub amount: Amount,
|
||||
}
|
||||
|
||||
impl From<ExternalBalance> for Balance {
|
||||
fn from(balance: ExternalBalance) -> Self {
|
||||
Balance { coin: balance.coin.into(), amount: balance.amount }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Balance> for ExternalBalance {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(balance: Balance) -> Result<Self, Self::Error> {
|
||||
match balance.coin {
|
||||
Coin::Serai => Err(())?,
|
||||
Coin::External(coin) => Ok(ExternalBalance { coin, amount: balance.amount }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: these impl either should be removed or return errors in case of overflows
|
||||
impl Add<Amount> for Balance {
|
||||
type Output = Balance;
|
||||
fn add(self, other: Amount) -> Balance {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(feature = "std")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale::{Decode, Encode, EncodeLike, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
@@ -10,6 +10,7 @@ use borsh::{BorshSerialize, BorshDeserialize};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use sp_core::{ConstU32, bounded::BoundedVec};
|
||||
use sp_std::{vec, vec::Vec};
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
use crate::{borsh_serialize_bounded_vec, borsh_deserialize_bounded_vec};
|
||||
@@ -24,28 +25,124 @@ pub enum EmbeddedEllipticCurve {
|
||||
Secq256k1,
|
||||
}
|
||||
|
||||
/// The type used to identify networks.
|
||||
/// The type used to identify external networks.
|
||||
#[derive(
|
||||
Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, PartialOrd, Ord, MaxEncodedLen, TypeInfo,
|
||||
)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum NetworkId {
|
||||
Serai,
|
||||
pub enum ExternalNetworkId {
|
||||
Bitcoin,
|
||||
Ethereum,
|
||||
Monero,
|
||||
}
|
||||
impl NetworkId {
|
||||
|
||||
impl Encode for ExternalNetworkId {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
match self {
|
||||
ExternalNetworkId::Bitcoin => vec![1],
|
||||
ExternalNetworkId::Ethereum => vec![2],
|
||||
ExternalNetworkId::Monero => vec![3],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for ExternalNetworkId {
|
||||
fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
|
||||
let kind = input.read_byte()?;
|
||||
match kind {
|
||||
1 => Ok(Self::Bitcoin),
|
||||
2 => Ok(Self::Ethereum),
|
||||
3 => Ok(Self::Monero),
|
||||
_ => Err(scale::Error::from("invalid format")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MaxEncodedLen for ExternalNetworkId {
|
||||
fn max_encoded_len() -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodeLike for ExternalNetworkId {}
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
impl BorshSerialize for ExternalNetworkId {
|
||||
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
|
||||
writer.write_all(&self.encode())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
impl BorshDeserialize for ExternalNetworkId {
|
||||
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
|
||||
let mut kind = [0; 1];
|
||||
reader.read_exact(&mut kind)?;
|
||||
ExternalNetworkId::decode(&mut kind.as_slice())
|
||||
.map_err(|_| std::io::Error::other("invalid format"))
|
||||
}
|
||||
}
|
||||
|
||||
/// The type used to identify networks.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum NetworkId {
|
||||
Serai,
|
||||
External(ExternalNetworkId),
|
||||
}
|
||||
|
||||
impl Encode for NetworkId {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
match self {
|
||||
NetworkId::Serai => vec![0],
|
||||
NetworkId::External(network) => network.encode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for NetworkId {
|
||||
fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
|
||||
let kind = input.read_byte()?;
|
||||
match kind {
|
||||
0 => Ok(Self::Serai),
|
||||
_ => Ok(ExternalNetworkId::decode(&mut [kind].as_slice())?.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MaxEncodedLen for NetworkId {
|
||||
fn max_encoded_len() -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodeLike for NetworkId {}
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
impl BorshSerialize for NetworkId {
|
||||
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
|
||||
writer.write_all(&self.encode())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
impl BorshDeserialize for NetworkId {
|
||||
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
|
||||
let mut kind = [0; 1];
|
||||
reader.read_exact(&mut kind)?;
|
||||
NetworkId::decode(&mut kind.as_slice()).map_err(|_| std::io::Error::other("invalid format"))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternalNetworkId {
|
||||
/// The embedded elliptic curve actively used for this network.
|
||||
///
|
||||
/// This is guaranteed to return `[]`, `[Embedwards25519]`, or
|
||||
/// `[Embedwards25519, *network specific curve*]`.
|
||||
pub fn embedded_elliptic_curves(&self) -> &'static [EmbeddedEllipticCurve] {
|
||||
match self {
|
||||
// We don't use any embedded elliptic curves for Serai as we don't perform a DKG for Serai
|
||||
Self::Serai => &[],
|
||||
// We need to generate a Ristretto key for oraclizing and a Secp256k1 key for the network
|
||||
Self::Bitcoin | Self::Ethereum => {
|
||||
&[EmbeddedEllipticCurve::Embedwards25519, EmbeddedEllipticCurve::Secq256k1]
|
||||
@@ -55,36 +152,227 @@ impl NetworkId {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn coins(&self) -> &'static [Coin] {
|
||||
pub fn coins(&self) -> Vec<ExternalCoin> {
|
||||
match self {
|
||||
Self::Serai => &[Coin::Serai],
|
||||
Self::Bitcoin => &[Coin::Bitcoin],
|
||||
Self::Ethereum => &[Coin::Ether, Coin::Dai],
|
||||
Self::Monero => &[Coin::Monero],
|
||||
Self::Bitcoin => vec![ExternalCoin::Bitcoin],
|
||||
Self::Ethereum => vec![ExternalCoin::Ether, ExternalCoin::Dai],
|
||||
Self::Monero => vec![ExternalCoin::Monero],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const NETWORKS: [NetworkId; 4] =
|
||||
[NetworkId::Serai, NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero];
|
||||
impl NetworkId {
|
||||
pub fn coins(&self) -> Vec<Coin> {
|
||||
match self {
|
||||
Self::Serai => vec![Coin::Serai],
|
||||
Self::External(network) => {
|
||||
network.coins().into_iter().map(core::convert::Into::into).collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const COINS: [Coin; 5] = [Coin::Serai, Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero];
|
||||
impl From<ExternalNetworkId> for NetworkId {
|
||||
fn from(network: ExternalNetworkId) -> Self {
|
||||
NetworkId::External(network)
|
||||
}
|
||||
}
|
||||
|
||||
/// The type used to identify coins.
|
||||
#[derive(
|
||||
Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Encode, Decode, MaxEncodedLen, TypeInfo,
|
||||
)]
|
||||
impl TryFrom<NetworkId> for ExternalNetworkId {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(network: NetworkId) -> Result<Self, Self::Error> {
|
||||
match network {
|
||||
NetworkId::Serai => Err(())?,
|
||||
NetworkId::External(n) => Ok(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const EXTERNAL_NETWORKS: [ExternalNetworkId; 3] =
|
||||
[ExternalNetworkId::Bitcoin, ExternalNetworkId::Ethereum, ExternalNetworkId::Monero];
|
||||
|
||||
pub const NETWORKS: [NetworkId; 4] = [
|
||||
NetworkId::Serai,
|
||||
NetworkId::External(ExternalNetworkId::Bitcoin),
|
||||
NetworkId::External(ExternalNetworkId::Ethereum),
|
||||
NetworkId::External(ExternalNetworkId::Monero),
|
||||
];
|
||||
|
||||
pub const EXTERNAL_COINS: [ExternalCoin; 4] =
|
||||
[ExternalCoin::Bitcoin, ExternalCoin::Ether, ExternalCoin::Dai, ExternalCoin::Monero];
|
||||
|
||||
pub const COINS: [Coin; 5] = [
|
||||
Coin::Serai,
|
||||
Coin::External(ExternalCoin::Bitcoin),
|
||||
Coin::External(ExternalCoin::Ether),
|
||||
Coin::External(ExternalCoin::Dai),
|
||||
Coin::External(ExternalCoin::Monero),
|
||||
];
|
||||
|
||||
/// The type used to identify external coins.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Coin {
|
||||
Serai,
|
||||
pub enum ExternalCoin {
|
||||
Bitcoin,
|
||||
Ether,
|
||||
Dai,
|
||||
Monero,
|
||||
}
|
||||
|
||||
impl Encode for ExternalCoin {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
match self {
|
||||
ExternalCoin::Bitcoin => vec![4],
|
||||
ExternalCoin::Ether => vec![5],
|
||||
ExternalCoin::Dai => vec![6],
|
||||
ExternalCoin::Monero => vec![7],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for ExternalCoin {
|
||||
fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
|
||||
let kind = input.read_byte()?;
|
||||
match kind {
|
||||
4 => Ok(Self::Bitcoin),
|
||||
5 => Ok(Self::Ether),
|
||||
6 => Ok(Self::Dai),
|
||||
7 => Ok(Self::Monero),
|
||||
_ => Err(scale::Error::from("invalid format")),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MaxEncodedLen for ExternalCoin {
|
||||
fn max_encoded_len() -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodeLike for ExternalCoin {}
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
impl BorshSerialize for ExternalCoin {
|
||||
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
|
||||
writer.write_all(&self.encode())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
impl BorshDeserialize for ExternalCoin {
|
||||
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
|
||||
let mut kind = [0; 1];
|
||||
reader.read_exact(&mut kind)?;
|
||||
ExternalCoin::decode(&mut kind.as_slice()).map_err(|_| std::io::Error::other("invalid format"))
|
||||
}
|
||||
}
|
||||
|
||||
/// The type used to identify coins.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Coin {
|
||||
Serai,
|
||||
External(ExternalCoin),
|
||||
}
|
||||
|
||||
impl Encode for Coin {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Coin::Serai => vec![0],
|
||||
Coin::External(ec) => ec.encode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for Coin {
|
||||
fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
|
||||
let kind = input.read_byte()?;
|
||||
match kind {
|
||||
0 => Ok(Self::Serai),
|
||||
_ => Ok(ExternalCoin::decode(&mut [kind].as_slice())?.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MaxEncodedLen for Coin {
|
||||
fn max_encoded_len() -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodeLike for Coin {}
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
impl BorshSerialize for Coin {
|
||||
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
|
||||
writer.write_all(&self.encode())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
impl BorshDeserialize for Coin {
|
||||
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
|
||||
let mut kind = [0; 1];
|
||||
reader.read_exact(&mut kind)?;
|
||||
Coin::decode(&mut kind.as_slice()).map_err(|_| std::io::Error::other("invalid format"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExternalCoin> for Coin {
|
||||
fn from(coin: ExternalCoin) -> Self {
|
||||
Coin::External(coin)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Coin> for ExternalCoin {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(coin: Coin) -> Result<Self, Self::Error> {
|
||||
match coin {
|
||||
Coin::Serai => Err(())?,
|
||||
Coin::External(c) => Ok(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternalCoin {
|
||||
pub fn network(&self) -> ExternalNetworkId {
|
||||
match self {
|
||||
ExternalCoin::Bitcoin => ExternalNetworkId::Bitcoin,
|
||||
ExternalCoin::Ether | ExternalCoin::Dai => ExternalNetworkId::Ethereum,
|
||||
ExternalCoin::Monero => ExternalNetworkId::Monero,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
ExternalCoin::Bitcoin => "Bitcoin",
|
||||
ExternalCoin::Ether => "Ether",
|
||||
ExternalCoin::Dai => "Dai Stablecoin",
|
||||
ExternalCoin::Monero => "Monero",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn symbol(&self) -> &'static str {
|
||||
match self {
|
||||
ExternalCoin::Bitcoin => "BTC",
|
||||
ExternalCoin::Ether => "ETH",
|
||||
ExternalCoin::Dai => "DAI",
|
||||
ExternalCoin::Monero => "XMR",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decimals(&self) -> u32 {
|
||||
match self {
|
||||
// Ether and DAI have 18 decimals, yet we only track 8 in order to fit them within u64s
|
||||
ExternalCoin::Bitcoin | ExternalCoin::Ether | ExternalCoin::Dai => 8,
|
||||
ExternalCoin::Monero => 12,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Coin {
|
||||
pub fn native() -> Coin {
|
||||
Coin::Serai
|
||||
@@ -93,37 +381,28 @@ impl Coin {
|
||||
pub fn network(&self) -> NetworkId {
|
||||
match self {
|
||||
Coin::Serai => NetworkId::Serai,
|
||||
Coin::Bitcoin => NetworkId::Bitcoin,
|
||||
Coin::Ether | Coin::Dai => NetworkId::Ethereum,
|
||||
Coin::Monero => NetworkId::Monero,
|
||||
Coin::External(c) => c.network().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Coin::Serai => "Serai",
|
||||
Coin::Bitcoin => "Bitcoin",
|
||||
Coin::Ether => "Ether",
|
||||
Coin::Dai => "Dai Stablecoin",
|
||||
Coin::Monero => "Monero",
|
||||
Coin::External(c) => c.name(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn symbol(&self) -> &'static str {
|
||||
match self {
|
||||
Coin::Serai => "SRI",
|
||||
Coin::Bitcoin => "BTC",
|
||||
Coin::Ether => "ETH",
|
||||
Coin::Dai => "DAI",
|
||||
Coin::Monero => "XMR",
|
||||
Coin::External(c) => c.symbol(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decimals(&self) -> u32 {
|
||||
match self {
|
||||
// Ether and DAI have 18 decimals, yet we only track 8 in order to fit them within u64s
|
||||
Coin::Serai | Coin::Bitcoin | Coin::Ether | Coin::Dai => 8,
|
||||
Coin::Monero => 12,
|
||||
Coin::Serai => 8,
|
||||
Coin::External(c) => c.decimals(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,9 @@ use sp_runtime::{
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use primitives::{
|
||||
NetworkId, PublicKey, AccountLookup, SubstrateAmount, Coin, NETWORKS, MEDIAN_PRICE_WINDOW_LENGTH,
|
||||
HOURS, DAYS, MINUTES, TARGET_BLOCK_TIME, BLOCK_SIZE, FAST_EPOCH_DURATION,
|
||||
NetworkId, PublicKey, AccountLookup, SubstrateAmount, Coin, EXTERNAL_NETWORKS,
|
||||
MEDIAN_PRICE_WINDOW_LENGTH, HOURS, DAYS, MINUTES, TARGET_BLOCK_TIME, BLOCK_SIZE,
|
||||
FAST_EPOCH_DURATION,
|
||||
};
|
||||
|
||||
use support::{
|
||||
@@ -570,10 +571,7 @@ sp_api::impl_runtime_apis! {
|
||||
.map(|(id, _)| id.into_inner().0)
|
||||
.collect::<hashbrown::HashSet<_>>();
|
||||
let mut all = serai_validators;
|
||||
for network in NETWORKS {
|
||||
if network == NetworkId::Serai {
|
||||
continue;
|
||||
}
|
||||
for network in EXTERNAL_NETWORKS {
|
||||
// Returning the latest-decided, not latest and active, means the active set
|
||||
// may fail to peer find if there isn't sufficient overlap. If a large amount reboot,
|
||||
// forcing some validators to successfully peer find in order for the threshold to become
|
||||
@@ -581,7 +579,7 @@ sp_api::impl_runtime_apis! {
|
||||
//
|
||||
// This is assumed not to matter in real life, yet an interesting note.
|
||||
let participants =
|
||||
ValidatorSets::participants_for_latest_decided_set(network)
|
||||
ValidatorSets::participants_for_latest_decided_set(NetworkId::from(network))
|
||||
.map_or(vec![], BoundedVec::into_inner);
|
||||
for (participant, _) in participants {
|
||||
all.insert(participant.0);
|
||||
@@ -610,25 +608,25 @@ sp_api::impl_runtime_apis! {
|
||||
|
||||
impl dex::DexApi<Block> for Runtime {
|
||||
fn quote_price_exact_tokens_for_tokens(
|
||||
asset1: Coin,
|
||||
asset2: Coin,
|
||||
coin1: Coin,
|
||||
coin2: Coin,
|
||||
amount: SubstrateAmount,
|
||||
include_fee: bool
|
||||
) -> Option<SubstrateAmount> {
|
||||
Dex::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee)
|
||||
Dex::quote_price_exact_tokens_for_tokens(coin1, coin2, amount, include_fee)
|
||||
}
|
||||
|
||||
fn quote_price_tokens_for_exact_tokens(
|
||||
asset1: Coin,
|
||||
asset2: Coin,
|
||||
coin1: Coin,
|
||||
coin2: Coin,
|
||||
amount: SubstrateAmount,
|
||||
include_fee: bool
|
||||
) -> Option<SubstrateAmount> {
|
||||
Dex::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee)
|
||||
Dex::quote_price_tokens_for_exact_tokens(coin1, coin2, amount, include_fee)
|
||||
}
|
||||
|
||||
fn get_reserves(asset1: Coin, asset2: Coin) -> Option<(SubstrateAmount, SubstrateAmount)> {
|
||||
Dex::get_reserves(&asset1, &asset2).ok()
|
||||
fn get_reserves(coin1: Coin, coin2: Coin) -> Option<(SubstrateAmount, SubstrateAmount)> {
|
||||
Dex::get_reserves(&coin1, &coin2).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
use serai_primitives::NetworkId;
|
||||
use serai_primitives::ExternalNetworkId;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(zeroize::Zeroize))]
|
||||
@@ -13,5 +13,5 @@ use serai_primitives::NetworkId;
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum SignalId {
|
||||
Retirement([u8; 32]),
|
||||
Halt(NetworkId),
|
||||
Halt(ExternalNetworkId),
|
||||
}
|
||||
|
||||
@@ -334,11 +334,13 @@ pub mod pallet {
|
||||
/// The generated key pair for a given validator set instance.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn keys)]
|
||||
pub type Keys<T: Config> = StorageMap<_, Twox64Concat, ValidatorSet, KeyPair, OptionQuery>;
|
||||
pub type Keys<T: Config> =
|
||||
StorageMap<_, Twox64Concat, ExternalValidatorSet, KeyPair, OptionQuery>;
|
||||
|
||||
/// The key for validator sets which can (and still need to) publish their slash reports.
|
||||
#[pallet::storage]
|
||||
pub type PendingSlashReport<T: Config> = StorageMap<_, Identity, NetworkId, Public, OptionQuery>;
|
||||
pub type PendingSlashReport<T: Config> =
|
||||
StorageMap<_, Identity, ExternalNetworkId, Public, OptionQuery>;
|
||||
|
||||
/// Disabled validators.
|
||||
#[pallet::storage]
|
||||
@@ -361,7 +363,7 @@ pub mod pallet {
|
||||
removed: T::AccountId,
|
||||
},
|
||||
KeyGen {
|
||||
set: ValidatorSet,
|
||||
set: ExternalValidatorSet,
|
||||
key_pair: KeyPair,
|
||||
},
|
||||
AcceptedHandover {
|
||||
@@ -636,14 +638,16 @@ pub mod pallet {
|
||||
amount: Amount,
|
||||
) -> Result<bool, DispatchError> {
|
||||
// Check it's safe to decrease this set's stake by this amount
|
||||
let new_total_staked = Self::total_allocated_stake(network)
|
||||
.unwrap()
|
||||
.0
|
||||
.checked_sub(amount.0)
|
||||
.ok_or(Error::<T>::NotEnoughAllocated)?;
|
||||
let required_stake = Self::required_stake_for_network(network);
|
||||
if new_total_staked < required_stake {
|
||||
Err(Error::<T>::DeallocationWouldRemoveEconomicSecurity)?;
|
||||
if let NetworkId::External(n) = network {
|
||||
let new_total_staked = Self::total_allocated_stake(NetworkId::from(n))
|
||||
.unwrap()
|
||||
.0
|
||||
.checked_sub(amount.0)
|
||||
.ok_or(Error::<T>::NotEnoughAllocated)?;
|
||||
let required_stake = Self::required_stake_for_network(n);
|
||||
if new_total_staked < required_stake {
|
||||
Err(Error::<T>::DeallocationWouldRemoveEconomicSecurity)?;
|
||||
}
|
||||
}
|
||||
|
||||
let old_allocation =
|
||||
@@ -726,20 +730,23 @@ pub mod pallet {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handover is automatically complete for Serai as it doesn't have a handover protocol
|
||||
if network == NetworkId::Serai {
|
||||
let NetworkId::External(n) = network else {
|
||||
// Handover is automatically complete for Serai as it doesn't have a handover protocol
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// The current session must have set keys for its handover to be completed
|
||||
if !Keys::<T>::contains_key(ValidatorSet { network, session }) {
|
||||
if !Keys::<T>::contains_key(ExternalValidatorSet { network: n, session }) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This must be the first session (which has set keys) OR the prior session must have been
|
||||
// retired (signified by its keys no longer being present)
|
||||
(session.0 == 0) ||
|
||||
(!Keys::<T>::contains_key(ValidatorSet { network, session: Session(session.0 - 1) }))
|
||||
(!Keys::<T>::contains_key(ExternalValidatorSet {
|
||||
network: n,
|
||||
session: Session(session.0 - 1),
|
||||
}))
|
||||
}
|
||||
|
||||
fn new_session() {
|
||||
@@ -769,19 +776,23 @@ pub mod pallet {
|
||||
// TODO: This is called retire_set, yet just starts retiring the set
|
||||
// Update the nomenclature within this function
|
||||
pub fn retire_set(set: ValidatorSet) {
|
||||
// If the prior prior set didn't report, emit they're retired now
|
||||
if PendingSlashReport::<T>::get(set.network).is_some() {
|
||||
Self::deposit_event(Event::SetRetired {
|
||||
set: ValidatorSet { network: set.network, session: Session(set.session.0 - 1) },
|
||||
});
|
||||
}
|
||||
|
||||
// Serai doesn't set keys and network slashes are handled by BABE/GRANDPA
|
||||
if set.network != NetworkId::Serai {
|
||||
if let NetworkId::External(n) = set.network {
|
||||
// If the prior prior set didn't report, emit they're retired now
|
||||
if PendingSlashReport::<T>::get(n).is_some() {
|
||||
Self::deposit_event(Event::SetRetired {
|
||||
set: ValidatorSet { network: set.network, session: Session(set.session.0 - 1) },
|
||||
});
|
||||
}
|
||||
|
||||
// This overwrites the prior value as the prior to-report set's stake presumably just
|
||||
// unlocked, making their report unenforceable
|
||||
let keys = Keys::<T>::take(set).unwrap();
|
||||
PendingSlashReport::<T>::set(set.network, Some(keys.0));
|
||||
let keys =
|
||||
Keys::<T>::take(ExternalValidatorSet { network: n, session: set.session }).unwrap();
|
||||
PendingSlashReport::<T>::set(n, Some(keys.0));
|
||||
} else {
|
||||
// emit the event for serai network
|
||||
Self::deposit_event(Event::SetRetired { set });
|
||||
}
|
||||
|
||||
// We're retiring this set because the set after it accepted the handover
|
||||
@@ -853,7 +864,7 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
/// Returns the required stake in terms SRI for a given `Balance`.
|
||||
pub fn required_stake(balance: &Balance) -> SubstrateAmount {
|
||||
pub fn required_stake(balance: &ExternalBalance) -> SubstrateAmount {
|
||||
use dex_pallet::HigherPrecisionBalance;
|
||||
|
||||
// This is inclusive to an increase in accuracy
|
||||
@@ -876,11 +887,11 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
/// Returns the current total required stake for a given `network`.
|
||||
pub fn required_stake_for_network(network: NetworkId) -> SubstrateAmount {
|
||||
pub fn required_stake_for_network(network: ExternalNetworkId) -> SubstrateAmount {
|
||||
let mut total_required = 0;
|
||||
for coin in network.coins() {
|
||||
let supply = Coins::<T>::supply(coin);
|
||||
total_required += Self::required_stake(&Balance { coin: *coin, amount: Amount(supply) });
|
||||
let supply = Coins::<T>::supply(Coin::from(coin));
|
||||
total_required += Self::required_stake(&ExternalBalance { coin, amount: Amount(supply) });
|
||||
}
|
||||
total_required
|
||||
}
|
||||
@@ -976,7 +987,7 @@ pub mod pallet {
|
||||
#[pallet::weight(0)] // TODO
|
||||
pub fn set_keys(
|
||||
origin: OriginFor<T>,
|
||||
network: NetworkId,
|
||||
network: ExternalNetworkId,
|
||||
key_pair: KeyPair,
|
||||
signature_participants: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
|
||||
signature: Signature,
|
||||
@@ -988,8 +999,8 @@ pub mod pallet {
|
||||
let _ = signature_participants;
|
||||
let _ = signature;
|
||||
|
||||
let session = Self::session(network).unwrap();
|
||||
let set = ValidatorSet { network, session };
|
||||
let session = Self::session(NetworkId::from(network)).unwrap();
|
||||
let set = ExternalValidatorSet { network, session };
|
||||
|
||||
Keys::<T>::set(set, Some(key_pair.clone()));
|
||||
|
||||
@@ -997,7 +1008,7 @@ pub mod pallet {
|
||||
// We generally set TotalAllocatedStake when the prior set retires, and the new set is fully
|
||||
// active and liable. Since this is the first set, there is no prior set to wait to retire
|
||||
if session == Session(0) {
|
||||
Self::set_total_allocated_stake(network);
|
||||
Self::set_total_allocated_stake(NetworkId::from(network));
|
||||
}
|
||||
|
||||
Self::deposit_event(Event::KeyGen { set, key_pair });
|
||||
@@ -1009,7 +1020,7 @@ pub mod pallet {
|
||||
#[pallet::weight(0)] // TODO
|
||||
pub fn report_slashes(
|
||||
origin: OriginFor<T>,
|
||||
network: NetworkId,
|
||||
network: ExternalNetworkId,
|
||||
slashes: SlashReport,
|
||||
signature: Signature,
|
||||
) -> DispatchResult {
|
||||
@@ -1024,7 +1035,10 @@ pub mod pallet {
|
||||
|
||||
// Emit set retireed
|
||||
Pallet::<T>::deposit_event(Event::SetRetired {
|
||||
set: ValidatorSet { network, session: Session(Self::session(network).unwrap().0 - 1) },
|
||||
set: ValidatorSet {
|
||||
network: network.into(),
|
||||
session: Session(Self::session(NetworkId::from(network)).unwrap().0 - 1),
|
||||
},
|
||||
});
|
||||
|
||||
Ok(())
|
||||
@@ -1124,17 +1138,12 @@ pub mod pallet {
|
||||
Call::set_keys { network, ref key_pair, ref signature_participants, ref signature } => {
|
||||
let network = *network;
|
||||
|
||||
// Don't allow the Serai set to set_keys, as they have no reason to do so
|
||||
if network == NetworkId::Serai {
|
||||
Err(InvalidTransaction::Custom(0))?;
|
||||
}
|
||||
|
||||
// Confirm this set has a session
|
||||
let Some(current_session) = Self::session(network) else {
|
||||
let Some(current_session) = Self::session(NetworkId::from(network)) else {
|
||||
Err(InvalidTransaction::Custom(1))?
|
||||
};
|
||||
|
||||
let set = ValidatorSet { network, session: current_session };
|
||||
let set = ExternalValidatorSet { network, session: current_session };
|
||||
|
||||
// Confirm it has yet to set keys
|
||||
if Keys::<T>::get(set).is_some() {
|
||||
@@ -1143,7 +1152,7 @@ pub mod pallet {
|
||||
|
||||
// This is a needed precondition as this uses storage variables for the latest decided
|
||||
// session on this assumption
|
||||
assert_eq!(Pallet::<T>::latest_decided_session(network), Some(current_session));
|
||||
assert_eq!(Pallet::<T>::latest_decided_session(network.into()), Some(current_session));
|
||||
|
||||
let participants =
|
||||
Participants::<T>::get(network).expect("session existed without participants");
|
||||
@@ -1158,7 +1167,7 @@ pub mod pallet {
|
||||
let mut signing_key_shares = 0;
|
||||
for (participant, in_use) in participants.into_iter().zip(signature_participants) {
|
||||
let participant = participant.0;
|
||||
let shares = InSet::<T>::get(network, participant)
|
||||
let shares = InSet::<T>::get(NetworkId::from(network), participant)
|
||||
.expect("participant from Participants wasn't InSet");
|
||||
all_key_shares += shares;
|
||||
|
||||
@@ -1192,17 +1201,14 @@ pub mod pallet {
|
||||
}
|
||||
Call::report_slashes { network, ref slashes, ref signature } => {
|
||||
let network = *network;
|
||||
// Don't allow Serai to publish a slash report as BABE/GRANDPA handles slashes directly
|
||||
if network == NetworkId::Serai {
|
||||
Err(InvalidTransaction::Custom(0))?;
|
||||
}
|
||||
let Some(key) = PendingSlashReport::<T>::take(network) else {
|
||||
// Assumed already published
|
||||
Err(InvalidTransaction::Stale)?
|
||||
};
|
||||
|
||||
// There must have been a previous session is PendingSlashReport is populated
|
||||
let set =
|
||||
ValidatorSet { network, session: Session(Self::session(network).unwrap().0 - 1) };
|
||||
ExternalValidatorSet { network, session: Session(Self::session(network).unwrap().0 - 1) };
|
||||
if !key.verify(&slashes.report_slashes_message(), signature) {
|
||||
Err(InvalidTransaction::BadProof)?;
|
||||
}
|
||||
@@ -1228,13 +1234,14 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
impl<T: Config> AllowMint for Pallet<T> {
|
||||
fn is_allowed(balance: &Balance) -> bool {
|
||||
fn is_allowed(balance: &ExternalBalance) -> bool {
|
||||
// get the required stake
|
||||
let current_required = Self::required_stake_for_network(balance.coin.network());
|
||||
let new_required = current_required + Self::required_stake(balance);
|
||||
|
||||
// get the total stake for the network & compare.
|
||||
let staked = Self::total_allocated_stake(balance.coin.network()).unwrap_or(Amount(0));
|
||||
let staked =
|
||||
Self::total_allocated_stake(NetworkId::from(balance.coin.network())).unwrap_or(Amount(0));
|
||||
staked.0 >= new_required
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use sp_core::{ConstU32, bounded::BoundedVec, sr25519::Public};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
use serai_primitives::NetworkId;
|
||||
use serai_primitives::{ExternalNetworkId, NetworkId};
|
||||
|
||||
mod slash_points;
|
||||
pub use slash_points::*;
|
||||
@@ -57,6 +57,33 @@ pub struct ValidatorSet {
|
||||
pub network: NetworkId,
|
||||
}
|
||||
|
||||
/// The type used to identify a specific validator set during a specific session.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct ExternalValidatorSet {
|
||||
pub session: Session,
|
||||
pub network: ExternalNetworkId,
|
||||
}
|
||||
|
||||
impl From<ExternalValidatorSet> for ValidatorSet {
|
||||
fn from(set: ExternalValidatorSet) -> Self {
|
||||
ValidatorSet { session: set.session, network: set.network.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ValidatorSet> for ExternalValidatorSet {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(set: ValidatorSet) -> Result<Self, Self::Error> {
|
||||
match set.network {
|
||||
NetworkId::Serai => Err(())?,
|
||||
NetworkId::External(network) => Ok(ExternalValidatorSet { session: set.session, network }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type representing a Key from an external network.
|
||||
pub type ExternalKey = BoundedVec<u8, ConstU32<MAX_KEY_LEN>>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user