add specific network/coin/balance types (#619)
Some checks failed
Full Stack Tests / build (push) Has been cancelled
Message Queue Tests / build (push) Has been cancelled
Coordinator Tests / build (push) Has been cancelled
Lint / clippy (macos-13) (push) Has been cancelled
Lint / machete (push) Has been cancelled
Lint / clippy (macos-14) (push) Has been cancelled
Lint / clippy (ubuntu-latest) (push) Has been cancelled
Lint / clippy (windows-latest) (push) Has been cancelled
Lint / deny (push) Has been cancelled
Lint / fmt (push) Has been cancelled
Processor Tests / build (push) Has been cancelled
Reproducible Runtime / build (push) Has been cancelled
Tests / test-infra (push) Has been cancelled
Tests / test-serai-client (push) Has been cancelled
Monero Tests / unit-tests (push) Has been cancelled
Monero Tests / integration-tests (v0.17.3.2) (push) Has been cancelled
Monero Tests / integration-tests (v0.18.3.4) (push) Has been cancelled
Tests / test-substrate (push) Has been cancelled

* 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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
91 changed files with 1536 additions and 1055 deletions

1
Cargo.lock generated
View file

@ -8393,6 +8393,7 @@ dependencies = [
"sp-core",
"sp-io",
"sp-runtime",
"sp-std",
"zeroize",
]

View file

@ -12,9 +12,9 @@ use tokio::{
use borsh::BorshSerialize;
use sp_application_crypto::RuntimePublic;
use serai_client::{
primitives::{NETWORKS, NetworkId, Signature},
validator_sets::primitives::{Session, ValidatorSet},
SeraiError, TemporalSerai, Serai,
primitives::{ExternalNetworkId, Signature, EXTERNAL_NETWORKS},
validator_sets::primitives::{ExternalValidatorSet, Session},
Serai, SeraiError, TemporalSerai,
};
use serai_db::{Get, DbTxn, Db, create_db};
@ -28,17 +28,17 @@ use crate::{
create_db! {
CosignDb {
ReceivedCosign: (set: ValidatorSet, block: [u8; 32]) -> CosignedBlock,
LatestCosign: (network: NetworkId) -> CosignedBlock,
DistinctChain: (set: ValidatorSet) -> (),
ReceivedCosign: (set: ExternalValidatorSet, block: [u8; 32]) -> CosignedBlock,
LatestCosign: (network: ExternalNetworkId) -> CosignedBlock,
DistinctChain: (set: ExternalValidatorSet) -> (),
}
}
pub struct CosignEvaluator<D: Db> {
db: Mutex<D>,
serai: Arc<Serai>,
stakes: RwLock<Option<HashMap<NetworkId, u64>>>,
latest_cosigns: RwLock<HashMap<NetworkId, CosignedBlock>>,
stakes: RwLock<Option<HashMap<ExternalNetworkId, u64>>>,
latest_cosigns: RwLock<HashMap<ExternalNetworkId, CosignedBlock>>,
}
impl<D: Db> CosignEvaluator<D> {
@ -79,7 +79,7 @@ impl<D: Db> CosignEvaluator<D> {
let serai = self.serai.as_of_latest_finalized_block().await?;
let mut stakes = HashMap::new();
for network in NETWORKS {
for network in EXTERNAL_NETWORKS {
// Use if this network has published a Batch for a short-circuit of if they've ever set a key
let set_key = serai.in_instructions().last_batch_for_network(network).await?.is_some();
if set_key {
@ -87,7 +87,7 @@ impl<D: Db> CosignEvaluator<D> {
network,
serai
.validator_sets()
.total_allocated_stake(network)
.total_allocated_stake(network.into())
.await?
.expect("network which published a batch didn't have a stake set")
.0,
@ -126,9 +126,9 @@ impl<D: Db> CosignEvaluator<D> {
async fn set_with_keys_fn(
serai: &TemporalSerai<'_>,
network: NetworkId,
) -> Result<Option<ValidatorSet>, SeraiError> {
let Some(latest_session) = serai.validator_sets().session(network).await? else {
network: ExternalNetworkId,
) -> Result<Option<ExternalValidatorSet>, SeraiError> {
let Some(latest_session) = serai.validator_sets().session(network.into()).await? else {
log::warn!("received cosign from {:?}, which doesn't yet have a session", network);
return Ok(None);
};
@ -136,13 +136,13 @@ impl<D: Db> CosignEvaluator<D> {
Ok(Some(
if serai
.validator_sets()
.keys(ValidatorSet { network, session: prior_session })
.keys(ExternalValidatorSet { network, session: prior_session })
.await?
.is_some()
{
ValidatorSet { network, session: prior_session }
ExternalValidatorSet { network, session: prior_session }
} else {
ValidatorSet { network, session: latest_session }
ExternalValidatorSet { network, session: latest_session }
},
))
}
@ -204,16 +204,12 @@ impl<D: Db> CosignEvaluator<D> {
let mut total_stake = 0;
let mut total_on_distinct_chain = 0;
for network in NETWORKS {
if network == NetworkId::Serai {
continue;
}
for network in EXTERNAL_NETWORKS {
// Get the current set for this network
let set_with_keys = {
let mut res;
while {
res = set_with_keys_fn(&serai, cosign.network).await;
res = set_with_keys_fn(&serai, network).await;
res.is_err()
} {
log::error!(
@ -231,7 +227,8 @@ impl<D: Db> CosignEvaluator<D> {
let stake = {
let mut res;
while {
res = serai.validator_sets().total_allocated_stake(set_with_keys.network).await;
res =
serai.validator_sets().total_allocated_stake(set_with_keys.network.into()).await;
res.is_err()
} {
log::error!(
@ -271,7 +268,7 @@ impl<D: Db> CosignEvaluator<D> {
#[allow(clippy::new_ret_no_self)]
pub fn new<P: P2p>(db: D, p2p: P, serai: Arc<Serai>) -> mpsc::UnboundedSender<CosignedBlock> {
let mut latest_cosigns = HashMap::new();
for network in NETWORKS {
for network in EXTERNAL_NETWORKS {
if let Some(cosign) = LatestCosign::get(&db, network) {
latest_cosigns.insert(network, cosign);
}

View file

@ -6,9 +6,9 @@ use blake2::{
use scale::Encode;
use borsh::{BorshSerialize, BorshDeserialize};
use serai_client::{
primitives::NetworkId,
validator_sets::primitives::{Session, ValidatorSet},
in_instructions::primitives::{Batch, SignedBatch},
primitives::ExternalNetworkId,
validator_sets::primitives::{ExternalValidatorSet, Session},
};
pub use serai_db::*;
@ -18,21 +18,21 @@ use crate::tributary::{TributarySpec, Transaction, scanner::RecognizedIdType};
create_db!(
MainDb {
HandledMessageDb: (network: NetworkId) -> u64,
HandledMessageDb: (network: ExternalNetworkId) -> u64,
ActiveTributaryDb: () -> Vec<u8>,
RetiredTributaryDb: (set: ValidatorSet) -> (),
RetiredTributaryDb: (set: ExternalValidatorSet) -> (),
FirstPreprocessDb: (
network: NetworkId,
network: ExternalNetworkId,
id_type: RecognizedIdType,
id: &[u8]
) -> Vec<Vec<u8>>,
LastReceivedBatchDb: (network: NetworkId) -> u32,
ExpectedBatchDb: (network: NetworkId, id: u32) -> [u8; 32],
BatchDb: (network: NetworkId, id: u32) -> SignedBatch,
LastVerifiedBatchDb: (network: NetworkId) -> u32,
HandoverBatchDb: (set: ValidatorSet) -> u32,
LookupHandoverBatchDb: (network: NetworkId, batch: u32) -> Session,
QueuedBatchesDb: (set: ValidatorSet) -> Vec<u8>
LastReceivedBatchDb: (network: ExternalNetworkId) -> u32,
ExpectedBatchDb: (network: ExternalNetworkId, id: u32) -> [u8; 32],
BatchDb: (network: ExternalNetworkId, id: u32) -> SignedBatch,
LastVerifiedBatchDb: (network: ExternalNetworkId) -> u32,
HandoverBatchDb: (set: ExternalValidatorSet) -> u32,
LookupHandoverBatchDb: (network: ExternalNetworkId, batch: u32) -> Session,
QueuedBatchesDb: (set: ExternalValidatorSet) -> Vec<u8>
}
);
@ -61,7 +61,7 @@ impl ActiveTributaryDb {
ActiveTributaryDb::set(txn, &existing_bytes);
}
pub fn retire_tributary(txn: &mut impl DbTxn, set: ValidatorSet) {
pub fn retire_tributary(txn: &mut impl DbTxn, set: ExternalValidatorSet) {
let mut active = Self::active_tributaries(txn).1;
for i in 0 .. active.len() {
if active[i].set() == set {
@ -82,7 +82,7 @@ impl ActiveTributaryDb {
impl FirstPreprocessDb {
pub fn save_first_preprocess(
txn: &mut impl DbTxn,
network: NetworkId,
network: ExternalNetworkId,
id_type: RecognizedIdType,
id: &[u8],
preprocess: &Vec<Vec<u8>>,
@ -108,19 +108,19 @@ impl ExpectedBatchDb {
}
impl HandoverBatchDb {
pub fn set_handover_batch(txn: &mut impl DbTxn, set: ValidatorSet, batch: u32) {
pub fn set_handover_batch(txn: &mut impl DbTxn, set: ExternalValidatorSet, batch: u32) {
Self::set(txn, set, &batch);
LookupHandoverBatchDb::set(txn, set.network, batch, &set.session);
}
}
impl QueuedBatchesDb {
pub fn queue(txn: &mut impl DbTxn, set: ValidatorSet, batch: &Transaction) {
pub fn queue(txn: &mut impl DbTxn, set: ExternalValidatorSet, batch: &Transaction) {
let mut batches = Self::get(txn, set).unwrap_or_default();
batch.write(&mut batches).unwrap();
Self::set(txn, set, &batches);
}
pub fn take(txn: &mut impl DbTxn, set: ValidatorSet) -> Vec<Transaction> {
pub fn take(txn: &mut impl DbTxn, set: ExternalValidatorSet) -> Vec<Transaction> {
let batches_vec = Self::get(txn, set).unwrap_or_default();
txn.del(Self::key(set));

View file

@ -23,8 +23,8 @@ use serai_db::{DbTxn, Db};
use scale::Encode;
use borsh::BorshSerialize;
use serai_client::{
primitives::NetworkId,
validator_sets::primitives::{Session, ValidatorSet, KeyPair},
primitives::ExternalNetworkId,
validator_sets::primitives::{ExternalValidatorSet, KeyPair, Session},
Public, Serai, SeraiInInstructions,
};
@ -79,7 +79,7 @@ pub struct ActiveTributary<D: Db, P: P2p> {
#[derive(Clone)]
pub enum TributaryEvent<D: Db, P: P2p> {
NewTributary(ActiveTributary<D, P>),
TributaryRetired(ValidatorSet),
TributaryRetired(ExternalValidatorSet),
}
// Creates a new tributary and sends it to all listeners.
@ -145,7 +145,7 @@ async fn handle_processor_message<D: Db, P: P2p>(
p2p: &P,
cosign_channel: &mpsc::UnboundedSender<CosignedBlock>,
tributaries: &HashMap<Session, ActiveTributary<D, P>>,
network: NetworkId,
network: ExternalNetworkId,
msg: &processors::Message,
) -> bool {
#[allow(clippy::nonminimal_bool)]
@ -193,7 +193,8 @@ async fn handle_processor_message<D: Db, P: P2p>(
.iter()
.map(|plan| plan.session)
.filter(|session| {
RetiredTributaryDb::get(&txn, ValidatorSet { network, session: *session }).is_none()
RetiredTributaryDb::get(&txn, ExternalValidatorSet { network, session: *session })
.is_none()
})
.collect::<HashSet<_>>();
@ -265,7 +266,7 @@ async fn handle_processor_message<D: Db, P: P2p>(
}
// This causes an action on Substrate yet not on any Tributary
coordinator::ProcessorMessage::SignedSlashReport { session, signature } => {
let set = ValidatorSet { network, session: *session };
let set = ExternalValidatorSet { network, session: *session };
let signature: &[u8] = signature.as_ref();
let signature = serai_client::Signature(signature.try_into().unwrap());
@ -393,7 +394,7 @@ async fn handle_processor_message<D: Db, P: P2p>(
if let Some(relevant_tributary_value) = relevant_tributary {
if RetiredTributaryDb::get(
&txn,
ValidatorSet { network: msg.network, session: relevant_tributary_value },
ExternalValidatorSet { network: msg.network, session: relevant_tributary_value },
)
.is_some()
{
@ -782,7 +783,7 @@ async fn handle_processor_messages<D: Db, Pro: Processors, P: P2p>(
processors: Pro,
p2p: P,
cosign_channel: mpsc::UnboundedSender<CosignedBlock>,
network: NetworkId,
network: ExternalNetworkId,
mut tributary_event: mpsc::UnboundedReceiver<TributaryEvent<D, P>>,
) {
let mut tributaries = HashMap::new();
@ -831,7 +832,7 @@ async fn handle_processor_messages<D: Db, Pro: Processors, P: P2p>(
#[allow(clippy::too_many_arguments)]
async fn handle_cosigns_and_batch_publication<D: Db, P: P2p>(
mut db: D,
network: NetworkId,
network: ExternalNetworkId,
mut tributary_event: mpsc::UnboundedReceiver<TributaryEvent<D, P>>,
) {
let mut tributaries = HashMap::new();
@ -905,7 +906,7 @@ async fn handle_cosigns_and_batch_publication<D: Db, P: P2p>(
for batch in start_id ..= last_id {
let is_pre_handover = LookupHandoverBatchDb::get(&txn, network, batch + 1);
if let Some(session) = is_pre_handover {
let set = ValidatorSet { network, session };
let set = ExternalValidatorSet { network, session };
let mut queued = QueuedBatchesDb::take(&mut txn, set);
// is_handover_batch is only set for handover `Batch`s we're participating in, making
// this safe
@ -923,7 +924,8 @@ async fn handle_cosigns_and_batch_publication<D: Db, P: P2p>(
let is_handover = LookupHandoverBatchDb::get(&txn, network, batch);
if let Some(session) = is_handover {
for queued in QueuedBatchesDb::take(&mut txn, ValidatorSet { network, session }) {
for queued in QueuedBatchesDb::take(&mut txn, ExternalValidatorSet { network, session })
{
to_publish.push((session, queued));
}
}
@ -970,10 +972,7 @@ pub async fn handle_processors<D: Db, Pro: Processors, P: P2p>(
mut tributary_event: broadcast::Receiver<TributaryEvent<D, P>>,
) {
let mut channels = HashMap::new();
for network in serai_client::primitives::NETWORKS {
if network == NetworkId::Serai {
continue;
}
for network in serai_client::primitives::EXTERNAL_NETWORKS {
let (processor_send, processor_recv) = mpsc::unbounded_channel();
tokio::spawn(handle_processor_messages(
db.clone(),
@ -1195,7 +1194,7 @@ pub async fn run<D: Db, Pro: Processors, P: P2p>(
}
});
move |set: ValidatorSet, genesis, id_type, id: Vec<u8>| {
move |set: ExternalValidatorSet, genesis, id_type, id: Vec<u8>| {
log::debug!("recognized ID {:?} {}", id_type, hex::encode(&id));
let mut raw_db = raw_db.clone();
let key = key.clone();

View file

@ -11,7 +11,9 @@ use rand_core::{RngCore, OsRng};
use scale::{Decode, Encode};
use borsh::{BorshSerialize, BorshDeserialize};
use serai_client::{primitives::NetworkId, validator_sets::primitives::ValidatorSet, Serai};
use serai_client::{
primitives::ExternalNetworkId, validator_sets::primitives::ExternalValidatorSet, Serai,
};
use serai_db::Db;
@ -69,7 +71,7 @@ const BLOCKS_PER_BATCH: usize = BLOCKS_PER_MINUTE + 1;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize)]
pub struct CosignedBlock {
pub network: NetworkId,
pub network: ExternalNetworkId,
pub block_number: u64,
pub block: [u8; 32],
pub signature: [u8; 64],
@ -208,8 +210,8 @@ pub struct HeartbeatBatch {
pub trait P2p: Send + Sync + Clone + fmt::Debug + TributaryP2p {
type Id: Send + Sync + Clone + Copy + fmt::Debug;
async fn subscribe(&self, set: ValidatorSet, genesis: [u8; 32]);
async fn unsubscribe(&self, set: ValidatorSet, genesis: [u8; 32]);
async fn subscribe(&self, set: ExternalValidatorSet, genesis: [u8; 32]);
async fn unsubscribe(&self, set: ExternalValidatorSet, genesis: [u8; 32]);
async fn send_raw(&self, to: Self::Id, msg: Vec<u8>);
async fn broadcast_raw(&self, kind: P2pMessageKind, msg: Vec<u8>);
@ -309,7 +311,7 @@ struct Behavior {
#[allow(clippy::type_complexity)]
#[derive(Clone)]
pub struct LibP2p {
subscribe: Arc<Mutex<mpsc::UnboundedSender<(bool, ValidatorSet, [u8; 32])>>>,
subscribe: Arc<Mutex<mpsc::UnboundedSender<(bool, ExternalValidatorSet, [u8; 32])>>>,
send: Arc<Mutex<mpsc::UnboundedSender<(PeerId, Vec<u8>)>>>,
broadcast: Arc<Mutex<mpsc::UnboundedSender<(P2pMessageKind, Vec<u8>)>>>,
receive: Arc<Mutex<mpsc::UnboundedReceiver<Message<Self>>>>,
@ -397,7 +399,7 @@ impl LibP2p {
let (receive_send, receive_recv) = mpsc::unbounded_channel();
let (subscribe_send, mut subscribe_recv) = mpsc::unbounded_channel();
fn topic_for_set(set: ValidatorSet) -> IdentTopic {
fn topic_for_set(set: ExternalValidatorSet) -> IdentTopic {
IdentTopic::new(format!("{LIBP2P_TOPIC}-{}", hex::encode(set.encode())))
}
@ -407,7 +409,8 @@ impl LibP2p {
// The addrs we're currently dialing, and the networks associated with them
let dialing_peers = Arc::new(RwLock::new(HashMap::new()));
// The peers we're currently connected to, and the networks associated with them
let connected_peers = Arc::new(RwLock::new(HashMap::<Multiaddr, HashSet<NetworkId>>::new()));
let connected_peers =
Arc::new(RwLock::new(HashMap::<Multiaddr, HashSet<ExternalNetworkId>>::new()));
// Find and connect to peers
let (connect_to_network_send, mut connect_to_network_recv) =
@ -420,7 +423,7 @@ impl LibP2p {
let connect_to_network_send = connect_to_network_send.clone();
async move {
loop {
let connect = |network: NetworkId, addr: Multiaddr| {
let connect = |network: ExternalNetworkId, addr: Multiaddr| {
let dialing_peers = dialing_peers.clone();
let connected_peers = connected_peers.clone();
let to_dial_send = to_dial_send.clone();
@ -507,7 +510,7 @@ impl LibP2p {
connect_to_network_networks.insert(network);
}
for network in connect_to_network_networks {
if let Ok(mut nodes) = serai.p2p_validators(network).await {
if let Ok(mut nodes) = serai.p2p_validators(network.into()).await {
// If there's an insufficient amount of nodes known, connect to all yet add it
// back and break
if nodes.len() < TARGET_PEERS {
@ -557,7 +560,7 @@ impl LibP2p {
// Subscribe to any new topics
set = subscribe_recv.recv() => {
let (subscribe, set, genesis): (_, ValidatorSet, [u8; 32]) =
let (subscribe, set, genesis): (_, ExternalValidatorSet, [u8; 32]) =
set.expect("subscribe_recv closed. are we shutting down?");
let topic = topic_for_set(set);
if subscribe {
@ -776,7 +779,7 @@ impl LibP2p {
impl P2p for LibP2p {
type Id = PeerId;
async fn subscribe(&self, set: ValidatorSet, genesis: [u8; 32]) {
async fn subscribe(&self, set: ExternalValidatorSet, genesis: [u8; 32]) {
self
.subscribe
.lock()
@ -785,7 +788,7 @@ impl P2p for LibP2p {
.expect("subscribe_send closed. are we shutting down?");
}
async fn unsubscribe(&self, set: ValidatorSet, genesis: [u8; 32]) {
async fn unsubscribe(&self, set: ExternalValidatorSet, genesis: [u8; 32]) {
self
.subscribe
.lock()

View file

@ -1,6 +1,6 @@
use std::sync::Arc;
use serai_client::primitives::NetworkId;
use serai_client::primitives::ExternalNetworkId;
use processor_messages::{ProcessorMessage, CoordinatorMessage};
use message_queue::{Service, Metadata, client::MessageQueue};
@ -8,27 +8,27 @@ use message_queue::{Service, Metadata, client::MessageQueue};
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Message {
pub id: u64,
pub network: NetworkId,
pub network: ExternalNetworkId,
pub msg: ProcessorMessage,
}
#[async_trait::async_trait]
pub trait Processors: 'static + Send + Sync + Clone {
async fn send(&self, network: NetworkId, msg: impl Send + Into<CoordinatorMessage>);
async fn recv(&self, network: NetworkId) -> Message;
async fn send(&self, network: ExternalNetworkId, msg: impl Send + Into<CoordinatorMessage>);
async fn recv(&self, network: ExternalNetworkId) -> Message;
async fn ack(&self, msg: Message);
}
#[async_trait::async_trait]
impl Processors for Arc<MessageQueue> {
async fn send(&self, network: NetworkId, msg: impl Send + Into<CoordinatorMessage>) {
async fn send(&self, network: ExternalNetworkId, msg: impl Send + Into<CoordinatorMessage>) {
let msg: CoordinatorMessage = msg.into();
let metadata =
Metadata { from: self.service, to: Service::Processor(network), intent: msg.intent() };
let msg = borsh::to_vec(&msg).unwrap();
self.queue(metadata, msg).await;
}
async fn recv(&self, network: NetworkId) -> Message {
async fn recv(&self, network: ExternalNetworkId) -> Message {
let msg = self.next(Service::Processor(network)).await;
assert_eq!(msg.from, Service::Processor(network));

View file

@ -19,9 +19,9 @@ use ciphersuite::{Ciphersuite, Ristretto};
use borsh::{BorshSerialize, BorshDeserialize};
use serai_client::{
SeraiError, Serai,
primitives::NetworkId,
validator_sets::primitives::{Session, ValidatorSet},
primitives::ExternalNetworkId,
validator_sets::primitives::{ExternalValidatorSet, Session},
Serai, SeraiError,
};
use serai_db::*;
@ -70,13 +70,18 @@ impl LatestCosignedBlock {
db_channel! {
SubstrateDbChannels {
CosignTransactions: (network: NetworkId) -> (Session, u64, [u8; 32]),
CosignTransactions: (network: ExternalNetworkId) -> (Session, u64, [u8; 32]),
}
}
impl CosignTransactions {
// Append a cosign transaction.
pub fn append_cosign(txn: &mut impl DbTxn, set: ValidatorSet, number: u64, hash: [u8; 32]) {
pub fn append_cosign(
txn: &mut impl DbTxn,
set: ExternalValidatorSet,
number: u64,
hash: [u8; 32],
) {
CosignTransactions::send(txn, set.network, &(set.session, number, hash))
}
}
@ -256,22 +261,22 @@ async fn advance_cosign_protocol_inner(
// Using the keys of the prior block ensures this deadlock isn't reached
let serai = serai.as_of(actual_block.header.parent_hash.into());
for network in serai_client::primitives::NETWORKS {
for network in serai_client::primitives::EXTERNAL_NETWORKS {
// Get the latest session to have set keys
let set_with_keys = {
let Some(latest_session) = serai.validator_sets().session(network).await? else {
let Some(latest_session) = serai.validator_sets().session(network.into()).await? else {
continue;
};
let prior_session = Session(latest_session.0.saturating_sub(1));
if serai
.validator_sets()
.keys(ValidatorSet { network, session: prior_session })
.keys(ExternalValidatorSet { network, session: prior_session })
.await?
.is_some()
{
ValidatorSet { network, session: prior_session }
ExternalValidatorSet { network, session: prior_session }
} else {
let set = ValidatorSet { network, session: latest_session };
let set = ExternalValidatorSet { network, session: latest_session };
if serai.validator_sets().keys(set).await?.is_none() {
continue;
}
@ -280,7 +285,7 @@ async fn advance_cosign_protocol_inner(
};
log::debug!("{:?} will be cosigning {block}", set_with_keys.network);
cosigning.push((set_with_keys, in_set(key, &serai, set_with_keys).await?.unwrap()));
cosigning.push((set_with_keys, in_set(key, &serai, set_with_keys.into()).await?.unwrap()));
}
break;

View file

@ -1,4 +1,4 @@
use serai_client::primitives::NetworkId;
use serai_client::primitives::ExternalNetworkId;
pub use serai_db::*;
@ -9,7 +9,7 @@ mod inner_db {
SubstrateDb {
NextBlock: () -> u64,
HandledEvent: (block: [u8; 32]) -> u32,
BatchInstructionsHashDb: (network: NetworkId, id: u32) -> [u8; 32]
BatchInstructionsHashDb: (network: ExternalNetworkId, id: u32) -> [u8; 32]
}
);
}

View file

@ -9,11 +9,14 @@ use zeroize::Zeroizing;
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
use serai_client::{
SeraiError, Block, Serai, TemporalSerai,
primitives::{BlockHash, NetworkId},
validator_sets::{primitives::ValidatorSet, ValidatorSetsEvent},
in_instructions::InInstructionsEvent,
coins::CoinsEvent,
in_instructions::InInstructionsEvent,
primitives::{BlockHash, ExternalNetworkId},
validator_sets::{
primitives::{ExternalValidatorSet, ValidatorSet},
ValidatorSetsEvent,
},
Block, Serai, SeraiError, TemporalSerai,
};
use serai_db::DbTxn;
@ -52,9 +55,9 @@ async fn handle_new_set<D: Db>(
new_tributary_spec: &mpsc::UnboundedSender<TributarySpec>,
serai: &Serai,
block: &Block,
set: ValidatorSet,
set: ExternalValidatorSet,
) -> Result<(), SeraiError> {
if in_set(key, &serai.as_of(block.hash()), set)
if in_set(key, &serai.as_of(block.hash()), set.into())
.await?
.expect("NewSet for set which doesn't exist")
{
@ -64,7 +67,7 @@ async fn handle_new_set<D: Db>(
let serai = serai.as_of(block.hash());
let serai = serai.validator_sets();
let set_participants =
serai.participants(set.network).await?.expect("NewSet for set which doesn't exist");
serai.participants(set.network.into()).await?.expect("NewSet for set which doesn't exist");
set_participants.into_iter().map(|(k, w)| (k, u16::try_from(w).unwrap())).collect::<Vec<_>>()
};
@ -131,7 +134,7 @@ async fn handle_batch_and_burns<Pro: Processors>(
};
let mut batch_block = HashMap::new();
let mut batches = HashMap::<NetworkId, Vec<u32>>::new();
let mut batches = HashMap::<ExternalNetworkId, Vec<u32>>::new();
let mut burns = HashMap::new();
let serai = serai.as_of(block.hash());
@ -205,8 +208,8 @@ async fn handle_block<D: Db, Pro: Processors>(
db: &mut D,
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
new_tributary_spec: &mpsc::UnboundedSender<TributarySpec>,
perform_slash_report: &mpsc::UnboundedSender<ValidatorSet>,
tributary_retired: &mpsc::UnboundedSender<ValidatorSet>,
perform_slash_report: &mpsc::UnboundedSender<ExternalValidatorSet>,
tributary_retired: &mpsc::UnboundedSender<ExternalValidatorSet>,
processors: &Pro,
serai: &Serai,
block: Block,
@ -226,12 +229,8 @@ async fn handle_block<D: Db, Pro: Processors>(
panic!("NewSet event wasn't NewSet: {new_set:?}");
};
// If this is Serai, do nothing
// We only coordinate/process external networks
if set.network == NetworkId::Serai {
continue;
}
let Ok(set) = ExternalValidatorSet::try_from(set) else { continue };
if HandledEvent::is_unhandled(db, hash, event_id) {
log::info!("found fresh new set event {:?}", new_set);
let mut txn = db.txn();
@ -286,10 +285,7 @@ async fn handle_block<D: Db, Pro: Processors>(
panic!("AcceptedHandover event wasn't AcceptedHandover: {accepted_handover:?}");
};
if set.network == NetworkId::Serai {
continue;
}
let Ok(set) = ExternalValidatorSet::try_from(set) else { continue };
if HandledEvent::is_unhandled(db, hash, event_id) {
log::info!("found fresh accepted handover event {:?}", accepted_handover);
// TODO: This isn't atomic with the event handling
@ -307,10 +303,7 @@ async fn handle_block<D: Db, Pro: Processors>(
panic!("SetRetired event wasn't SetRetired: {retired_set:?}");
};
if set.network == NetworkId::Serai {
continue;
}
let Ok(set) = ExternalValidatorSet::try_from(set) else { continue };
if HandledEvent::is_unhandled(db, hash, event_id) {
log::info!("found fresh set retired event {:?}", retired_set);
let mut txn = db.txn();
@ -340,8 +333,8 @@ async fn handle_new_blocks<D: Db, Pro: Processors>(
db: &mut D,
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
new_tributary_spec: &mpsc::UnboundedSender<TributarySpec>,
perform_slash_report: &mpsc::UnboundedSender<ValidatorSet>,
tributary_retired: &mpsc::UnboundedSender<ValidatorSet>,
perform_slash_report: &mpsc::UnboundedSender<ExternalValidatorSet>,
tributary_retired: &mpsc::UnboundedSender<ExternalValidatorSet>,
processors: &Pro,
serai: &Serai,
next_block: &mut u64,
@ -395,8 +388,8 @@ pub async fn scan_task<D: Db, Pro: Processors>(
processors: Pro,
serai: Arc<Serai>,
new_tributary_spec: mpsc::UnboundedSender<TributarySpec>,
perform_slash_report: mpsc::UnboundedSender<ValidatorSet>,
tributary_retired: mpsc::UnboundedSender<ValidatorSet>,
perform_slash_report: mpsc::UnboundedSender<ExternalValidatorSet>,
tributary_retired: mpsc::UnboundedSender<ExternalValidatorSet>,
) {
log::info!("scanning substrate");
let mut next_substrate_block = NextBlock::get(&db).unwrap_or_default();
@ -494,9 +487,12 @@ pub async fn scan_task<D: Db, Pro: Processors>(
/// retry.
pub(crate) async fn expected_next_batch(
serai: &Serai,
network: NetworkId,
network: ExternalNetworkId,
) -> Result<u32, SeraiError> {
async fn expected_next_batch_inner(serai: &Serai, network: NetworkId) -> Result<u32, SeraiError> {
async fn expected_next_batch_inner(
serai: &Serai,
network: ExternalNetworkId,
) -> Result<u32, SeraiError> {
let serai = serai.as_of_latest_finalized_block().await?;
let last = serai.in_instructions().last_batch_for_network(network).await?;
Ok(if let Some(last) = last { last + 1 } else { 0 })
@ -519,7 +515,7 @@ pub(crate) async fn expected_next_batch(
/// This is deemed fine.
pub(crate) async fn verify_published_batches<D: Db>(
txn: &mut D::Transaction<'_>,
network: NetworkId,
network: ExternalNetworkId,
optimistic_up_to: u32,
) -> Option<u32> {
// TODO: Localize from MainDb to SubstrateDb

View file

@ -4,7 +4,7 @@ use std::{
collections::{VecDeque, HashSet, HashMap},
};
use serai_client::{primitives::NetworkId, validator_sets::primitives::ValidatorSet};
use serai_client::{primitives::ExternalNetworkId, validator_sets::primitives::ExternalValidatorSet};
use processor_messages::CoordinatorMessage;
@ -20,7 +20,7 @@ use crate::{
pub mod tributary;
#[derive(Clone)]
pub struct MemProcessors(pub Arc<RwLock<HashMap<NetworkId, VecDeque<CoordinatorMessage>>>>);
pub struct MemProcessors(pub Arc<RwLock<HashMap<ExternalNetworkId, VecDeque<CoordinatorMessage>>>>);
impl MemProcessors {
#[allow(clippy::new_without_default)]
pub fn new() -> MemProcessors {
@ -30,12 +30,12 @@ impl MemProcessors {
#[async_trait::async_trait]
impl Processors for MemProcessors {
async fn send(&self, network: NetworkId, msg: impl Send + Into<CoordinatorMessage>) {
async fn send(&self, network: ExternalNetworkId, msg: impl Send + Into<CoordinatorMessage>) {
let mut processors = self.0.write().await;
let processor = processors.entry(network).or_insert_with(VecDeque::new);
processor.push_back(msg.into());
}
async fn recv(&self, _: NetworkId) -> Message {
async fn recv(&self, _: ExternalNetworkId) -> Message {
todo!()
}
async fn ack(&self, _: Message) {
@ -65,8 +65,8 @@ impl LocalP2p {
impl P2p for LocalP2p {
type Id = usize;
async fn subscribe(&self, _set: ValidatorSet, _genesis: [u8; 32]) {}
async fn unsubscribe(&self, _set: ValidatorSet, _genesis: [u8; 32]) {}
async fn subscribe(&self, _set: ExternalValidatorSet, _genesis: [u8; 32]) {}
async fn unsubscribe(&self, _set: ExternalValidatorSet, _genesis: [u8; 32]) {}
async fn send_raw(&self, to: Self::Id, msg: Vec<u8>) {
let mut msg_ref = msg.as_slice();

View file

@ -15,8 +15,8 @@ use ciphersuite::{
use sp_application_crypto::sr25519;
use borsh::BorshDeserialize;
use serai_client::{
primitives::NetworkId,
validator_sets::primitives::{Session, ValidatorSet},
primitives::ExternalNetworkId,
validator_sets::primitives::{ExternalValidatorSet, Session},
};
use tokio::time::sleep;
@ -50,7 +50,7 @@ pub fn new_spec<R: RngCore + CryptoRng>(
let start_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
let set = ValidatorSet { session: Session(0), network: NetworkId::Bitcoin };
let set = ExternalValidatorSet { session: Session(0), network: ExternalNetworkId::Bitcoin };
let set_participants = keys
.iter()

View file

@ -10,7 +10,7 @@ use frost::Participant;
use sp_runtime::traits::Verify;
use serai_client::{
primitives::{SeraiAddress, Signature},
validator_sets::primitives::{ValidatorSet, KeyPair},
validator_sets::primitives::{ExternalValidatorSet, KeyPair},
};
use tokio::time::sleep;
@ -350,7 +350,7 @@ async fn dkg_test() {
async fn publish_set_keys(
&self,
_db: &(impl Sync + Get),
set: ValidatorSet,
set: ExternalValidatorSet,
removed: Vec<SeraiAddress>,
key_pair: KeyPair,
signature: Signature,
@ -362,7 +362,7 @@ async fn dkg_test() {
&*serai_client::validator_sets::primitives::set_keys_message(&set, &[], &key_pair),
&serai_client::Public(
frost::dkg::musig::musig_key::<Ristretto>(
&serai_client::validator_sets::primitives::musig_context(set),
&serai_client::validator_sets::primitives::musig_context(set.into()),
&self.spec.validators().into_iter().map(|(validator, _)| validator).collect::<Vec<_>>()
)
.unwrap()

View file

@ -7,7 +7,7 @@ use ciphersuite::{group::Group, Ciphersuite, Ristretto};
use scale::{Encode, Decode};
use serai_client::{
primitives::{SeraiAddress, Signature},
validator_sets::primitives::{MAX_KEY_SHARES_PER_SET, ValidatorSet, KeyPair},
validator_sets::primitives::{ExternalValidatorSet, KeyPair, MAX_KEY_SHARES_PER_SET},
};
use processor_messages::coordinator::SubstrateSignableId;
@ -31,7 +31,7 @@ impl PublishSeraiTransaction for () {
async fn publish_set_keys(
&self,
_db: &(impl Sync + serai_db::Get),
_set: ValidatorSet,
_set: ExternalValidatorSet,
_removed: Vec<SeraiAddress>,
_key_pair: KeyPair,
_signature: Signature,

View file

@ -6,7 +6,7 @@ use borsh::{BorshSerialize, BorshDeserialize};
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
use frost::Participant;
use serai_client::validator_sets::primitives::{KeyPair, ValidatorSet};
use serai_client::validator_sets::primitives::{KeyPair, ExternalValidatorSet};
use processor_messages::coordinator::SubstrateSignableId;
@ -46,7 +46,7 @@ pub enum Accumulation {
create_db!(
Tributary {
SeraiBlockNumber: (hash: [u8; 32]) -> u64,
SeraiDkgCompleted: (spec: ValidatorSet) -> [u8; 32],
SeraiDkgCompleted: (spec: ExternalValidatorSet) -> [u8; 32],
TributaryBlockNumber: (block: [u8; 32]) -> u32,
LastHandledBlock: (genesis: [u8; 32]) -> [u8; 32],
@ -80,7 +80,7 @@ create_db!(
SlashReports: (genesis: [u8; 32], signer: [u8; 32]) -> Vec<u32>,
SlashReported: (genesis: [u8; 32]) -> u16,
SlashReportCutOff: (genesis: [u8; 32]) -> u64,
SlashReport: (set: ValidatorSet) -> Vec<([u8; 32], u32)>,
SlashReport: (set: ExternalValidatorSet) -> Vec<([u8; 32], u32)>,
}
);

View file

@ -1,6 +1,6 @@
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
use serai_client::validator_sets::primitives::ValidatorSet;
use serai_client::validator_sets::primitives::ExternalValidatorSet;
use tributary::{
ReadWrite,
@ -40,7 +40,7 @@ pub fn removed_as_of_dkg_attempt(
pub fn removed_as_of_set_keys(
getter: &impl Get,
set: ValidatorSet,
set: ExternalValidatorSet,
genesis: [u8; 32],
) -> Option<Vec<<Ristretto as Ciphersuite>::G>> {
// SeraiDkgCompleted has the key placed on-chain.

View file

@ -10,7 +10,7 @@ use tokio::sync::broadcast;
use scale::{Encode, Decode};
use serai_client::{
primitives::{SeraiAddress, Signature},
validator_sets::primitives::{KeyPair, ValidatorSet},
validator_sets::primitives::{ExternalValidatorSet, KeyPair},
Serai,
};
@ -38,7 +38,7 @@ pub enum RecognizedIdType {
pub trait RIDTrait {
async fn recognized_id(
&self,
set: ValidatorSet,
set: ExternalValidatorSet,
genesis: [u8; 32],
kind: RecognizedIdType,
id: Vec<u8>,
@ -47,12 +47,12 @@ pub trait RIDTrait {
#[async_trait::async_trait]
impl<
FRid: Send + Future<Output = ()>,
F: Sync + Fn(ValidatorSet, [u8; 32], RecognizedIdType, Vec<u8>) -> FRid,
F: Sync + Fn(ExternalValidatorSet, [u8; 32], RecognizedIdType, Vec<u8>) -> FRid,
> RIDTrait for F
{
async fn recognized_id(
&self,
set: ValidatorSet,
set: ExternalValidatorSet,
genesis: [u8; 32],
kind: RecognizedIdType,
id: Vec<u8>,
@ -66,7 +66,7 @@ pub trait PublishSeraiTransaction {
async fn publish_set_keys(
&self,
db: &(impl Sync + Get),
set: ValidatorSet,
set: ExternalValidatorSet,
removed: Vec<SeraiAddress>,
key_pair: KeyPair,
signature: Signature,
@ -86,7 +86,7 @@ mod impl_pst_for_serai {
async fn publish(
serai: &Serai,
db: &impl Get,
set: ValidatorSet,
set: ExternalValidatorSet,
tx: serai_client::Transaction,
meta: $Meta,
) -> bool {
@ -128,7 +128,7 @@ mod impl_pst_for_serai {
async fn publish_set_keys(
&self,
db: &(impl Sync + Get),
set: ValidatorSet,
set: ExternalValidatorSet,
removed: Vec<SeraiAddress>,
key_pair: KeyPair,
signature: Signature,
@ -140,7 +140,7 @@ mod impl_pst_for_serai {
key_pair,
signature,
);
async fn check(serai: SeraiValidatorSets<'_>, set: ValidatorSet, (): ()) -> bool {
async fn check(serai: SeraiValidatorSets<'_>, set: ExternalValidatorSet, (): ()) -> bool {
if matches!(serai.keys(set).await, Ok(Some(_))) {
log::info!("another coordinator set key pair for {:?}", set);
return true;

View file

@ -119,7 +119,7 @@ impl<T: DbTxn, C: Encode> SigningProtocol<'_, T, C> {
let algorithm = Schnorrkel::new(b"substrate");
let keys: ThresholdKeys<Ristretto> =
musig(&musig_context(self.spec.set()), self.key, participants)
musig(&musig_context(self.spec.set().into()), self.key, participants)
.expect("signing for a set we aren't in/validator present multiple times")
.into();

View file

@ -9,7 +9,7 @@ use frost::Participant;
use scale::Encode;
use borsh::{BorshSerialize, BorshDeserialize};
use serai_client::{primitives::PublicKey, validator_sets::primitives::ValidatorSet};
use serai_client::{primitives::PublicKey, validator_sets::primitives::ExternalValidatorSet};
fn borsh_serialize_validators<W: io::Write>(
validators: &Vec<(<Ristretto as Ciphersuite>::G, u16)>,
@ -43,7 +43,7 @@ fn borsh_deserialize_validators<R: io::Read>(
pub struct TributarySpec {
serai_block: [u8; 32],
start_time: u64,
set: ValidatorSet,
set: ExternalValidatorSet,
#[borsh(
serialize_with = "borsh_serialize_validators",
deserialize_with = "borsh_deserialize_validators"
@ -55,7 +55,7 @@ impl TributarySpec {
pub fn new(
serai_block: [u8; 32],
start_time: u64,
set: ValidatorSet,
set: ExternalValidatorSet,
set_participants: Vec<(PublicKey, u16)>,
) -> TributarySpec {
let mut validators = vec![];
@ -68,7 +68,7 @@ impl TributarySpec {
Self { serai_block, start_time, set, validators }
}
pub fn set(&self) -> ValidatorSet {
pub fn set(&self) -> ExternalValidatorSet {
self.set
}

View file

@ -6,7 +6,7 @@ pub(crate) use std::{
pub(crate) use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
pub(crate) use schnorr_signatures::SchnorrSignature;
pub(crate) use serai_primitives::NetworkId;
pub(crate) use serai_primitives::ExternalNetworkId;
pub(crate) use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
@ -193,10 +193,7 @@ async fn main() {
KEYS.write().unwrap().insert(service, key);
let mut queues = QUEUES.write().unwrap();
if service == Service::Coordinator {
for network in serai_primitives::NETWORKS {
if network == NetworkId::Serai {
continue;
}
for network in serai_primitives::EXTERNAL_NETWORKS {
queues.insert(
(service, Service::Processor(network)),
RwLock::new(Queue(db.clone(), service, Service::Processor(network))),
@ -210,17 +207,13 @@ async fn main() {
}
};
// Make queues for each NetworkId, other than Serai
for network in serai_primitives::NETWORKS {
if network == NetworkId::Serai {
continue;
}
// Make queues for each ExternalNetworkId
for network in serai_primitives::EXTERNAL_NETWORKS {
// Use a match so we error if the list of NetworkIds changes
let Some(key) = read_key(match network {
NetworkId::Serai => unreachable!(),
NetworkId::Bitcoin => "BITCOIN_KEY",
NetworkId::Ethereum => "ETHEREUM_KEY",
NetworkId::Monero => "MONERO_KEY",
ExternalNetworkId::Bitcoin => "BITCOIN_KEY",
ExternalNetworkId::Ethereum => "ETHEREUM_KEY",
ExternalNetworkId::Monero => "MONERO_KEY",
}) else {
continue;
};

View file

@ -3,11 +3,11 @@ use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
use borsh::{BorshSerialize, BorshDeserialize};
use serai_primitives::NetworkId;
use serai_primitives::ExternalNetworkId;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize)]
pub enum Service {
Processor(NetworkId),
Processor(ExternalNetworkId),
Coordinator,
}

View file

@ -17,7 +17,7 @@ use frost_schnorrkel::Schnorrkel;
use log::{info, debug, warn};
use serai_client::{
primitives::{NetworkId, BlockHash},
primitives::{ExternalNetworkId, BlockHash},
in_instructions::primitives::{Batch, SignedBatch, batch_message},
validator_sets::primitives::Session,
};
@ -41,7 +41,7 @@ type SignatureShare = <AlgorithmSignMachine<Ristretto, Schnorrkel> as SignMachin
pub struct BatchSigner<D: Db> {
db: PhantomData<D>,
network: NetworkId,
network: ExternalNetworkId,
session: Session,
keys: Vec<ThresholdKeys<Ristretto>>,
@ -65,7 +65,7 @@ impl<D: Db> fmt::Debug for BatchSigner<D> {
impl<D: Db> BatchSigner<D> {
pub fn new(
network: NetworkId,
network: ExternalNetworkId,
session: Session,
keys: Vec<ThresholdKeys<Ristretto>>,
) -> BatchSigner<D> {

View file

@ -9,7 +9,7 @@ use log::{info, warn};
use tokio::time::sleep;
use serai_client::{
primitives::{BlockHash, NetworkId},
primitives::{BlockHash, ExternalNetworkId},
validator_sets::primitives::{Session, KeyPair},
};
@ -736,19 +736,23 @@ async fn main() {
"http://".to_string() + &login + "@" + &hostname + ":" + &port
};
let network_id = match env::var("NETWORK").expect("network wasn't specified").as_str() {
"bitcoin" => NetworkId::Bitcoin,
"ethereum" => NetworkId::Ethereum,
"monero" => NetworkId::Monero,
"bitcoin" => ExternalNetworkId::Bitcoin,
"ethereum" => ExternalNetworkId::Ethereum,
"monero" => ExternalNetworkId::Monero,
_ => panic!("unrecognized network"),
};
let coordinator = MessageQueue::from_env(Service::Processor(network_id));
// This allow is necessary since each configuration deletes the other networks from the following
// match arms. So we match all cases but since all cases already there according to the compiler
// we put this to allow clippy to get pass this.
#[allow(unreachable_patterns)]
match network_id {
#[cfg(feature = "bitcoin")]
NetworkId::Bitcoin => run(db, Bitcoin::new(url).await, coordinator).await,
ExternalNetworkId::Bitcoin => run(db, Bitcoin::new(url).await, coordinator).await,
#[cfg(feature = "ethereum")]
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
let relayer_hostname = env::var("ETHEREUM_RELAYER_HOSTNAME")
.expect("ethereum relayer hostname wasn't specified")
.to_string();
@ -758,7 +762,7 @@ async fn main() {
run(db.clone(), Ethereum::new(db, url, relayer_url).await, coordinator).await
}
#[cfg(feature = "monero")]
NetworkId::Monero => run(db, Monero::new(url).await, coordinator).await,
ExternalNetworkId::Monero => run(db, Monero::new(url).await, coordinator).await,
_ => panic!("spawning a processor for an unsupported network"),
}
}

View file

@ -4,7 +4,11 @@ use ciphersuite::Ciphersuite;
pub use serai_db::*;
use scale::{Encode, Decode};
use serai_client::{primitives::Balance, in_instructions::primitives::InInstructionWithBalance};
#[rustfmt::skip]
use serai_client::{
in_instructions::primitives::InInstructionWithBalance,
primitives::ExternalBalance
};
use crate::{
Get, Plan,
@ -69,7 +73,7 @@ create_db!(
OperatingCostsDb: () -> u64,
ResolvedDb: (tx: &[u8]) -> [u8; 32],
SigningDb: (key: &[u8]) -> Vec<u8>,
ForwardedOutputDb: (balance: Balance) -> Vec<u8>,
ForwardedOutputDb: (balance: ExternalBalance) -> Vec<u8>,
DelayedOutputDb: () -> Vec<u8>
}
);
@ -224,7 +228,7 @@ impl ForwardedOutputDb {
pub fn take_forwarded_output(
txn: &mut impl DbTxn,
balance: Balance,
balance: ExternalBalance,
) -> Option<InInstructionWithBalance> {
let outputs = Self::get(txn, balance)?;
let mut outputs_ref = outputs.as_slice();

View file

@ -3,7 +3,7 @@ use std::io;
use ciphersuite::Ciphersuite;
use serai_client::primitives::{NetworkId, Balance};
use serai_client::primitives::{ExternalBalance, ExternalNetworkId};
use crate::{networks::Network, Db, Payment, Plan};
@ -34,18 +34,18 @@ pub trait Scheduler<N: Network>: Sized + Clone + PartialEq + Debug {
fn new<D: Db>(
txn: &mut D::Transaction<'_>,
key: <N::Curve as Ciphersuite>::G,
network: NetworkId,
network: ExternalNetworkId,
) -> Self;
/// Load a Scheduler from the DB.
fn from_db<D: Db>(
db: &D,
key: <N::Curve as Ciphersuite>::G,
network: NetworkId,
network: ExternalNetworkId,
) -> io::Result<Self>;
/// Check if a branch is usable.
fn can_use_branch(&self, balance: Balance) -> bool;
fn can_use_branch(&self, balance: ExternalBalance) -> bool;
/// Schedule a series of outputs/payments.
fn schedule<D: Db>(

View file

@ -2,7 +2,7 @@ use std::{io, collections::HashSet};
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use serai_client::primitives::{NetworkId, Coin, Balance};
use serai_client::primitives::{ExternalBalance, ExternalCoin, ExternalNetworkId};
use crate::{
Get, DbTxn, Db, Payment, Plan, create_db,
@ -13,7 +13,7 @@ use crate::{
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Scheduler<N: Network> {
key: <N::Curve as Ciphersuite>::G,
coins: HashSet<Coin>,
coins: HashSet<ExternalCoin>,
rotated: bool,
}
@ -78,7 +78,7 @@ impl<N: Network<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
fn new<D: Db>(
_txn: &mut D::Transaction<'_>,
key: <N::Curve as Ciphersuite>::G,
network: NetworkId,
network: ExternalNetworkId,
) -> Self {
assert!(N::branch_address(key).is_none());
assert!(N::change_address(key).is_none());
@ -91,7 +91,7 @@ impl<N: Network<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
fn from_db<D: Db>(
db: &D,
key: <N::Curve as Ciphersuite>::G,
network: NetworkId,
network: ExternalNetworkId,
) -> io::Result<Self> {
Ok(Scheduler {
key,
@ -100,7 +100,7 @@ impl<N: Network<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
})
}
fn can_use_branch(&self, _balance: Balance) -> bool {
fn can_use_branch(&self, _balance: ExternalBalance) -> bool {
false
}

View file

@ -5,7 +5,7 @@ use std::{
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use serai_client::primitives::{NetworkId, Coin, Amount, Balance};
use serai_client::primitives::{ExternalNetworkId, ExternalCoin, Amount, ExternalBalance};
use crate::{
DbTxn, Db, Payment, Plan,
@ -17,7 +17,7 @@ use crate::{
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Scheduler<N: UtxoNetwork> {
key: <N::Curve as Ciphersuite>::G,
coin: Coin,
coin: ExternalCoin,
// Serai, when it has more outputs expected than it can handle in a single transaction, will
// schedule the outputs to be handled later. Immediately, it just creates additional outputs
@ -57,7 +57,7 @@ impl<N: UtxoNetwork<Scheduler = Self>> Scheduler<N> {
fn read<R: Read>(
key: <N::Curve as Ciphersuite>::G,
coin: Coin,
coin: ExternalCoin,
reader: &mut R,
) -> io::Result<Self> {
let mut read_plans = || -> io::Result<_> {
@ -145,7 +145,7 @@ impl<N: UtxoNetwork<Scheduler = Self>> Scheduler<N> {
pub fn new<D: Db>(
txn: &mut D::Transaction<'_>,
key: <N::Curve as Ciphersuite>::G,
network: NetworkId,
network: ExternalNetworkId,
) -> Self {
assert!(N::branch_address(key).is_some());
assert!(N::change_address(key).is_some());
@ -173,7 +173,7 @@ impl<N: UtxoNetwork<Scheduler = Self>> Scheduler<N> {
pub fn from_db<D: Db>(
db: &D,
key: <N::Curve as Ciphersuite>::G,
network: NetworkId,
network: ExternalNetworkId,
) -> io::Result<Self> {
let coin = {
let coins = network.coins();
@ -190,7 +190,7 @@ impl<N: UtxoNetwork<Scheduler = Self>> Scheduler<N> {
Self::read(key, coin, reader)
}
pub fn can_use_branch(&self, balance: Balance) -> bool {
pub fn can_use_branch(&self, balance: ExternalBalance) -> bool {
assert_eq!(balance.coin, self.coin);
self.plans.contains_key(&balance.amount.0)
}
@ -249,7 +249,7 @@ impl<N: UtxoNetwork<Scheduler = Self>> Scheduler<N> {
Payment {
address: branch_address.clone(),
data: None,
balance: Balance { coin: self.coin, amount: Amount(amount) },
balance: ExternalBalance { coin: self.coin, amount: Amount(amount) },
},
);
}
@ -536,7 +536,7 @@ impl<N: UtxoNetwork<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
fn new<D: Db>(
txn: &mut D::Transaction<'_>,
key: <N::Curve as Ciphersuite>::G,
network: NetworkId,
network: ExternalNetworkId,
) -> Self {
Scheduler::new::<D>(txn, key, network)
}
@ -545,13 +545,13 @@ impl<N: UtxoNetwork<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
fn from_db<D: Db>(
db: &D,
key: <N::Curve as Ciphersuite>::G,
network: NetworkId,
network: ExternalNetworkId,
) -> io::Result<Self> {
Scheduler::from_db::<D>(db, key, network)
}
/// Check if a branch is usable.
fn can_use_branch(&self, balance: Balance) -> bool {
fn can_use_branch(&self, balance: ExternalBalance) -> bool {
Scheduler::can_use_branch(self, balance)
}
@ -574,7 +574,7 @@ impl<N: UtxoNetwork<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
/// Note a branch output as having been created, with the amount it was actually created with,
/// or not having been created due to being too small.
// TODO: Move this to Balance.
// TODO: Move this to ExternalBalance.
fn created_output<D: Db>(
&mut self,
txn: &mut D::Transaction<'_>,

View file

@ -42,7 +42,7 @@ use bitcoin_serai::bitcoin::{
};
use serai_client::{
primitives::{MAX_DATA_LEN, Coin, NetworkId, Amount, Balance},
primitives::{MAX_DATA_LEN, ExternalCoin, ExternalNetworkId, Amount, ExternalBalance},
networks::bitcoin::Address,
};
@ -125,8 +125,8 @@ impl OutputTrait<Bitcoin> for Output {
self.presumed_origin.clone()
}
fn balance(&self) -> Balance {
Balance { coin: Coin::Bitcoin, amount: Amount(self.output.value()) }
fn balance(&self) -> ExternalBalance {
ExternalBalance { coin: ExternalCoin::Bitcoin, amount: Amount(self.output.value()) }
}
fn data(&self) -> &[u8] {
@ -423,7 +423,7 @@ impl Bitcoin {
calculating_fee: bool,
) -> Result<Option<BSignableTransaction>, NetworkError> {
for payment in payments {
assert_eq!(payment.balance.coin, Coin::Bitcoin);
assert_eq!(payment.balance.coin, ExternalCoin::Bitcoin);
}
// TODO2: Use an fee representative of several blocks, cached inside Self
@ -598,7 +598,7 @@ impl Network for Bitcoin {
type Address = Address;
const NETWORK: NetworkId = NetworkId::Bitcoin;
const NETWORK: ExternalNetworkId = ExternalNetworkId::Bitcoin;
const ID: &'static str = "Bitcoin";
const ESTIMATED_BLOCK_TIME_IN_SECONDS: usize = 600;
const CONFIRMATIONS: usize = 6;

View file

@ -38,7 +38,7 @@ use tokio::{
};
use serai_client::{
primitives::{Coin, Amount, Balance, NetworkId},
primitives::{ExternalCoin, Amount, ExternalBalance, ExternalNetworkId},
validator_sets::primitives::Session,
};
@ -68,20 +68,20 @@ const DAI: [u8; 20] =
Err(_) => panic!("invalid test DAI hex address"),
};
fn coin_to_serai_coin(coin: &EthereumCoin) -> Option<Coin> {
fn coin_to_serai_coin(coin: &EthereumCoin) -> Option<ExternalCoin> {
match coin {
EthereumCoin::Ether => Some(Coin::Ether),
EthereumCoin::Ether => Some(ExternalCoin::Ether),
EthereumCoin::Erc20(token) => {
if *token == DAI {
return Some(Coin::Dai);
return Some(ExternalCoin::Dai);
}
None
}
}
}
fn amount_to_serai_amount(coin: Coin, amount: U256) -> Amount {
assert_eq!(coin.network(), NetworkId::Ethereum);
fn amount_to_serai_amount(coin: ExternalCoin, amount: U256) -> Amount {
assert_eq!(coin.network(), ExternalNetworkId::Ethereum);
assert_eq!(coin.decimals(), 8);
// Remove 10 decimals so we go from 18 decimals to 8 decimals
let divisor = U256::from(10_000_000_000u64);
@ -89,8 +89,8 @@ fn amount_to_serai_amount(coin: Coin, amount: U256) -> Amount {
Amount(u64::try_from(amount / divisor).unwrap())
}
fn balance_to_ethereum_amount(balance: Balance) -> U256 {
assert_eq!(balance.coin.network(), NetworkId::Ethereum);
fn balance_to_ethereum_amount(balance: ExternalBalance) -> U256 {
assert_eq!(balance.coin.network(), ExternalNetworkId::Ethereum);
assert_eq!(balance.coin.decimals(), 8);
// Restore 10 decimals so we go from 8 decimals to 18 decimals
let factor = U256::from(10_000_000_000u64);
@ -201,14 +201,14 @@ impl<D: Db> Output<Ethereum<D>> for EthereumInInstruction {
Some(Address(self.from))
}
fn balance(&self) -> Balance {
fn balance(&self) -> ExternalBalance {
let coin = coin_to_serai_coin(&self.coin).unwrap_or_else(|| {
panic!(
"requesting coin for an EthereumInInstruction with a coin {}",
"we don't handle. this never should have been yielded"
)
});
Balance { coin, amount: amount_to_serai_amount(coin, self.amount) }
ExternalBalance { coin, amount: amount_to_serai_amount(coin, self.amount) }
}
fn data(&self) -> &[u8] {
&self.data
@ -394,7 +394,7 @@ impl<D: Db> Network for Ethereum<D> {
type Address = Address;
const NETWORK: NetworkId = NetworkId::Ethereum;
const NETWORK: ExternalNetworkId = ExternalNetworkId::Ethereum;
const ID: &'static str = "Ethereum";
const ESTIMATED_BLOCK_TIME_IN_SECONDS: usize = 32 * 12;
const CONFIRMATIONS: usize = 1;
@ -681,7 +681,7 @@ impl<D: Db> Network for Ethereum<D> {
OutInstructionTarget::Direct(payment.address.0)
},
value: {
assert_eq!(payment.balance.coin, Coin::Ether); // TODO
assert_eq!(payment.balance.coin, ExternalCoin::Ether); // TODO
balance_to_ethereum_amount(payment.balance)
},
})

View file

@ -10,7 +10,7 @@ use frost::{
sign::PreprocessMachine,
};
use serai_client::primitives::{NetworkId, Balance};
use serai_client::primitives::{ExternalBalance, ExternalNetworkId};
use log::error;
@ -115,7 +115,7 @@ pub trait Output<N: Network>: Send + Sync + Sized + Clone + PartialEq + Eq + Deb
fn presumed_origin(&self) -> Option<N::Address>;
fn balance(&self) -> Balance;
fn balance(&self) -> ExternalBalance;
fn data(&self) -> &[u8];
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()>;
@ -126,13 +126,13 @@ pub trait Output<N: Network>: Send + Sync + Sized + Clone + PartialEq + Eq + Deb
pub trait Transaction<N: Network>: Send + Sync + Sized + Clone + PartialEq + Debug {
type Id: 'static + Id;
fn id(&self) -> Self::Id;
// TODO: Move to Balance
// TODO: Move to ExternalBalance
#[cfg(test)]
async fn fee(&self, network: &N) -> u64;
}
pub trait SignableTransaction: Send + Sync + Clone + Debug {
// TODO: Move to Balance
// TODO: Move to ExternalBalance
fn fee(&self) -> u64;
}
@ -280,7 +280,7 @@ pub trait Network: 'static + Send + Sync + Clone + PartialEq + Debug {
+ TryFrom<Vec<u8>>;
/// Network ID for this network.
const NETWORK: NetworkId;
const NETWORK: ExternalNetworkId;
/// String ID for this network.
const ID: &'static str;
/// The estimated amount of time a block will take.
@ -297,7 +297,7 @@ pub trait Network: 'static + Send + Sync + Clone + PartialEq + Debug {
/// For any received output, there's the cost to spend the output. This value MUST exceed the
/// cost to spend said output, and should by a notable margin (not just 2x, yet an order of
/// magnitude).
// TODO: Dust needs to be diversified per Coin
// TODO: Dust needs to be diversified per ExternalCoin
const DUST: u64;
/// The cost to perform input aggregation with a 2-input 1-output TX.

View file

@ -31,7 +31,7 @@ use monero_wallet::Scanner;
use tokio::time::sleep;
pub use serai_client::{
primitives::{MAX_DATA_LEN, Coin, NetworkId, Amount, Balance},
primitives::{MAX_DATA_LEN, ExternalCoin, ExternalNetworkId, Amount, ExternalBalance},
networks::monero::Address,
};
@ -85,8 +85,8 @@ impl OutputTrait<Monero> for Output {
None
}
fn balance(&self) -> Balance {
Balance { coin: Coin::Monero, amount: Amount(self.0.commitment().amount) }
fn balance(&self) -> ExternalBalance {
ExternalBalance { coin: ExternalCoin::Monero, amount: Amount(self.0.commitment().amount) }
}
fn data(&self) -> &[u8] {
@ -308,7 +308,7 @@ impl Monero {
calculating_fee: bool,
) -> Result<Option<MakeSignableTransactionResult>, NetworkError> {
for payment in payments {
assert_eq!(payment.balance.coin, Coin::Monero);
assert_eq!(payment.balance.coin, ExternalCoin::Monero);
}
// TODO2: Use an fee representative of several blocks, cached inside Self
@ -363,7 +363,7 @@ impl Monero {
.legacy_address(MoneroNetwork::Mainnet),
)
.unwrap(),
balance: Balance { coin: Coin::Monero, amount: Amount(0) },
balance: ExternalBalance { coin: ExternalCoin::Monero, amount: Amount(0) },
data: None,
});
}
@ -470,7 +470,7 @@ impl Network for Monero {
type Address = Address;
const NETWORK: NetworkId = NetworkId::Monero;
const NETWORK: ExternalNetworkId = ExternalNetworkId::Monero;
const ID: &'static str = "Monero";
const ESTIMATED_BLOCK_TIME_IN_SECONDS: usize = 120;
const CONFIRMATIONS: usize = 10;

View file

@ -6,7 +6,7 @@ use transcript::{Transcript, RecommendedTranscript};
use ciphersuite::group::GroupEncoding;
use frost::curve::Ciphersuite;
use serai_client::primitives::Balance;
use serai_client::primitives::ExternalBalance;
use crate::{
networks::{Output, Network},
@ -17,7 +17,7 @@ use crate::{
pub struct Payment<N: Network> {
pub address: N::Address,
pub data: Option<Vec<u8>>,
pub balance: Balance,
pub balance: ExternalBalance,
}
impl<N: Network> Payment<N> {
@ -69,7 +69,7 @@ impl<N: Network> Payment<N> {
None
};
let balance = Balance::decode(&mut scale::IoReader(reader))
let balance = ExternalBalance::decode(&mut scale::IoReader(reader))
.map_err(|_| io::Error::other("invalid balance"))?;
Ok(Payment { address, data, balance })

View file

@ -17,9 +17,9 @@ use frost_schnorrkel::Schnorrkel;
use log::{info, warn};
use serai_client::{
primitives::ExternalNetworkId,
validator_sets::primitives::{report_slashes_message, ExternalValidatorSet, Session},
Public,
primitives::NetworkId,
validator_sets::primitives::{Session, ValidatorSet, report_slashes_message},
};
use messages::coordinator::*;
@ -38,7 +38,7 @@ type SignatureShare = <AlgorithmSignMachine<Ristretto, Schnorrkel> as SignMachin
>>::SignatureShare;
pub struct SlashReportSigner {
network: NetworkId,
network: ExternalNetworkId,
session: Session,
keys: Vec<ThresholdKeys<Ristretto>>,
report: Vec<([u8; 32], u32)>,
@ -66,7 +66,7 @@ impl fmt::Debug for SlashReportSigner {
impl SlashReportSigner {
pub fn new(
txn: &mut impl DbTxn,
network: NetworkId,
network: ExternalNetworkId,
session: Session,
keys: Vec<ThresholdKeys<Ristretto>>,
report: Vec<([u8; 32], u32)>,
@ -178,7 +178,7 @@ impl SlashReportSigner {
let (machine, share) = match machine.sign(
preprocesses,
&report_slashes_message(
&ValidatorSet { network: self.network, session: self.session },
&ExternalValidatorSet { network: self.network, session: self.session },
&self
.report
.clone()

View file

@ -33,17 +33,17 @@ fn test_batch_signer() {
let block = BlockHash([0xaa; 32]);
let batch = Batch {
network: NetworkId::Monero,
network: ExternalNetworkId::Monero,
id,
block,
instructions: vec![
InInstructionWithBalance {
instruction: InInstruction::Transfer(SeraiAddress([0xbb; 32])),
balance: Balance { coin: Coin::Bitcoin, amount: Amount(1000) },
balance: ExternalBalance { coin: ExternalCoin::Bitcoin, amount: Amount(1000) },
},
InInstructionWithBalance {
instruction: InInstruction::Dex(DexCall::SwapAndAddLiquidity(SeraiAddress([0xbb; 32]))),
balance: Balance { coin: Coin::Monero, amount: Amount(9999999999999999) },
balance: ExternalBalance { coin: ExternalCoin::Monero, amount: Amount(9999999999999999) },
},
],
};
@ -70,7 +70,7 @@ fn test_batch_signer() {
let i = Participant::new(u16::try_from(i).unwrap()).unwrap();
let keys = keys.get(&i).unwrap().clone();
let mut signer = BatchSigner::<MemDb>::new(NetworkId::Monero, Session(0), vec![keys]);
let mut signer = BatchSigner::<MemDb>::new(ExternalNetworkId::Monero, Session(0), vec![keys]);
let mut db = MemDb::new();
let mut txn = db.txn();

View file

@ -12,7 +12,7 @@ use frost::{
use serai_db::{DbTxn, Db, MemDb};
use serai_client::{
primitives::{NetworkId, Coin, Amount, Balance},
primitives::{ExternalNetworkId, ExternalCoin, Amount, ExternalBalance},
validator_sets::primitives::Session,
};
@ -185,12 +185,11 @@ pub async fn test_signer<N: Network>(
let payments = vec![Payment {
address: N::external_address(&network, key).await,
data: None,
balance: Balance {
balance: ExternalBalance {
coin: match N::NETWORK {
NetworkId::Serai => panic!("test_signer called with Serai"),
NetworkId::Bitcoin => Coin::Bitcoin,
NetworkId::Ethereum => Coin::Ether,
NetworkId::Monero => Coin::Monero,
ExternalNetworkId::Bitcoin => ExternalCoin::Bitcoin,
ExternalNetworkId::Ethereum => ExternalCoin::Ether,
ExternalNetworkId::Monero => ExternalCoin::Monero,
},
amount: Amount(amount),
},
@ -224,7 +223,7 @@ pub async fn test_signer<N: Network>(
.await;
// Don't run if Ethereum as the received output will revert by the contract
// (and therefore not actually exist)
if N::NETWORK != NetworkId::Ethereum {
if N::NETWORK != ExternalNetworkId::Ethereum {
assert_eq!(outputs.len(), 1 + usize::from(u8::from(plan.change.is_some())));
// Adjust the amount for the fees
let amount = amount - tx.fee(&network).await;

View file

@ -11,7 +11,7 @@ use tokio::time::timeout;
use serai_db::{DbTxn, Db, MemDb};
use serai_client::{
primitives::{NetworkId, Coin, Amount, Balance},
primitives::{ExternalNetworkId, ExternalCoin, Amount, ExternalBalance},
validator_sets::primitives::Session,
};
@ -89,12 +89,11 @@ pub async fn test_wallet<N: Network>(
vec![Payment {
address: N::external_address(&network, key).await,
data: None,
balance: Balance {
balance: ExternalBalance {
coin: match N::NETWORK {
NetworkId::Serai => panic!("test_wallet called with Serai"),
NetworkId::Bitcoin => Coin::Bitcoin,
NetworkId::Ethereum => Coin::Ether,
NetworkId::Monero => Coin::Monero,
ExternalNetworkId::Bitcoin => ExternalCoin::Bitcoin,
ExternalNetworkId::Ethereum => ExternalCoin::Ether,
ExternalNetworkId::Monero => ExternalCoin::Monero,
},
amount: Amount(amount),
},
@ -117,12 +116,11 @@ pub async fn test_wallet<N: Network>(
vec![Payment {
address: N::external_address(&network, key).await,
data: None,
balance: Balance {
balance: ExternalBalance {
coin: match N::NETWORK {
NetworkId::Serai => panic!("test_wallet called with Serai"),
NetworkId::Bitcoin => Coin::Bitcoin,
NetworkId::Ethereum => Coin::Ether,
NetworkId::Monero => Coin::Monero,
ExternalNetworkId::Bitcoin => ExternalCoin::Bitcoin,
ExternalNetworkId::Ethereum => ExternalCoin::Ether,
ExternalNetworkId::Monero => ExternalCoin::Monero,
},
amount: Amount(amount),
}
@ -160,7 +158,7 @@ pub async fn test_wallet<N: Network>(
// Don't run if Ethereum as the received output will revert by the contract
// (and therefore not actually exist)
if N::NETWORK != NetworkId::Ethereum {
if N::NETWORK != ExternalNetworkId::Ethereum {
assert_eq!(outputs.len(), 1 + usize::from(u8::from(plans[0].change.is_some())));
// Adjust the amount for the fees
let amount = amount - tx.fee(&network).await;
@ -183,7 +181,7 @@ pub async fn test_wallet<N: Network>(
network.mine_block().await;
}
if N::NETWORK != NetworkId::Ethereum {
if N::NETWORK != ExternalNetworkId::Ethereum {
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
ScannerEvent::Block { is_retirement_block, block: block_id, outputs: these_outputs } => {
scanner.multisig_completed.send(false).unwrap();

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

View file

@ -19,7 +19,7 @@ use ciphersuite::{
Ciphersuite, Ristretto,
};
use serai_client::primitives::NetworkId;
use serai_client::primitives::ExternalNetworkId;
use messages::{
coordinator::{SubstrateSignableId, SubstrateSignId, cosign_block_msg},
@ -108,7 +108,7 @@ pub struct Handles {
}
pub struct Processor {
network: NetworkId,
network: ExternalNetworkId,
serai_rpc: String,
#[allow(unused)]
@ -132,7 +132,7 @@ impl Drop for Processor {
impl Processor {
pub async fn new(
raw_i: u8,
network: NetworkId,
network: ExternalNetworkId,
ops: &DockerOperations,
handles: Handles,
processor_key: <Ristretto as Ciphersuite>::F,

View file

@ -16,7 +16,7 @@ use dkg::Participant;
use scale::Encode;
use serai_client::{
primitives::{NetworkId, BlockHash, Signature},
primitives::{BlockHash, Signature},
in_instructions::{
primitives::{Batch, SignedBatch, batch_message},
InInstructionsEvent,
@ -274,7 +274,7 @@ async fn batch_test() {
Session(0),
&substrate_key,
Batch {
network: NetworkId::Bitcoin,
network: ExternalNetworkId::Bitcoin,
id: 0,
block: BlockHash([0x22; 32]),
instructions: vec![],

View file

@ -13,9 +13,8 @@ use ciphersuite::{
use dkg::ThresholdParams;
use serai_client::{
primitives::NetworkId,
validator_sets::primitives::{ExternalValidatorSet, KeyPair, Session},
Public,
validator_sets::primitives::{Session, ValidatorSet, KeyPair},
};
use messages::{key_gen::KeyGenId, CoordinatorMessage};
@ -28,7 +27,7 @@ pub async fn key_gen<C: Ciphersuite>(
let coordinators = processors.len();
let mut participant_is = vec![];
let set = ValidatorSet { session, network: NetworkId::Bitcoin };
let set = ExternalValidatorSet { session, network: ExternalNetworkId::Bitcoin };
let id = KeyGenId { session: set.session, attempt: 0 };
for (i, processor) in processors.iter_mut().enumerate() {

View file

@ -104,7 +104,7 @@ pub(crate) async fn new_test(test_body: impl TestBody, fast_epoch: bool) {
handles.insert(name, handle);
}
let processor_key = message_queue_keys[&NetworkId::Bitcoin];
let processor_key = message_queue_keys[&ExternalNetworkId::Bitcoin];
coordinators.push((
Handles {
@ -198,7 +198,7 @@ pub(crate) async fn new_test(test_body: impl TestBody, fast_epoch: bool) {
processors.push(
Processor::new(
i.try_into().unwrap(),
NetworkId::Bitcoin,
ExternalNetworkId::Bitcoin,
&outer_ops,
handles.clone(),
*key,

View file

@ -132,13 +132,13 @@ async fn set_rotation_test() {
// excluded participant
let pair5 = insecure_pair_from_name("Eve");
let network = NetworkId::Bitcoin;
let network = ExternalNetworkId::Bitcoin;
let amount = Amount(1_000_000 * 10_u64.pow(8));
let serai = processors[0].serai().await;
// allocate now for the last participant so that it is guaranteed to be included into session
// 1 set. This doesn't affect the genesis set at all since that is a predetermined set.
allocate_stake(&serai, network, amount, &pair5, 0).await;
allocate_stake(&serai, network.into(), amount, &pair5, 0).await;
// genesis keygen
let _ = key_gen::<Secp256k1>(&mut processors, Session(0)).await;
@ -151,12 +151,14 @@ async fn set_rotation_test() {
}
// wait until next session to see the effect on coordinator
wait_till_session_1(&serai, network).await;
wait_till_session_1(&serai, network.into()).await;
// Ensure the new validator was included in the new set
assert_eq!(
most_recent_new_set_event(&serai, network).await,
ValidatorSetsEvent::NewSet { set: ValidatorSet { session: Session(1), network } },
most_recent_new_set_event(&serai, network.into()).await,
ValidatorSetsEvent::NewSet {
set: ValidatorSet { session: Session(1), network: network.into() }
},
);
// add the last participant & do the keygen

View file

@ -10,18 +10,17 @@ use ciphersuite::Secp256k1;
use dkg::Participant;
use serai_client::{
PairTrait,
primitives::{
NetworkId, Coin, Amount, Balance, BlockHash, SeraiAddress, ExternalAddress,
insecure_pair_from_name,
},
coins::{
primitives::{OutInstruction, OutInstructionWithBalance},
CoinsEvent,
},
in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch},
in_instructions::primitives::{Batch, InInstruction, InInstructionWithBalance},
primitives::{
insecure_pair_from_name, Amount, Balance, BlockHash, Coin, ExternalAddress, ExternalBalance,
ExternalCoin, SeraiAddress,
},
validator_sets::primitives::Session,
SeraiCoins,
PairTrait, SeraiCoins,
};
use messages::{coordinator::PlanMeta, sign::SignId, SubstrateContext, CoordinatorMessage};
@ -202,7 +201,7 @@ async fn sign_test() {
#[allow(clippy::inconsistent_digit_grouping)]
let amount = Amount(1_000_000_00);
let balance = Balance { coin: Coin::Bitcoin, amount };
let balance = ExternalBalance { coin: ExternalCoin::Bitcoin, amount };
let coin_block = BlockHash([0x33; 32]);
let block_included_in = batch(
@ -211,7 +210,7 @@ async fn sign_test() {
Session(0),
&substrate_key,
Batch {
network: NetworkId::Bitcoin,
network: balance.coin.network(),
id: 0,
block: coin_block,
instructions: vec![InInstructionWithBalance {
@ -236,10 +235,13 @@ async fn sign_test() {
// Verify the mint occurred as expected
assert_eq!(
serai.mint_events().await.unwrap(),
vec![CoinsEvent::Mint { to: serai_addr, balance }]
vec![CoinsEvent::Mint { to: serai_addr, balance: balance.into() }]
);
assert_eq!(serai.coin_supply(ExternalCoin::Bitcoin.into()).await.unwrap(), amount);
assert_eq!(
serai.coin_balance(ExternalCoin::Bitcoin.into(), serai_addr).await.unwrap(),
amount
);
assert_eq!(serai.coin_supply(Coin::Bitcoin).await.unwrap(), amount);
assert_eq!(serai.coin_balance(Coin::Bitcoin, serai_addr).await.unwrap(), amount);
}
// Trigger a burn
@ -296,8 +298,11 @@ async fn sign_test() {
let last_serai_block_hash = last_serai_block.hash();
let serai = serai.as_of(last_serai_block_hash);
let serai = serai.coins();
assert_eq!(serai.coin_supply(Coin::Bitcoin).await.unwrap(), Amount(0));
assert_eq!(serai.coin_balance(Coin::Bitcoin, serai_addr).await.unwrap(), Amount(0));
assert_eq!(serai.coin_supply(ExternalCoin::Bitcoin.into()).await.unwrap(), Amount(0));
assert_eq!(
serai.coin_balance(ExternalCoin::Bitcoin.into(), serai_addr).await.unwrap(),
Amount(0)
);
let mut plan_id = [0; 32];
OsRng.fill_bytes(&mut plan_id);

View file

@ -9,12 +9,13 @@ use rand_core::{RngCore, OsRng};
use scale::Encode;
use serai_client::{
primitives::{
NetworkId, Coin, Amount, Balance, SeraiAddress, ExternalAddress, insecure_pair_from_name,
},
validator_sets::primitives::{Session, ValidatorSet},
in_instructions::primitives::Shorthand,
coins::primitives::{OutInstruction, OutInstructionWithBalance},
in_instructions::primitives::Shorthand,
primitives::{
insecure_pair_from_name, Amount, Balance, Coin, ExternalAddress, ExternalBalance, ExternalCoin,
SeraiAddress,
},
validator_sets::primitives::{ExternalValidatorSet, Session},
PairTrait, SeraiCoins,
};
@ -199,7 +200,7 @@ async fn mint_and_burn_test() {
.await
.unwrap()
.validator_sets()
.keys(ValidatorSet { network, session: Session(0) })
.keys(ExternalValidatorSet { network, session: Session(0) })
.await
.unwrap()
{
@ -224,7 +225,10 @@ async fn mint_and_burn_test() {
}
};
(key_pair(false, NetworkId::Bitcoin).await, key_pair(true, NetworkId::Monero).await)
(
key_pair(false, ExternalNetworkId::Bitcoin).await,
key_pair(true, ExternalNetworkId::Monero).await,
)
};
// Because the initial keys only become active when the network's time matches the Serai
@ -439,8 +443,8 @@ async fn mint_and_burn_test() {
);
}
};
wait_for_batch(false, NetworkId::Bitcoin).await;
wait_for_batch(true, NetworkId::Monero).await;
wait_for_batch(false, ExternalNetworkId::Bitcoin).await;
wait_for_batch(true, ExternalNetworkId::Monero).await;
}
// TODO: Verify the mints
@ -492,7 +496,7 @@ async fn mint_and_burn_test() {
let serai_pair = &serai_pair;
move |nonce, coin, amount, address| async move {
let out_instruction = OutInstructionWithBalance {
balance: Balance { coin, amount: Amount(amount) },
balance: ExternalBalance { coin, amount: Amount(amount) },
instruction: OutInstruction { address, data: None },
};
@ -511,7 +515,7 @@ async fn mint_and_burn_test() {
#[allow(clippy::inconsistent_digit_grouping)]
burn(
0,
Coin::Bitcoin,
ExternalCoin::Bitcoin,
1_000_000_00,
ExternalAddress::new(
serai_client::networks::bitcoin::Address::new(bitcoin_addr.clone()).unwrap().into(),
@ -522,7 +526,7 @@ async fn mint_and_burn_test() {
burn(
1,
Coin::Monero,
ExternalCoin::Monero,
1_000_000_000_000,
ExternalAddress::new(
serai_client::networks::monero::Address::new(monero_addr).unwrap().into(),

View file

@ -3,7 +3,7 @@ use std::{sync::OnceLock, collections::HashMap};
use tokio::sync::Mutex;
use serai_client::primitives::NetworkId;
use serai_client::primitives::ExternalNetworkId;
use dockertest::{
LogAction, LogPolicy, LogSource, LogOptions, StartPolicy, TestBodySpecification,
@ -56,15 +56,21 @@ pub(crate) async fn new_test(test_body: impl TestBody) {
let (coord_key, message_queue_keys, message_queue_composition) = message_queue_instance();
let (bitcoin_composition, bitcoin_port) = network_instance(NetworkId::Bitcoin);
let mut bitcoin_processor_composition =
processor_instance(NetworkId::Bitcoin, bitcoin_port, message_queue_keys[&NetworkId::Bitcoin]);
let (bitcoin_composition, bitcoin_port) = network_instance(ExternalNetworkId::Bitcoin);
let mut bitcoin_processor_composition = processor_instance(
ExternalNetworkId::Bitcoin,
bitcoin_port,
message_queue_keys[&ExternalNetworkId::Bitcoin],
);
assert_eq!(bitcoin_processor_composition.len(), 1);
let bitcoin_processor_composition = bitcoin_processor_composition.swap_remove(0);
let (monero_composition, monero_port) = network_instance(NetworkId::Monero);
let mut monero_processor_composition =
processor_instance(NetworkId::Monero, monero_port, message_queue_keys[&NetworkId::Monero]);
let (monero_composition, monero_port) = network_instance(ExternalNetworkId::Monero);
let mut monero_processor_composition = processor_instance(
ExternalNetworkId::Monero,
monero_port,
message_queue_keys[&ExternalNetworkId::Monero],
);
assert_eq!(monero_processor_composition.len(), 1);
let monero_processor_composition = monero_processor_composition.swap_remove(0);

View file

@ -7,23 +7,25 @@ use ciphersuite::{
Ciphersuite, Ristretto,
};
use serai_primitives::NetworkId;
use serai_primitives::{ExternalNetworkId, EXTERNAL_NETWORKS};
use dockertest::{
PullPolicy, Image, LogAction, LogPolicy, LogSource, LogOptions, TestBodySpecification,
};
pub type MessageQueuePrivateKey = <Ristretto as Ciphersuite>::F;
pub fn instance(
) -> (MessageQueuePrivateKey, HashMap<NetworkId, MessageQueuePrivateKey>, TestBodySpecification) {
pub fn instance() -> (
MessageQueuePrivateKey,
HashMap<ExternalNetworkId, MessageQueuePrivateKey>,
TestBodySpecification,
) {
serai_docker_tests::build("message-queue".to_string());
let coord_key = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
let priv_keys = HashMap::from([
(NetworkId::Bitcoin, <Ristretto as Ciphersuite>::F::random(&mut OsRng)),
(NetworkId::Ethereum, <Ristretto as Ciphersuite>::F::random(&mut OsRng)),
(NetworkId::Monero, <Ristretto as Ciphersuite>::F::random(&mut OsRng)),
]);
let priv_keys = EXTERNAL_NETWORKS
.into_iter()
.map(|n| (n, <Ristretto as Ciphersuite>::F::random(&mut OsRng)))
.collect::<HashMap<_, _>>();
let composition = TestBodySpecification::with_image(
Image::with_repository("serai-dev-message-queue").pull_policy(PullPolicy::Never),
@ -38,15 +40,15 @@ pub fn instance(
("COORDINATOR_KEY".to_string(), hex::encode((Ristretto::generator() * coord_key).to_bytes())),
(
"BITCOIN_KEY".to_string(),
hex::encode((Ristretto::generator() * priv_keys[&NetworkId::Bitcoin]).to_bytes()),
hex::encode((Ristretto::generator() * priv_keys[&ExternalNetworkId::Bitcoin]).to_bytes()),
),
(
"ETHEREUM_KEY".to_string(),
hex::encode((Ristretto::generator() * priv_keys[&NetworkId::Ethereum]).to_bytes()),
hex::encode((Ristretto::generator() * priv_keys[&ExternalNetworkId::Ethereum]).to_bytes()),
),
(
"MONERO_KEY".to_string(),
hex::encode((Ristretto::generator() * priv_keys[&NetworkId::Monero]).to_bytes()),
hex::encode((Ristretto::generator() * priv_keys[&ExternalNetworkId::Monero]).to_bytes()),
),
("DB_PATH".to_string(), "./message-queue-db".to_string()),
("RUST_LOG".to_string(), "serai_message_queue=trace,".to_string()),
@ -85,7 +87,7 @@ fn basic_functionality() {
.queue(
Metadata {
from: Service::Coordinator,
to: Service::Processor(NetworkId::Bitcoin),
to: Service::Processor(ExternalNetworkId::Bitcoin),
intent: b"intent".to_vec(),
},
b"Hello, World!".to_vec(),
@ -98,7 +100,7 @@ fn basic_functionality() {
.queue(
Metadata {
from: Service::Coordinator,
to: Service::Processor(NetworkId::Bitcoin),
to: Service::Processor(ExternalNetworkId::Bitcoin),
intent: b"intent 2".to_vec(),
},
b"Hello, World, again!".to_vec(),
@ -108,9 +110,9 @@ fn basic_functionality() {
// Successfully get it
let bitcoin = MessageQueue::new(
Service::Processor(NetworkId::Bitcoin),
Service::Processor(ExternalNetworkId::Bitcoin),
rpc.clone(),
Zeroizing::new(priv_keys[&NetworkId::Bitcoin]),
Zeroizing::new(priv_keys[&ExternalNetworkId::Bitcoin]),
);
let msg = bitcoin.next(Service::Coordinator).await;
assert_eq!(msg.from, Service::Coordinator);
@ -140,7 +142,7 @@ fn basic_functionality() {
.queue(
Metadata {
from: Service::Coordinator,
to: Service::Processor(NetworkId::Monero),
to: Service::Processor(ExternalNetworkId::Monero),
// Intents should be per-from-to, making this valid
intent: b"intent".to_vec(),
},
@ -149,9 +151,9 @@ fn basic_functionality() {
.await;
let monero = MessageQueue::new(
Service::Processor(NetworkId::Monero),
Service::Processor(ExternalNetworkId::Monero),
rpc,
Zeroizing::new(priv_keys[&NetworkId::Monero]),
Zeroizing::new(priv_keys[&ExternalNetworkId::Monero]),
);
assert_eq!(monero.next(Service::Coordinator).await.id, 0);
monero.ack(Service::Coordinator, 0).await;

View file

@ -7,7 +7,7 @@ use rand_core::{RngCore, OsRng};
use ciphersuite::{group::ff::PrimeField, Ciphersuite, Ristretto};
use serai_client::primitives::NetworkId;
use serai_client::primitives::ExternalNetworkId;
use messages::{ProcessorMessage, CoordinatorMessage};
use serai_message_queue::{Service, Metadata, client::MessageQueue};
@ -25,7 +25,7 @@ mod tests;
static UNIQUE_ID: OnceLock<Mutex<u16>> = OnceLock::new();
pub fn processor_instance(
network: NetworkId,
network: ExternalNetworkId,
port: u32,
message_queue_key: <Ristretto as Ciphersuite>::F,
) -> Vec<TestBodySpecification> {
@ -33,10 +33,9 @@ pub fn processor_instance(
OsRng.fill_bytes(&mut entropy);
let network_str = match network {
NetworkId::Serai => panic!("starting a processor for Serai"),
NetworkId::Bitcoin => "bitcoin",
NetworkId::Ethereum => "ethereum",
NetworkId::Monero => "monero",
ExternalNetworkId::Bitcoin => "bitcoin",
ExternalNetworkId::Ethereum => "ethereum",
ExternalNetworkId::Monero => "monero",
};
let image = format!("{network_str}-processor");
serai_docker_tests::build(image.clone());
@ -57,7 +56,7 @@ pub fn processor_instance(
.into(),
)];
if network == NetworkId::Ethereum {
if network == ExternalNetworkId::Ethereum {
serai_docker_tests::build("ethereum-relayer".to_string());
res.push(
TestBodySpecification::with_image(
@ -80,7 +79,7 @@ pub fn processor_instance(
pub type Handles = (String, String, String, String);
pub fn processor_stack(
network: NetworkId,
network: ExternalNetworkId,
network_hostname_override: Option<String>,
) -> (Handles, <Ristretto as Ciphersuite>::F, Vec<TestBodySpecification>) {
let (network_composition, network_rpc_port) = network_instance(network);
@ -106,10 +105,9 @@ pub fn processor_stack(
for (name, composition) in [
Some((
match network {
NetworkId::Serai => unreachable!(),
NetworkId::Bitcoin => "bitcoin",
NetworkId::Ethereum => "ethereum",
NetworkId::Monero => "monero",
ExternalNetworkId::Bitcoin => "bitcoin",
ExternalNetworkId::Ethereum => "ethereum",
ExternalNetworkId::Monero => "monero",
},
network_composition,
)),
@ -161,7 +159,7 @@ pub fn processor_stack(
}
pub struct Coordinator {
network: NetworkId,
network: ExternalNetworkId,
network_handle: String,
#[allow(unused)]
@ -177,7 +175,7 @@ pub struct Coordinator {
impl Coordinator {
pub fn new(
network: NetworkId,
network: ExternalNetworkId,
ops: &DockerOperations,
handles: Handles,
coord_key: <Ristretto as Ciphersuite>::F,
@ -213,7 +211,7 @@ impl Coordinator {
let mut iters = 0;
while iters < 60 {
match network {
NetworkId::Bitcoin => {
ExternalNetworkId::Bitcoin => {
use bitcoin_serai::rpc::Rpc;
// Bitcoin's Rpc::new will test the connection
@ -221,7 +219,7 @@ impl Coordinator {
break;
}
}
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
use std::sync::Arc;
use ethereum_serai::{
alloy::{
@ -270,7 +268,7 @@ impl Coordinator {
break;
}
}
NetworkId::Monero => {
ExternalNetworkId::Monero => {
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::rpc::Rpc;
@ -284,7 +282,6 @@ impl Coordinator {
break;
}
}
NetworkId::Serai => panic!("processor is booting with external network of Serai"),
}
println!("external network RPC has yet to boot, waiting 1 sec, attempt {iters}");
@ -337,7 +334,7 @@ impl Coordinator {
pub async fn add_block(&self, ops: &DockerOperations) -> ([u8; 32], Vec<u8>) {
let rpc_url = network_rpc(self.network, ops, &self.network_handle);
match self.network {
NetworkId::Bitcoin => {
ExternalNetworkId::Bitcoin => {
use bitcoin_serai::{
bitcoin::{consensus::Encodable, network::Network, Script, Address},
rpc::Rpc,
@ -360,7 +357,7 @@ impl Coordinator {
block.consensus_encode(&mut block_buf).unwrap();
(hash, block_buf)
}
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
use ethereum_serai::alloy::{
simple_request_transport::SimpleRequest,
rpc_types::{BlockTransactionsKind, BlockNumberOrTag},
@ -397,7 +394,7 @@ impl Coordinator {
.into_bytes();
(hash.into(), state)
}
NetworkId::Monero => {
ExternalNetworkId::Monero => {
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{rpc::Rpc, address::Network, ViewPair};
@ -415,14 +412,13 @@ impl Coordinator {
let hash = rpc.get_block_hash(rpc.get_height().await.unwrap() - 1).await.unwrap();
(hash, rpc.get_block(hash).await.unwrap().serialize())
}
NetworkId::Serai => panic!("processor tests adding block to Serai"),
}
}
pub async fn sync(&self, ops: &DockerOperations, others: &[Coordinator]) {
let rpc_url = network_rpc(self.network, ops, &self.network_handle);
match self.network {
NetworkId::Bitcoin => {
ExternalNetworkId::Bitcoin => {
use bitcoin_serai::{bitcoin::consensus::Encodable, rpc::Rpc};
let rpc = Rpc::new(rpc_url).await.expect("couldn't connect to the Bitcoin RPC");
@ -452,7 +448,7 @@ impl Coordinator {
}
}
}
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
use ethereum_serai::alloy::{
simple_request_transport::SimpleRequest,
rpc_types::{BlockTransactionsKind, BlockNumberOrTag},
@ -503,7 +499,7 @@ impl Coordinator {
//assert_eq!(expected_number, new_number);
}
}
NetworkId::Monero => {
ExternalNetworkId::Monero => {
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::rpc::Rpc;
@ -534,14 +530,13 @@ impl Coordinator {
}
}
}
NetworkId::Serai => panic!("processors tests syncing Serai nodes"),
}
}
pub async fn publish_transaction(&self, ops: &DockerOperations, tx: &[u8]) {
let rpc_url = network_rpc(self.network, ops, &self.network_handle);
match self.network {
NetworkId::Bitcoin => {
ExternalNetworkId::Bitcoin => {
use bitcoin_serai::{
bitcoin::{consensus::Decodable, Transaction},
rpc::Rpc,
@ -551,7 +546,7 @@ impl Coordinator {
Rpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Bitcoin RPC");
rpc.send_raw_transaction(&Transaction::consensus_decode(&mut &*tx).unwrap()).await.unwrap();
}
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
use ethereum_serai::alloy::{
simple_request_transport::SimpleRequest,
rpc_client::ClientBuilder,
@ -564,7 +559,7 @@ impl Coordinator {
);
let _ = provider.send_raw_transaction(tx).await.unwrap();
}
NetworkId::Monero => {
ExternalNetworkId::Monero => {
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{transaction::Transaction, rpc::Rpc};
@ -573,15 +568,15 @@ impl Coordinator {
.expect("couldn't connect to the coordinator's Monero RPC");
rpc.publish_transaction(&Transaction::read(&mut &*tx).unwrap()).await.unwrap();
}
NetworkId::Serai => panic!("processor tests broadcasting block to Serai"),
}
}
pub async fn publish_eventuality_completion(&self, ops: &DockerOperations, tx: &[u8]) {
match self.network {
NetworkId::Bitcoin | NetworkId::Monero => self.publish_transaction(ops, tx).await,
NetworkId::Ethereum => (),
NetworkId::Serai => panic!("processor tests broadcasting block to Serai"),
ExternalNetworkId::Bitcoin | ExternalNetworkId::Monero => {
self.publish_transaction(ops, tx).await
}
ExternalNetworkId::Ethereum => (),
}
}
@ -592,7 +587,7 @@ impl Coordinator {
) -> Option<Vec<u8>> {
let rpc_url = network_rpc(self.network, ops, &self.network_handle);
match self.network {
NetworkId::Bitcoin => {
ExternalNetworkId::Bitcoin => {
use bitcoin_serai::{bitcoin::consensus::Encodable, rpc::Rpc};
let rpc =
@ -614,7 +609,7 @@ impl Coordinator {
None
}
}
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
/*
let provider = RootProvider::<_, Ethereum>::new(
ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true),
@ -664,7 +659,7 @@ impl Coordinator {
None
}
NetworkId::Monero => {
ExternalNetworkId::Monero => {
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::rpc::Rpc;
@ -679,7 +674,6 @@ impl Coordinator {
None
}
}
NetworkId::Serai => panic!("processor tests broadcasting block to Serai"),
}
}
}

View file

@ -4,9 +4,9 @@ use rand_core::{RngCore, OsRng};
use scale::Encode;
use serai_client::{
primitives::{Amount, NetworkId, Coin, Balance, ExternalAddress},
validator_sets::primitives::ExternalKey,
in_instructions::primitives::{InInstruction, RefundableInInstruction, Shorthand},
primitives::{Amount, ExternalAddress, ExternalBalance, ExternalCoin, ExternalNetworkId},
validator_sets::primitives::ExternalKey,
};
use dockertest::{PullPolicy, Image, StartPolicy, TestBodySpecification, DockerOperations};
@ -52,37 +52,32 @@ pub fn monero_instance() -> (TestBodySpecification, u32) {
(composition, XMR_PORT)
}
pub fn network_instance(network: NetworkId) -> (TestBodySpecification, u32) {
pub fn network_instance(network: ExternalNetworkId) -> (TestBodySpecification, u32) {
match network {
NetworkId::Bitcoin => bitcoin_instance(),
NetworkId::Ethereum => ethereum_instance(),
NetworkId::Monero => monero_instance(),
NetworkId::Serai => {
panic!("Serai is not a valid network to spawn an instance of for a processor")
}
ExternalNetworkId::Bitcoin => bitcoin_instance(),
ExternalNetworkId::Ethereum => ethereum_instance(),
ExternalNetworkId::Monero => monero_instance(),
}
}
pub fn network_rpc(network: NetworkId, ops: &DockerOperations, handle: &str) -> String {
pub fn network_rpc(network: ExternalNetworkId, ops: &DockerOperations, handle: &str) -> String {
let (ip, port) = ops
.handle(handle)
.host_port(match network {
NetworkId::Bitcoin => BTC_PORT,
NetworkId::Ethereum => ETH_PORT,
NetworkId::Monero => XMR_PORT,
NetworkId::Serai => panic!("getting port for external network yet it was Serai"),
ExternalNetworkId::Bitcoin => BTC_PORT,
ExternalNetworkId::Ethereum => ETH_PORT,
ExternalNetworkId::Monero => XMR_PORT,
})
.unwrap();
format!("http://{RPC_USER}:{RPC_PASS}@{ip}:{port}")
}
pub fn confirmations(network: NetworkId) -> usize {
pub fn confirmations(network: ExternalNetworkId) -> usize {
use processor::networks::*;
match network {
NetworkId::Bitcoin => Bitcoin::CONFIRMATIONS,
NetworkId::Ethereum => Ethereum::<serai_db::MemDb>::CONFIRMATIONS,
NetworkId::Monero => Monero::CONFIRMATIONS,
NetworkId::Serai => panic!("getting confirmations required for Serai"),
ExternalNetworkId::Bitcoin => Bitcoin::CONFIRMATIONS,
ExternalNetworkId::Ethereum => Ethereum::<serai_db::MemDb>::CONFIRMATIONS,
ExternalNetworkId::Monero => Monero::CONFIRMATIONS,
}
}
@ -108,11 +103,11 @@ pub enum Wallet {
// TODO: Merge these functions with the processor's tests, which offers very similar functionality
impl Wallet {
pub async fn new(network: NetworkId, ops: &DockerOperations, handle: String) -> Wallet {
pub async fn new(network: ExternalNetworkId, ops: &DockerOperations, handle: String) -> Wallet {
let rpc_url = network_rpc(network, ops, &handle);
match network {
NetworkId::Bitcoin => {
ExternalNetworkId::Bitcoin => {
use bitcoin_serai::{
bitcoin::{
secp256k1::{SECP256K1, SecretKey},
@ -153,7 +148,7 @@ impl Wallet {
Wallet::Bitcoin { private_key, public_key, input_tx: funds }
}
NetworkId::Ethereum => {
ExternalNetworkId::Ethereum => {
use ciphersuite::{group::ff::Field, Secp256k1};
use ethereum_serai::alloy::{
primitives::{U256, Address},
@ -185,7 +180,7 @@ impl Wallet {
Wallet::Ethereum { rpc_url: rpc_url.clone(), key, nonce: 0 }
}
NetworkId::Monero => {
ExternalNetworkId::Monero => {
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{rpc::Rpc, address::Network, ViewPair};
@ -210,7 +205,6 @@ impl Wallet {
last_tx: (height, block.miner_transaction.hash()),
}
}
NetworkId::Serai => panic!("creating a wallet for for Serai"),
}
}
@ -219,7 +213,7 @@ impl Wallet {
ops: &DockerOperations,
to: &ExternalKey,
instruction: Option<InInstruction>,
) -> (Vec<u8>, Balance) {
) -> (Vec<u8>, ExternalBalance) {
match self {
Wallet::Bitcoin { private_key, public_key, ref mut input_tx } => {
use bitcoin_serai::bitcoin::{
@ -298,7 +292,7 @@ impl Wallet {
let mut buf = vec![];
tx.consensus_encode(&mut buf).unwrap();
*input_tx = tx;
(buf, Balance { coin: Coin::Bitcoin, amount: Amount(AMOUNT) })
(buf, ExternalBalance { coin: ExternalCoin::Bitcoin, amount: Amount(AMOUNT) })
}
Wallet::Ethereum { rpc_url, key, ref mut nonce } => {
@ -400,7 +394,10 @@ impl Wallet {
// We drop the bottom 10 decimals
(
bytes,
Balance { coin: Coin::Ether, amount: Amount(u64::try_from(eight_decimals).unwrap()) },
ExternalBalance {
coin: ExternalCoin::Ether,
amount: Amount(u64::try_from(eight_decimals).unwrap()),
},
)
}
@ -417,7 +414,7 @@ impl Wallet {
};
use processor::{additional_key, networks::Monero};
let rpc_url = network_rpc(NetworkId::Monero, ops, handle);
let rpc_url = network_rpc(ExternalNetworkId::Monero, ops, handle);
let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
// Prepare inputs
@ -485,7 +482,7 @@ impl Wallet {
last_tx.0 = current_height;
last_tx.1 = tx.hash();
(tx.serialize(), Balance { coin: Coin::Monero, amount: Amount(AMOUNT) })
(tx.serialize(), ExternalBalance { coin: ExternalCoin::Monero, amount: Amount(AMOUNT) })
}
}
}

View file

@ -8,11 +8,12 @@ use dkg::{Participant, tests::clone_without};
use messages::{coordinator::*, SubstrateContext};
use serai_client::{
primitives::{
BlockHash, Amount, Balance, crypto::RuntimePublic, PublicKey, SeraiAddress, NetworkId,
},
in_instructions::primitives::{
InInstruction, InInstructionWithBalance, Batch, SignedBatch, batch_message,
batch_message, Batch, InInstruction, InInstructionWithBalance, SignedBatch,
},
primitives::{
crypto::RuntimePublic, Amount, BlockHash, ExternalBalance, ExternalNetworkId, PublicKey,
SeraiAddress, EXTERNAL_NETWORKS,
},
validator_sets::primitives::Session,
};
@ -189,7 +190,7 @@ pub(crate) async fn substrate_block(
#[test]
fn batch_test() {
for network in [NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero] {
for network in EXTERNAL_NETWORKS {
let (coordinators, test) = new_test(network);
test.run(|ops| async move {
@ -255,15 +256,14 @@ fn batch_test() {
instructions: if let Some(instruction) = &instruction {
vec![InInstructionWithBalance {
instruction: instruction.clone(),
balance: Balance {
balance: ExternalBalance {
coin: balance_sent.coin,
amount: Amount(
balance_sent.amount.0 -
(2 * match network {
NetworkId::Bitcoin => Bitcoin::COST_TO_AGGREGATE,
NetworkId::Ethereum => Ethereum::<MemDb>::COST_TO_AGGREGATE,
NetworkId::Monero => Monero::COST_TO_AGGREGATE,
NetworkId::Serai => panic!("minted for Serai?"),
ExternalNetworkId::Bitcoin => Bitcoin::COST_TO_AGGREGATE,
ExternalNetworkId::Ethereum => Ethereum::<MemDb>::COST_TO_AGGREGATE,
ExternalNetworkId::Monero => Monero::COST_TO_AGGREGATE,
}),
),
},
@ -322,7 +322,9 @@ fn batch_test() {
},
)
.await;
if instruction.is_some() || (instruction.is_none() && (network == NetworkId::Monero)) {
if instruction.is_some() ||
(instruction.is_none() && (network == ExternalNetworkId::Monero))
{
assert!(plans.is_empty());
} else {
// If no instruction was used, and the processor csn presume the origin, it'd have
@ -335,7 +337,7 @@ fn batch_test() {
// With the latter InInstruction not existing, we should've triggered a refund if the origin
// was detectable
// Check this is trying to sign a Plan
if network != NetworkId::Monero {
if network != ExternalNetworkId::Monero {
let mut refund_id = None;
for coordinator in &mut coordinators {
match coordinator.recv_message().await {

View file

@ -3,8 +3,8 @@ use std::{collections::HashMap, time::SystemTime};
use dkg::{Participant, ThresholdParams, tests::clone_without};
use serai_client::{
primitives::{NetworkId, BlockHash, PublicKey},
validator_sets::primitives::{Session, KeyPair},
primitives::{BlockHash, PublicKey, EXTERNAL_NETWORKS},
validator_sets::primitives::{KeyPair, Session},
};
use messages::{SubstrateContext, key_gen::KeyGenId, CoordinatorMessage, ProcessorMessage};
@ -144,7 +144,7 @@ pub(crate) async fn key_gen(coordinators: &mut [Coordinator]) -> KeyPair {
#[test]
fn key_gen_test() {
for network in [NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero] {
for network in EXTERNAL_NETWORKS {
let (coordinators, test) = new_test(network);
test.run(|ops| async move {

View file

@ -1,7 +1,5 @@
use ciphersuite::{Ciphersuite, Ristretto};
use serai_client::primitives::NetworkId;
use dockertest::DockerTest;
use crate::*;
@ -17,7 +15,9 @@ mod send;
pub(crate) const COORDINATORS: usize = 4;
pub(crate) const THRESHOLD: usize = ((COORDINATORS * 2) / 3) + 1;
fn new_test(network: NetworkId) -> (Vec<(Handles, <Ristretto as Ciphersuite>::F)>, DockerTest) {
fn new_test(
network: ExternalNetworkId,
) -> (Vec<(Handles, <Ristretto as Ciphersuite>::F)>, DockerTest) {
let mut coordinators = vec![];
let mut test = DockerTest::new().with_network(dockertest::Network::Isolated);
let mut eth_handle = None;
@ -25,7 +25,7 @@ fn new_test(network: NetworkId) -> (Vec<(Handles, <Ristretto as Ciphersuite>::F)
let (handles, coord_key, compositions) = processor_stack(network, eth_handle.clone());
// TODO: Remove this once https://github.com/foundry-rs/foundry/issues/7955
// This has all processors share an Ethereum node until we can sync controlled nodes
if network == NetworkId::Ethereum {
if network == ExternalNetworkId::Ethereum {
eth_handle = eth_handle.or_else(|| Some(handles.0.clone()));
}
coordinators.push((handles, coord_key));

View file

@ -8,9 +8,9 @@ use dkg::{Participant, tests::clone_without};
use messages::{sign::SignId, SubstrateContext};
use serai_client::{
primitives::{BlockHash, NetworkId, Amount, Balance, SeraiAddress},
coins::primitives::{OutInstruction, OutInstructionWithBalance},
in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch},
in_instructions::primitives::{Batch, InInstruction, InInstructionWithBalance},
primitives::{Amount, BlockHash, ExternalBalance, SeraiAddress, EXTERNAL_NETWORKS},
validator_sets::primitives::Session,
};
@ -147,7 +147,7 @@ pub(crate) async fn sign_tx(
#[test]
fn send_test() {
for network in [NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero] {
for network in EXTERNAL_NETWORKS {
let (coordinators, test) = new_test(network);
test.run(|ops| async move {
@ -202,10 +202,9 @@ fn send_test() {
let amount_minted = Amount(
balance_sent.amount.0 -
(2 * match network {
NetworkId::Bitcoin => Bitcoin::COST_TO_AGGREGATE,
NetworkId::Ethereum => Ethereum::<MemDb>::COST_TO_AGGREGATE,
NetworkId::Monero => Monero::COST_TO_AGGREGATE,
NetworkId::Serai => panic!("minted for Serai?"),
ExternalNetworkId::Bitcoin => Bitcoin::COST_TO_AGGREGATE,
ExternalNetworkId::Ethereum => Ethereum::<MemDb>::COST_TO_AGGREGATE,
ExternalNetworkId::Monero => Monero::COST_TO_AGGREGATE,
}),
);
@ -215,7 +214,7 @@ fn send_test() {
block: BlockHash(block_with_tx.unwrap()),
instructions: vec![InInstructionWithBalance {
instruction,
balance: Balance { coin: balance_sent.coin, amount: amount_minted },
balance: ExternalBalance { coin: balance_sent.coin, amount: amount_minted },
}],
};
@ -245,7 +244,7 @@ fn send_test() {
block: substrate_block_num,
burns: vec![OutInstructionWithBalance {
instruction: OutInstruction { address: wallet.address(), data: None },
balance: Balance { coin: balance_sent.coin, amount: amount_minted },
balance: ExternalBalance { coin: balance_sent.coin, amount: amount_minted },
}],
batches: vec![batch.batch.id],
},