use candid::{CandidType, Deserialize};
use ex3_node_error::OtherError;
use ex3_serde::bincode;
use ic_stable_structures::{storable::Bound, Storable};
use num_bigint::BigUint;
use serde::Serialize;
use crate::chain::{CandidChain, Chain};
use crate::{
impl_from_uint_for, AssetId, BlockHeight, CandidAssetId, CandidBlockHeight,
CandidWalletRegisterId, WalletRegisterId,
};
#[derive(
CandidType, Debug, Clone, Hash, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord,
)]
pub enum TokenType {
Native = 0,
NativeGas = 1,
ERC20 = 3,
ERC721 = 4,
DFT = 5,
ICRC1 = 6,
BRC20 = 7,
SPLToken = 8,
TRC20 = 9,
Jetton = 10,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize, Ord, PartialOrd)]
pub struct CryptoAsset {
pub chain: Chain,
pub r#type: TokenType,
pub address: String,
}
#[derive(CandidType, Debug, Clone, Hash, PartialEq, Eq, Deserialize, Ord, PartialOrd)]
pub struct CandidCryptoAsset {
pub chain: CandidChain,
pub r#type: TokenType,
pub address: String,
}
impl From<CandidCryptoAsset> for CryptoAsset {
fn from(candid_crypto_asset: CandidCryptoAsset) -> Self {
CryptoAsset {
chain: candid_crypto_asset.chain.into(),
r#type: candid_crypto_asset.r#type,
address: candid_crypto_asset.address,
}
}
}
impl From<CryptoAsset> for CandidCryptoAsset {
fn from(crypto_asset: CryptoAsset) -> Self {
CandidCryptoAsset {
chain: crypto_asset.chain.into(),
r#type: crypto_asset.r#type,
address: crypto_asset.address,
}
}
}
impl TryFrom<&[u8]> for CryptoAsset {
type Error = OtherError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let asset = bincode::deserialize(value).map_err(|e| {
OtherError::new(format!(
"Failed to deserialize CryptoAsset from bytes: {:?}",
e
))
})?;
Ok(asset)
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
pub struct RegisteredCryptoAsset {
pub id: AssetId,
pub parent_id: Option<AssetId>,
pub asset: CryptoAsset,
pub fee_to: Option<WalletRegisterId>,
pub registered_height: BlockHeight,
}
#[derive(CandidType, Debug, Clone, Hash, PartialEq, Eq, Deserialize)]
pub struct CandidRegisteredCryptoAsset {
pub id: CandidAssetId,
pub parent_id: Option<CandidAssetId>,
pub asset: CandidCryptoAsset,
pub fee_to: Option<CandidWalletRegisterId>,
pub registered_height: CandidBlockHeight,
}
impl From<CandidRegisteredCryptoAsset> for RegisteredCryptoAsset {
fn from(candid_registered_crypto_asset: CandidRegisteredCryptoAsset) -> Self {
RegisteredCryptoAsset {
id: candid_registered_crypto_asset.id.into(),
parent_id: candid_registered_crypto_asset.parent_id.map(|id| id.into()),
asset: candid_registered_crypto_asset.asset.into(),
registered_height: candid_registered_crypto_asset.registered_height.into(),
fee_to: candid_registered_crypto_asset.fee_to.map(|id| id.into()),
}
}
}
impl From<RegisteredCryptoAsset> for CandidRegisteredCryptoAsset {
fn from(registered_crypto_asset: RegisteredCryptoAsset) -> Self {
CandidRegisteredCryptoAsset {
id: CandidAssetId::from(registered_crypto_asset.id),
parent_id: registered_crypto_asset.parent_id.map(|id| id.into()),
asset: registered_crypto_asset.asset.into(),
registered_height: registered_crypto_asset.registered_height.into(),
fee_to: registered_crypto_asset
.fee_to
.map(|id| CandidWalletRegisterId::from(id)),
}
}
}
impl From<BigUint> for TokenType {
fn from(token_type: BigUint) -> Self {
match token_type {
token_type if token_type == BigUint::from(TokenType::Native) => TokenType::Native,
token_type if token_type == BigUint::from(TokenType::NativeGas) => TokenType::NativeGas,
token_type if token_type == BigUint::from(TokenType::ERC20) => TokenType::ERC20,
token_type if token_type == BigUint::from(TokenType::ERC721) => TokenType::ERC721,
token_type if token_type == BigUint::from(TokenType::DFT) => TokenType::DFT,
token_type if token_type == BigUint::from(TokenType::ICRC1) => TokenType::ICRC1,
token_type if token_type == BigUint::from(TokenType::BRC20) => TokenType::BRC20,
token_type if token_type == BigUint::from(TokenType::SPLToken) => TokenType::SPLToken,
token_type if token_type == BigUint::from(TokenType::TRC20) => TokenType::TRC20,
token_type if token_type == BigUint::from(TokenType::Jetton) => TokenType::Jetton,
_ => panic!("Invalid token type: {}", token_type),
}
}
}
impl From<TokenType> for BigUint {
fn from(token_type: TokenType) -> Self {
BigUint::from(token_type as u128)
}
}
impl_from_uint_for!(TokenType, u8, u16, u32, u64, u128);
impl Storable for CryptoAsset {
fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
bincode::serialize(self).unwrap().into()
}
fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
bincode::deserialize(&bytes).unwrap()
}
const BOUND: Bound = Bound::Bounded {
max_size: 256,
is_fixed_size: false,
};
}
impl Storable for RegisteredCryptoAsset {
fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
bincode::serialize(self).unwrap().into()
}
fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
bincode::deserialize(&bytes).unwrap()
}
const BOUND: Bound = Bound::Unbounded;
}
#[cfg(test)]
mod tests {
use super::CryptoAsset;
use crate::asset::TokenType;
use crate::chain::{Chain, ChainType};
use ex3_serde::{bincode, cbor};
#[test]
fn test_serde() {
let asset_1 = CryptoAsset {
chain: Chain {
r#type: ChainType::Ethereum,
network: 1u8.into(),
},
r#type: TokenType::ERC20,
address: "0x1234567889123456789123456".to_string(),
};
let bincode_bytes = bincode::serialize(&asset_1).unwrap();
let bincode_asset: CryptoAsset = bincode::deserialize(&bincode_bytes).unwrap();
assert_eq!(asset_1, bincode_asset);
let cbor_bytes = cbor::serialize(&asset_1).unwrap();
let cbor_asset: CryptoAsset = cbor::deserialize(&cbor_bytes).unwrap();
assert_eq!(asset_1, cbor_asset);
let asset_2 = CryptoAsset {
chain: Chain {
r#type: ChainType::Ethereum,
network: 1u8.into(),
},
r#type: TokenType::ERC721,
address: "0x1234567889123456789123456".to_string(),
};
let bincode_bytes = bincode::serialize(&asset_2).unwrap();
let bincode_asset: CryptoAsset = bincode::deserialize(&bincode_bytes).unwrap();
assert_eq!(asset_2, bincode_asset);
let cbor_bytes = cbor::serialize(&asset_2).unwrap();
let cbor_asset: CryptoAsset = cbor::deserialize(&cbor_bytes).unwrap();
assert_eq!(asset_2, cbor_asset);
}
#[test]
fn test_serde_size() {
let asset_1 = CryptoAsset {
chain: Chain {
r#type: ChainType::Ethereum,
network: 1u8.into(),
},
r#type: TokenType::ERC20,
address: "0x1234567889123456789123456".to_string(),
};
let bincode_bytes = bincode::serialize(&asset_1).unwrap();
let custom_bincode_bytes =
bincode::serialize(&(&asset_1.chain, &asset_1.r#type, &asset_1.address)).unwrap();
let cbor_bytes = cbor::serialize(&asset_1).unwrap();
let custom_cbor_bytes =
cbor::serialize(&(&asset_1.chain, &asset_1.r#type, &asset_1.address)).unwrap();
assert!(bincode_bytes.len() < cbor_bytes.len());
assert!(
custom_cbor_bytes.len() < cbor_bytes.len(),
" custom_bincode_bytes.len(): {}, cbor_bytes.len(): {}",
custom_cbor_bytes.len(),
cbor_bytes.len()
);
assert!(
custom_bincode_bytes.len() < cbor_bytes.len(),
"custom_bincode_bytes.len(): {}, cbor_bytes.len(): {}",
custom_bincode_bytes.len(),
cbor_bytes.len()
);
assert_eq!(
custom_bincode_bytes.len(),
bincode_bytes.len(),
"custom_bincode_bytes.len(): {}, bincode_bytes.len(): {}",
custom_bincode_bytes.len(),
bincode_bytes.len()
);
}
}