use std::fmt;
use std::ops;
use candid::CandidType;
use ciborium::tag::Captured;
use ex3_crypto::sha256;
use ex3_node_error::OtherError;
use ex3_payload_derive::Ex3Payload;
use ex3_serde::{bincode, cbor};
use ic_stable_structures::storable::Bound;
use ic_stable_structures::Storable;
use num_bigint::BigUint;
use serde::de::{Deserializer, SeqAccess, Visitor};
use serde::ser::{SerializeSeq, Serializer};
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
pub use asset::*;
pub use asset_account_binding::*;
pub use deposit::*;
pub use market::*;
pub use order::*;
pub use secret::*;
pub use transfer::*;
pub use types::*;
pub use wallet_registration::*;
pub use withdrawal::*;
use crate::PublicKey;
use crate::{Nonce, TransactionHash, Version, WalletRegisterId};
mod asset;
mod deposit;
mod market;
mod order;
mod secret;
mod transfer;
mod types;
mod withdrawal;
mod asset_account_binding;
mod wallet_registration;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Transaction {
pub version: Version,
pub r#type: TransactionType,
pub from: WalletRegisterId,
pub nonce: Nonce,
pub payload: ByteBuf,
pub signature: ByteBuf,
}
#[derive(Debug, Clone, PartialEq, Eq, Ex3Payload)]
struct TxData {
#[index(0)]
version: u8,
#[index(1)]
r#type: BigUint,
#[index(2)]
from: BigUint,
#[index(3)]
nonce: BigUint,
#[index(4)]
payload: ByteBuf,
}
impl Transaction {
pub fn hash(&self) -> TransactionHash {
let bytes = bincode::serialize(&(
&self.version,
&self.r#type,
&self.from,
&self.nonce,
&self.payload,
))
.unwrap();
sha256(&bytes)
}
pub fn hash_for_wallet_registration(&self, recovery_public_key: PublicKey) -> TransactionHash {
let bytes = bincode::serialize(&(
&self.version,
&self.r#type,
&self.from,
&self.nonce,
&recovery_public_key,
))
.unwrap();
sha256(&bytes)
}
pub fn tx_data_bytes(&self) -> Vec<u8> {
let tx_data = TxData {
version: self.version.encode(),
r#type: self.r#type.clone().into(),
from: self.from.clone().into(),
nonce: self.nonce.clone().into(),
payload: self.payload.clone(),
};
let bytes = cbor::serialize(&tx_data).unwrap();
bytes
}
pub fn encode(&self) -> Vec<u8> {
let data_bytes = bincode::serialize(&(
&self.r#type,
&self.from,
&self.nonce,
&self.payload,
&self.signature,
))
.unwrap();
let mut bytes = vec![self.version.encode()];
bytes.extend_from_slice(&data_bytes);
bytes
}
pub fn decode(bytes: &[u8]) -> Result<Self, OtherError> {
let version = Version::decode(bytes[0..1].try_into().unwrap())
.map_err(|_| OtherError::new("failed to decode transaction version".to_string()))?;
if version != Version::V0 {
return Err(OtherError::new(
format!(
"unsupported transaction version, expected 0, but {}",
version.encode()
)
.to_string(),
));
}
let (r#type, from, nonce, payload, signature) = bincode::deserialize(&bytes[1..]).unwrap();
Ok(Transaction {
version,
r#type,
from,
nonce,
payload,
signature,
})
}
}
#[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(transparent)]
pub struct EncodedTransaction(ByteBuf);
impl From<Transaction> for EncodedTransaction {
fn from(tx: Transaction) -> Self {
let bytes = tx.encode();
EncodedTransaction(ByteBuf::from(bytes))
}
}
impl From<&Transaction> for EncodedTransaction {
fn from(tx: &Transaction) -> Self {
let bytes = tx.encode();
EncodedTransaction(ByteBuf::from(bytes))
}
}
impl From<EncodedTransaction> for Transaction {
fn from(tx: EncodedTransaction) -> Self {
Transaction::decode(tx.0.as_ref()).expect("failed to decode transaction")
}
}
impl From<&EncodedTransaction> for Transaction {
fn from(tx: &EncodedTransaction) -> Self {
Transaction::decode(tx.0.as_ref()).expect("failed to decode transaction")
}
}
impl ops::Deref for EncodedTransaction {
type Target = ByteBuf;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Storable for EncodedTransaction {
fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
self.0.as_ref().into()
}
fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
EncodedTransaction(ByteBuf::from(bytes))
}
const BOUND: Bound = Bound::Unbounded;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_decode_with_empty_signature() {
let tx = Transaction {
version: Version::V0,
r#type: TransactionType::Deposit,
from: WalletRegisterId::from(1000000u32),
nonce: Nonce::from(1u32),
payload: ByteBuf::from(vec![1u8, 2u8, 3u8]),
signature: ByteBuf::from(vec![]),
};
let bytes = tx.encode();
let tx2 = Transaction::decode(&bytes).unwrap();
assert_eq!(tx, tx2);
}
#[test]
fn test_encode_decode_with_non_empty_signature() {
let tx = Transaction {
version: Version::V0,
r#type: TransactionType::Deposit,
from: WalletRegisterId::from(1000000u32),
nonce: Nonce::from(1u32),
payload: ByteBuf::from(vec![1u8, 2u8, 3u8]),
signature: ByteBuf::from(vec![1u8; 65]),
};
let bytes = tx.encode();
let tx2 = Transaction::decode(&bytes).unwrap();
assert_eq!(tx, tx2);
}
}