use candid::{CandidType, Deserialize};
use ex3_node_error::OtherError;
use ex3_serde::bincode::deserialize;
use num_bigint::BigUint;
use serde::Serialize;
use crate::chain::Chain;
use crate::{
impl_from_uint_for_enum, AssetId, CandidAssetId, CandidWalletRegisterId, WalletRegisterId,
};
#[derive(CandidType, Debug, Clone, Hash, Deserialize, Serialize, PartialEq, Eq)]
pub enum TokenType {
Native = 0,
NativeGas = 1,
ERC20 = 3,
ERC721 = 4,
DFT = 5,
ICRC1 = 6,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
pub struct CryptoAsset {
pub id: AssetId,
pub parent_id: Option<AssetId>,
pub chain: Chain,
pub network: u8,
pub r#type: TokenType,
pub address: String,
pub fee_to: Option<WalletRegisterId>,
}
#[derive(CandidType, Debug, Clone, Hash, PartialEq, Eq, Deserialize)]
pub struct CandidCryptoAsset {
pub id: CandidAssetId,
pub parent_id: Option<CandidAssetId>,
pub chain: Chain,
pub network: u8,
pub r#type: TokenType,
pub address: String,
pub fee_to: Option<CandidWalletRegisterId>,
}
impl From<CandidCryptoAsset> for CryptoAsset {
fn from(candid_crypto_asset: CandidCryptoAsset) -> Self {
CryptoAsset {
id: candid_crypto_asset.id.0,
parent_id: candid_crypto_asset.parent_id.map(|id| id.0),
chain: candid_crypto_asset.chain,
network: candid_crypto_asset.network,
r#type: candid_crypto_asset.r#type,
address: candid_crypto_asset.address,
fee_to: candid_crypto_asset.fee_to.map(|id| id.0),
}
}
}
impl From<CryptoAsset> for CandidCryptoAsset {
fn from(crypto_asset: CryptoAsset) -> Self {
CandidCryptoAsset {
id: crypto_asset.id.into(),
parent_id: crypto_asset.parent_id.map(|id| CandidAssetId::from(id)),
chain: crypto_asset.chain,
network: crypto_asset.network,
r#type: crypto_asset.r#type,
address: crypto_asset.address,
fee_to: crypto_asset.fee_to.map(|id| id.into()),
}
}
}
impl TryFrom<&[u8]> for CryptoAsset {
type Error = OtherError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let asset = deserialize(value).map_err(|e| {
OtherError::new(format!(
"Failed to deserialize CryptoAsset from bytes: {:?}",
e
))
})?;
Ok(asset)
}
}
impl From<BigUint> for TokenType {
fn from(token_type: BigUint) -> Self {
match token_type {
token_type if token_type == TokenType::Native.into() => TokenType::Native,
token_type if token_type == TokenType::NativeGas.into() => TokenType::NativeGas,
token_type if token_type == TokenType::ERC20.into() => TokenType::ERC20,
token_type if token_type == TokenType::ERC721.into() => TokenType::ERC721,
token_type if token_type == TokenType::DFT.into() => TokenType::DFT,
token_type if token_type == TokenType::ICRC1.into() => TokenType::ICRC1,
_ => panic!("Invalid token type: {}", token_type),
}
}
}
impl Into<BigUint> for TokenType {
fn into(self) -> BigUint {
BigUint::from(self as u128)
}
}
impl_from_uint_for_enum!(TokenType, u8, u16, u32, u64, u128);
#[cfg(test)]
mod tests {
use ex3_serde::{bincode, cbor};
use crate::asset::TokenType;
use crate::chain::Chain;
use crate::{AssetId, WalletRegisterId};
use super::CryptoAsset;
#[test]
fn test_serde() {
let asset_1 = CryptoAsset {
id: AssetId::from(1000000u32),
parent_id: None,
chain: Chain::Ethereum,
network: 1,
r#type: TokenType::ERC20,
address: "0x1234567889123456789123456".to_string(),
fee_to: Some(WalletRegisterId::from(100000u32)),
};
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 {
id: AssetId::from(2000000u32),
parent_id: Some(AssetId::from(1u8)),
chain: Chain::Ethereum,
network: 1,
r#type: TokenType::ERC721,
address: "0x1234567889123456789123456".to_string(),
fee_to: None,
};
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 {
id: AssetId::from(1000001u32),
parent_id: None,
chain: Chain::Ethereum,
network: 1,
r#type: TokenType::ERC20,
address: "0x1234567889123456789123456".to_string(),
fee_to: Some(WalletRegisterId::from(100000u32)),
};
let bincode_bytes = bincode::serialize(&asset_1).unwrap();
let custom_bincode_bytes = bincode::serialize(&(
&asset_1.id,
&asset_1.parent_id,
&asset_1.chain,
&asset_1.network,
&asset_1.r#type,
&asset_1.address,
&asset_1.fee_to,
))
.unwrap();
let cbor_bytes = cbor::serialize(&asset_1).unwrap();
let custom_cbor_bytes = cbor::serialize(&(
&asset_1.id,
&asset_1.parent_id,
&asset_1.chain,
&asset_1.network,
&asset_1.r#type,
&asset_1.address,
&asset_1.fee_to,
))
.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()
);
}
}