xrpl_hooks/
helpers.rs

1use core::ops::Range;
2
3use crate::api::*;
4use crate::uninit_buf;
5
6/// Tests two buffers for equality
7///
8/// Pay attention to the GUARD_ID parameter.
9/// This should be unique on every call, through the entire hook code.
10/// Otherwise you will encounter guard violation during the execution of your hook.
11#[inline(always)]
12pub fn is_buffer_equal<const GUARD_ID: u32>(buf_1: &[u8], buf_2: &[u8]) -> bool {
13    let buf1_len = buf_1.len();
14
15    if buf1_len != buf_2.len() {
16        return false;
17    };
18
19    // guarded loop
20    let mut i = 0;
21    while {
22        _g(GUARD_ID, buf1_len as u32 + 1);
23        i < buf1_len
24    } {
25        if buf_1[i] != buf_2[i] {
26            return false;
27        }
28        i += 1;
29    }
30
31    true
32}
33
34/// Zeroize a buffer
35///
36/// Pay attention to the GUARD_ID parameter.
37/// This should be unique on every call, through the entire hook code.
38/// Otherwise you will encounter guard violation during the execution of your hook.
39#[inline(always)]
40pub fn buffer_zeroize<const GUARD_ID: u32>(buf: &mut [u8]) {
41    let buf_len = buf.len();
42    // guarded loop
43    let mut i = 0;
44    while {
45        _g(GUARD_ID, buf_len as u32 + 1);
46        i < buf_len
47    } {
48        buf[0] = 0;
49        i += 1;
50    }
51}
52
53/// Checks whether the transaction is outgoing
54///
55/// Pay attention to the GUARD_ID parameter.
56/// This should be unique on every call, through the entire hook code.
57/// Otherwise you will encounter guard violation during the execution of your hook.
58#[inline(always)]
59pub fn is_txn_outgoing<const GUARD_ID: u32>(
60    hook_acc_id: &mut AccountId,
61    otnx_acc_id: &mut AccountId,
62) -> Result<bool> {
63    match hook_account(hook_acc_id) {
64        Err(e) => return Err(e),
65        Ok(_) => {}
66    }
67
68    match otxn_field(otnx_acc_id, FieldId::Account) {
69        Err(e) => return Err(e),
70        Ok(_) => {}
71    }
72
73    Ok(is_buffer_equal::<GUARD_ID>(
74        &hook_acc_id[..],
75        &otnx_acc_id[..],
76    ))
77}
78
79/// Checks whether the transaction is ingoing
80///
81/// Pay attention to the GUARD_ID parameter.
82/// This should be unique on every call, through the entire hook code.
83/// Otherwise you will encounter guard violation during the execution of your hook.
84#[inline(always)]
85pub fn is_txn_ingoing<const GUARD_ID: u32>(
86    hook_acc_id: &mut AccountId,
87    otnx_acc_id: &mut AccountId,
88) -> Result<bool> {
89    match is_txn_outgoing::<GUARD_ID>(hook_acc_id, otnx_acc_id) {
90        Err(e) => Err(e),
91        Ok(res) => Ok(!res),
92    }
93}
94
95/// Convert amount to drops
96#[inline(always)]
97pub const fn amount_to_drops(amount_buf: &Amount) -> Result<u64> {
98    if (amount_buf[0] >> 7) == 1 {
99        return Err(Error::InternalError);
100    }
101
102    Ok((((amount_buf[0] as u64) & 0xb00111111) << 56)
103        + ((amount_buf[1] as u64) << 48)
104        + ((amount_buf[2] as u64) << 40)
105        + ((amount_buf[3] as u64) << 32)
106        + ((amount_buf[4] as u64) << 24)
107        + ((amount_buf[5] as u64) << 16)
108        + ((amount_buf[6] as u64) << 8)
109        + (amount_buf[7] as u64))
110}
111
112/// Prepares payment for emitting
113#[inline(always)]
114pub fn prepare_payment_simple(
115    buf_out: &mut TxnPaymentSimple,
116    drops_amount: u64,
117    drops_fee: u64,
118    to_address: &AccountId,
119    dest_tag: u32,
120    src_tag: u32,
121) -> Result<()> {
122    const TT_RANGE: Range<usize> = Range { start: 0, end: 3 };
123    const FLAGS_RANGE: Range<usize> = Range { start: 3, end: 8 };
124    const TAG_SRC_RANGE: Range<usize> = Range { start: 8, end: 13 };
125    const SEQUENCE_RANGE: Range<usize> = Range { start: 13, end: 18 };
126    const TAG_DST_RANGE: Range<usize> = Range { start: 18, end: 23 };
127    const FLS_RANGE: Range<usize> = Range { start: 23, end: 29 };
128    const LLS_RANGE: Range<usize> = Range { start: 29, end: 35 };
129    const DROPS_RANGE: Range<usize> = Range { start: 35, end: 44 };
130    const DROPS_FEE_RANGE: Range<usize> = Range { start: 44, end: 53 };
131    const SIGNING_PUBKEY_RANGE: Range<usize> = Range { start: 53, end: 88 };
132    const ACCOUNT_SRC_RANGE: Range<usize> = Range {
133        start: 88,
134        end: 110,
135    };
136    const ACCOUNT_DST_RANGE: Range<usize> = Range {
137        start: 110,
138        end: 132,
139    };
140    const ETXN_DETAILS_RANGE: Range<usize> = Range {
141        start: 132,
142        end: 237,
143    };
144
145    let mut acc: AccountId = uninit_buf!();
146    match hook_account(&mut acc) {
147        Err(e) => return Err(e),
148        Ok(_) => {}
149    }
150
151    let cls = ledger_seq() as u32;
152
153    encode_tt(&mut buf_out[TT_RANGE], TxnType::Payment);
154    encode_flags(&mut buf_out[FLAGS_RANGE], TF_CANONICAL);
155    encode_tag_src(&mut buf_out[TAG_SRC_RANGE], src_tag);
156    encode_sequence(&mut buf_out[SEQUENCE_RANGE], 0);
157    encode_tag_dst(&mut buf_out[TAG_DST_RANGE], dest_tag);
158    encode_fls(&mut buf_out[FLS_RANGE], cls + 1);
159    encode_lls(&mut buf_out[LLS_RANGE], cls + 5);
160    encode_drops_amount(&mut buf_out[DROPS_RANGE], drops_amount);
161    encode_drops_fee(&mut buf_out[DROPS_FEE_RANGE], drops_fee);
162    encode_signing_pubkey_null(&mut buf_out[SIGNING_PUBKEY_RANGE]);
163    encode_account_src(&mut buf_out[ACCOUNT_SRC_RANGE], &acc);
164    encode_account_dst(&mut buf_out[ACCOUNT_DST_RANGE], to_address);
165    match etxn_details(&mut buf_out[ETXN_DETAILS_RANGE]) {
166        Err(e) => return Err(e),
167        Ok(_) => {}
168    }
169
170    Ok(())
171}
172
173#[inline(always)]
174fn encode_tt(buf_out: &mut [u8], tt: TxnType) {
175    buf_out[0] = 0x12;
176    buf_out[1] = ((tt as u16 >> 8) & 0xFF) as u8;
177    buf_out[2] = ((tt as u16 >> 0) & 0xFF) as u8;
178}
179
180#[inline(always)]
181fn encode_flags(buf_out: &mut [u8], flags: u32) {
182    encode_u32_common(buf_out, flags, 0x2)
183}
184
185#[inline(always)]
186fn encode_tag_src(buf_out: &mut [u8], tag: u32) {
187    encode_u32_common(buf_out, tag, 0x3)
188}
189
190#[inline(always)]
191fn encode_sequence(buf_out: &mut [u8], sequence: u32) {
192    encode_u32_common(buf_out, sequence, 0x4)
193}
194
195#[inline(always)]
196fn encode_tag_dst(buf_out: &mut [u8], tag: u32) {
197    encode_u32_common(buf_out, tag, 0xE)
198}
199
200#[inline(always)]
201fn encode_fls(buf_out: &mut [u8], fls: u32) {
202    encode_u32_uncommon(buf_out, fls, 0x1A)
203}
204
205#[inline(always)]
206fn encode_lls(buf_out: &mut [u8], lls: u32) {
207    encode_u32_uncommon(buf_out, lls, 0x1B)
208}
209
210#[inline(always)]
211fn encode_drops_amount(buf_out: &mut [u8], drops: u64) {
212    encode_drops(buf_out, drops, AmountType::Amount)
213}
214
215#[inline(always)]
216fn encode_drops_fee(buf_out: &mut [u8], drops: u64) {
217    encode_drops(buf_out, drops, AmountType::Fee)
218}
219
220#[inline(always)]
221fn encode_account_src(buf_out: &mut [u8], account_id: &Buffer<ACC_ID_LEN>) {
222    encode_account(buf_out, account_id, AccountType::Account)
223}
224
225#[inline(always)]
226fn encode_account_dst(buf_out: &mut [u8], account_id: &Buffer<ACC_ID_LEN>) {
227    encode_account(buf_out, account_id, AccountType::Destination)
228}
229
230#[inline(always)]
231fn encode_u32_common(buf_out: &mut [u8], i: u32, field: u8) {
232    buf_out[0] = 0x20 + (field & 0x0F);
233    buf_out[1] = ((i >> 24) & 0xFF) as u8;
234    buf_out[2] = ((i >> 16) & 0xFF) as u8;
235    buf_out[3] = ((i >> 8) & 0xFF) as u8;
236    buf_out[4] = ((i >> 0) & 0xFF) as u8;
237}
238
239#[inline(always)]
240fn encode_u32_uncommon(buf_out: &mut [u8], i: u32, field: u8) {
241    buf_out[0] = 0x20;
242    buf_out[1] = field;
243    buf_out[2] = ((i >> 24) & 0xFF) as u8;
244    buf_out[3] = ((i >> 16) & 0xFF) as u8;
245    buf_out[4] = ((i >> 8) & 0xFF) as u8;
246    buf_out[5] = ((i >> 0) & 0xFF) as u8;
247}
248
249#[inline(always)]
250fn encode_drops(buf_out: &mut [u8], drops: u64, amount_type: AmountType) {
251    buf_out[0] = 0x60 + (amount_type as u8 & 0x0F);
252    buf_out[1] = (0b01000000 + ((drops >> 56) & 0b00111111)) as u8;
253    buf_out[2] = ((drops >> 48) & 0xFF) as u8;
254    buf_out[3] = ((drops >> 40) & 0xFF) as u8;
255    buf_out[4] = ((drops >> 32) & 0xFF) as u8;
256    buf_out[5] = ((drops >> 24) & 0xFF) as u8;
257    buf_out[6] = ((drops >> 16) & 0xFF) as u8;
258    buf_out[7] = ((drops >> 8) & 0xFF) as u8;
259    buf_out[8] = ((drops >> 0) & 0xFF) as u8;
260}
261
262#[inline(always)]
263fn encode_signing_pubkey_null(buf_out: &mut [u8]) {
264    buf_out[0] = 0x73;
265    buf_out[1] = 0x21;
266    buf_out[2..35].clone_from_slice(&[0; 33]);
267}
268
269#[inline(always)]
270fn encode_account(buf_out: &mut [u8], account_id: &AccountId, account_type: AccountType) {
271    buf_out[0] = 0x80 + account_type as u8;
272    buf_out[1] = 0x14;
273    buf_out[2..22].clone_from_slice(&account_id[..]);
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use crate::_c;
280
281    const ACCOUNT_ID: AccountId = [
282        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
283    ];
284
285    #[test]
286    fn enc_account() {
287        let mut encoded: [u8; _c::ENCODE_ACCOUNT_SIZE as usize] = uninit_buf!();
288
289        encode_account(&mut encoded, &ACCOUNT_ID, AccountType::Account);
290
291        assert_eq!(
292            encoded,
293            [0x81, 0x14, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
294        );
295    }
296
297    #[test]
298    fn enc_signing_pubkey_null() {
299        let mut key: [u8; _c::ENCODE_SIGNING_PUBKEY_NULL_SIZE as usize] = [255; 35];
300
301        encode_signing_pubkey_null(&mut key);
302
303        assert_eq!(
304            key,
305            [
306                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,
307                0, 0, 0, 0, 0, 0, 0, 0, 0
308            ]
309        )
310    }
311}