From 9da0eb69c79b9b0ac4c93dd7d2f2cc0ad880c2bd Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 18 Apr 2023 02:01:53 -0400 Subject: [PATCH] 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. --- Cargo.lock | 1 + coordinator/Cargo.toml | 2 + coordinator/src/substrate.rs | 13 +---- coordinator/src/tributary.rs | 4 +- docs/protocol/Constants.md | 7 +-- processor/src/coins/bitcoin.rs | 6 +-- processor/src/coins/monero.rs | 6 +-- processor/src/tests/key_gen.rs | 4 +- processor/src/tests/substrate_signer.rs | 6 +-- substrate/client/tests/batch.rs | 6 +-- substrate/client/tests/burn.rs | 6 +-- substrate/client/tests/validator_sets.rs | 10 ++-- substrate/node/src/chain_spec.rs | 16 +++--- substrate/primitives/src/coins.rs | 63 +++++++++++++++--------- substrate/runtime/src/lib.rs | 17 ++++++- 15 files changed, 97 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b47eb7e..5b44e173 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1310,6 +1310,7 @@ dependencies = [ "flexible-transcript", "log", "modular-frost", + "parity-scale-codec", "processor-messages", "rand_core 0.6.4", "serai-client", diff --git a/coordinator/Cargo.toml b/coordinator/Cargo.toml index 4a30b5a5..f113a69d 100644 --- a/coordinator/Cargo.toml +++ b/coordinator/Cargo.toml @@ -24,6 +24,8 @@ transcript = { package = "flexible-transcript", path = "../crypto/transcript", f ciphersuite = { path = "../crypto/ciphersuite" } frost = { package = "modular-frost", path = "../crypto/frost" } +scale = { package = "parity-scale-codec", version = "3", features = ["derive"] } + serai-db = { path = "../common/db" } processor-messages = { package = "processor-messages", path = "../processor/messages" } diff --git a/coordinator/src/substrate.rs b/coordinator/src/substrate.rs index 7dc1878a..f852ac41 100644 --- a/coordinator/src/substrate.rs +++ b/coordinator/src/substrate.rs @@ -177,18 +177,7 @@ async fn handle_batch_and_burns( for burn in serai.get_burn_events(hash).await? { if let TokensEvent::Burn { address: _, balance, instruction } = burn { - // TODO: Move Network/Coin to an enum and provide this mapping - 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:?}"), - } - }; - + let network = balance.coin.network(); network_had_event(&mut burns, network); // network_had_event should register an entry in burns diff --git a/coordinator/src/tributary.rs b/coordinator/src/tributary.rs index 9618bb15..2fe03d24 100644 --- a/coordinator/src/tributary.rs +++ b/coordinator/src/tributary.rs @@ -5,6 +5,8 @@ use transcript::{Transcript, RecommendedTranscript}; use frost::Participant; +use scale::Encode; + use serai_client::validator_sets::primitives::ValidatorSet; #[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 genesis.append_message(b"serai_block", serai_block); 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_ref: &[u8] = genesis.as_ref(); genesis_ref[.. 32].try_into().unwrap() diff --git a/docs/protocol/Constants.md b/docs/protocol/Constants.md index f92011fd..dacfae1d 100644 --- a/docs/protocol/Constants.md +++ b/docs/protocol/Constants.md @@ -28,9 +28,10 @@ other properties). The network's key is used for all coins on that network. | Network | Curve | ID | |----------|-----------|----| -| Bitcoin | Secp256k1 | 0 | -| Ethereum | Secp256k1 | 1 | -| Monero | Ed25519 | 2 | +| Serai | Ristretto | 0 | +| Bitcoin | Secp256k1 | 1 | +| Ethereum | Secp256k1 | 2 | +| Monero | Ed25519 | 3 | ### Coins diff --git a/processor/src/coins/bitcoin.rs b/processor/src/coins/bitcoin.rs index e491c92f..d0cdab88 100644 --- a/processor/src/coins/bitcoin.rs +++ b/processor/src/coins/bitcoin.rs @@ -37,7 +37,7 @@ use bitcoin_serai::bitcoin::{ }; 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, }; @@ -97,7 +97,7 @@ impl OutputTrait for Output { } 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] { @@ -293,7 +293,7 @@ impl Coin for Bitcoin { type Address = Address; - const NETWORK: NetworkId = BITCOIN_NET_ID; + const NETWORK: NetworkId = NetworkId::Bitcoin; const ID: &'static str = "Bitcoin"; const CONFIRMATIONS: usize = 6; diff --git a/processor/src/coins/monero.rs b/processor/src/coins/monero.rs index 4d4b8531..daaa3d23 100644 --- a/processor/src/coins/monero.rs +++ b/processor/src/coins/monero.rs @@ -26,7 +26,7 @@ use monero_serai::{ use tokio::time::sleep; 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, }; @@ -66,7 +66,7 @@ impl OutputTrait for Output { } 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] { @@ -221,7 +221,7 @@ impl Coin for Monero { type Address = Address; - const NETWORK: NetworkId = MONERO_NET_ID; + const NETWORK: NetworkId = NetworkId::Monero; const ID: &'static str = "Monero"; const CONFIRMATIONS: usize = 10; diff --git a/processor/src/tests/key_gen.rs b/processor/src/tests/key_gen.rs index cd0dcea0..35bc8513 100644 --- a/processor/src/tests/key_gen.rs +++ b/processor/src/tests/key_gen.rs @@ -11,7 +11,7 @@ use serai_db::{DbTxn, Db, MemDb}; use sp_application_crypto::sr25519; use serai_client::{ - primitives::{MONERO_NET_ID, BlockHash}, + primitives::{BlockHash, NetworkId}, validator_sets::primitives::{Session, ValidatorSet}, }; @@ -22,7 +22,7 @@ use crate::{ }; 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() { let mut entropies = HashMap::new(); diff --git a/processor/src/tests/substrate_signer.rs b/processor/src/tests/substrate_signer.rs index 01cd3787..503acb76 100644 --- a/processor/src/tests/substrate_signer.rs +++ b/processor/src/tests/substrate_signer.rs @@ -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 }; let batch = Batch { - network: MONERO_NET_ID, + network: NetworkId::Monero, id: 5, block, instructions: vec![ InInstructionWithBalance { instruction: InInstruction::Transfer(SeraiAddress([0xbb; 32])), - balance: Balance { coin: BITCOIN, amount: Amount(1000) }, + balance: Balance { coin: Coin::Bitcoin, amount: Amount(1000) }, }, InInstructionWithBalance { instruction: InInstruction::Call(ApplicationCall { application: Application::DEX, data: Data::new(vec![0xcc; 128]).unwrap(), }), - balance: Balance { coin: MONERO, amount: Amount(9999999999999999) }, + balance: Balance { coin: Coin::Monero, amount: Amount(9999999999999999) }, }, ], }; diff --git a/substrate/client/tests/batch.rs b/substrate/client/tests/batch.rs index 31f669dc..84ae0d6b 100644 --- a/substrate/client/tests/batch.rs +++ b/substrate/client/tests/batch.rs @@ -1,7 +1,7 @@ use rand_core::{RngCore, OsRng}; use serai_client::{ - primitives::{BITCOIN_NET_ID, BITCOIN, BlockHash, SeraiAddress, Amount, Balance}, + primitives::{Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress}, in_instructions::{ primitives::{InInstruction, InInstructionWithBalance, Batch}, InInstructionsEvent, @@ -15,7 +15,7 @@ use common::{serai, in_instructions::provide_batch}; serai_test!( async fn publish_batch() { - let network = BITCOIN_NET_ID; + let network = NetworkId::Bitcoin; let id = 0; let mut block_hash = BlockHash([0; 32]); @@ -24,7 +24,7 @@ serai_test!( let mut address = SeraiAddress::new([0; 32]); OsRng.fill_bytes(&mut address.0); - let coin = BITCOIN; + let coin = Coin::Bitcoin; let amount = Amount(OsRng.next_u64().saturating_add(1)); let balance = Balance { coin, amount }; diff --git a/substrate/client/tests/burn.rs b/substrate/client/tests/burn.rs index 7d626756..ddaf260c 100644 --- a/substrate/client/tests/burn.rs +++ b/substrate/client/tests/burn.rs @@ -5,7 +5,7 @@ use sp_core::Pair; use serai_client::{ subxt::config::extrinsic_params::BaseExtrinsicParamsBuilder, primitives::{ - BITCOIN_NET_ID, BITCOIN, BlockHash, SeraiAddress, Amount, Balance, Data, ExternalAddress, + Amount, NetworkId, Coin, Balance, BlockHash, SeraiAddress, Data, ExternalAddress, insecure_pair_from_name, }, in_instructions::{ @@ -21,7 +21,7 @@ use common::{serai, tx::publish_tx, in_instructions::provide_batch}; serai_test!( async fn burn() { - let network = BITCOIN_NET_ID; + let network = NetworkId::Bitcoin; let id = 0; let mut block_hash = BlockHash([0; 32]); @@ -31,7 +31,7 @@ serai_test!( let public = pair.public(); let address = SeraiAddress::from(public); - let coin = BITCOIN; + let coin = Coin::Bitcoin; let amount = Amount(OsRng.next_u64().saturating_add(1)); let balance = Balance { coin, amount }; diff --git a/substrate/client/tests/validator_sets.rs b/substrate/client/tests/validator_sets.rs index 48ad0459..17385a83 100644 --- a/substrate/client/tests/validator_sets.rs +++ b/substrate/client/tests/validator_sets.rs @@ -3,9 +3,7 @@ use rand_core::{RngCore, OsRng}; use sp_core::{sr25519::Public, Pair}; use serai_client::{ - primitives::{ - BITCOIN_NET_ID, ETHEREUM_NET_ID, MONERO_NET_ID, BITCOIN_NET, insecure_pair_from_name, - }, + primitives::{NETWORKS, NetworkId, insecure_pair_from_name}, validator_sets::{ primitives::{Session, ValidatorSet}, ValidatorSetsEvent, @@ -18,7 +16,7 @@ use common::{serai, validator_sets::vote_in_keys}; serai_test!( async fn vote_keys() { - let network = BITCOIN_NET_ID; + let network = NetworkId::Bitcoin; let set = ValidatorSet { session: Session(0), network }; 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()) .await .unwrap(), - [BITCOIN_NET_ID, ETHEREUM_NET_ID, MONERO_NET_ID] + [NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero] .iter() .copied() .map(|network| ValidatorSetsEvent::NewSet { @@ -50,7 +48,7 @@ serai_test!( ); 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(); assert_eq!(participants_ref, [(public, set_data.bond)].as_ref()); diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index b2c75917..715f42ea 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -37,16 +37,16 @@ fn testnet_genesis( transaction_payment: Default::default(), assets: AssetsConfig { - assets: [BITCOIN, ETHER, DAI, MONERO] + assets: [Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero] .iter() .map(|coin| (*coin, TOKENS_ADDRESS.into(), true, 1)) .collect(), 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) - (ETHER, b"Ether".to_vec(), b"ETH".to_vec(), 8), - (DAI, b"Dai Stablecoin".to_vec(), b"DAI".to_vec(), 8), - (MONERO, b"Monero".to_vec(), b"XMR".to_vec(), 12), + (Coin::Ether, b"Ether".to_vec(), b"ETH".to_vec(), 8), + (Coin::Dai, b"Dai Stablecoin".to_vec(), b"DAI".to_vec(), 8), + (Coin::Monero, b"Monero".to_vec(), b"XMR".to_vec(), 12), ], accounts: vec![], }, @@ -54,9 +54,9 @@ fn testnet_genesis( validator_sets: ValidatorSetsConfig { bond: Amount(1_000_000 * 10_u64.pow(8)), networks: vec![ - (BITCOIN_NET_ID, BITCOIN_NET.clone()), - (ETHEREUM_NET_ID, ETHEREUM_NET.clone()), - (MONERO_NET_ID, MONERO_NET.clone()), + (NetworkId::Bitcoin, NETWORKS[&NetworkId::Bitcoin].clone()), + (NetworkId::Ethereum, NETWORKS[&NetworkId::Ethereum].clone()), + (NetworkId::Monero, NETWORKS[&NetworkId::Monero].clone()), ], participants: validators.iter().map(|name| account_from_name(name)).collect(), }, diff --git a/substrate/primitives/src/coins.rs b/substrate/primitives/src/coins.rs index 7f03fc09..e4c695d3 100644 --- a/substrate/primitives/src/coins.rs +++ b/substrate/primitives/src/coins.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "std")] +use std::collections::HashMap; + #[cfg(feature = "std")] use zeroize::Zeroize; @@ -12,32 +15,35 @@ use serde::{Serialize, Deserialize}; /// The type used to identify networks. #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(Zeroize, Serialize, Deserialize))] -pub struct NetworkId(pub u16); -impl From for NetworkId { - fn from(network: u16) -> NetworkId { - NetworkId(network) - } +pub enum NetworkId { + Serai, + Bitcoin, + 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. #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(Zeroize, Serialize, Deserialize))] -pub struct Coin(pub u32); -impl From for Coin { - fn from(coin: u32) -> Coin { - Coin(coin) - } +pub enum Coin { + Serai, + Bitcoin, + Ether, + Dai, + Monero, } -pub const SERAI: Coin = Coin(0); -pub const BITCOIN: Coin = Coin(1); -pub const ETHER: Coin = Coin(2); -pub const DAI: Coin = Coin(3); -pub const MONERO: Coin = Coin(4); +impl Coin { + pub fn network(&self) -> NetworkId { + match self { + Coin::Serai => NetworkId::Serai, + Coin::Bitcoin => NetworkId::Bitcoin, + Coin::Ether => NetworkId::Ethereum, + Coin::Dai => NetworkId::Ethereum, + Coin::Monero => NetworkId::Monero, + } + } +} // Max of 8 coins per network // 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 { #[cfg(feature = "std")] pub fn new(coins: Vec) -> Result { + 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 { coins: coins.try_into().map_err(|_| "coins length exceeds {MAX_COINS_PER_NETWORK}")?, }) @@ -80,7 +97,9 @@ impl Network { #[cfg(feature = "std")] lazy_static::lazy_static! { - pub static ref BITCOIN_NET: Network = Network::new(vec![BITCOIN]).unwrap(); - pub static ref ETHEREUM_NET: Network = Network::new(vec![ETHER, DAI]).unwrap(); - pub static ref MONERO_NET: Network = Network::new(vec![MONERO]).unwrap(); + pub static ref NETWORKS: HashMap = HashMap::from([ + (NetworkId::Bitcoin, Network::new(vec![Coin::Bitcoin]).unwrap()), + (NetworkId::Ethereum, Network::new(vec![Coin::Ether, Coin::Dai]).unwrap()), + (NetworkId::Monero, Network::new(vec![Coin::Monero]).unwrap()), + ]); } diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index f24aa291..5511a36d 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -247,6 +247,21 @@ impl transaction_payment::Config for Runtime { type FeeMultiplierUpdate = (); } +#[cfg(feature = "runtime-benchmarks")] +pub struct SeraiAssetBenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] +impl assets::BenchmarkHelper 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 { type RuntimeEvent = RuntimeEvent; type Balance = SubstrateAmount; @@ -275,7 +290,7 @@ impl assets::Config for Runtime { type WeightInfo = assets::weights::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = (); + type BenchmarkHelper = SeraiAssetBenchmarkHelper; } impl tokens::Config for Runtime {