add specific network/coin/balance types (#619)

* add specific network/coin/balance types

* misc fixes

* fix clippy

* misc fixes

* fix pr comments

* Make halting for external networks

* fix encode/decode
This commit is contained in:
akildemir
2024-10-07 05:16:11 +03:00
committed by GitHub
parent d7ecab605e
commit 435f1d9ae1
91 changed files with 1536 additions and 1055 deletions

View File

@@ -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,

View File

@@ -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 },
}

View File

@@ -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 },
}

View File

@@ -16,7 +16,7 @@ pub enum Call {
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
pub enum Event {
Batch { network: NetworkId, id: u32, block: BlockHash, instructions_hash: [u8; 32] },
InstructionFailure { network: NetworkId, id: u32, index: u32 },
Halt { network: NetworkId },
Batch { network: ExternalNetworkId, id: u32, block: BlockHash, instructions_hash: [u8; 32] },
InstructionFailure { network: ExternalNetworkId, id: u32, index: u32 },
Halt { network: ExternalNetworkId },
}

View File

@@ -10,13 +10,13 @@ use serai_validator_sets_primitives::*;
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
pub enum Call {
set_keys {
network: NetworkId,
network: ExternalNetworkId,
removed_participants: BoundedVec<SeraiAddress, ConstU32<{ MAX_KEY_SHARES_PER_SET / 3 }>>,
key_pair: KeyPair,
signature: Signature,
},
report_slashes {
network: NetworkId,
network: ExternalNetworkId,
slashes: BoundedVec<(SeraiAddress, u32), ConstU32<{ MAX_KEY_SHARES_PER_SET / 3 }>>,
signature: Signature,
},
@@ -47,7 +47,7 @@ pub enum Event {
removed: SeraiAddress,
},
KeyGen {
set: ValidatorSet,
set: ExternalValidatorSet,
key_pair: KeyPair,
},
AcceptedHandover {

View File

@@ -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<'a> SeraiDex<'a> {
}
pub fn add_liquidity(
coin: Coin,
coin: ExternalCoin,
coin_amount: Amount,
sri_amount: Amount,
min_coin_amount: Amount,
@@ -61,11 +61,14 @@ impl<'a> SeraiDex<'a> {
}
/// 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
}
}

View File

@@ -35,7 +35,7 @@ impl<'a> SeraiGenesisLiquidity<'a> {
))
}
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<'a> SeraiGenesisLiquidity<'a> {
pub async fn liquidity(
&self,
address: &SeraiAddress,
coin: Coin,
coin: ExternalCoin,
) -> Result<LiquidityAmount, SeraiError> {
Ok(
self
@@ -59,7 +59,7 @@ impl<'a> SeraiGenesisLiquidity<'a> {
)
}
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()))
}

View File

@@ -2,7 +2,7 @@ pub use serai_abi::in_instructions::primitives;
use primitives::SignedBatch;
use crate::{
primitives::{BlockHash, NetworkId},
primitives::{BlockHash, ExternalNetworkId},
Transaction, SeraiError, Serai, TemporalSerai,
};
@@ -15,14 +15,14 @@ pub struct SeraiInInstructions<'a>(pub(crate) &'a TemporalSerai<'a>);
impl<'a> SeraiInInstructions<'a> {
pub async fn latest_block_for_network(
&self,
network: NetworkId,
network: ExternalNetworkId,
) -> Result<Option<BlockHash>, SeraiError> {
self.0.storage(PALLET, "LatestNetworkBlock", network).await
}
pub async fn last_batch_for_network(
&self,
network: NetworkId,
network: ExternalNetworkId,
) -> Result<Option<u32>, SeraiError> {
self.0.storage(PALLET, "LastBatch", network).await
}

View File

@@ -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<'a> SeraiLiquidityTokens<'a> {
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<'a> SeraiLiquidityTokens<'a> {
)
}
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(),
})
}
}

View File

