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
No known key found for this signature in database
15 changed files with 97 additions and 70 deletions

1
Cargo.lock generated
View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<C: Coin>() {
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 };
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) },
},
],
};

View file

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

View file

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

View file

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

View file

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

View file

@ -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<u16> 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<u32> 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<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 {
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<NetworkId, Network> = 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()),
]);
}

View file

@ -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<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 {
type RuntimeEvent = RuntimeEvent;
type Balance = SubstrateAmount;
@ -275,7 +290,7 @@ impl assets::Config for Runtime {
type WeightInfo = assets::weights::SubstrateWeight<Runtime>;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = ();
type BenchmarkHelper = SeraiAssetBenchmarkHelper;
}
impl tokens::Config for Runtime {