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    use crate::host::host_bindings_trait::MockHostBindings;
133    use crate::host::setup_mock;
134    use mockall::predicate::always;
135
136    #[test]
137    fn test_trace_amount_xrp() {
138        let mut mock = MockHostBindings::new();
139
140        let message = "Test XRP amount";
141
142        // Set up expectations for trace_amount call
143        mock.expect_trace_amount()
144            .with(always(), always(), always(), always())
145            .returning(move |_, msg_len, _, _| msg_len as i32);
146
147        let _guard = setup_mock(mock);
148
149        // Create a test XRP Amount
150        let amount = Amount::XRP {
151            num_drops: 1_000_000,
152        };
153
154        // Call trace_amount function
155        let result = trace_amount(message, &amount);
156
157        // Should return Ok with the message length
158        assert!(result.is_ok());
159        assert_eq!(result.unwrap(), message.len() as i32);
160    }
161
162    #[test]
163    fn test_trace_amount_mpt() {
164        let mut mock = MockHostBindings::new();
165
166        let message = "Test MPT amount";
167
168        // Set up expectations for trace_amount call
169        mock.expect_trace_amount()
170            .with(always(), always(), always(), always())
171            .returning(move |_, msg_len, _, _| msg_len as i32);
172
173        let _guard = setup_mock(mock);
174
175        // Create a test MPT Amount
176        use crate::core::types::account_id::AccountID;
177        use crate::core::types::mpt_id::MptId;
178
179        const VALUE: u64 = 500_000;
180        const SEQUENCE_NUM: u32 = 12345;
181        const ISSUER_BYTES: [u8; 20] = [1u8; 20];
182
183        let issuer = AccountID::from(ISSUER_BYTES);
184        let mpt_id = MptId::new(SEQUENCE_NUM, issuer);
185        let amount = Amount::MPT {
186            num_units: VALUE,
187            is_positive: true,
188            mpt_id,
189        };
190
191        // Call trace_amount function
192        let result = trace_amount(message, &amount);
193
194        // Should return Ok with the message length
195        assert!(result.is_ok());
196        assert_eq!(result.unwrap(), message.len() as i32);
197    }
198
199    #[test]
200    fn test_trace_amount_iou() {
201        let mut mock = MockHostBindings::new();
202
203        let message = "Test IOU amount";
204
205        // Set up expectations for trace_amount call
206        mock.expect_trace_amount()
207            .with(always(), always(), always(), always())
208            .returning(move |_, msg_len, _, _| msg_len as i32);
209
210        let _guard = setup_mock(mock);
211
212        // Create a test IOU Amount
213        use crate::core::types::account_id::AccountID;
214        use crate::core::types::currency::Currency;
215        use crate::core::types::opaque_float::OpaqueFloat;
216
217        let currency_bytes = [2u8; 20];
218        let issuer_bytes = [3u8; 20];
219        let amount_bytes = [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x39]; // Simple test float
220
221        let currency = Currency::from(currency_bytes);
222        let issuer = AccountID::from(issuer_bytes);
223        let amount = OpaqueFloat(amount_bytes);
224
225        let amount = Amount::IOU {
226            amount,
227            issuer,
228            currency,
229        };
230
231        // Call trace_amount function
232        let result = trace_amount(message, &amount);
233
234        // Should return Ok with the message length
235        assert!(result.is_ok());
236        assert_eq!(result.unwrap(), message.len() as i32);
237    }
238
239    #[test]
240    fn test_trace_amount_negative_xrp() {
241        let mut mock = MockHostBindings::new();
242
243        let message = "Test negative XRP amount";
244
245        // Set up expectations for trace_amount call
246        mock.expect_trace_amount()
247            .with(always(), always(), always(), always())
248            .returning(move |_, msg_len, _, _| msg_len as i32);
249
250        let _guard = setup_mock(mock);
251
252        // Create a test negative XRP Amount
253        let amount = Amount::XRP {
254            num_drops: -1_000_000,
255        };
256
257        // Call trace_amount function
258        let result = trace_amount(message, &amount);
259
260        // Should return Ok with the message length
261        assert!(result.is_ok());
262        assert_eq!(result.unwrap(), message.len() as i32);
263    }
264
265    #[test]
266    fn test_trace_bytes_format() {
267        // Test XRP format
268        let xrp_amount = Amount::XRP {
269            num_drops: 1_000_000,
270        };
271        let (_bytes, len) = xrp_amount.to_stamount_bytes();
272        assert_eq!(len, 48); // All Amount types should return 48 bytes
273
274        // Test specific fee amount (10 drops)
275        let fee_amount = Amount::XRP { num_drops: 10 };
276        let (bytes, len) = fee_amount.to_stamount_bytes();
277        assert_eq!(len, 48); // All Amount types should return 48 bytes
278
279        // Check the actual bytes for 10 drops
280        // Expected: just the raw drop amount (10)
281        let expected_bytes = [64, 0, 0, 0, 0, 0, 0, 10];
282        assert_eq!(&bytes[0..8], &expected_bytes);
283
284        // Test IOU format
285        use crate::core::types::account_id::AccountID;
286        use crate::core::types::currency::Currency;
287        use crate::core::types::opaque_float::OpaqueFloat;
288
289        let currency_bytes = [2u8; 20];
290        let issuer_bytes = [3u8; 20];
291        let amount_bytes = [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x39];
292
293        let iou_amount = Amount::IOU {
294            amount: OpaqueFloat(amount_bytes),
295            issuer: AccountID::from(issuer_bytes),
296            currency: Currency::from(currency_bytes),
297        };
298        let (bytes, len) = iou_amount.to_stamount_bytes();
299        assert_eq!(len, 48); // All Amount types should return 48 bytes
300        assert_eq!(&bytes[0..8], &amount_bytes); // Should match the opaque float bytes
301
302        // Test MPT format
303        use crate::core::types::mpt_id::MptId;
304
305        const VALUE: u64 = 500_000;
306        const SEQUENCE_NUM: u32 = 12345;
307        const ISSUER_BYTES: [u8; 20] = [1u8; 20];
308
309        let issuer = AccountID::from(ISSUER_BYTES);
310        let mpt_id = MptId::new(SEQUENCE_NUM, issuer);
311        let mpt_amount = Amount::MPT {
312            num_units: VALUE,
313            is_positive: true,
314            mpt_id,
315        };
316        let (bytes, len) = mpt_amount.to_stamount_bytes();
317        assert_eq!(len, 48); // All Amount types should return 48 bytes
318        assert_eq!(bytes[0], 0b_0110_0000); // Positive MPT prefix
319        assert_eq!(&bytes[1..9], &VALUE.to_be_bytes()); // Amount bytes
320    }
321}