Skip to main content

xrpl_common_stdlib/host/
trace.rs

1use crate::host;
2use crate::types::account_id::AccountID;
3use crate::types::amount::Amount;
4
5/// How the host should read the data buffer. Mirrors xrpld's `TraceDataType`; the discriminants
6/// are wire values, numbered from 1 so a zeroed `data_type` hits the host's invalid branch.
7#[derive(Clone, Copy)]
8#[repr(i32)]
9pub enum TraceDataType {
10    /// 8 little-endian bytes, printed as a signed decimal.
11    Int64 = 1,
12    /// 8 little-endian bytes, printed as an unsigned decimal.
13    Uint64 = 2,
14    /// 8 bytes holding an opaque float.
15    Xfloat = 3,
16    /// A 20-byte account ID, printed as base58.
17    Account = 4,
18    /// A serialized `STAmount`.
19    Amount = 5,
20    /// Raw bytes, hex-encoded by the host.
21    AsHex = 6,
22    /// Bytes printed verbatim as text.
23    AsText = 7,
24}
25
26/// Fire-and-forget: the host checks its log level, swallows every error, and drops the call
27/// silently when `msg.len() + data.len()` exceeds 1024 bytes.
28#[inline(always)]
29fn trace_impl(msg: &str, data_type: TraceDataType, data: &[u8]) {
30    unsafe {
31        host::trace(
32            msg.as_ptr(),
33            msg.len(),
34            data_type as i32,
35            data.as_ptr(),
36            data.len(),
37        )
38    }
39}
40
41/// Write the contents of a message to the xrpld trace log.
42///
43/// # Parameters
44/// * `msg`: A str ref pointing to an array of bytes containing UTF-8 characters.
45#[inline(always)] // <-- Inline because this function is very small
46pub fn trace(msg: &str) {
47    trace_impl(msg, TraceDataType::AsText, &[]);
48}
49
50/// Write a message and a data buffer to the xrpld trace log, hex-encoded by the host.
51///
52/// # Parameters
53/// * `msg`: A str ref pointing to an array of bytes containing UTF-8 characters.
54/// * `data`: The bytes to emit alongside `msg`.
55#[inline(always)] // <-- Inline because this function is very small
56pub fn trace_hex(msg: &str, data: &[u8]) {
57    trace_impl(msg, TraceDataType::AsHex, data);
58}
59
60/// Write a message and a data buffer to the xrpld trace log, printed verbatim as text.
61///
62/// # Parameters
63/// * `msg`: A str ref pointing to an array of bytes containing UTF-8 characters.
64/// * `data`: The bytes to emit alongside `msg`.
65#[inline(always)] // <-- Inline because this function is very small
66pub fn trace_text(msg: &str, data: &[u8]) {
67    trace_impl(msg, TraceDataType::AsText, data);
68}
69
70/// Write the contents of a message, and a number, to the xrpld trace log.
71///
72/// # Parameters
73/// * `msg`: A str ref pointing to an array of bytes containing UTF-8 characters.
74/// * `number`: A number to emit into the trace logs.
75#[inline(always)]
76pub fn trace_num(msg: &str, number: i64) {
77    trace_impl(msg, TraceDataType::Int64, &number.to_le_bytes());
78}
79
80/// Write the contents of a message, and an unsigned number, to the xrpld trace log. Use this
81/// over [`trace_num`] for values above [`i64::MAX`].
82///
83/// # Parameters
84/// * `msg`: A str ref pointing to an array of bytes containing UTF-8 characters.
85/// * `number`: A number to emit into the trace logs.
86#[inline(always)]
87pub fn trace_num_unsigned(msg: &str, number: u64) {
88    trace_impl(msg, TraceDataType::Uint64, &number.to_le_bytes());
89}
90
91#[inline(always)]
92pub fn trace_acct_buf(msg: &str, account_id: &[u8; 20]) {
93    trace_impl(msg, TraceDataType::Account, account_id);
94}
95
96#[inline(always)]
97pub fn trace_acct(msg: &str, account_id: &AccountID) {
98    trace_impl(msg, TraceDataType::Account, &account_id.0);
99}
100
101#[inline(always)]
102pub fn trace_amt(msg: &str, amount: &Amount) {
103    let (amount_bytes, len) = amount.to_stamount_bytes();
104
105    trace_impl(msg, TraceDataType::Amount, &amount_bytes[..len]);
106}
107
108/// Write a float to the xrpld trace log.
109#[inline(always)]
110pub fn trace_float(msg: &str, f: &[u8; 8]) {
111    trace_impl(msg, TraceDataType::Xfloat, f);
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::host::host_bindings_trait::MockHostBindings;
118    use crate::host::setup_mock;
119    use crate::types::amount::Amount;
120
121    #[test]
122    fn test_trace_amt_xrp() {
123        let mut mock = MockHostBindings::new();
124
125        let message = "Test XRP amount";
126
127        mock.expect_trace()
128            .withf(|_, _, data_type, _, _| *data_type == TraceDataType::Amount as i32)
129            .times(1)
130            .returning(|_, _, _, _, _| ());
131
132        let _guard = setup_mock(mock);
133
134        // Create a test XRP Amount
135        let amount = Amount::XRP {
136            num_drops: 1_000_000,
137        };
138
139        trace_amt(message, &amount);
140    }
141
142    #[test]
143    fn test_trace_amt_mpt() {
144        let mut mock = MockHostBindings::new();
145
146        let message = "Test MPT amount";
147
148        mock.expect_trace()
149            .withf(|_, _, data_type, _, _| *data_type == TraceDataType::Amount as i32)
150            .times(1)
151            .returning(|_, _, _, _, _| ());
152
153        let _guard = setup_mock(mock);
154
155        // Create a test MPT Amount
156        use crate::types::account_id::AccountID;
157        use crate::types::mpt_id::MptId;
158
159        const VALUE: u64 = 500_000;
160        const SEQUENCE_NUM: u32 = 12345;
161        const ISSUER_BYTES: [u8; 20] = [1u8; 20];
162
163        let issuer = AccountID::from(ISSUER_BYTES);
164        let mpt_id = MptId::new(SEQUENCE_NUM, issuer);
165        let amount = Amount::MPT {
166            num_units: VALUE,
167            is_positive: true,
168            mpt_id,
169        };
170
171        trace_amt(message, &amount);
172    }
173
174    #[test]
175    fn test_trace_amt_iou() {
176        let mut mock = MockHostBindings::new();
177
178        let message = "Test IOU amount";
179
180        mock.expect_trace()
181            .withf(|_, _, data_type, _, _| *data_type == TraceDataType::Amount as i32)
182            .times(1)
183            .returning(|_, _, _, _, _| ());
184
185        let _guard = setup_mock(mock);
186
187        // Create a test IOU Amount
188        use crate::types::account_id::AccountID;
189        use crate::types::currency::Currency;
190        use crate::types::iou_number::IOUNumber;
191
192        let currency_bytes = [2u8; 20];
193        let issuer_bytes = [3u8; 20];
194        let amount_bytes = [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x39]; // Simple test float
195
196        let currency = Currency::from(currency_bytes);
197        let issuer = AccountID::from(issuer_bytes);
198        let amount = IOUNumber(amount_bytes);
199
200        let amount = Amount::IOU {
201            amount,
202            issuer,
203            currency,
204        };
205
206        trace_amt(message, &amount);
207    }
208
209    #[test]
210    fn test_trace_amt_negative_xrp() {
211        let mut mock = MockHostBindings::new();
212
213        let message = "Test negative XRP amount";
214
215        mock.expect_trace()
216            .withf(|_, _, data_type, _, _| *data_type == TraceDataType::Amount as i32)
217            .times(1)
218            .returning(|_, _, _, _, _| ());
219
220        let _guard = setup_mock(mock);
221
222        // Create a test negative XRP Amount
223        let amount = Amount::XRP {
224            num_drops: -1_000_000,
225        };
226
227        trace_amt(message, &amount);
228    }
229
230    #[test]
231    fn test_trace_bytes_format() {
232        // Test XRP format
233        let xrp_amount = Amount::XRP {
234            num_drops: 1_000_000,
235        };
236        let (_bytes, len) = xrp_amount.to_stamount_bytes();
237        assert_eq!(len, 48); // All Amount types should return 48 bytes
238
239        // Test specific fee amount (10 drops)
240        let fee_amount = Amount::XRP { num_drops: 10 };
241        let (bytes, len) = fee_amount.to_stamount_bytes();
242        assert_eq!(len, 48); // All Amount types should return 48 bytes
243
244        // Check the actual bytes for 10 drops
245        // Expected: just the raw drop amount (10)
246        let expected_bytes = [64, 0, 0, 0, 0, 0, 0, 10];
247        assert_eq!(&bytes[0..8], &expected_bytes);
248
249        // Test IOU format
250        use crate::types::account_id::AccountID;
251        use crate::types::currency::Currency;
252        use crate::types::iou_number::IOUNumber;
253
254        let currency_bytes = [2u8; 20];
255        let issuer_bytes = [3u8; 20];
256        let amount_bytes = [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x39];
257
258        let iou_amount = Amount::IOU {
259            amount: IOUNumber(amount_bytes),
260            issuer: AccountID::from(issuer_bytes),
261            currency: Currency::from(currency_bytes),
262        };
263        let (bytes, len) = iou_amount.to_stamount_bytes();
264        assert_eq!(len, 48); // All Amount types should return 48 bytes
265        assert_eq!(&bytes[0..8], &amount_bytes); // Should match the opaque float bytes
266
267        // Test MPT format
268        use crate::types::mpt_id::MptId;
269
270        const VALUE: u64 = 500_000;
271        const SEQUENCE_NUM: u32 = 12345;
272        const ISSUER_BYTES: [u8; 20] = [1u8; 20];
273
274        let issuer = AccountID::from(ISSUER_BYTES);
275        let mpt_id = MptId::new(SEQUENCE_NUM, issuer);
276        let mpt_amount = Amount::MPT {
277            num_units: VALUE,
278            is_positive: true,
279            mpt_id,
280        };
281        let (bytes, len) = mpt_amount.to_stamount_bytes();
282        assert_eq!(len, 48); // All Amount types should return 48 bytes
283        assert_eq!(bytes[0], 0b_0110_0000); // Positive MPT prefix
284        assert_eq!(&bytes[1..9], &VALUE.to_be_bytes()); // Amount bytes
285    }
286}