@@ -2,12 +2,12 @@ use scale::Encode;
use sp_core::sr25519::{Public, Signature};
use serai_abi::primitives::Amount;
use serai_abi::{primitives::Amount, validator_sets::primitives::ExternalValidatorSet};
pub use serai_abi::validator_sets::primitives;
use primitives::{Session, ValidatorSet, KeyPair};
use primitives::{Session, KeyPair};
use crate::{
primitives::{NetworkId, SeraiAddress},
primitives::{NetworkId, ExternalNetworkId, SeraiAddress},
Transaction, Serai, TemporalSerai, SeraiError,
};
@@ -167,13 +167,13 @@ impl<'a> SeraiValidatorSets<'a> {
}
// 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
}
@@ -187,7 +187,7 @@ impl<'a> SeraiValidatorSets<'a> {
}
pub fn set_keys(
network: NetworkId,
network: ExternalNetworkId,
removed_participants: sp_runtime::BoundedVec<
SeraiAddress,
sp_core::ConstU32<{ primitives::MAX_KEY_SHARES_PER_SET / 3 }>,
@@ -212,7 +212,7 @@ impl<'a> SeraiValidatorSets<'a> {
}
pub fn report_slashes(
network: NetworkId,
network: ExternalNetworkId,
slashes: sp_runtime::BoundedVec<
(SeraiAddress, u32),
sp_core::ConstU32<{ primitives::MAX_KEY_SHARES_PER_SET / 3 }>,

View File

@@ -8,7 +8,7 @@ use blake2::{
use scale::Encode;
use serai_client::{
primitives::{Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress},
primitives::{Amount, BlockHash, ExternalBalance, ExternalCoin, SeraiAddress},
in_instructions::{
primitives::{InInstruction, InInstructionWithBalance, Batch},
InInstructionsEvent,
@@ -22,18 +22,17 @@ use common::in_instructions::provide_batch;
serai_test!(
publish_batch: (|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);
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 batch = Batch {
network,
@@ -67,9 +66,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);
})
);

View File

@@ -12,7 +12,7 @@ use sp_core::Pair;
use serai_client::{
primitives::{
Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, Data, ExternalAddress,
Amount, ExternalCoin, ExternalBalance, BlockHash, SeraiAddress, Data, ExternalAddress,
insecure_pair_from_name,
},
in_instructions::{
@@ -28,9 +28,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);
@@ -38,9 +36,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,
@@ -69,10 +68,10 @@ serai_test!(
assert_eq!(
serai.coins().mint_events().await.unwrap(),
vec![CoinsEvent::Mint { to: address, balance }]
vec![CoinsEvent::Mint { to: address, balance: balance.into() }]
);
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().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];
@@ -99,7 +98,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));
})
);

View File

@@ -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,

View File

@@ -11,11 +11,12 @@ use sp_core::{sr25519::Signature, Pair as PairTrait};
use serai_abi::{
genesis_liquidity::primitives::{oraclize_values_message, Values},
validator_sets::primitives::{musig_context, Session, ValidatorSet},
in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch},
in_instructions::primitives::{Batch, InInstruction, InInstructionWithBalance},
primitives::{
Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, insecure_pair_from_name,
insecure_pair_from_name, Amount, ExternalBalance, BlockHash, ExternalCoin, ExternalNetworkId,
NetworkId, SeraiAddress, EXTERNAL_COINS,
},
validator_sets::primitives::{musig_context, Session, ValidatorSet},
};
use serai_client::{Serai, SeraiGenesisLiquidity};
@@ -25,12 +26,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 +38,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<_>>();
@@ -73,8 +73,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)

View File

@@ -9,8 +9,8 @@ use scale::Encode;
use sp_core::Pair;
use serai_client::{
primitives::{insecure_pair_from_name, BlockHash, NetworkId, Balance, SeraiAddress},
validator_sets::primitives::{ValidatorSet, KeyPair},
primitives::{insecure_pair_from_name, BlockHash, ExternalBalance, SeraiAddress},
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() {
@@ -65,8 +65,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] {
@@ -74,7 +73,7 @@ pub async fn mint_coin(
OsRng.fill_bytes(&mut block_hash.0);
let batch = Batch {
network,
network: balance.coin.network(),
id: batch_id,
block: block_hash,
instructions: vec![InInstructionWithBalance {

View File

@@ -15,7 +15,7 @@ use schnorrkel::Schnorrkel;
use serai_client::{
validator_sets::{
primitives::{ValidatorSet, KeyPair, musig_context, set_keys_message},
primitives::{ExternalValidatorSet, KeyPair, musig_context, set_keys_message},
ValidatorSetsEvent,
},
Amount, Serai, SeraiValidatorSets,
@@ -26,7 +26,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] {
@@ -46,7 +46,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(),
);
}

View File

@@ -6,8 +6,8 @@ use serai_abi::in_instructions::primitives::DexCall;
use serai_client::{
primitives::{
Amount, NetworkId, Coin, Balance, BlockHash, insecure_pair_from_name, ExternalAddress,
SeraiAddress,
Amount, Coin, Balance, BlockHash, insecure_pair_from_name, ExternalAddress, SeraiAddress,
ExternalCoin, ExternalBalance,
},
in_instructions::primitives::{
InInstruction, InInstructionWithBalance, Batch, IN_INSTRUCTION_EXECUTOR, OutAddress,
@@ -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,
block: 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,
block: 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,
block: 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,
block: 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 {

View File

@@ -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());

View File

@@ -7,16 +7,13 @@ use serai_abi::{
emissions::primitives::{INITIAL_REWARD_PER_BLOCK, SECURE_BY},
in_instructions::primitives::Batch,
primitives::{
BlockHash, Coin, COINS, FAST_EPOCH_DURATION, FAST_EPOCH_INITIAL_PERIOD, NETWORKS,
TARGET_BLOCK_TIME,
BlockHash, ExternalBalance, ExternalCoin, ExternalNetworkId, EXTERNAL_NETWORKS,
FAST_EPOCH_DURATION, FAST_EPOCH_INITIAL_PERIOD, NETWORKS, TARGET_BLOCK_TIME, Amount, NetworkId,
},
validator_sets::primitives::Session,
};
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,31 +24,32 @@ 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, Batch { network, id: ids[&network], block, instructions: vec![] }).await;
}
provide_batch(serai, Batch { network, id: ids[&network], block, instructions: vec![] }).await;
}
}
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;
@@ -144,7 +142,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));
@@ -208,18 +206,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;
}
@@ -227,7 +221,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%)

