Skip to main content

rshooks_api/
helpers.rs

1use crate::api::*;
2use byteorder::{ByteOrder, LittleEndian};
3
4/// Returns a slice of the given data with the specified length
5///
6/// # Arguments
7/// * `data` - The data to slice
8/// * `offset` - The offset to start the slice
9///
10/// # Returns
11/// A slice of the given data with the specified length
12///
13/// # Example
14/// ```
15/// let data = [1, 2, 3, 4, 5];
16/// let sliced = slice::<3>(&data, 1);
17/// assert_eq!(sliced, [2, 3, 4]);
18/// ```
19#[cfg(not(doctest))]
20#[inline]
21pub fn slice<const T: usize>(data: &[u8], offset: usize) -> &[u8; T] {
22    let sliced_data: &[u8] = &data[offset..(T + offset)];
23    if sliced_data.len() < T {
24        rollback(b"Too Big slice length.", 0);
25    }
26    let ptr = sliced_data.as_ptr() as *const [u8; T];
27    return unsafe { &*ptr };
28}
29
30/// Returns a mutable slice of the given data with the specified length
31///
32/// # Arguments
33/// * `data` - The data to slice
34/// * `offset` - The offset to start the slice
35///
36/// # Returns
37/// A mutable slice of the given data with the specified length
38///
39/// # Example
40/// ```
41/// let mut data = [1, 2, 3, 4, 5];
42/// let sliced = slice_mut::<3>(&mut data, 1);
43/// assert_eq!(sliced, [2, 3, 4]);
44/// ```
45#[cfg(not(doctest))]
46#[inline]
47pub fn slice_mut<const T: usize>(data: &mut [u8], offset: usize) -> &mut [u8; T] {
48    let sliced_data: &mut [u8] = &mut data[offset..(T + offset)];
49    if sliced_data.len() < T {
50        rollback(b"Too Big slice length.", 0);
51    }
52    let ptr = sliced_data.as_mut_ptr() as *mut [u8; T];
53    return unsafe { &mut *ptr };
54}
55
56/// Rolls back the transaction if the condition is not met
57///
58/// # Arguments
59/// * `cond` - The condition to check
60/// * `message` - The error message to display on rollback
61///
62/// # Example
63/// ```
64/// require(amount > 0, b"Amount must be positive");
65/// ```
66#[cfg(not(doctest))]
67#[inline(always)]
68pub fn require(cond: bool, message: &[u8]) -> () {
69    if !cond {
70        rollback(message, 0);
71    }
72}
73
74/// Tests two buffers for equality
75///
76/// Pay attention to the GUARD_ID parameter.
77/// This should be unique on every call, through the entire hook code.
78/// Otherwise you will encounter guard violation during the execution of your hook.
79#[inline(always)]
80pub fn is_buffer_equal<const GUARD_ID: u32>(buf_1: &[u8], buf_2: &[u8]) -> bool {
81    let buf1_len = buf_1.len();
82
83    if buf1_len != buf_2.len() {
84        return false;
85    };
86
87    // guarded loop
88    let mut i = 0;
89    while {
90        _g(GUARD_ID, buf1_len as u32 + 1);
91        i < buf1_len
92    } {
93        if buf_1[i] != buf_2[i] {
94            return false;
95        }
96        i += 1;
97    }
98
99    true
100}
101
102/// Tests two 20-byte buffers for equality
103///
104/// This function efficiently compares two 20-byte buffers by reading them as
105/// little-endian integers. It's optimized for performance and avoids byte-by-byte
106/// comparison.
107///
108/// # Arguments
109/// * `buf_1` - The first 20-byte buffer to compare
110/// * `buf_2` - The second 20-byte buffer to compare
111///
112/// # Returns
113/// `true` if the buffers are equal, `false` otherwise
114#[inline(always)]
115pub fn is_buffer_equal_20(buf_1: &[u8], buf_2: &[u8]) -> bool {
116    LittleEndian::read_u64(&buf_1[0..]) == LittleEndian::read_u64(&buf_2[0..])
117        && LittleEndian::read_u64(&buf_1[8..]) == LittleEndian::read_u64(&buf_2[8..])
118        && LittleEndian::read_u32(&buf_1[16..]) == LittleEndian::read_u32(&buf_2[16..])
119}
120
121/// Tests two 32-byte buffers for equality
122///
123/// This function efficiently compares two 32-byte buffers by reading them as
124/// little-endian 64-bit integers. It's optimized for performance and avoids
125/// byte-by-byte comparison.
126///
127/// # Arguments
128/// * `buf_1` - The first 32-byte buffer to compare
129/// * `buf_2` - The second 32-byte buffer to compare
130///
131/// # Returns
132/// `true` if the buffers are equal, `false` otherwise
133#[inline(always)]
134pub fn is_buffer_equal_32(buf_1: &[u8], buf_2: &[u8]) -> bool {
135    if LittleEndian::read_u64(&buf_1[0..]) == LittleEndian::read_u64(&buf_2[0..])
136        && LittleEndian::read_u64(&buf_1[8..]) == LittleEndian::read_u64(&buf_2[8..])
137        && LittleEndian::read_u64(&buf_1[16..]) == LittleEndian::read_u64(&buf_2[16..])
138        && LittleEndian::read_u64(&buf_1[24..]) == LittleEndian::read_u64(&buf_2[24..])
139    {
140        return true;
141    }
142
143    return false;
144}
145
146/// Zeroize a buffer
147///
148/// Pay attention to the GUARD_ID parameter.
149/// This should be unique on every call, through the entire hook code.
150/// Otherwise you will encounter guard violation during the execution of your hook.
151#[inline(always)]
152pub fn buffer_zeroize<const GUARD_ID: u32>(buf: &mut [u8]) {
153    let buf_len = buf.len();
154    // guarded loop
155    let mut i = 0;
156    while {
157        _g(GUARD_ID, buf_len as u32 + 1);
158        i < buf_len
159    } {
160        buf[0] = 0;
161        i += 1;
162    }
163}
164
165/// Checks whether the transaction is outgoing
166///
167/// Pay attention to the GUARD_ID parameter.
168/// This should be unique on every call, through the entire hook code.
169/// Otherwise you will encounter guard violation during the execution of your hook.
170#[inline(always)]
171pub fn is_txn_outgoing<const GUARD_ID: u32>(
172    hook_acc_id: &mut [u8],
173    otnx_acc_id: &mut [u8],
174) -> Result<bool> {
175    match hook_account(hook_acc_id) {
176        Err(e) => return Err(e),
177        Ok(_) => {}
178    }
179
180    match otxn_field(otnx_acc_id, FieldId::Account) {
181        Err(e) => return Err(e),
182        Ok(_) => {}
183    }
184
185    Ok(is_buffer_equal_20(&hook_acc_id[..], &otnx_acc_id[..]))
186}
187
188/// Checks whether the transaction is ingoing
189///
190/// Pay attention to the GUARD_ID parameter.
191/// This should be unique on every call, through the entire hook code.
192/// Otherwise you will encounter guard violation during the execution of your hook.
193#[inline(always)]
194pub fn is_txn_ingoing<const GUARD_ID: u32>(
195    hook_acc_id: &mut [u8],
196    otnx_acc_id: &mut [u8],
197) -> Result<bool> {
198    match is_txn_outgoing::<GUARD_ID>(hook_acc_id, otnx_acc_id) {
199        Err(e) => Err(e),
200        Ok(res) => Ok(!res),
201    }
202}
203
204/// Convert amount to drops
205#[inline(always)]
206pub const fn amount_to_drops(amount_buf: &[u8]) -> Result<u64> {
207    if (amount_buf[0] >> 7) == 1 {
208        return Err(Error::InternalError);
209    }
210
211    Ok((((amount_buf[0] as u64) & 0xb00111111) << 56)
212        + ((amount_buf[1] as u64) << 48)
213        + ((amount_buf[2] as u64) << 40)
214        + ((amount_buf[3] as u64) << 32)
215        + ((amount_buf[4] as u64) << 24)
216        + ((amount_buf[5] as u64) << 16)
217        + ((amount_buf[6] as u64) << 8)
218        + (amount_buf[7] as u64))
219}
220
221/// Convert amount to drops
222#[inline(always)]
223pub const fn drops_to_amount(drops: u64) -> Result<NativeAmount> {
224    if (drops >> 63) == 1 {
225        return Err(Error::InternalError);
226    }
227
228    let mut out: NativeAmount = [0; 8];
229    out[0] = 0b01000000 + ((drops >> 56) & 0b00111111) as u8;
230    out[1] = ((drops >> 48) & 0xFF) as u8;
231    out[2] = ((drops >> 40) & 0xFF) as u8;
232    out[3] = ((drops >> 32) & 0xFF) as u8;
233    out[4] = ((drops >> 24) & 0xFF) as u8;
234    out[5] = ((drops >> 16) & 0xFF) as u8;
235    out[6] = ((drops >> 8) & 0xFF) as u8;
236    out[7] = ((drops >> 0) & 0xFF) as u8;
237    Ok(out)
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    #[test]
244    fn is_buffer_equal_20_test() {
245        const ACCOUNT_ID: AccountId = [
246            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
247        ];
248        const ACCOUNT_ID_2: AccountId = [
249            0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
250        ];
251        const ACCOUNT_ID_3: [u8; 21] = [
252            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
253        ];
254        assert_eq!(is_buffer_equal_20(&ACCOUNT_ID, &ACCOUNT_ID), true);
255        assert_eq!(is_buffer_equal_20(&ACCOUNT_ID, &ACCOUNT_ID_2), false);
256        assert_eq!(is_buffer_equal_20(&ACCOUNT_ID, &ACCOUNT_ID_3), true);
257    }
258
259    #[test]
260    fn is_buffer_equal_32_test() {
261        const DATA_1: Hash = [
262            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
263            25, 26, 27, 28, 29, 30, 31, 32,
264        ];
265        const DATA_2: Hash = [
266            0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
267            25, 26, 27, 28, 29, 30, 31, 32,
268        ];
269        const DATA_3: [u8; 33] = [
270            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
271            25, 26, 27, 28, 29, 30, 31, 32, 33,
272        ];
273        assert_eq!(is_buffer_equal_32(&DATA_1, &DATA_1), true);
274        assert_eq!(is_buffer_equal_32(&DATA_1, &DATA_2), false);
275        assert_eq!(is_buffer_equal_32(&DATA_1, &DATA_3), true);
276    }
277
278    #[test]
279    fn amount_to_drops_test() {
280        let amount_buf: NativeAmount = [0b10000000, 0, 0, 0, 0, 0, 0, 0];
281        assert!(amount_to_drops(&amount_buf).is_err());
282        let amount_buf: NativeAmount = [0, 0, 0, 0, 0, 0, 0, 0];
283        match amount_to_drops(&amount_buf) {
284            Ok(amount) => assert_eq!(amount, 0),
285            Err(_) => (),
286        }
287        let amount_buf: NativeAmount = [0, 0, 0, 0, 0, 0, 0, 1];
288        match amount_to_drops(&amount_buf) {
289            Ok(amount) => assert_eq!(amount, 1),
290            Err(_) => (),
291        }
292        let amount_buf: NativeAmount = [0, 0, 0, 0, 0, 0, 0, 100];
293        match amount_to_drops(&amount_buf) {
294            Ok(amount) => assert_eq!(amount, 100),
295            Err(_) => (),
296        }
297        let amount_buf: NativeAmount = [0, 0, 0, 0, 0x05, 0xF5, 0xE1, 0];
298        match amount_to_drops(&amount_buf) {
299            Ok(amount) => assert_eq!(amount, 100000000),
300            Err(_) => (),
301        }
302        let amount_buf: NativeAmount = [0x01, 0x63, 0x45, 0x78, 0x5D, 0x8A, 0, 0];
303        match amount_to_drops(&amount_buf) {
304            Ok(amount) => assert_eq!(amount, 100000000000000000),
305            Err(_) => (),
306        }
307    }
308
309    #[test]
310    fn drops_to_amount_test() {
311        assert!(drops_to_amount(0x8000000000000000).is_err());
312        let amount_buf: NativeAmount = [0b01000000, 0, 0, 0, 0, 0, 0, 0];
313        match drops_to_amount(0) {
314            Ok(amount) => assert_eq!(amount, amount_buf),
315            Err(_) => (),
316        }
317        let amount_buf: NativeAmount = [0b01000000, 0, 0, 0, 0, 0, 0, 1];
318        match drops_to_amount(1) {
319            Ok(amount) => assert_eq!(amount, amount_buf),
320            Err(_) => (),
321        }
322        let amount_buf: NativeAmount = [0b01000000, 0, 0, 0, 0, 0, 0, 100];
323        match drops_to_amount(100) {
324            Ok(amount) => assert_eq!(amount, amount_buf),
325            Err(_) => (),
326        }
327        let amount_buf: NativeAmount = [0b01000000, 0, 0, 0, 0x05, 0xF5, 0xE1, 0];
328        match drops_to_amount(100000000) {
329            Ok(amount) => assert_eq!(amount, amount_buf),
330            Err(_) => (),
331        }
332        let amount_buf: NativeAmount = [0b01000000 | 0x01, 0x63, 0x45, 0x78, 0x5D, 0x8A, 0, 0];
333        match drops_to_amount(100000000000000000) {
334            Ok(amount) => assert_eq!(amount, amount_buf),
335            Err(_) => (),
336        }
337    }
338}