use crate::error::{Error, Result};
pub const CHAIN_ID_LEN: usize = 32;
pub const HIVE_CHAIN_ID: ChainId = ChainId([
0xbe, 0xea, 0xb0, 0xde, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,
]);
pub const ZERO_CHAIN_ID: ChainId = ChainId([0u8; CHAIN_ID_LEN]);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChainId(pub [u8; CHAIN_ID_LEN]);
impl ChainId {
pub fn from_hex(s: &str) -> Result<Self> {
let s = s.trim();
if s.len() != CHAIN_ID_LEN * 2 {
return Err(Error::Chain(format!(
"chain id must be {} hex characters, got {}",
CHAIN_ID_LEN * 2,
s.len()
)));
}
let mut out = [0u8; CHAIN_ID_LEN];
crate::hex::decode_exact(s, &mut out)
.map_err(|_| Error::Chain("chain id is not valid hex".into()))?;
Ok(ChainId(out))
}
pub fn to_hex(&self) -> String {
self.0.iter().map(|b| format!("{b:02x}")).collect()
}
pub fn as_bytes(&self) -> &[u8; CHAIN_ID_LEN] {
&self.0
}
pub fn is_all_zero(&self) -> bool {
self.0 == [0u8; CHAIN_ID_LEN]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChainAsset {
pub nai: &'static str,
pub symbol: &'static str,
pub wire_symbol: &'static str,
pub precision: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[derive(Default)]
pub enum Chain {
#[default]
Hive,
HiveTestnet,
SteemLegacy,
}
#[derive(Debug, Clone)]
pub struct ChainProperties {
pub chain_id: ChainId,
pub prefix: &'static str,
pub assets: &'static [ChainAsset],
}
const HIVE_ASSETS: &[ChainAsset] = &[
ChainAsset {
nai: "@@000000013",
symbol: "HBD",
wire_symbol: "SBD",
precision: 3,
},
ChainAsset {
nai: "@@000000021",
symbol: "HIVE",
wire_symbol: "STEEM",
precision: 3,
},
ChainAsset {
nai: "@@000000037",
symbol: "VESTS",
wire_symbol: "VESTS",
precision: 6,
},
];
const TESTNET_ASSETS: &[ChainAsset] = &[
ChainAsset {
nai: "@@000000013",
symbol: "TBD",
wire_symbol: "TBD",
precision: 3,
},
ChainAsset {
nai: "@@000000021",
symbol: "TESTS",
wire_symbol: "TESTS",
precision: 3,
},
ChainAsset {
nai: "@@000000037",
symbol: "VESTS",
wire_symbol: "VESTS",
precision: 6,
},
];
const STEEM_ASSETS: &[ChainAsset] = &[
ChainAsset {
nai: "@@000000013",
symbol: "SBD",
wire_symbol: "SBD",
precision: 3,
},
ChainAsset {
nai: "@@000000021",
symbol: "STEEM",
wire_symbol: "STEEM",
precision: 3,
},
ChainAsset {
nai: "@@000000037",
symbol: "VESTS",
wire_symbol: "VESTS",
precision: 6,
},
];
impl Chain {
pub fn properties(&self) -> ChainProperties {
match self {
Chain::Hive => ChainProperties {
chain_id: HIVE_CHAIN_ID,
prefix: "STM",
assets: HIVE_ASSETS,
},
Chain::HiveTestnet => ChainProperties {
chain_id: ChainId([
0x18, 0xdc, 0xf0, 0xa2, 0x85, 0x36, 0x5f, 0xc5, 0x8b, 0x71, 0xf1, 0x8b, 0x3d,
0x3f, 0xec, 0x95, 0x4a, 0xa0, 0xc1, 0x41, 0xc4, 0x4e, 0x4e, 0x5c, 0xb4, 0xcf,
0x77, 0x7b, 0x9e, 0xab, 0x27, 0x4e,
]),
prefix: "TST",
assets: TESTNET_ASSETS,
},
Chain::SteemLegacy => ChainProperties {
chain_id: ZERO_CHAIN_ID,
prefix: "STM",
assets: STEEM_ASSETS,
},
}
}
pub fn chain_id(&self) -> ChainId {
self.properties().chain_id
}
pub fn prefix(&self) -> &'static str {
self.properties().prefix
}
pub fn asset(&self, symbol: &str) -> Result<ChainAsset> {
let props = self.properties();
props
.assets
.iter()
.find(|a| a.symbol == symbol || a.nai == symbol || a.wire_symbol == symbol)
.copied()
.ok_or_else(|| Error::Unknown {
kind: "asset",
name: symbol.to_string(),
})
}
pub fn from_name(name: &str) -> Result<Self> {
match name.to_ascii_uppercase().as_str() {
"HIVE" | "HIVE2" => Ok(Chain::Hive),
"TESTNET" | "TESTDEV" | "HIVE_TESTNET" => Ok(Chain::HiveTestnet),
"STEEM" | "STEEM_LEGACY" => Ok(Chain::SteemLegacy),
other => Err(Error::Unknown {
kind: "chain",
name: other.to_string(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hive_chain_id_is_the_post_hf24_constant() {
assert_eq!(
HIVE_CHAIN_ID.to_hex(),
"beeab0de00000000000000000000000000000000000000000000000000000000"
);
assert!(!HIVE_CHAIN_ID.is_all_zero());
}
#[test]
fn hive_name_resolves_to_the_live_chain_not_the_zero_id() {
let c = Chain::from_name("HIVE").unwrap();
assert_eq!(c.chain_id(), HIVE_CHAIN_ID);
assert!(!c.chain_id().is_all_zero());
}
#[test]
fn unknown_chain_is_an_error_not_a_fallback() {
assert!(Chain::from_name("NOPE").is_err());
}
#[test]
fn hex_roundtrip() {
let id = ChainId::from_hex(&HIVE_CHAIN_ID.to_hex()).unwrap();
assert_eq!(id, HIVE_CHAIN_ID);
assert!(ChainId::from_hex("abc").is_err());
assert!(ChainId::from_hex(&"z".repeat(64)).is_err());
}
#[test]
fn assets_resolve_by_symbol_nai_and_wire_symbol() {
let hive = Chain::Hive;
assert_eq!(hive.asset("HIVE").unwrap().precision, 3);
assert_eq!(hive.asset("@@000000037").unwrap().symbol, "VESTS");
assert_eq!(hive.asset("SBD").unwrap().symbol, "HBD");
assert!(hive.asset("DOGE").is_err());
}
}