View File

@@ -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;

View File

@@ -7,17 +7,18 @@ use sp_core::{
use serai_client::{
primitives::{
NETWORKS, NetworkId, BlockHash, insecure_pair_from_name, FAST_EPOCH_DURATION, TARGET_BLOCK_TIME,
NETWORKS, NetworkId, BlockHash, insecure_pair_from_name, FAST_EPOCH_DURATION,
TARGET_BLOCK_TIME, ExternalNetworkId, Amount,
},
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;
@@ -58,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();
@@ -89,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()
@@ -197,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
@@ -208,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
@@ -237,7 +238,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;
@@ -265,7 +267,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) = {
@@ -283,7 +286,12 @@ async fn validator_set_rotation() {
// provide a batch to complete the handover and retire the previous set
let mut block_hash = BlockHash([0; 32]);
OsRng.fill_bytes(&mut block_hash.0);
let batch = Batch { network, id: 0, block: block_hash, instructions: vec![] };
let batch = Batch {
network: network.try_into().unwrap(),
id: 0,
block: block_hash,
instructions: vec![],
};
publish_tx(
&serai,
&SeraiInInstructions::execute_batch(SignedBatch {

View File

@@ -1,13 +1,13 @@
#![cfg_attr(not(feature = "std"), no_std)]
use serai_primitives::{Coin, SubstrateAmount, Balance};
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 +161,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 +233,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(())
}

View File

@@ -13,7 +13,7 @@ use serde::{Serialize, Deserialize};
use scale::{Encode, Decode, MaxEncodedLen};
use scale_info::TypeInfo;
use serai_primitives::{Balance, SeraiAddress, ExternalAddress, Data, system_address};
use serai_primitives::{system_address, Data, ExternalAddress, ExternalBalance, SeraiAddress};
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)]

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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() {

View File

@@ -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() });

View File

@@ -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(),

View File

@@ -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(())
}
}

View File

@@ -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,
};
@@ -60,9 +58,9 @@ pub mod pallet {
#[pallet::event]
#[pallet::generate_deposit(fn deposit_event)]
pub enum Event<T: Config> {
Batch { network: NetworkId, id: u32, block: BlockHash, instructions_hash: [u8; 32] },
InstructionFailure { network: NetworkId, id: u32, index: u32 },
Halt { network: NetworkId },
Batch { network: ExternalNetworkId, id: u32, block: BlockHash, instructions_hash: [u8; 32] },
InstructionFailure { network: ExternalNetworkId, id: u32, index: u32 },
Halt { network: ExternalNetworkId },
}
#[pallet::error]
@@ -77,23 +75,24 @@ 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>;
// The latest block a network has acknowledged as finalized
#[pallet::storage]
#[pallet::getter(fn latest_network_block)]
pub(crate) type LatestNetworkBlock<T: Config> =
StorageMap<_, Identity, NetworkId, BlockHash, OptionQuery>;
StorageMap<_, Identity, ExternalNetworkId, BlockHash, OptionQuery>;
impl<T: Config> Pallet<T> {
// Use a dedicated transaction layer when executing this InInstruction
@@ -102,7 +101,7 @@ pub mod pallet {
fn execute(instruction: InInstructionWithBalance) -> Result<(), DispatchError> {
match instruction.instruction {
InInstruction::Transfer(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
@@ -114,11 +113,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,
@@ -144,13 +143,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) {
@@ -171,10 +170,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);
}
@@ -210,7 +209,10 @@ pub mod pallet {
// TODO: Properly pass data. Replace address with an OutInstruction entirely?
data: None,
},
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)?;
}
@@ -218,18 +220,18 @@ pub mod pallet {
}
}
InInstruction::GenesisLiquidity(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) => {
Coins::<T>::mint(POL_ACCOUNT.into(), 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(())
@@ -237,13 +239,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;
@@ -303,12 +305,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)?;
@@ -336,7 +333,7 @@ pub mod pallet {
// `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,
network: network.into(),
session: Session(current_session.0 - 1),
});
}

