Use an enum for Coin/NetworkId

It originally wasn't an enum so software which had yet to update before an
integration wouldn't error (as now enums are strictly typed). The strict typing
is preferable though.
This commit is contained in:
Luke Parker
2023-04-18 02:01:53 -04:00
parent 6f3b5f4535
commit 9da0eb69c7
15 changed files with 97 additions and 70 deletions

1
Cargo.lock generated
View File

@@ -1310,6 +1310,7 @@ dependencies = [
"flexible-transcript", "flexible-transcript",
"log", "log",
"modular-frost", "modular-frost",
"parity-scale-codec",
"processor-messages", "processor-messages",
"rand_core 0.6.4", "rand_core 0.6.4",
"serai-client", "serai-client",

View File

@@ -24,6 +24,8 @@ transcript = { package = "flexible-transcript", path = "../crypto/transcript", f
ciphersuite = { path = "../crypto/ciphersuite" } ciphersuite = { path = "../crypto/ciphersuite" }
frost = { package = "modular-frost", path = "../crypto/frost" } frost = { package = "modular-frost", path = "../crypto/frost" }
scale = { package = "parity-scale-codec", version = "3", features = ["derive"] }
serai-db = { path = "../common/db" } serai-db = { path = "../common/db" }
processor-messages = { package = "processor-messages", path = "../processor/messages" } processor-messages = { package = "processor-messages", path = "../processor/messages" }

View File

@@ -177,18 +177,7 @@ async fn handle_batch_and_burns<D: Db, Pro: Processor>(
for burn in serai.get_burn_events(hash).await? { for burn in serai.get_burn_events(hash).await? {
if let TokensEvent::Burn { address: _, balance, instruction } = burn { if let TokensEvent::Burn { address: _, balance, instruction } = burn {
// TODO: Move Network/Coin to an enum and provide this mapping let network = balance.coin.network();
let network = {
use serai_client::primitives::*;
match balance.coin {
BITCOIN => BITCOIN_NET_ID,
ETHER => ETHEREUM_NET_ID,
DAI => ETHEREUM_NET_ID,
MONERO => MONERO_NET_ID,
invalid => panic!("burn from unrecognized coin: {invalid:?}"),
}
};
network_had_event(&mut burns, network); network_had_event(&mut burns, network);
// network_had_event should register an entry in burns // network_had_event should register an entry in burns

View File

@@ -5,6 +5,8 @@ use transcript::{Transcript, RecommendedTranscript};
use frost::Participant; use frost::Participant;
use scale::Encode;
use serai_client::validator_sets::primitives::ValidatorSet; use serai_client::validator_sets::primitives::ValidatorSet;
#[rustfmt::skip] #[rustfmt::skip]
@@ -18,7 +20,7 @@ pub fn genesis(serai_block: [u8; 32], set: ValidatorSet) -> [u8; 32] {
// This locks it to a specific Serai chain // This locks it to a specific Serai chain
genesis.append_message(b"serai_block", serai_block); genesis.append_message(b"serai_block", serai_block);
genesis.append_message(b"session", set.session.0.to_le_bytes()); genesis.append_message(b"session", set.session.0.to_le_bytes());
genesis.append_message(b"network", set.network.0.to_le_bytes()); genesis.append_message(b"network", set.network.encode());
let genesis = genesis.challenge(b"genesis"); let genesis = genesis.challenge(b"genesis");
let genesis_ref: &[u8] = genesis.as_ref(); let genesis_ref: &[u8] = genesis.as_ref();
genesis_ref[.. 32].try_into().unwrap() genesis_ref[.. 32].try_into().unwrap()

View File

@@ -28,9 +28,10 @@ other properties). The network's key is used for all coins on that network.
| Network | Curve | ID | | Network | Curve | ID |
|----------|-----------|----| |----------|-----------|----|
| Bitcoin | Secp256k1 | 0 | | Serai | Ristretto | 0 |
| Ethereum | Secp256k1 | 1 | | Bitcoin | Secp256k1 | 1 |
| Monero | Ed25519 | 2 | | Ethereum | Secp256k1 | 2 |
| Monero | Ed25519 | 3 |
### Coins ### Coins

View File

@@ -37,7 +37,7 @@ use bitcoin_serai::bitcoin::{
}; };
use serai_client::{ use serai_client::{
primitives::{MAX_DATA_LEN, BITCOIN, BITCOIN_NET_ID, NetworkId, Amount, Balance}, primitives::{MAX_DATA_LEN, Coin as SeraiCoin, NetworkId, Amount, Balance},
coins::bitcoin::Address, coins::bitcoin::Address,
}; };
@@ -97,7 +97,7 @@ impl OutputTrait for Output {
} }
fn balance(&self) -> Balance { fn balance(&self) -> Balance {
Balance { coin: BITCOIN, amount: Amount(self.output.value()) } Balance { coin: SeraiCoin::Bitcoin, amount: Amount(self.output.value()) }
} }
fn data(&self) -> &[u8] { fn data(&self) -> &[u8] {
@@ -293,7 +293,7 @@ impl Coin for Bitcoin {
type Address = Address; type Address = Address;
const NETWORK: NetworkId = BITCOIN_NET_ID; const NETWORK: NetworkId = NetworkId::Bitcoin;
const ID: &'static str = "Bitcoin"; const ID: &'static str = "Bitcoin";
const CONFIRMATIONS: usize = 6; const CONFIRMATIONS: usize = 6;

View File

@@ -26,7 +26,7 @@ use monero_serai::{
use tokio::time::sleep; use tokio::time::sleep;
pub use serai_client::{ pub use serai_client::{
primitives::{MAX_DATA_LEN, MONERO, MONERO_NET_ID, NetworkId, Amount, Balance}, primitives::{MAX_DATA_LEN, Coin as SeraiCoin, NetworkId, Amount, Balance},
coins::monero::Address, coins::monero::Address,
}; };
@@ -66,7 +66,7 @@ impl OutputTrait for Output {
} }
fn balance(&self) -> Balance { fn balance(&self) -> Balance {
Balance { coin: MONERO, amount: Amount(self.0.commitment().amount) } Balance { coin: SeraiCoin::Monero, amount: Amount(self.0.commitment().amount) }
} }
fn data(&self) -> &[u8] { fn data(&self) -> &[u8] {
@@ -221,7 +221,7 @@ impl Coin for Monero {
type Address = Address; type Address = Address;
const NETWORK: NetworkId = MONERO_NET_ID; const NETWORK: NetworkId = NetworkId::Monero;
const ID: &'static str = "Monero"; const ID: &'static str = "Monero";
const CONFIRMATIONS: usize = 10; const CONFIRMATIONS: usize = 10;

View File

@@ -11,7 +11,7 @@ use serai_db::{DbTxn, Db, MemDb};
use sp_application_crypto::sr25519; use sp_application_crypto::sr25519;
use serai_client::{ use serai_client::{
primitives::{MONERO_NET_ID, BlockHash}, primitives::{BlockHash, NetworkId},
validator_sets::primitives::{Session, ValidatorSet}, validator_sets::primitives::{Session, ValidatorSet},
}; };
@@ -22,7 +22,7 @@ use crate::{
}; };
const ID: KeyGenId = const ID: KeyGenId =
KeyGenId { set: ValidatorSet { session: Session(1), network: MONERO_NET_ID }, attempt: 3 }; KeyGenId { set: ValidatorSet { session: Session(1), network: NetworkId::Monero }, attempt: 3 };
pub async fn test_key_gen<C: Coin>() { pub async fn test_key_gen<C: Coin>() {
let mut entropies = HashMap::new(); let mut entropies = HashMap::new();

View File

@@ -30,20 +30,20 @@ async fn test_substrate_signer() {
SignId { key: keys[&participant_one].group_key().to_bytes().to_vec(), id: block.0, attempt: 0 }; SignId { key: keys[&participant_one].group_key().to_bytes().to_vec(), id: block.0, attempt: 0 };
let batch = Batch { let batch = Batch {
network: MONERO_NET_ID, network: NetworkId::Monero,
id: 5, id: 5,
block, block,
instructions: vec![ instructions: vec![
InInstructionWithBalance { InInstructionWithBalance {
instruction: InInstruction::Transfer(SeraiAddress([0xbb; 32])), instruction: InInstruction::Transfer(SeraiAddress([0xbb; 32])),
balance: Balance { coin: BITCOIN, amount: Amount(1000) }, balance: Balance { coin: Coin::Bitcoin, amount: Amount(1000) },
}, },
InInstructionWithBalance { InInstructionWithBalance {
instruction: InInstruction::Call(ApplicationCall { instruction: InInstruction::Call(ApplicationCall {
application: Application::DEX, application: Application::DEX,
data: Data::new(vec![0xcc; 128]).unwrap(), data: Data::new(vec![0xcc; 128]).unwrap(),
}), }),
balance: Balance { coin: MONERO, amount: Amount(9999999999999999) }, balance: Balance { coin: Coin::Monero, amount: Amount(9999999999999999) },
}, },
], ],
}; };

View File

@@ -1,7 +1,7 @@
use rand_core::{RngCore, OsRng}; use rand_core::{RngCore, OsRng};
use serai_client::{ use serai_client::{
primitives::{BITCOIN_NET_ID, BITCOIN, BlockHash, SeraiAddress, Amount, Balance}, primitives::{Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress},
in_instructions::{ in_instructions::{
primitives::{InInstruction, InInstructionWithBalance, Batch}, primitives::{InInstruction, InInstructionWithBalance, Batch},
InInstructionsEvent, InInstructionsEvent,
@@ -15,7 +15,7 @@ use common::{serai, in_instructions::provide_batch};
serai_test!( serai_test!(
async fn publish_batch() { async fn publish_batch() {
let network = BITCOIN_NET_ID; let network = NetworkId::Bitcoin;
let id = 0; let id = 0;
let mut block_hash = BlockHash([0; 32]); let mut block_hash = BlockHash([0; 32]);
@@ -24,7 +24,7 @@ serai_test!(
let mut address = SeraiAddress::new([0; 32]); let mut address = SeraiAddress::new([0; 32]);
OsRng.fill_bytes(&mut address.0); OsRng.fill_bytes(&mut address.0);
let coin = BITCOIN; let coin = Coin::Bitcoin;
let amount = Amount(OsRng.next_u64().saturating_add(1)); let amount = Amount(OsRng.next_u64().saturating_add(1));
let balance = Balance { coin, amount }; let balance = Balance { coin, amount };

View File

@@ -5,7 +5,7 @@ use sp_core::Pair;
use serai_client::{ use serai_client::{
subxt::config::extrinsic_params::BaseExtrinsicParamsBuilder, subxt::config::extrinsic_params::BaseExtrinsicParamsBuilder,
primitives::{ primitives::{
BITCOIN_NET_ID, BITCOIN, BlockHash, SeraiAddress, Amount, Balance, Data, ExternalAddress, Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, Data, ExternalAddress,
insecure_pair_from_name, insecure_pair_from_name,
}, },
in_instructions::{ in_instructions::{
@@ -21,7 +21,7 @@ use common::{serai, tx::publish_tx, in_instructions::provide_batch};
serai_test!( serai_test!(
async fn burn() { async fn burn() {
let network = BITCOIN_NET_ID; let network = NetworkId::Bitcoin;
let id = 0; let id = 0;
let mut block_hash = BlockHash([0; 32]); let mut block_hash = BlockHash([0; 32]);
@@ -31,7 +31,7 @@ serai_test!(
let public = pair.public(); let public = pair.public();
let address = SeraiAddress::from(public); let address = SeraiAddress::from(public);
let coin = BITCOIN; let coin = Coin::Bitcoin;
let amount = Amount(OsRng.next_u64().saturating_add(1)); let amount = Amount(OsRng.next_u64().saturating_add(1));
let balance = Balance { coin, amount }; let balance = Balance { coin, amount };

View File

@@ -3,9 +3,7 @@ use rand_core::{RngCore, OsRng};
use sp_core::{sr25519::Public, Pair}; use sp_core::{sr25519::Public, Pair};
use serai_client::{ use serai_client::{
primitives::{ primitives::{NETWORKS, NetworkId, insecure_pair_from_name},
BITCOIN_NET_ID, ETHEREUM_NET_ID, MONERO_NET_ID, BITCOIN_NET, insecure_pair_from_name,
},
validator_sets::{ validator_sets::{
primitives::{Session, ValidatorSet}, primitives::{Session, ValidatorSet},
ValidatorSetsEvent, ValidatorSetsEvent,
@@ -18,7 +16,7 @@ use common::{serai, validator_sets::vote_in_keys};
serai_test!( serai_test!(
async fn vote_keys() { async fn vote_keys() {
let network = BITCOIN_NET_ID; let network = NetworkId::Bitcoin;
let set = ValidatorSet { session: Session(0), network }; let set = ValidatorSet { session: Session(0), network };
let public = insecure_pair_from_name("Alice").public(); let public = insecure_pair_from_name("Alice").public();
@@ -40,7 +38,7 @@ serai_test!(
.get_new_set_events(serai.get_block_by_number(0).await.unwrap().unwrap().hash()) .get_new_set_events(serai.get_block_by_number(0).await.unwrap().unwrap().hash())
.await .await
.unwrap(), .unwrap(),
[BITCOIN_NET_ID, ETHEREUM_NET_ID, MONERO_NET_ID] [NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero]
.iter() .iter()
.copied() .copied()
.map(|network| ValidatorSetsEvent::NewSet { .map(|network| ValidatorSetsEvent::NewSet {
@@ -50,7 +48,7 @@ serai_test!(
); );
let set_data = serai.get_validator_set(set).await.unwrap().unwrap(); let set_data = serai.get_validator_set(set).await.unwrap().unwrap();
assert_eq!(set_data.network, *BITCOIN_NET); assert_eq!(set_data.network, NETWORKS[&NetworkId::Bitcoin]);
let participants_ref: &[_] = set_data.participants.as_ref(); let participants_ref: &[_] = set_data.participants.as_ref();
assert_eq!(participants_ref, [(public, set_data.bond)].as_ref()); assert_eq!(participants_ref, [(public, set_data.bond)].as_ref());

View File

@@ -37,16 +37,16 @@ fn testnet_genesis(
transaction_payment: Default::default(), transaction_payment: Default::default(),
assets: AssetsConfig { assets: AssetsConfig {
assets: [BITCOIN, ETHER, DAI, MONERO] assets: [Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero]
.iter() .iter()
.map(|coin| (*coin, TOKENS_ADDRESS.into(), true, 1)) .map(|coin| (*coin, TOKENS_ADDRESS.into(), true, 1))
.collect(), .collect(),
metadata: vec![ metadata: vec![
(BITCOIN, b"Bitcoin".to_vec(), b"BTC".to_vec(), 8), (Coin::Bitcoin, b"Bitcoin".to_vec(), b"BTC".to_vec(), 8),
// Reduce to 8 decimals to feasibly fit within u64 (instead of its native u256) // Reduce to 8 decimals to feasibly fit within u64 (instead of its native u256)
(ETHER, b"Ether".to_vec(), b"ETH".to_vec(), 8), (Coin::Ether, b"Ether".to_vec(), b"ETH".to_vec(), 8),
(DAI, b"Dai Stablecoin".to_vec(), b"DAI".to_vec(), 8), (Coin::Dai, b"Dai Stablecoin".to_vec(), b"DAI".to_vec(), 8),
(MONERO, b"Monero".to_vec(), b"XMR".to_vec(), 12), (Coin::Monero, b"Monero".to_vec(), b"XMR".to_vec(), 12),
], ],
accounts: vec![], accounts: vec![],
}, },
@@ -54,9 +54,9 @@ fn testnet_genesis(
validator_sets: ValidatorSetsConfig { validator_sets: ValidatorSetsConfig {
bond: Amount(1_000_000 * 10_u64.pow(8)), bond: Amount(1_000_000 * 10_u64.pow(8)),
networks: vec![ networks: vec![
(BITCOIN_NET_ID, BITCOIN_NET.clone()), (NetworkId::Bitcoin, NETWORKS[&NetworkId::Bitcoin].clone()),
(ETHEREUM_NET_ID, ETHEREUM_NET.clone()), (NetworkId::Ethereum, NETWORKS[&NetworkId::Ethereum].clone()),
(MONERO_NET_ID, MONERO_NET.clone()), (NetworkId::Monero, NETWORKS[&NetworkId::Monero].clone()),
], ],
participants: validators.iter().map(|name| account_from_name(name)).collect(), participants: validators.iter().map(|name| account_from_name(name)).collect(),
}, },

View File

@@ -1,3 +1,6 @@
#[cfg(feature = "std")]
use std::collections::HashMap;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use zeroize::Zeroize; use zeroize::Zeroize;
@@ -12,32 +15,35 @@ use serde::{Serialize, Deserialize};
/// The type used to identify networks. /// The type used to identify networks.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
#[cfg_attr(feature = "std", derive(Zeroize, Serialize, Deserialize))] #[cfg_attr(feature = "std", derive(Zeroize, Serialize, Deserialize))]
pub struct NetworkId(pub u16); pub enum NetworkId {
impl From<u16> for NetworkId { Serai,
fn from(network: u16) -> NetworkId { Bitcoin,
NetworkId(network) Ethereum,
Monero,
} }
}
pub const BITCOIN_NET_ID: NetworkId = NetworkId(0);
pub const ETHEREUM_NET_ID: NetworkId = NetworkId(1);
pub const MONERO_NET_ID: NetworkId = NetworkId(2);
/// The type used to identify coins. /// The type used to identify coins.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
#[cfg_attr(feature = "std", derive(Zeroize, Serialize, Deserialize))] #[cfg_attr(feature = "std", derive(Zeroize, Serialize, Deserialize))]
pub struct Coin(pub u32); pub enum Coin {
impl From<u32> for Coin { Serai,
fn from(coin: u32) -> Coin { Bitcoin,
Coin(coin) Ether,
} Dai,
Monero,
} }
pub const SERAI: Coin = Coin(0); impl Coin {
pub const BITCOIN: Coin = Coin(1); pub fn network(&self) -> NetworkId {
pub const ETHER: Coin = Coin(2); match self {
pub const DAI: Coin = Coin(3); Coin::Serai => NetworkId::Serai,
pub const MONERO: Coin = Coin(4); Coin::Bitcoin => NetworkId::Bitcoin,
Coin::Ether => NetworkId::Ethereum,
Coin::Dai => NetworkId::Ethereum,
Coin::Monero => NetworkId::Monero,
}
}
}
// Max of 8 coins per network // Max of 8 coins per network
// Since Serai isn't interested in listing tokens, as on-chain DEXs will almost certainly have // Since Serai isn't interested in listing tokens, as on-chain DEXs will almost certainly have
@@ -68,6 +74,17 @@ impl Zeroize for Network {
impl Network { impl Network {
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn new(coins: Vec<Coin>) -> Result<Network, &'static str> { pub fn new(coins: Vec<Coin>) -> Result<Network, &'static str> {
if coins.is_empty() {
Err("no coins provided")?;
}
let network = coins[0].network();
for coin in coins.iter().skip(1) {
if coin.network() != network {
Err("coins have different networks")?;
}
}
Ok(Network { Ok(Network {
coins: coins.try_into().map_err(|_| "coins length exceeds {MAX_COINS_PER_NETWORK}")?, coins: coins.try_into().map_err(|_| "coins length exceeds {MAX_COINS_PER_NETWORK}")?,
}) })
@@ -80,7 +97,9 @@ impl Network {
#[cfg(feature = "std")] #[cfg(feature = "std")]
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref BITCOIN_NET: Network = Network::new(vec![BITCOIN]).unwrap(); pub static ref NETWORKS: HashMap<NetworkId, Network> = HashMap::from([
pub static ref ETHEREUM_NET: Network = Network::new(vec![ETHER, DAI]).unwrap(); (NetworkId::Bitcoin, Network::new(vec![Coin::Bitcoin]).unwrap()),
pub static ref MONERO_NET: Network = Network::new(vec![MONERO]).unwrap(); (NetworkId::Ethereum, Network::new(vec![Coin::Ether, Coin::Dai]).unwrap()),
(NetworkId::Monero, Network::new(vec![Coin::Monero]).unwrap()),
]);
} }

View File

@@ -247,6 +247,21 @@ impl transaction_payment::Config for Runtime {
type FeeMultiplierUpdate = (); type FeeMultiplierUpdate = ();
} }
#[cfg(feature = "runtime-benchmarks")]
pub struct SeraiAssetBenchmarkHelper;
#[cfg(feature = "runtime-benchmarks")]
impl assets::BenchmarkHelper<Coin> for SeraiAssetBenchmarkHelper {
fn create_asset_id_parameter(id: u32) -> Coin {
match (id % 4) + 1 {
1 => Coin::Bitcoin,
2 => Coin::Ether,
3 => Coin::Dai,
4 => Coin::Monero,
_ => panic!("(id % 4) + 1 wasn't in [1, 4]"),
}
}
}
impl assets::Config for Runtime { impl assets::Config for Runtime {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type Balance = SubstrateAmount; type Balance = SubstrateAmount;
@@ -275,7 +290,7 @@ impl assets::Config for Runtime {
type WeightInfo = assets::weights::SubstrateWeight<Runtime>; type WeightInfo = assets::weights::SubstrateWeight<Runtime>;
#[cfg(feature = "runtime-benchmarks")] #[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = (); type BenchmarkHelper = SeraiAssetBenchmarkHelper;
} }
impl tokens::Config for Runtime { impl tokens::Config for Runtime {