use core::mem::MaybeUninit;
use crate::api::*;
use crate::{c, hook_account, ledger_seq, AccountId, AccountType, AmountType, TxnType};
pub struct XrpPaymentBuilder<'a> {
drops: u64,
to_address: &'a [u8; 20],
dest_tag: u32,
src_tag: u32,
}
#[repr(u8)]
enum FieldCode {
TransactionType = 0x12,
Flags = 0x2,
SourceTag = 0x3,
Sequence = 0x4,
DestinationTag = 0xE,
FirstLedgerSequence = 0x1A,
LastLedgerSequence = 0x1B,
}
pub trait TransactionBuilder<const TXN_LEN: usize> {
const TXN_LEN: usize = TXN_LEN;
const TXN_TYPE: TxnType;
fn build(&self, uninitialized_buffer: &mut [MaybeUninit<u8>; TXN_LEN]) -> Result<()>;
#[inline(always)]
fn uninit_buffer() -> [MaybeUninit<u8>; TXN_LEN] {
MaybeUninit::uninit_array()
}
}
pub struct TransactionBuffer<'a, const TXN_LEN: usize> {
buf: &'a mut [MaybeUninit<u8>; TXN_LEN],
pos: usize,
}
impl<const TXN_LEN: usize> TransactionBuffer<'_, TXN_LEN> {
#[inline(always)]
pub fn encode_txn_type(&mut self, tt: TxnType) {
unsafe {
self.buf
.get_unchecked_mut(self.pos)
.as_mut_ptr()
.write(FieldCode::TransactionType.into());
self.buf
.get_unchecked_mut(self.pos + 1)
.as_mut_ptr()
.write(((tt as u16 >> 8) & 0xFF) as u8);
self.buf
.get_unchecked_mut(self.pos + 2)
.as_mut_ptr()
.write((tt as u16 & 0xFF) as u8);
}
self.pos += 3;
}
#[inline(always)]
pub fn encode_u32(&mut self, data: u32, field: u8) {
unsafe {
self.buf
.get_unchecked_mut(self.pos)
.as_mut_ptr()
.write(0x20 + (field & 0x0F));
self.buf
.get_unchecked_mut(self.pos + 1)
.as_mut_ptr()
.write(((data >> 24) & 0xFF) as u8);
self.buf
.get_unchecked_mut(self.pos + 2)
.as_mut_ptr()
.write(((data >> 16) & 0xFF) as u8);
self.buf
.get_unchecked_mut(self.pos + 3)
.as_mut_ptr()
.write(((data >> 8) & 0xFF) as u8);
self.buf
.get_unchecked_mut(self.pos + 4)
.as_mut_ptr()
.write((data & 0xFF) as u8);
}
self.pos += 5;
}
#[inline(always)]
pub fn encode_u32_with_field_id(&mut self, data: u32, field: u8) {
unsafe {
self.buf
.get_unchecked_mut(self.pos)
.as_mut_ptr()
.write(0x20);
self.buf
.get_unchecked_mut(self.pos + 1)
.as_mut_ptr()
.write(field);
self.buf
.get_unchecked_mut(self.pos + 2)
.as_mut_ptr()
.write(((data >> 24) & 0xFF) as u8);
self.buf
.get_unchecked_mut(self.pos + 3)
.as_mut_ptr()
.write(((data >> 16) & 0xFF) as u8);
self.buf
.get_unchecked_mut(self.pos + 4)
.as_mut_ptr()
.write(((data >> 8) & 0xFF) as u8);
self.buf
.get_unchecked_mut(self.pos + 5)
.as_mut_ptr()
.write((data & 0xFF) as u8);
}
self.pos += 6;
}
#[inline(always)]
pub fn encode_drops(&mut self, drops: u64, amount_type: AmountType) {
self.encode_drops_at(self.pos, drops, amount_type);
}
#[inline(always)]
pub fn encode_drops_at(&mut self, pos: usize, drops: u64, amount_type: AmountType) {
let amount_type: u8 = amount_type.into();
unsafe {
self.buf
.get_unchecked_mut(pos)
.as_mut_ptr()
.write(0x60 + (amount_type & 0x0F));
self.buf
.get_unchecked_mut(pos + 1)
.as_mut_ptr()
.write((0b01000000 + ((drops >> 56) & 0b00111111)) as u8);
self.buf
.get_unchecked_mut(pos + 2)
.as_mut_ptr()
.write(((drops >> 48) & 0xFF) as u8);
self.buf
.get_unchecked_mut(pos + 3)
.as_mut_ptr()
.write(((drops >> 40) & 0xFF) as u8);
self.buf
.get_unchecked_mut(pos + 4)
.as_mut_ptr()
.write(((drops >> 32) & 0xFF) as u8);
self.buf
.get_unchecked_mut(pos + 5)
.as_mut_ptr()
.write(((drops >> 24) & 0xFF) as u8);
self.buf
.get_unchecked_mut(pos + 6)
.as_mut_ptr()
.write(((drops >> 16) & 0xFF) as u8);
self.buf
.get_unchecked_mut(pos + 7)
.as_mut_ptr()
.write(((drops >> 8) & 0xFF) as u8);
self.buf
.get_unchecked_mut(pos + 8)
.as_mut_ptr()
.write((drops & 0xFF) as u8);
}
self.pos += 9;
}
#[inline(always)]
pub unsafe fn encode_drops_at_buf_ptr(
uninitialized_buf: *mut MaybeUninit<u8>,
pos: usize,
drops: u64,
amount_type: AmountType,
) {
let amount_type: u8 = amount_type.into();
let mut pos_0 = MaybeUninit::uninit();
let mut pos_1 = MaybeUninit::uninit();
let mut pos_2 = MaybeUninit::uninit();
let mut pos_3 = MaybeUninit::uninit();
let mut pos_4 = MaybeUninit::uninit();
let mut pos_5 = MaybeUninit::uninit();
let mut pos_6 = MaybeUninit::uninit();
let mut pos_7 = MaybeUninit::uninit();
let mut pos_8 = MaybeUninit::uninit();
pos_0.write(0x60 + (amount_type & 0x0F));
pos_1.write((0b01000000 + ((drops >> 56) & 0b00111111)) as u8);
pos_2.write(((drops >> 48) & 0xFF) as u8);
pos_3.write(((drops >> 40) & 0xFF) as u8);
pos_4.write(((drops >> 32) & 0xFF) as u8);
pos_5.write(((drops >> 24) & 0xFF) as u8);
pos_6.write(((drops >> 16) & 0xFF) as u8);
pos_7.write(((drops >> 8) & 0xFF) as u8);
pos_8.write((drops & 0xFF) as u8);
unsafe {
uninitialized_buf.add(pos).write(pos_0);
uninitialized_buf.add(pos + 1).write(pos_1);
uninitialized_buf.add(pos + 2).write(pos_2);
uninitialized_buf.add(pos + 3).write(pos_3);
uninitialized_buf.add(pos + 4).write(pos_4);
uninitialized_buf.add(pos + 5).write(pos_5);
uninitialized_buf.add(pos + 6).write(pos_6);
uninitialized_buf.add(pos + 7).write(pos_7);
uninitialized_buf.add(pos + 8).write(pos_8);
}
}
#[inline(always)]
pub fn encode_signing_pubkey_as_null(&mut self) {
unsafe {
self.buf
.get_unchecked_mut(self.pos)
.as_mut_ptr()
.write(0x73);
self.buf
.get_unchecked_mut(self.pos + 1)
.as_mut_ptr()
.write(0x21);
let u64_ptr = self.buf.get_unchecked_mut(self.pos + 2).as_mut_ptr() as *mut u64;
u64_ptr.write(0);
u64_ptr.offset(1).write(0);
u64_ptr.offset(2).write(0);
u64_ptr.offset(3).write(0); }
self.pos += 35;
}
#[inline(always)]
pub fn encode_account(&mut self, account_id: &AccountId, account_type: AccountType) {
unsafe {
let account_type: u8 = account_type.into();
self.buf
.get_unchecked_mut(self.pos)
.as_mut_ptr()
.write(0x80 + account_type);
self.buf
.get_unchecked_mut(self.pos + 1)
.as_mut_ptr()
.write(0x14);
let u64_account_id_ptr = account_id.as_ptr() as *mut u64;
let u64_buf_ptr = self.buf.get_unchecked_mut(self.pos + 2).as_ptr() as *mut u64;
u64_buf_ptr.write(u64_account_id_ptr.read());
u64_buf_ptr
.offset(1)
.write(u64_account_id_ptr.offset(1).read());
let u32_account_id_ptr = account_id.as_ptr().offset(16) as *mut u32;
let u32_buf_ptr = self.buf.get_unchecked_mut(self.pos + 18).as_ptr() as *mut u32;
u32_buf_ptr.write(u32_account_id_ptr.read());
}
self.pos += 22;
}
}
impl<'a> XrpPaymentBuilder<'a> {
#[inline(always)]
pub fn new(drops: u64, to_address: &'a [u8; 20], dest_tag: u32, src_tag: u32) -> Self {
Self {
drops,
to_address,
dest_tag,
src_tag,
}
}
}
impl TransactionBuilder<270> for XrpPaymentBuilder<'_> {
const TXN_TYPE: TxnType = TxnType::Payment;
#[inline(always)]
fn build(
&self,
uninitialized_buffer: &mut [MaybeUninit<u8>; XrpPaymentBuilder::TXN_LEN],
) -> Result<()> {
let current_ledger_sequence = ledger_seq() as u32;
let hook_account = match hook_account() {
Err(e) => return Err(e),
Ok(acc) => acc,
};
let mut txn_buffer = TransactionBuffer {
buf: uninitialized_buffer,
pos: 0,
};
txn_buffer.encode_txn_type(Self::TXN_TYPE);
txn_buffer.encode_u32(c::tfCANONICAL, FieldCode::Flags.into());
txn_buffer.encode_u32(self.src_tag, FieldCode::SourceTag.into());
txn_buffer.encode_u32(0, FieldCode::Sequence.into());
txn_buffer.encode_u32(self.dest_tag, FieldCode::DestinationTag.into());
txn_buffer.encode_u32_with_field_id(
current_ledger_sequence + 1,
FieldCode::FirstLedgerSequence.into(),
);
txn_buffer.encode_u32_with_field_id(
current_ledger_sequence + 5,
FieldCode::LastLedgerSequence.into(),
);
txn_buffer.encode_drops(self.drops, AmountType::Amount);
let fee_pos = txn_buffer.pos;
txn_buffer.encode_drops(0, AmountType::Fee);
txn_buffer.encode_signing_pubkey_as_null();
txn_buffer.encode_account(&hook_account, AccountType::Account);
txn_buffer.encode_account(self.to_address, AccountType::Destination);
let buf_mut_ptr = txn_buffer.buf.as_mut_ptr();
let insert_etxn_details_from_ptr_result: Result<u64> =
insert_etxn_details_from_ptr(unsafe { buf_mut_ptr.add(txn_buffer.pos) as u32 }, 138);
match insert_etxn_details_from_ptr_result {
Err(e) => return Err(e),
Ok(_) => {}
}
txn_buffer.pos += 138;
let fee = match etxn_fee_base_from_ptr(buf_mut_ptr, XrpPaymentBuilder::TXN_LEN) {
Err(e) => return Err(e),
Ok(fee) => fee,
};
unsafe {
TransactionBuffer::<{ XrpPaymentBuilder::TXN_LEN }>::encode_drops_at_buf_ptr(
buf_mut_ptr,
fee_pos,
fee,
AmountType::Fee,
)
};
Ok(())
}
}
impl From<FieldCode> for u8 {
#[inline(always)]
fn from(field_code: FieldCode) -> Self {
field_code as u8
}
}
#[cfg(test)]
mod tests {
use core::mem::MaybeUninit;
use wasm_bindgen_test::wasm_bindgen_test;
use crate::{AccountType, AmountType, TransactionBuffer, ACC_ID_LEN};
#[wasm_bindgen_test]
fn can_encode_transaction_type() {
use super::*;
let txn_types = [
TxnType::Payment,
TxnType::EscrowCreate,
TxnType::EscrowFinish,
TxnType::AccountSet,
TxnType::EscrowCancel,
TxnType::RegularKeySet,
TxnType::OfferCreate,
TxnType::OfferCancel,
TxnType::TicketCreate,
TxnType::TicketCancel,
TxnType::SignerListSet,
TxnType::PaychanCreate,
TxnType::PaychanFund,
TxnType::PaychanClaim,
TxnType::CheckCreate,
TxnType::CheckCash,
TxnType::CheckCancel,
TxnType::DepositPreauth,
TxnType::TrustSet,
TxnType::AccountDelete,
TxnType::HookSet,
TxnType::Amendment,
TxnType::Fee,
TxnType::UnlModify,
];
for txn_type in txn_types {
let mut uninitialized_buffer: [MaybeUninit<u8>; 270] = MaybeUninit::uninit_array();
for i in 0..270 {
unsafe {
uninitialized_buffer
.get_unchecked_mut(i)
.as_mut_ptr()
.write(0);
}
}
let mut txn_buffer = TransactionBuffer {
buf: &mut uninitialized_buffer,
pos: 0,
};
txn_buffer.encode_txn_type(txn_type);
let txn_type: u8 = txn_type.into();
unsafe {
assert_eq!(txn_buffer.buf[0].assume_init(), 0x12);
assert_eq!(
txn_buffer.buf[1].assume_init(),
((txn_type as u16 >> 8) & 0xFF) as u8
);
assert_eq!(
txn_buffer.buf[2].assume_init(),
(txn_type as u16 & 0xFF) as u8
);
}
assert_eq!(txn_buffer.pos, 3);
}
}
#[wasm_bindgen_test]
fn can_encode_drops_at_buf_ptr() {
let mut uninitialized_buffer: [MaybeUninit<u8>; 270] = MaybeUninit::uninit_array();
for i in 0..270 {
unsafe {
uninitialized_buffer
.get_unchecked_mut(i)
.as_mut_ptr()
.write(0);
}
}
unsafe {
TransactionBuffer::<270>::encode_drops_at_buf_ptr(
uninitialized_buffer.as_mut_ptr(),
44,
12_u64,
AmountType::Fee,
);
}
assert_eq!(
unsafe { MaybeUninit::array_assume_init(uninitialized_buffer) },
[
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,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 104, 64, 0, 0, 0, 0, 0, 0, 12, 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, 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, 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, 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, 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, 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, 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, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
}
#[wasm_bindgen_test]
fn can_encode_account() {
let account: [u8; ACC_ID_LEN] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
];
let mut uninitialized_buffer: [MaybeUninit<u8>; 270] = MaybeUninit::uninit_array();
for i in 0..270 {
unsafe {
uninitialized_buffer
.get_unchecked_mut(i)
.as_mut_ptr()
.write(0);
}
}
let mut txn_buffer = TransactionBuffer {
buf: &mut uninitialized_buffer,
pos: 15,
};
txn_buffer.encode_account(&account, AccountType::Account);
assert_eq!(txn_buffer.pos, 37);
assert_eq!(
unsafe { MaybeUninit::array_assume_init(uninitialized_buffer) },
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0x80 + AccountType::Account as u8,
0x14,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
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,
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,
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,
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,
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,
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,
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,
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,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
)
}
#[wasm_bindgen_test]
fn can_encode_signing_pupkey_as_null() {
let mut uninitialized_buffer: [MaybeUninit<u8>; 270] = MaybeUninit::uninit_array();
for i in 0..270 {
unsafe {
uninitialized_buffer
.get_unchecked_mut(i)
.as_mut_ptr()
.write(0);
}
}
let mut txn_buffer = TransactionBuffer {
buf: &mut uninitialized_buffer,
pos: 30,
};
txn_buffer.encode_signing_pubkey_as_null();
assert_eq!(txn_buffer.pos, 65);
assert_eq!(
unsafe { MaybeUninit::array_assume_init(uninitialized_buffer) },
[
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,
0, 0, 0x73, 0x21, 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, 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, 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, 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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
)
}
}