xrpl_wasm_stdlib/host/
trace.rs

1use crate::host::error_codes::match_result_code;
2
3use crate::core::types::account_id::AccountID;
4use crate::core::types::amount::Amount;
5use crate::host;
6use crate::host::Result;
7
8/// Data representation
9#[derive(Clone, Copy)]
10pub enum DataRepr {
11    /// As UTF-8
12    AsUTF8 = 0,
13    /// As hexadecimal
14    AsHex = 1,
15}
16
17/// Write the contents of a message to the xrpld trace log.
18///
19/// # Parameters
20/// * `msg`: A str ref pointing to an array of bytes containing UTF-8 characters.
21///
22/// # Returns
23///
24/// Returns an integer representing the result of the operation. A value of `0` or higher signifies
25/// the number of message bytes that were written to the trace function. Non-zero values indicate
26/// an error (e.g., incorrect buffer sizes).
27#[inline(always)] // <-- Inline because this function is very small
28pub fn trace(msg: &str) -> Result<i32> {
29    // Use an empty slice's pointer instead of null to satisfy Rust's safety requirements
30    // Even for zero-length slices, `slice::from_raw_parts` requires a non-null, aligned pointer
31    let empty_data: &[u8] = &[];
32
33    let result_code = unsafe {
34        host::trace(
35            msg.as_ptr(),
36            msg.len(),
37            empty_data.as_ptr(),
38            0usize,
39            DataRepr::AsUTF8 as _,
40        )
41    };
42
43    match_result_code(result_code, || result_code)
44}
45
46/// Write the contents of a message to the xrpld trace log.
47///
48/// # Parameters
49/// * `msg`: A str ref pointing to an array of bytes containing UTF-8 characters.
50///
51/// # Returns
52///
53/// Returns an integer representing the result of the operation. A value of `0` or higher signifies
54/// the number of message bytes that were written to the trace function. Non-zero values indicate
55/// an error (e.g., incorrect buffer sizes).
56#[inline(always)] // <-- Inline because this function is very small
57pub fn trace_data(msg: &str, data: &[u8], data_repr: DataRepr) -> Result<i32> {
58    let result_code = unsafe {
59        let data_ptr = data.as_ptr();
60        let data_len = data.len();
61        host::trace(msg.as_ptr(), msg.len(), data_ptr, data_len, data_repr as _)
62    };
63
64    match_result_code(result_code, || result_code)
65}
66
67/// Write the contents of a message, and a number, to the xrpld trace log.
68///
69/// # Parameters
70/// * `msg`: A str ref pointing to an array of bytes containing UTF-8 characters.
71/// * `number`: A number to emit into the trace logs.
72///
73/// # Returns
74///
75/// Returns an integer representing the result of the operation. A value of `0` or higher signifies
76/// the number of message bytes that were written to the trace function. Non-zero values indicate
77/// an error (e.g., incorrect buffer sizes).
78#[inline(always)]
79pub fn trace_num(msg: &str, number: i64) -> Result<i32> {
80    let result_code = unsafe { host::trace_num(msg.as_ptr(), msg.len(), number) };
81    match_result_code(result_code, || result_code)
82}
83
84#[inline(always)]
85pub fn trace_account_buf(msg: &str, account_id: &[u8; 20]) -> Result<i32> {
86    let result_code = unsafe {
87        host::trace_account(
88            msg.as_ptr(),
89            msg.len(),
90            account_id.as_ptr(),
91            account_id.len(),
92        )
93    };
94    match_result_code(result_code, || result_code)
95}
96
97#[inline(always)]
98pub fn trace_account(msg: &str, account_id: &AccountID) -> Result<i32> {
99    let result_code = unsafe {
100        host::trace_account(
101            msg.as_ptr(),
102            msg.len(),
103            account_id.0.as_ptr(),
104            account_id.0.len(),
105        )
106    };
107    match_result_code(result_code, || result_code)
108}
109
110#[inline(always)]
111pub fn trace_amount(msg: &str, amount: &Amount) -> Result<i32> {
112    // Convert Amount to the STAmount format expected by the host trace function
113    let (amount_bytes, len) = amount.to_stamount_bytes();
114
115    let result_code =
116        unsafe { host::trace_amount(msg.as_ptr(), msg.len(), amount_bytes.as_ptr(), len) };
117
118    match_result_code(result_code, || result_code)
119}
120
121/// Write a float to the XRPLD trace log
122#[inline(always)]
123pub fn trace_float(msg: &str, f: &[u8; 8]) -> Result<i32> {
124    let result_code = unsafe { host::trace_opaque_float(msg.as_ptr(), msg.len(), f.as_ptr(), 8) };
125    match_result_code(result_code, || result_code)
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use crate::core::types::amount::Amount;
132
133    #[test]
134    fn test_trace_amount_xrp() {
135        // Create a test XRP Amount
136        let amount = Amount::XRP {
137            num_drops: 1_000_000,
138        };
139        let message = "Test XRP amount";
140
141        // Call trace_amount function
142        let result = trace_amount(message, &amount);
143
144        // Should return Ok
145        assert!(result.is_ok());
146    }
147
148    #[test]
149    fn test_trace_amount_mpt() {
150        // Create a test MPT Amount
151        use crate::core::types::account_id::AccountID;
152        use crate::core::types::mpt_id::MptId;
153
154        const VALUE: u64 = 500_000;
155        const SEQUENCE_NUM: u32 = 12345;
156        const ISSUER_BYTES: [u8; 20] = [1u8; 20];
157
158        let issuer = AccountID::from(ISSUER_BYTES);
159        let mpt_id = MptId::new(SEQUENCE_NUM, issuer);
160        let amount = Amount::MPT {
161            num_units: VALUE,
162            is_positive: true,
163            mpt_id,
164        };
165
166        let message = "Test MPT amount";
167
168        // Call trace_amount function
169        let result = trace_amount(message, &amount);
170
171        // Should return Ok
172        assert!(result.is_ok());
173    }
174
175    #[test]
176    fn test_trace_amount_iou() {
177        // Create a test IOU Amount
178        use crate::core::types::account_id::AccountID;
179        use crate::core::types::currency::Currency;
180        use crate::core::types::opaque_float::OpaqueFloat;
181
182        let currency_bytes = [2u8; 20];
183        let issuer_bytes = [3u8; 20];
184        let amount_bytes = [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x39]; // Simple test float
185
186        let currency = Currency::from(currency_bytes);
187        let issuer = AccountID::from(issuer_bytes);
188        let amount = OpaqueFloat(amount_bytes);
189
190        let amount = Amount::IOU {
191            amount,
192            issuer,
193            currency,
194        };
195
196        let message = "Test IOU amount";
197
198        // Call trace_amount function
199        let result = trace_amount(message, &amount);
200
201        // Should return Ok
202        assert!(result.is_ok());
203    }
204
205    #[test]
206    fn test_trace_amount_negative_xrp() {
207        // Create a test negative XRP Amount
208        let amount = Amount::XRP {
209            num_drops: -1_000_000,
210        };
211        let message = "Test negative XRP amount";
212
213        // Call trace_amount function
214        let result = trace_amount(message, &amount);
215
216        // Should return Ok
217        assert!(result.is_ok());
218    }
219
220    #[test]
221    fn test_trace_bytes_format() {
222        // Test XRP format
223        let xrp_amount = Amount::XRP {
224            num_drops: 1_000_000,
225        };
226        let (_bytes, len) = xrp_amount.to_stamount_bytes();
227        assert_eq!(len, 48); // All Amount types should return 48 bytes
228
229        // Test specific fee amount (10 drops)
230        let fee_amount = Amount::XRP { num_drops: 10 };
231        let (bytes, len) = fee_amount.to_stamount_bytes();
232        assert_eq!(len, 48); // All Amount types should return 48 bytes
233
234        // Check the actual bytes for 10 drops
235        // Expected: just the raw drop amount (10)
236        let expected_bytes = [64, 0, 0, 0, 0, 0, 0, 10];
237        assert_eq!(&bytes[0..8], &expected_bytes);
238
239        // Test IOU format
240        use crate::core::types::account_id::AccountID;
241        use crate::core::types::currency::Currency;
242        use crate::core::types::opaque_float::OpaqueFloat;
243
244        let currency_bytes = [2u8; 20];
245        let issuer_bytes = [3u8; 20];
246        let amount_bytes = [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x39];
247
248        let iou_amount = Amount::IOU {
249            amount: OpaqueFloat(amount_bytes),
250            issuer: AccountID::from(issuer_bytes),
251            currency: Currency::from(currency_bytes),
252        };
253        let (bytes, len) = iou_amount.to_stamount_bytes();
254        assert_eq!(len, 48); // All Amount types should return 48 bytes
255        assert_eq!(&bytes[0..8], &amount_bytes); // Should match the opaque float bytes
256
257        // Test MPT format
258        use crate::core::types::mpt_id::MptId;
259
260        const VALUE: u64 = 500_000;
261        const SEQUENCE_NUM: u32 = 12345;
262        const ISSUER_BYTES: [u8; 20] = [1u8; 20];
263
264        let issuer = AccountID::from(ISSUER_BYTES);
265        let mpt_id = MptId::new(SEQUENCE_NUM, issuer);
266        let mpt_amount = Amount::MPT {
267            num_units: VALUE,
268            is_positive: true,
269            mpt_id,
270        };
271        let (bytes, len) = mpt_amount.to_stamount_bytes();
272        assert_eq!(len, 48); // All Amount types should return 48 bytes
273        assert_eq!(bytes[0], 0b_0110_0000); // Positive MPT prefix
274        assert_eq!(&bytes[1..9], &VALUE.to_be_bytes()); // Amount bytes
275    }
276}