View File

@@ -20,7 +20,7 @@ use sp_std::vec::Vec;
use sp_runtime::RuntimeDebug;
#[rustfmt::skip]
use serai_primitives::{BlockHash, Balance, NetworkId, SeraiAddress, ExternalAddress, system_address};
use serai_primitives::{BlockHash, Balance, ExternalNetworkId, NetworkId, SeraiAddress, ExternalBalance, ExternalAddress, system_address};
mod shorthand;
pub use shorthand::*;
@@ -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 block: BlockHash,
pub instructions: Vec<InInstructionWithBalance>,

View File

@@ -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,
},

View File

@@ -33,6 +33,22 @@ fn devnet_genesis(
endowed_accounts: Vec<PublicKey>,
) -> RuntimeGenesisConfig {
let validators = validators.iter().map(|name| account_from_name(name)).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 },
@@ -47,29 +63,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(),
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(),
@@ -88,6 +85,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());
@@ -105,29 +117,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(),

View File

@@ -28,6 +28,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 }
@@ -35,7 +36,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", "scale/std", "borsh?/std", "serde?/std", "scale-info/std", "sp-core/std", "sp-runtime/std", "frame-support/std"]
std = ["zeroize", "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"]

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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,54 +10,342 @@ 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};
/// The type used to identify networks.
#[derive(
Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, PartialOrd, Ord, MaxEncodedLen, TypeInfo,
)]
/// The type used to identify external networks.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord, 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 {
pub fn coins(&self) -> &'static [Coin] {
impl Encode for ExternalNetworkId {
fn encode(&self) -> Vec<u8> {
match self {
Self::Serai => &[Coin::Serai],
Self::Bitcoin => &[Coin::Bitcoin],
Self::Ethereum => &[Coin::Ether, Coin::Dai],
Self::Monero => &[Coin::Monero],
ExternalNetworkId::Bitcoin => vec![1],
ExternalNetworkId::Ethereum => vec![2],
ExternalNetworkId::Monero => vec![3],
}
}
}
pub const NETWORKS: [NetworkId; 4] =
[NetworkId::Serai, NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero];
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")),
}
}
}
pub const COINS: [Coin; 5] = [Coin::Serai, Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero];
impl MaxEncodedLen for ExternalNetworkId {
fn max_encoded_len() -> usize {
1
}
}
/// The type used to identify coins.
#[derive(
Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Encode, Decode, MaxEncodedLen, TypeInfo,
)]
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 = "borsh", derive(BorshSerialize, BorshDeserialize))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Coin {
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 {
pub fn coins(&self) -> Vec<ExternalCoin> {
match self {
Self::Bitcoin => vec![ExternalCoin::Bitcoin],
Self::Ethereum => vec![ExternalCoin::Ether, ExternalCoin::Dai],
Self::Monero => vec![ExternalCoin::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()
}
}
}
}
impl From<ExternalNetworkId> for NetworkId {
fn from(network: ExternalNetworkId) -> Self {
NetworkId::External(network)
}
}
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 = "serde", derive(Serialize, Deserialize))]
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
@@ -66,37 +354,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(),
}
}

View File

@@ -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()
}
}
}

View File

@@ -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),
}

View File

