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 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::{
AssetRegistration, UpdateAssetWithdrawalFeeTo, UpdateChainConfirmationTimes,
UpdateGlobalWithdrawalFeeTo,
};
pub use deposit::{
CandidConfirmedDeposit, CandidDeposit, CandidDepositIdentifier, ConfirmedDeposit, Deposit,
DepositIdentifier, OriginalDeposit,
};
pub use market::{
SpotMarketRegistration, UpdateSpotMarketFeeTo, UpdateSpotMarketInitialFeeTo,
UpdateSpotMarketInitialTradingFee, UpdateSpotMarketTradingFee, UpdateSpotMarketTradingSettings,
};
pub use order::{
AddAmmV2Liquidity, AmmV2ExactTokens, AmmV2OrderDetail, CancelSpotOrder, OrderScope, OrderSide,
RemoveAmmV2Liquidity, SpotOrder, SpotOrderDetail, SubmitSpotOrder,
};
pub use secret::{CreateApiSecret, DestroyApiSecret, ResetMainSecret};
pub use transfer::{BatchTransfer, TransferDetail};
pub use types::TransactionType;
pub use withdrawal::{ForceWithdrawal, Withdrawal};
use crate::{Nonce, TransactionHash, Version, WalletRegisterId};
mod asset;
mod deposit;
mod market;
mod order;
mod secret;
mod transfer;
mod types;
mod withdrawal;
#[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, external_payload: Option<ByteBuf>) -> TransactionHash {
let bytes = bincode::serialize(&(
&self.version,
&self.r#type,
&self.from,
&self.nonce,
&external_payload.map_or(self.payload.clone(), |p| p),
))
.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
}
}
#[derive(CandidType, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum TransactionRejectionReason {
InsufficientBalance,
DuplicateSecretKey,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RejectedTransaction {
pub tx: Transaction,
pub reason: TransactionRejectionReason,
}
impl RejectedTransaction {
pub fn encode(&self) -> Vec<u8> {
bincode::serialize(&(&self.tx.encode(), &self.reason)).unwrap()
}
pub fn decode(bytes: &[u8]) -> Result<Self, OtherError> {
let (tx_bytes, reason): (Vec<u8>, TransactionRejectionReason) =
bincode::deserialize(bytes).map_err(|e| OtherError::new(format!("{:?}", e)))?;
Ok(Self {
tx: Transaction::decode(&tx_bytes).map_err(|e| OtherError::new(format!("{:?}", e)))?,
reason,
})
}
}
#[derive(CandidType, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct EncodedRejectedTransaction(ByteBuf);
impl ops::Deref for EncodedRejectedTransaction {
type Target = ByteBuf;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<EncodedRejectedTransaction> for RejectedTransaction {
fn from(encoded: EncodedRejectedTransaction) -> Self {
RejectedTransaction::decode(encoded.as_ref()).expect("failed to decode")
}
}
impl From<&EncodedRejectedTransaction> for RejectedTransaction {
fn from(encoded: &EncodedRejectedTransaction) -> Self {
RejectedTransaction::decode(encoded.as_ref()).expect("failed to decode")
}
}
impl From<RejectedTransaction> for EncodedRejectedTransaction {
fn from(rejected: RejectedTransaction) -> Self {
let bytes = rejected.encode();
EncodedRejectedTransaction(ByteBuf::from(bytes))
}
}
impl From<&RejectedTransaction> for EncodedRejectedTransaction {
fn from(rejected: &RejectedTransaction) -> Self {
let bytes = rejected.encode();
EncodedRejectedTransaction(ByteBuf::from(bytes))
}
}
#[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);
}
}