@@ -316,11 +316,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]
@@ -343,7 +345,7 @@ pub mod pallet {
removed: T::AccountId,
},
KeyGen {
set: ValidatorSet,
set: ExternalValidatorSet,
key_pair: KeyPair,
},
AcceptedHandover {
@@ -600,14 +602,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 =
@@ -690,20 +694,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() {
@@ -733,19 +740,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
@@ -817,7 +828,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
@@ -840,11 +851,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
}
@@ -940,7 +951,7 @@ pub mod pallet {
#[pallet::weight(0)] // TODO
pub fn set_keys(
origin: OriginFor<T>,
network: NetworkId,
network: ExternalNetworkId,
removed_participants: BoundedVec<Public, ConstU32<{ MAX_KEY_SHARES_PER_SET / 3 }>>,
key_pair: KeyPair,
signature: Signature,
@@ -951,8 +962,8 @@ pub mod pallet {
// (called by pre_dispatch) checks it
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()));
@@ -960,7 +971,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));
}
// This does not remove from TotalAllocatedStake or InSet in order to:
@@ -970,7 +981,7 @@ pub mod pallet {
// 2) Not allow parties removed to immediately deallocate, per commentary on deallocation
// scheduling (https://github.com/serai-dex/serai/issues/394).
for removed in removed_participants {
Self::deposit_event(Event::ParticipantRemoved { set, removed });
Self::deposit_event(Event::ParticipantRemoved { set: set.into(), removed });
}
Self::deposit_event(Event::KeyGen { set, key_pair });
@@ -981,7 +992,7 @@ pub mod pallet {
#[pallet::weight(0)] // TODO
pub fn report_slashes(
origin: OriginFor<T>,
network: NetworkId,
network: ExternalNetworkId,
slashes: BoundedVec<(Public, u32), ConstU32<{ MAX_KEY_SHARES_PER_SET / 3 }>>,
signature: Signature,
) -> DispatchResult {
@@ -996,7 +1007,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(())
@@ -1062,17 +1076,12 @@ pub mod pallet {
Call::set_keys { network, ref removed_participants, ref key_pair, 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() {
@@ -1081,7 +1090,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));
// This does not slash the removed participants as that'll be done at the end of the
// set's lifetime
@@ -1094,15 +1103,15 @@ pub mod pallet {
removed.insert(participant.0);
}
let participants =
Participants::<T>::get(network).expect("session existed without participants");
let participants = Participants::<T>::get(NetworkId::from(network))
.expect("session existed without participants");
let mut all_key_shares = 0;
let mut signers = vec![];
let mut signing_key_shares = 0;
for participant in 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;
@@ -1124,7 +1133,7 @@ pub mod pallet {
// Verify the signature with the MuSig key of the signers
// We theoretically don't need set_keys_message to bind to removed_participants, as the
// key we're signing with effectively already does so, yet there's no reason not to
if !musig_key(set, &signers)
if !musig_key(set.into(), &signers)
.verify(&set_keys_message(&set, removed_participants, key_pair), signature)
{
Err(InvalidTransaction::BadProof)?;
@@ -1138,17 +1147,16 @@ 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) };
let set = ExternalValidatorSet {
network,
session: Session(Self::session(NetworkId::from(network)).unwrap().0 - 1),
};
if !key.verify(&report_slashes_message(&set, slashes), signature) {
Err(InvalidTransaction::BadProof)?;
}
@@ -1173,13 +1181,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
}
}

View File

@@ -17,7 +17,7 @@ use sp_core::{ConstU32, sr25519::Public, bounded::BoundedVec};
#[cfg(not(feature = "std"))]
use sp_std::vec::Vec;
use serai_primitives::NetworkId;
use serai_primitives::{ExternalNetworkId, NetworkId};
/// The maximum amount of key shares per set.
pub const MAX_KEY_SHARES_PER_SET: u32 = 150;
@@ -43,6 +43,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 }),
}
}
}
type MaxKeyLen = ConstU32<MAX_KEY_LEN>;
/// The type representing a Key from an external network.
pub type ExternalKey = BoundedVec<u8, MaxKeyLen>;
@@ -100,14 +127,14 @@ pub fn musig_key(set: ValidatorSet, set_keys: &[Public]) -> Public {
/// The message for the set_keys signature.
pub fn set_keys_message(
set: &ValidatorSet,
set: &ExternalValidatorSet,
removed_participants: &[Public],
key_pair: &KeyPair,
) -> Vec<u8> {
(b"ValidatorSets-set_keys", set, removed_participants, key_pair).encode()
}
pub fn report_slashes_message(set: &ValidatorSet, slashes: &[(Public, u32)]) -> Vec<u8> {
pub fn report_slashes_message(set: &ExternalValidatorSet, slashes: &[(Public, u32)]) -> Vec<u8> {
(b"ValidatorSets-report_slashes", set, slashes).encode()
}