wasm_host_simulator/
host_functions_wamr.rs

1#![allow(unused)]
2use crate::data_provider::{
3    DataProvider, HostError, RippledRoundingMode, XRPL_CONTRACT_DATA_SIZE, error_code_to_string,
4    unpack_locator,
5};
6use crate::decoding::{
7    _deserialize_issued_currency_amount, _serialize_issued_currency_value, ACCOUNT_ID_LEN,
8    CURRENCY_LEN, MPT_ID_LEN, decode_account_id,
9};
10use crate::hashing::{HASH256_LEN, LedgerNameSpace, index_hash, sha512_half};
11use crate::mock_data::{DataSource, Keylet};
12use bigdecimal::num_bigint::{BigInt, ToBigInt};
13use bigdecimal::num_traits::real::Real;
14use bigdecimal::{BigDecimal, ToPrimitive};
15use hex::decode;
16use log::{debug, warn};
17use num_traits::FromPrimitive;
18use wamr_rust_sdk::sys::{
19    wasm_exec_env_t, wasm_runtime_get_function_attachment, wasm_runtime_get_module_inst,
20    wasm_runtime_validate_native_addr,
21};
22use xrpl::core::addresscodec::utils::encode_base58;
23use xrpl_wasm_std::core::types::amount::token_amount::TokenAmount;
24use xrpld_number::{
25    FLOAT_NEGATIVE_ONE, FLOAT_ONE, Number, RoundingMode as NumberRoundingMode, XrplIouValue,
26};
27
28/// RAII guard for temporarily setting rounding mode
29/// Automatically restores the previous rounding mode when dropped
30struct RoundingModeGuard {
31    previous_mode: Option<NumberRoundingMode>,
32}
33
34impl RoundingModeGuard {
35    /// Set a new rounding mode and return a guard that will restore the previous mode
36    fn new(mode: NumberRoundingMode) -> Self {
37        let previous_mode = Number::get_rounding_mode();
38        Number::set_rounding_mode(mode);
39        Self {
40            previous_mode: Some(previous_mode),
41        }
42    }
43
44    /// Create a guard without changing the rounding mode (for conditional usage)
45    fn noop() -> Self {
46        Self {
47            previous_mode: None,
48        }
49    }
50}
51
52impl Drop for RoundingModeGuard {
53    fn drop(&mut self) {
54        if let Some(mode) = self.previous_mode {
55            Number::set_rounding_mode(mode);
56        }
57    }
58}
59
60/// Helper function to set rounding mode from WASM parameter and return RAII guard
61/// Returns a guard that will automatically restore the previous rounding mode
62fn set_rounding_mode_from_param(rounding_mode: i32) -> RoundingModeGuard {
63    if (0..=3).contains(&rounding_mode) {
64        let mode = match rounding_mode {
65            0 => NumberRoundingMode::ToNearest,
66            1 => NumberRoundingMode::TowardsZero,
67            2 => NumberRoundingMode::Downward,
68            3 => NumberRoundingMode::Upward,
69            _ => NumberRoundingMode::ToNearest, // Default fallback
70        };
71        RoundingModeGuard::new(mode)
72    } else {
73        RoundingModeGuard::noop()
74    }
75}
76
77const MAX_WASM_PARAM_LENGTH: usize = 1024;
78
79pub fn get_dp(env: wasm_exec_env_t) -> &'static mut DataProvider {
80    unsafe { &mut *(wasm_runtime_get_function_attachment(env) as *mut DataProvider) }
81}
82
83fn get_data(in_buf_ptr: *const u8, in_buf_len: usize) -> Vec<u8> {
84    let mut buffer = vec![0u8; in_buf_len];
85    unsafe {
86        std::ptr::copy_nonoverlapping(in_buf_ptr, buffer.as_mut_ptr(), in_buf_len);
87    }
88    buffer
89}
90
91fn get_keylet(in_buf_ptr: *const u8, in_buf_len: usize) -> Keylet {
92    get_data(in_buf_ptr, in_buf_len)
93}
94
95fn set_data(dp_res: i32, out_buf_ptr: *mut u8, data_to_write: Vec<u8>) {
96    if dp_res > 0 {
97        unsafe {
98            std::ptr::copy_nonoverlapping(data_to_write.as_ptr(), out_buf_ptr, data_to_write.len());
99        }
100    }
101}
102
103pub fn get_ledger_sqn(env: wasm_exec_env_t) -> i32 {
104    let data_provider = get_dp(env);
105    data_provider.get_ledger_sqn()
106}
107
108pub fn get_parent_ledger_time(
109    env: wasm_exec_env_t,
110    out_buf_ptr: *mut u8,
111    out_buf_cap: usize,
112) -> i32 {
113    let data_provider = get_dp(env);
114    data_provider.get_parent_ledger_time()
115}
116
117pub fn get_parent_ledger_hash(
118    env: wasm_exec_env_t,
119    out_buf_ptr: *mut u8,
120    out_buf_cap: usize,
121) -> i32 {
122    let data_provider = get_dp(env);
123    let dp_res = data_provider.get_parent_ledger_hash(out_buf_cap);
124    set_data(dp_res.0, out_buf_ptr, dp_res.1);
125    dp_res.0
126}
127
128pub fn cache_ledger_obj(
129    env: wasm_exec_env_t,
130    in_buf_ptr: *const u8,
131    in_buf_cap: usize,
132    cache_num: i32,
133) -> i32 {
134    let data_provider = get_dp(env);
135    let keylet = get_keylet(in_buf_ptr, in_buf_cap);
136    data_provider.slot_set(keylet, cache_num as usize)
137}
138
139pub fn get_tx_field(
140    env: wasm_exec_env_t,
141    field: i32,
142    out_buf_ptr: *mut u8,
143    out_buf_cap: usize,
144) -> i32 {
145    let data_provider = get_dp(env);
146    let dp_res = data_provider.get_field_value(DataSource::Tx, vec![field], out_buf_cap);
147    set_data(dp_res.0, out_buf_ptr, dp_res.1);
148    dp_res.0
149}
150
151pub fn get_current_ledger_obj_field(
152    env: wasm_exec_env_t,
153    field: i32,
154    out_buf_ptr: *mut u8,
155    out_buf_cap: usize,
156) -> i32 {
157    let data_provider = get_dp(env);
158    let dp_res =
159        data_provider.get_field_value(DataSource::CurrentLedgerObj, vec![field], out_buf_cap);
160    set_data(dp_res.0, out_buf_ptr, dp_res.1);
161    dp_res.0
162}
163
164pub fn get_ledger_obj_field(
165    env: wasm_exec_env_t,
166    slot: i32,
167    field: i32,
168    out_buf_ptr: *mut u8,
169    out_buf_cap: usize,
170) -> i32 {
171    let data_provider = get_dp(env);
172    let keylet = match data_provider.slot_get(slot as usize) {
173        None => return HostError::EmptySlot as i32,
174        Some(key) => key.clone(),
175    };
176    let dp_res = data_provider.get_field_value(
177        DataSource::KeyletLedgerObj(keylet),
178        vec![field],
179        out_buf_cap,
180    );
181
182    set_data(dp_res.0, out_buf_ptr, dp_res.1);
183    dp_res.0
184}
185
186pub fn get_tx_nested_field(
187    env: wasm_exec_env_t,
188    in_buf_ptr: *const u8,
189    in_buf_len: usize,
190    out_buf_ptr: *mut u8,
191    out_buf_cap: usize,
192) -> i32 {
193    let data_provider = get_dp(env);
194    let data = get_data(in_buf_ptr, in_buf_len);
195    let idx_fields: Vec<i32> = match unpack_locator(data) {
196        Ok(fields) => fields,
197        Err(host_err) => return host_err as i32,
198    };
199
200    let dp_res = data_provider.get_field_value(DataSource::Tx, idx_fields, out_buf_cap);
201    set_data(dp_res.0, out_buf_ptr, dp_res.1);
202    dp_res.0
203}
204
205pub fn get_current_ledger_obj_nested_field(
206    env: wasm_exec_env_t,
207    in_buf_ptr: *const u8,
208    in_buf_len: usize,
209    out_buf_ptr: *mut u8,
210    out_buf_cap: usize,
211) -> i32 {
212    let data_provider = get_dp(env);
213    let data = get_data(in_buf_ptr, in_buf_len);
214    let idx_fields: Vec<i32> = match unpack_locator(data) {
215        Ok(fields) => fields,
216        Err(host_err) => return host_err as i32,
217    };
218
219    let dp_res =
220        data_provider.get_field_value(DataSource::CurrentLedgerObj, idx_fields, out_buf_cap);
221    set_data(dp_res.0, out_buf_ptr, dp_res.1);
222    dp_res.0
223}
224
225pub fn get_ledger_obj_nested_field(
226    env: wasm_exec_env_t,
227    slot: i32,
228    in_buf_ptr: *const u8,
229    in_buf_len: usize,
230    out_buf_ptr: *mut u8,
231    out_buf_cap: usize,
232) -> i32 {
233    let data_provider = get_dp(env);
234    let keylet = match data_provider.slot_get(slot as usize) {
235        None => return HostError::EmptySlot as i32,
236        Some(key) => key.clone(),
237    };
238
239    let data = get_data(in_buf_ptr, in_buf_len);
240    let idx_fields: Vec<i32> = match unpack_locator(data) {
241        Ok(fields) => fields,
242        Err(host_err) => return host_err as i32,
243    };
244
245    let dp_res =
246        data_provider.get_field_value(DataSource::KeyletLedgerObj(keylet), idx_fields, out_buf_cap);
247    set_data(dp_res.0, out_buf_ptr, dp_res.1);
248    dp_res.0
249}
250
251pub fn get_tx_array_len(env: wasm_exec_env_t, field: i32) -> i32 {
252    let data_provider = get_dp(env);
253    data_provider.get_array_len(DataSource::Tx, vec![field])
254}
255pub fn get_current_ledger_obj_array_len(env: wasm_exec_env_t, field: i32) -> i32 {
256    let data_provider = get_dp(env);
257    data_provider.get_array_len(DataSource::CurrentLedgerObj, vec![field])
258}
259pub fn get_ledger_obj_array_len(env: wasm_exec_env_t, slot: i32, field: i32) -> i32 {
260    let data_provider = get_dp(env);
261    let keylet = match data_provider.slot_get(slot as usize) {
262        None => return HostError::EmptySlot as i32,
263        Some(key) => key.clone(),
264    };
265    data_provider.get_array_len(DataSource::KeyletLedgerObj(keylet), vec![field])
266}
267pub fn get_tx_nested_array_len(
268    env: wasm_exec_env_t,
269    in_buf_ptr: *const u8,
270    in_buf_len: usize,
271) -> i32 {
272    let data_provider = get_dp(env);
273    let data = get_data(in_buf_ptr, in_buf_len);
274    let idx_fields: Vec<i32> = match unpack_locator(data) {
275        Ok(fields) => fields,
276        Err(host_err) => return host_err as i32,
277    };
278    data_provider.get_array_len(DataSource::Tx, idx_fields)
279}
280pub fn get_current_ledger_obj_nested_array_len(
281    env: wasm_exec_env_t,
282    in_buf_ptr: *const u8,
283    in_buf_len: usize,
284) -> i32 {
285    let data_provider = get_dp(env);
286    let data = get_data(in_buf_ptr, in_buf_len);
287    let idx_fields: Vec<i32> = match unpack_locator(data) {
288        Ok(fields) => fields,
289        Err(host_err) => return host_err as i32,
290    };
291    data_provider.get_array_len(DataSource::CurrentLedgerObj, idx_fields)
292}
293pub fn get_ledger_obj_nested_array_len(
294    env: wasm_exec_env_t,
295    slot: i32,
296    in_buf_ptr: *const u8,
297    in_buf_len: usize,
298) -> i32 {
299    let data_provider = get_dp(env);
300    let keylet = match data_provider.slot_get(slot as usize) {
301        None => return HostError::EmptySlot as i32,
302        Some(key) => key.clone(),
303    };
304
305    let data = get_data(in_buf_ptr, in_buf_len);
306    let idx_fields: Vec<i32> = match unpack_locator(data) {
307        Ok(fields) => fields,
308        Err(host_err) => return host_err as i32,
309    };
310
311    data_provider.get_array_len(DataSource::KeyletLedgerObj(keylet), idx_fields)
312}
313pub fn update_data(env: wasm_exec_env_t, in_buf_ptr: *const u8, in_buf_len: usize) -> i32 {
314    let data_provider = get_dp(env);
315    if in_buf_len > XRPL_CONTRACT_DATA_SIZE {
316        return HostError::DataFieldTooLarge as i32;
317    }
318    let data = get_data(in_buf_ptr, in_buf_len);
319    data_provider.set_current_ledger_obj_data(data);
320    0
321}
322pub fn compute_sha512_half(
323    _env: wasm_exec_env_t,
324    in_buf_ptr: *const u8,
325    in_buf_len: usize,
326    out_buf_ptr: *mut u8,
327    out_buf_cap: usize,
328) -> i32 {
329    if HASH256_LEN > out_buf_cap {
330        return HostError::BufferTooSmall as i32;
331    }
332    if in_buf_len > MAX_WASM_PARAM_LENGTH {
333        return HostError::DataFieldTooLarge as i32;
334    }
335    let data = get_data(in_buf_ptr, in_buf_len);
336    let hash_half = sha512_half(&data);
337    set_data(hash_half.len() as i32, out_buf_ptr, hash_half);
338    HASH256_LEN as i32
339}
340
341pub fn account_keylet(
342    _env: wasm_exec_env_t,
343    account_buf_ptr: *const u8,
344    account_buf_len: usize,
345    out_buf_ptr: *mut u8,
346    out_buf_cap: usize,
347) -> i32 {
348    if HASH256_LEN > out_buf_cap {
349        return HostError::BufferTooSmall as i32;
350    }
351    let data = get_data(account_buf_ptr, account_buf_len);
352    if ACCOUNT_ID_LEN != data.len() {
353        return HostError::InvalidAccount as i32;
354    }
355    let keylet_hash = index_hash(LedgerNameSpace::Account, &data);
356    // let hex_str = hex::encode(&keylet_hash);
357    // println!("Data (keylet_hash): {:?}", hex_str);
358    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
359    HASH256_LEN as i32
360}
361
362struct Issue {
363    currency: [u8; CURRENCY_LEN],
364    issuer: [u8; ACCOUNT_ID_LEN],
365}
366
367impl PartialEq for Issue {
368    fn eq(&self, other: &Self) -> bool {
369        self.currency == other.currency && self.issuer == other.issuer
370    }
371}
372
373impl Eq for Issue {}
374
375impl PartialOrd for Issue {
376    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
377        Some(self.cmp(other))
378    }
379}
380
381impl Ord for Issue {
382    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
383        match self.currency.cmp(&other.currency) {
384            std::cmp::Ordering::Equal => self.issuer.cmp(&other.issuer),
385            ord => ord,
386        }
387    }
388}
389
390fn parse_asset(asset_data: &[u8]) -> Result<Issue, HostError> {
391    // MPT Asset
392    if asset_data.len() == MPT_ID_LEN {
393        // MPT IDs not supported for AMMs yet
394        // return Ok(asset_data.to_vec());
395        return Err(HostError::InvalidParams);
396    }
397
398    // XRP Asset
399    if asset_data.len() == CURRENCY_LEN {
400        // Construct Issue { currency, xrpAccount }
401        // Check if native (XRP) - in C++: issue.native() must be true
402        return Ok(Issue {
403            currency: asset_data.try_into().unwrap(),
404            issuer: [0u8; ACCOUNT_ID_LEN],
405        });
406    }
407
408    // IOU Asset
409    if asset_data.len() == CURRENCY_LEN + ACCOUNT_ID_LEN {
410        // Construct Issue { currency, issuer }
411        let currency = &asset_data[..CURRENCY_LEN];
412        let issuer = &asset_data[CURRENCY_LEN..];
413        // Check if native (should NOT be native for IOU)
414        // If currency is all zeros and issuer is all zeros, it's native (invalid for IOU)
415        let is_native = currency.iter().all(|&b| b == 0) && issuer.iter().all(|&b| b == 0);
416        if is_native {
417            return Err(HostError::InvalidParams);
418        }
419        return Ok(Issue {
420            currency: currency.try_into().unwrap(),
421            issuer: issuer.try_into().unwrap(),
422        });
423    }
424
425    Err(HostError::InvalidParams)
426}
427
428pub fn amm_keylet(
429    _env: wasm_exec_env_t,
430    asset1_ptr: *const u8,
431    asset1_len: usize,
432    asset2_ptr: *const u8,
433    asset2_len: usize,
434    out_buf_ptr: *mut u8,
435    out_buf_cap: usize,
436) -> i32 {
437    if HASH256_LEN > out_buf_cap {
438        return HostError::BufferTooSmall as i32;
439    }
440    let asset1 = match parse_asset(&get_data(asset1_ptr, asset1_len)) {
441        Ok(a) => a,
442        Err(e) => return e as i32,
443    };
444    let asset2 = match parse_asset(&get_data(asset2_ptr, asset2_len)) {
445        Ok(a) => a,
446        Err(e) => return e as i32,
447    };
448    // Sort assets lexicographically to match C++ minmax logic
449    let (min_asset, mut max_asset) = if asset1 <= asset2 {
450        (asset1, asset2)
451    } else {
452        (asset2, asset1)
453    };
454    let mut data = min_asset.issuer.to_vec();
455    data.extend_from_slice(&min_asset.currency);
456    data.extend_from_slice(&max_asset.issuer);
457    data.extend_from_slice(&max_asset.currency);
458    let keylet_hash = index_hash(LedgerNameSpace::Amm, &data);
459    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
460    HASH256_LEN as i32
461}
462
463pub fn check_keylet(
464    _env: wasm_exec_env_t,
465    account_buf_ptr: *const u8,
466    account_buf_len: usize,
467    sequence: i32,
468    out_buf_ptr: *mut u8,
469    out_buf_cap: usize,
470) -> i32 {
471    if HASH256_LEN > out_buf_cap {
472        return HostError::BufferTooSmall as i32;
473    }
474    let mut data = get_data(account_buf_ptr, account_buf_len);
475    if ACCOUNT_ID_LEN != data.len() {
476        return HostError::InvalidAccount as i32;
477    }
478    let sqn_data = sequence.to_be_bytes();
479    data.extend_from_slice(&sqn_data);
480    let keylet_hash = index_hash(LedgerNameSpace::Check, &data);
481    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
482    HASH256_LEN as i32
483}
484
485#[allow(clippy::too_many_arguments)]
486pub fn credential_keylet(
487    _env: wasm_exec_env_t,
488    subject_ptr: *const u8,
489    subject_len: usize,
490    issuer_ptr: *const u8,
491    issuer_len: usize,
492    cred_type_ptr: *const u8,
493    cred_type_len: usize,
494    out_buf_ptr: *mut u8,
495    out_buf_cap: usize,
496) -> i32 {
497    if HASH256_LEN > out_buf_cap {
498        return HostError::BufferTooSmall as i32;
499    }
500    let subject = get_data(subject_ptr, subject_len); // check length?
501    let mut issuer = get_data(issuer_ptr, issuer_len);
502    if ACCOUNT_ID_LEN != issuer.len() {
503        return HostError::InvalidAccount as i32;
504    }
505    let mut cred_type = get_data(cred_type_ptr, cred_type_len); // check length?
506    let mut data = subject;
507    data.append(&mut issuer);
508    data.append(&mut cred_type);
509    let keylet_hash = index_hash(LedgerNameSpace::Credential, &data);
510    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
511    HASH256_LEN as i32
512}
513
514pub fn delegate_keylet(
515    _env: wasm_exec_env_t,
516    account_ptr: *const u8,
517    account_len: usize,
518    authorize_ptr: *const u8,
519    authorize_len: usize,
520    out_buf_ptr: *mut u8,
521    out_buf_cap: usize,
522) -> i32 {
523    if HASH256_LEN > out_buf_cap {
524        return HostError::BufferTooSmall as i32;
525    }
526    let mut data = get_data(account_ptr, account_len);
527    let mut authorized = get_data(authorize_ptr, authorize_len);
528    if ACCOUNT_ID_LEN != data.len() || ACCOUNT_ID_LEN != authorized.len() {
529        return HostError::InvalidAccount as i32;
530    }
531    data.append(&mut authorized);
532    let keylet_hash = index_hash(LedgerNameSpace::Delegate, &data);
533    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
534    HASH256_LEN as i32
535}
536
537pub fn deposit_preauth_keylet(
538    _env: wasm_exec_env_t,
539    account_ptr: *const u8,
540    account_len: usize,
541    authorize_ptr: *const u8,
542    authorize_len: usize,
543    out_buf_ptr: *mut u8,
544    out_buf_cap: usize,
545) -> i32 {
546    if HASH256_LEN > out_buf_cap {
547        return HostError::BufferTooSmall as i32;
548    }
549    let mut data = get_data(account_ptr, account_len);
550    let mut authorized = get_data(authorize_ptr, authorize_len);
551    if ACCOUNT_ID_LEN != data.len() || ACCOUNT_ID_LEN != authorized.len() {
552        return HostError::InvalidAccount as i32;
553    }
554    data.append(&mut authorized);
555    let keylet_hash = index_hash(LedgerNameSpace::DepositPreauth, &data);
556    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
557    HASH256_LEN as i32
558}
559
560pub fn did_keylet(
561    _env: wasm_exec_env_t,
562    account_ptr: *const u8,
563    account_len: usize,
564    out_buf_ptr: *mut u8,
565    out_buf_cap: usize,
566) -> i32 {
567    if HASH256_LEN > out_buf_cap {
568        return HostError::BufferTooSmall as i32;
569    }
570    let mut data = get_data(account_ptr, account_len);
571    if ACCOUNT_ID_LEN != data.len() {
572        return HostError::InvalidAccount as i32;
573    }
574    let keylet_hash = index_hash(LedgerNameSpace::Did, &data);
575    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
576    HASH256_LEN as i32
577}
578
579pub fn escrow_keylet(
580    _env: wasm_exec_env_t,
581    account_ptr: *const u8,
582    account_len: usize,
583    sequence: u32,
584    out_buf_ptr: *mut u8,
585    out_buf_cap: usize,
586) -> i32 {
587    if HASH256_LEN > out_buf_cap {
588        return HostError::BufferTooSmall as i32;
589    }
590    let mut data = get_data(account_ptr, account_len);
591    if ACCOUNT_ID_LEN != data.len() {
592        return HostError::InvalidAccount as i32;
593    }
594    let sqn_data = sequence.to_be_bytes();
595    data.extend_from_slice(&sqn_data);
596    let keylet_hash = index_hash(LedgerNameSpace::Escrow, &data);
597    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
598    HASH256_LEN as i32
599}
600
601#[allow(clippy::too_many_arguments)]
602pub fn line_keylet(
603    _env: wasm_exec_env_t,
604    account1_ptr: *const u8,
605    account1_len: usize,
606    account2_ptr: *const u8,
607    account2_len: usize,
608    currency_ptr: *const u8,
609    currency_len: usize,
610    out_buf_ptr: *mut u8,
611    out_buf_cap: usize,
612) -> i32 {
613    if HASH256_LEN > out_buf_cap {
614        return HostError::BufferTooSmall as i32;
615    }
616    let mut account1 = get_data(account1_ptr, account1_len);
617    let mut account2 = get_data(account2_ptr, account2_len);
618    let mut currency = get_data(currency_ptr, currency_len);
619    if ACCOUNT_ID_LEN != account1.len() || ACCOUNT_ID_LEN != account2.len() {
620        return HostError::InvalidAccount as i32;
621    }
622    if CURRENCY_LEN != currency.len() {
623        return HostError::InvalidParams as i32;
624    }
625    let mut data = account1;
626    data.append(&mut account2);
627    data.append(&mut currency);
628    let keylet_hash = index_hash(LedgerNameSpace::TrustLine, &data);
629    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
630    HASH256_LEN as i32
631}
632
633pub fn mpt_issuance_keylet(
634    _env: wasm_exec_env_t,
635    issuer_buf_ptr: *const u8,
636    issuer_buf_len: usize,
637    sequence: i32,
638    out_buf_ptr: *mut u8,
639    out_buf_cap: usize,
640) -> i32 {
641    if HASH256_LEN > out_buf_cap {
642        return HostError::BufferTooSmall as i32;
643    }
644    let mut account = get_data(issuer_buf_ptr, issuer_buf_len);
645    if ACCOUNT_ID_LEN != account.len() {
646        return HostError::InvalidAccount as i32;
647    }
648    // Write the sequence (big endian) followed by the account bytes into data
649    let sqn_data = (sequence as u32).to_be_bytes();
650    let mut mpt_id: Vec<u8> = sqn_data.to_vec();
651    mpt_id.append(&mut account);
652    let data = mpt_id;
653    let keylet_hash = index_hash(LedgerNameSpace::MptokenIssuance, &data);
654    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
655    HASH256_LEN as i32
656}
657
658pub fn mptoken_keylet(
659    _env: wasm_exec_env_t,
660    mpt_id_ptr: *const u8,
661    mpt_id_len: usize,
662    holder_ptr: *const u8,
663    holder_len: usize,
664    out_buf_ptr: *mut u8,
665    out_buf_cap: usize,
666) -> i32 {
667    if HASH256_LEN > out_buf_cap {
668        return HostError::BufferTooSmall as i32;
669    }
670    let mut mpt_id = get_data(mpt_id_ptr, mpt_id_len);
671    let mut holder = get_data(holder_ptr, holder_len);
672    if MPT_ID_LEN != mpt_id.len() {
673        return HostError::InvalidParams as i32;
674    }
675    if ACCOUNT_ID_LEN != holder.len() {
676        return HostError::InvalidAccount as i32;
677    }
678    let mpt_id_hash = index_hash(LedgerNameSpace::MptokenIssuance, &mpt_id);
679    let mut data = mpt_id_hash;
680    data.append(&mut holder);
681    let keylet_hash = index_hash(LedgerNameSpace::Mptoken, &data);
682    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
683    HASH256_LEN as i32
684}
685
686pub fn nft_offer_keylet(
687    _env: wasm_exec_env_t,
688    account_buf_ptr: *const u8,
689    account_buf_len: usize,
690    sequence: i32,
691    out_buf_ptr: *mut u8,
692    out_buf_cap: usize,
693) -> i32 {
694    if HASH256_LEN > out_buf_cap {
695        return HostError::BufferTooSmall as i32;
696    }
697    let mut data = get_data(account_buf_ptr, account_buf_len);
698    if ACCOUNT_ID_LEN != data.len() {
699        return HostError::InvalidAccount as i32;
700    }
701    let sqn_data = sequence.to_be_bytes();
702    data.extend_from_slice(&sqn_data);
703    let keylet_hash = index_hash(LedgerNameSpace::NftokenOffer, &data);
704    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
705    HASH256_LEN as i32
706}
707
708pub fn offer_keylet(
709    _env: wasm_exec_env_t,
710    account_buf_ptr: *const u8,
711    account_buf_len: usize,
712    sequence: i32,
713    out_buf_ptr: *mut u8,
714    out_buf_cap: usize,
715) -> i32 {
716    if HASH256_LEN > out_buf_cap {
717        return HostError::BufferTooSmall as i32;
718    }
719    let mut data = get_data(account_buf_ptr, account_buf_len);
720    if ACCOUNT_ID_LEN != data.len() {
721        return HostError::InvalidAccount as i32;
722    }
723    let sqn_data = sequence.to_be_bytes();
724    data.extend_from_slice(&sqn_data);
725    let keylet_hash = index_hash(LedgerNameSpace::Offer, &data);
726    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
727    HASH256_LEN as i32
728}
729
730pub fn oracle_keylet(
731    _env: wasm_exec_env_t,
732    account_ptr: *const u8,
733    account_len: usize,
734    document_id: u32,
735    out_buf_ptr: *mut u8,
736    out_buf_cap: usize,
737) -> i32 {
738    if HASH256_LEN > out_buf_cap {
739        return HostError::BufferTooSmall as i32;
740    }
741    let mut data = get_data(account_ptr, account_len);
742    if ACCOUNT_ID_LEN != data.len() {
743        return HostError::InvalidAccount as i32;
744    }
745    let sqn_data = document_id.to_be_bytes();
746    data.extend_from_slice(&sqn_data);
747    let keylet_hash = index_hash(LedgerNameSpace::Oracle, &data);
748    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
749    HASH256_LEN as i32
750}
751
752#[allow(clippy::too_many_arguments)]
753pub fn paychan_keylet(
754    _env: wasm_exec_env_t,
755    account_ptr: *const u8,
756    account_len: usize,
757    destination_ptr: *const u8,
758    destination_len: usize,
759    sequence: i32,
760    out_buf_ptr: *mut u8,
761    out_buf_cap: usize,
762) -> i32 {
763    if HASH256_LEN > out_buf_cap {
764        return HostError::BufferTooSmall as i32;
765    }
766    let mut data = get_data(account_ptr, account_len);
767    let mut destination = get_data(destination_ptr, destination_len);
768    if ACCOUNT_ID_LEN != data.len() || ACCOUNT_ID_LEN != destination.len() {
769        return HostError::InvalidAccount as i32;
770    }
771    let sqn_data = sequence.to_be_bytes();
772    data.append(&mut destination);
773    data.extend_from_slice(&sqn_data);
774    let keylet_hash = index_hash(LedgerNameSpace::XrpPaymentChannel, &data);
775    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
776    HASH256_LEN as i32
777}
778
779pub fn permissioned_domain_keylet(
780    _env: wasm_exec_env_t,
781    account_buf_ptr: *const u8,
782    account_buf_len: usize,
783    sequence: i32,
784    out_buf_ptr: *mut u8,
785    out_buf_cap: usize,
786) -> i32 {
787    if HASH256_LEN > out_buf_cap {
788        return HostError::BufferTooSmall as i32;
789    }
790    let mut data = get_data(account_buf_ptr, account_buf_len);
791    if ACCOUNT_ID_LEN != data.len() {
792        return HostError::InvalidAccount as i32;
793    }
794    let sqn_data = sequence.to_be_bytes();
795    data.extend_from_slice(&sqn_data);
796    let keylet_hash = index_hash(LedgerNameSpace::PermissionedDomain, &data);
797    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
798    HASH256_LEN as i32
799}
800
801pub fn signers_keylet(
802    _env: wasm_exec_env_t,
803    account_buf_ptr: *const u8,
804    account_buf_len: usize,
805    out_buf_ptr: *mut u8,
806    out_buf_cap: usize,
807) -> i32 {
808    if HASH256_LEN > out_buf_cap {
809        return HostError::BufferTooSmall as i32;
810    }
811    let mut data = get_data(account_buf_ptr, account_buf_len);
812    if ACCOUNT_ID_LEN != data.len() {
813        return HostError::InvalidAccount as i32;
814    }
815    let default_signer_list_id = 0u32;
816    let sid_data = default_signer_list_id.to_be_bytes();
817    data.extend_from_slice(&sid_data);
818    let keylet_hash = index_hash(LedgerNameSpace::SignerList, &data);
819    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
820    HASH256_LEN as i32
821}
822
823pub fn ticket_keylet(
824    _env: wasm_exec_env_t,
825    account_buf_ptr: *const u8,
826    account_buf_len: usize,
827    sequence: i32,
828    out_buf_ptr: *mut u8,
829    out_buf_cap: usize,
830) -> i32 {
831    if HASH256_LEN > out_buf_cap {
832        return HostError::BufferTooSmall as i32;
833    }
834    let mut data = get_data(account_buf_ptr, account_buf_len);
835    if ACCOUNT_ID_LEN != data.len() {
836        return HostError::InvalidAccount as i32;
837    }
838    let sqn_data = sequence.to_be_bytes();
839    data.extend_from_slice(&sqn_data);
840    let keylet_hash = index_hash(LedgerNameSpace::Ticket, &data);
841    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
842    HASH256_LEN as i32
843}
844
845pub fn vault_keylet(
846    _env: wasm_exec_env_t,
847    account_buf_ptr: *const u8,
848    account_buf_len: usize,
849    sequence: i32,
850    out_buf_ptr: *mut u8,
851    out_buf_cap: usize,
852) -> i32 {
853    if HASH256_LEN > out_buf_cap {
854        return HostError::BufferTooSmall as i32;
855    }
856    let mut data = get_data(account_buf_ptr, account_buf_len);
857    if ACCOUNT_ID_LEN != data.len() {
858        return HostError::InvalidAccount as i32;
859    }
860    let sqn_data = sequence.to_be_bytes();
861    data.extend_from_slice(&sqn_data);
862    let keylet_hash = index_hash(LedgerNameSpace::Vault, &data);
863    set_data(keylet_hash.len() as i32, out_buf_ptr, keylet_hash);
864    HASH256_LEN as i32
865}
866
867pub fn get_nft(
868    env: wasm_exec_env_t,
869    owner_ptr: *const u8,
870    owner_len: usize,
871    nft_id_ptr: *const u8,
872    nft_id_len: usize,
873    out_buf_ptr: *mut u8,
874    out_buf_cap: usize,
875) -> i32 {
876    let data_provider = get_dp(env);
877    let owner_id = get_data(owner_ptr, owner_len);
878    if ACCOUNT_ID_LEN != owner_id.len() {
879        return HostError::InvalidAccount as i32;
880    }
881    let nft_id = get_data(nft_id_ptr, nft_id_len);
882    if HASH256_LEN != nft_id.len() {
883        return HostError::InvalidParams as i32;
884    }
885    let dp_res = data_provider.get_nft_uri(&nft_id, &owner_id, out_buf_cap);
886    set_data(dp_res.0, out_buf_ptr, dp_res.1);
887    dp_res.0
888}
889
890fn unpack_in_float(env: wasm_exec_env_t, in_buf: *const u8) -> Result<Number, HostError> {
891    let bytes: [u8; 8] = unsafe {
892        let inst = wasm_runtime_get_module_inst(env);
893        if !wasm_runtime_validate_native_addr(inst, in_buf as *mut ::core::ffi::c_void, 8) {
894            return Err(HostError::PointerOutOfBound);
895        }
896        match std::slice::from_raw_parts(in_buf, 8).try_into() {
897            Ok(bytes) => bytes,
898            Err(_) => return Err(HostError::InvalidFloatInput),
899        }
900    };
901
902    Number::from_xrpl_iou_value(bytes).map_err(|_| HostError::InvalidFloatInput)
903}
904
905fn pack_out_float(number: Number, env: wasm_exec_env_t, out_buf: *mut u8) -> i32 {
906    // Convert Number directly to XRPL IOU format
907    let bytes = match number.to_xrpl_iou_value() {
908        Ok(bytes) => bytes,
909        Err(_) => return HostError::InvalidFloatComputation as i32,
910    };
911
912    unsafe {
913        let inst = wasm_runtime_get_module_inst(env);
914        if !wasm_runtime_validate_native_addr(inst, out_buf as *mut ::core::ffi::c_void, 8) {
915            return HostError::PointerOutOfBound as i32;
916        }
917        std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_buf, 8);
918    }
919
920    8
921}
922
923#[allow(clippy::too_many_arguments)]
924pub fn float_add(
925    env: wasm_exec_env_t,
926    in_buff1: *const u8,
927    in_buff1_len: usize,
928    in_buff2: *const u8,
929    in_buff2_len: usize,
930    out_buff: *mut u8,
931    out_buff_len: usize,
932    rounding_mode: i32,
933) -> i32 {
934    let _rounding_guard = set_rounding_mode_from_param(rounding_mode);
935
936    let n1 = match unpack_in_float(env, in_buff1) {
937        Ok(val) => val,
938        Err(e) => return e as i32,
939    };
940    let n2 = match unpack_in_float(env, in_buff2) {
941        Ok(val) => val,
942        Err(e) => return e as i32,
943    };
944    let result = match (&n1 + &n2) {
945        Ok(r) => r,
946        Err(_) => return HostError::InvalidFloatComputation as i32,
947    };
948
949    pack_out_float(result, env, out_buff)
950}
951
952pub fn float_from_int(
953    env: wasm_exec_env_t,
954    in_int: i64,
955    out_buf: *mut u8,
956    out_buff_len: usize,
957    rounding_mode: i32,
958) -> i32 {
959    let _rounding_guard = set_rounding_mode_from_param(rounding_mode);
960
961    let number = match Number::from_i64(in_int) {
962        Ok(n) => n,
963        Err(_) => return HostError::InvalidFloatComputation as i32,
964    };
965
966    pack_out_float(number, env, out_buf)
967}
968
969pub fn float_from_uint(
970    env: wasm_exec_env_t,
971    in_uint_ptr: *const u8,
972    in_uint_len: usize,
973    out_buff: *mut u8,
974    out_buff_len: usize,
975    rounding_mode: i32,
976) -> i32 {
977    let _rounding_guard = set_rounding_mode_from_param(rounding_mode);
978
979    let v: u64 = unsafe {
980        let inst = wasm_runtime_get_module_inst(env);
981        if !wasm_runtime_validate_native_addr(inst, in_uint_ptr as *mut ::core::ffi::c_void, 8) {
982            return HostError::PointerOutOfBound as i32;
983        }
984        let bytes: [u8; 8] = match std::slice::from_raw_parts(in_uint_ptr, 8).try_into() {
985            Ok(bytes) => bytes,
986            Err(_) => return HostError::InvalidFloatInput as i32,
987        };
988        u64::from_le_bytes(bytes)
989    };
990
991    // Convert u64 to i64 safely, checking for overflow
992    let signed_val = if v <= i64::MAX as u64 {
993        v as i64
994    } else {
995        return HostError::InvalidFloatComputation as i32;
996    };
997
998    let number = match Number::from_i64(signed_val) {
999        Ok(n) => n,
1000        Err(_) => return HostError::InvalidFloatComputation as i32,
1001    };
1002
1003    pack_out_float(number, env, out_buff)
1004}
1005
1006pub fn float_set(
1007    env: wasm_exec_env_t,
1008    exponent: i32,
1009    mantissa: i64,
1010    out_buff: *mut u8,
1011    out_buff_len: usize,
1012    rounding_mode: i32,
1013) -> i32 {
1014    let _rounding_guard = set_rounding_mode_from_param(rounding_mode);
1015
1016    let number = match Number::from_mantissa_exponent(mantissa, exponent) {
1017        Ok(n) => n,
1018        Err(_) => return HostError::InvalidFloatComputation as i32,
1019    };
1020
1021    pack_out_float(number, env, out_buff)
1022}
1023
1024pub fn float_compare(
1025    env: wasm_exec_env_t,
1026    in_buff1: *const u8,
1027    in_buff1_len: usize,
1028    in_buff2: *const u8,
1029    in_buff2_len: usize,
1030) -> i32 {
1031    let n1 = match unpack_in_float(env, in_buff1) {
1032        Ok(val) => val,
1033        Err(e) => return e as i32,
1034    };
1035    let n2 = match unpack_in_float(env, in_buff2) {
1036        Ok(val) => val,
1037        Err(e) => return e as i32,
1038    };
1039
1040    match n1.cmp(&n2) {
1041        std::cmp::Ordering::Equal => 0,
1042        std::cmp::Ordering::Greater => 1,
1043        std::cmp::Ordering::Less => 2,
1044    }
1045}
1046
1047#[allow(clippy::too_many_arguments)]
1048pub fn float_subtract(
1049    env: wasm_exec_env_t,
1050    in_buff1: *const u8,
1051    in_buff1_len: usize,
1052    in_buff2: *const u8,
1053    in_buff2_len: usize,
1054    out_buff: *mut u8,
1055    out_buff_len: usize,
1056    rounding_mode: i32,
1057) -> i32 {
1058    let _rounding_guard = set_rounding_mode_from_param(rounding_mode);
1059
1060    let n1 = match unpack_in_float(env, in_buff1) {
1061        Ok(val) => val,
1062        Err(e) => return e as i32,
1063    };
1064    let n2 = match unpack_in_float(env, in_buff2) {
1065        Ok(val) => val,
1066        Err(e) => return e as i32,
1067    };
1068
1069    let result = match (&n1 - &n2) {
1070        Ok(r) => r,
1071        Err(_) => return HostError::InvalidFloatComputation as i32,
1072    };
1073
1074    pack_out_float(result, env, out_buff)
1075}
1076
1077#[allow(clippy::too_many_arguments)]
1078pub fn float_multiply(
1079    env: wasm_exec_env_t,
1080    in_buff1: *const u8,
1081    in_buff1_len: usize,
1082    in_buff2: *const u8,
1083    in_buff2_len: usize,
1084    out_buff: *mut u8,
1085    out_buff_len: usize,
1086    rounding_mode: i32,
1087) -> i32 {
1088    let _rounding_guard = set_rounding_mode_from_param(rounding_mode);
1089
1090    let n1 = match unpack_in_float(env, in_buff1) {
1091        Ok(val) => val,
1092        Err(e) => return e as i32,
1093    };
1094    let n2 = match unpack_in_float(env, in_buff2) {
1095        Ok(val) => val,
1096        Err(e) => return e as i32,
1097    };
1098
1099    let result = match (&n1 * &n2) {
1100        Ok(r) => r,
1101        Err(_) => return HostError::InvalidFloatComputation as i32,
1102    };
1103
1104    pack_out_float(result, env, out_buff)
1105}
1106
1107#[allow(clippy::too_many_arguments)]
1108pub fn float_divide(
1109    env: wasm_exec_env_t,
1110    in_buff1: *const u8,
1111    in_buff1_len: usize,
1112    in_buff2: *const u8,
1113    in_buff2_len: usize,
1114    out_buff: *mut u8,
1115    out_buff_len: usize,
1116    rounding_mode: i32,
1117) -> i32 {
1118    let _rounding_guard = set_rounding_mode_from_param(rounding_mode);
1119
1120    let n1 = match unpack_in_float(env, in_buff1) {
1121        Ok(val) => val,
1122        Err(e) => return e as i32,
1123    };
1124    let n2 = match unpack_in_float(env, in_buff2) {
1125        Ok(val) => val,
1126        Err(e) => return e as i32,
1127    };
1128
1129    let result = match (&n1 / &n2) {
1130        Ok(r) => r,
1131        Err(_) => return HostError::InvalidFloatComputation as i32,
1132    };
1133
1134    pack_out_float(result, env, out_buff)
1135}
1136
1137pub fn float_pow(
1138    env: wasm_exec_env_t,
1139    in_buff: *const u8,
1140    in_buff_len: usize,
1141    in_int: i32,
1142    out_buff: *mut u8,
1143    out_buff_len: usize,
1144    rounding_mode: i32,
1145) -> i32 {
1146    let _rounding_guard = set_rounding_mode_from_param(rounding_mode);
1147
1148    let n = match unpack_in_float(env, in_buff) {
1149        Ok(val) => val,
1150        Err(e) => return e as i32,
1151    };
1152
1153    if in_int < 0 {
1154        return HostError::InvalidParams as i32;
1155    }
1156
1157    // Check for 0^0 case
1158    if n.is_zero() && in_int == 0 {
1159        return HostError::InvalidParams as i32;
1160    }
1161
1162    let result = match n.pow(in_int as u32) {
1163        Ok(r) => r,
1164        Err(_) => return HostError::InvalidFloatComputation as i32,
1165    };
1166
1167    pack_out_float(result, env, out_buff)
1168}
1169
1170pub fn float_root(
1171    env: wasm_exec_env_t,
1172    in_buff: *const u8,
1173    in_buff_len: usize,
1174    in_int: i32,
1175    out_buff: *mut u8,
1176    out_buff_len: usize,
1177    rounding_mode: i32,
1178) -> i32 {
1179    let _rounding_guard = set_rounding_mode_from_param(rounding_mode);
1180
1181    let n = match unpack_in_float(env, in_buff) {
1182        Ok(val) => val,
1183        Err(e) => return e as i32,
1184    };
1185
1186    if in_int <= 0 {
1187        return HostError::InvalidParams as i32;
1188    }
1189
1190    let result = match n.root(in_int as u32) {
1191        Ok(r) => r,
1192        Err(_) => return HostError::InvalidFloatComputation as i32,
1193    };
1194
1195    pack_out_float(result, env, out_buff)
1196}
1197
1198pub fn float_log(
1199    env: wasm_exec_env_t,
1200    in_buff: *const u8,
1201    in_buff_len: usize,
1202    out_buff: *mut u8,
1203    out_buff_len: usize,
1204    rounding_mode: i32,
1205) -> i32 {
1206    let _rounding_guard = set_rounding_mode_from_param(rounding_mode);
1207
1208    let n = match unpack_in_float(env, in_buff) {
1209        Ok(val) => val,
1210        Err(e) => return e as i32,
1211    };
1212
1213    let result = match n.log10() {
1214        Ok(r) => r,
1215        Err(_) => return HostError::InvalidFloatComputation as i32,
1216    };
1217
1218    pack_out_float(result, env, out_buff)
1219}
1220
1221///////////////////////////////////////////////////////////////////////////////
1222
1223fn read_utf8_from_wasm(msg_read_ptr: *const u8, msg_read_len: usize) -> Option<String> {
1224    String::from_utf8(get_data(msg_read_ptr, msg_read_len)).ok()
1225}
1226fn read_hex_from_wasm(
1227    data_read_ptr: *const u8,
1228    data_read_len: usize,
1229    data_as_hex: bool,
1230) -> Option<String> {
1231    if data_as_hex {
1232        // Read the data from memory.
1233        let bytes_vec: Vec<u8> = get_data(data_read_ptr, data_read_len);
1234        let mut final_hex_string = "0x".to_owned();
1235        let hex_data = hex::encode_upper(&bytes_vec);
1236        final_hex_string.push_str(hex_data.as_str());
1237        Some(final_hex_string)
1238    } else {
1239        read_utf8_from_wasm(data_read_ptr, data_read_len)
1240    }
1241}
1242
1243pub fn trace(
1244    _env: wasm_exec_env_t,
1245    msg_read_ptr: *const u8,
1246    msg_read_len: usize,
1247    data_read_ptr: *const u8,
1248    data_read_len: usize,
1249    data_as_hex: i32,
1250) -> i32 {
1251    // Don't need to check number of inputs or types since these will manifest at runtime and
1252    // cancel execution of the contract.
1253
1254    if msg_read_len > MAX_WASM_PARAM_LENGTH || data_read_len > MAX_WASM_PARAM_LENGTH {
1255        return HostError::DataFieldTooLarge as i32;
1256    }
1257
1258    let data_as_hex = {
1259        match data_as_hex {
1260            0 => false,
1261            1 => true,
1262            // If an invalid value is supplied, assume `true`
1263            _ => true,
1264        }
1265    };
1266
1267    debug!(
1268        "trace() params: msg_read_ptr={:?} msg_read_len={} data_read_ptr={:?} data_read_len={}",
1269        msg_read_ptr, msg_read_len, data_read_ptr, data_read_len
1270    );
1271
1272    let Some(message) = read_utf8_from_wasm(msg_read_ptr, msg_read_len) else {
1273        return HostError::InvalidDecoding as i32;
1274    };
1275
1276    let Some(data_string) = read_hex_from_wasm(data_read_ptr, data_read_len, data_as_hex) else {
1277        return HostError::InvalidDecoding as i32;
1278    };
1279
1280    if data_read_len > 0 {
1281        println!(
1282            "WASM TRACE: {message} ({data_string} | {} data bytes)",
1283            data_read_len
1284        );
1285    } else {
1286        println!("WASM TRACE: {message}");
1287    }
1288
1289    (data_read_len + msg_read_len + 1) as i32
1290}
1291
1292pub fn trace_num(
1293    _env: wasm_exec_env_t,
1294    msg_read_ptr: *const u8,
1295    msg_read_len: usize,
1296    number: i64,
1297) -> i32 {
1298    // Don't need to check number of inputs or types since these will manifest at runtime and
1299    // cancel execution of the contract.
1300
1301    if msg_read_len > MAX_WASM_PARAM_LENGTH {
1302        return HostError::DataFieldTooLarge as i32;
1303    }
1304
1305    debug!(
1306        "trace() params: msg_read_ptr={:?} msg_read_len={} number={} ",
1307        msg_read_ptr, msg_read_len, number
1308    );
1309    let Some(message) = read_utf8_from_wasm(msg_read_ptr, msg_read_len) else {
1310        return HostError::InvalidDecoding as i32;
1311    };
1312
1313    if (number < 0) {
1314        let error_code_str = error_code_to_string(number);
1315        println!("WASM TRACE[ERROR]: {message} {error_code_str}");
1316    } else {
1317        println!("WASM TRACE: {message} {number}");
1318    }
1319    0
1320}
1321
1322pub fn trace_opaque_float(
1323    _env: wasm_exec_env_t,
1324    msg_read_ptr: *const u8,
1325    msg_read_len: usize,
1326    op_float: *const u8,
1327    float_len: usize,
1328) -> i32 {
1329    if msg_read_len > MAX_WASM_PARAM_LENGTH || float_len > MAX_WASM_PARAM_LENGTH {
1330        return HostError::DataFieldTooLarge as i32;
1331    }
1332    let bytes: [u8; 8] = unsafe {
1333        match std::slice::from_raw_parts(op_float, 8).try_into() {
1334            Ok(bytes) => bytes,
1335            Err(_) => return HostError::InvalidFloatInput as i32,
1336        }
1337    };
1338
1339    let f = match _deserialize_issued_currency_amount(bytes) {
1340        Ok(f) => f,
1341        Err(_) => return HostError::InvalidFloatInput as i32,
1342    };
1343
1344    debug!(
1345        "trace() params: msg_read_ptr={:?} msg_read_len={} float={} ",
1346        msg_read_ptr, msg_read_len, f
1347    );
1348    let Some(message) = read_utf8_from_wasm(msg_read_ptr, msg_read_len) else {
1349        return HostError::InvalidDecoding as i32;
1350    };
1351
1352    println!("WASM TRACE: {message} {f}");
1353    0
1354}
1355
1356pub fn trace_account(
1357    _env: wasm_exec_env_t,
1358    msg_read_ptr: *const u8,
1359    msg_read_len: usize,
1360    account_ptr: *const u8,
1361    account_len: usize,
1362) -> i32 {
1363    // Don't need to check number of inputs or types since these will manifest at runtime and
1364    // cancel execution of the contract.
1365
1366    if msg_read_len > MAX_WASM_PARAM_LENGTH || account_len > MAX_WASM_PARAM_LENGTH {
1367        return HostError::DataFieldTooLarge as i32;
1368    }
1369    if ACCOUNT_ID_LEN != account_len {
1370        return HostError::InvalidAccount as i32;
1371    }
1372
1373    debug!(
1374        "trace() params: msg_read_ptr={:?} msg_read_len={} account_ptr={:?} account_len={}",
1375        msg_read_ptr, msg_read_len, account_ptr, account_len
1376    );
1377
1378    let Some(message) = read_utf8_from_wasm(msg_read_ptr, msg_read_len) else {
1379        return HostError::InvalidDecoding as i32;
1380    };
1381
1382    let bytes: [u8; ACCOUNT_ID_LEN] = unsafe {
1383        match std::slice::from_raw_parts(account_ptr, account_len).try_into() {
1384            Ok(arr) => arr,
1385            Err(_) => return HostError::InvalidAccount as i32,
1386        }
1387    };
1388    let account_id = match encode_base58(&bytes, &[0x0], Some(20)) {
1389        Ok(val) => val,
1390        Err(_) => return HostError::InvalidAccount as i32,
1391    };
1392
1393    if account_len > 0 {
1394        println!(
1395            "WASM TRACE: {message} ({account_id} | {} data bytes)",
1396            account_len
1397        );
1398    } else {
1399        println!("WASM TRACE: {message}");
1400    }
1401
1402    (account_id.len() + msg_read_len + 1) as i32
1403}
1404
1405pub fn trace_amount(
1406    _env: wasm_exec_env_t,
1407    msg_read_ptr: *const u8,
1408    msg_read_len: usize,
1409    amount_ptr: *const u8,
1410    amount_len: usize,
1411) -> i32 {
1412    // Don't need to check number of inputs or types since these will manifest at runtime and
1413    // cancel execution of the contract.
1414
1415    if msg_read_len > MAX_WASM_PARAM_LENGTH || amount_len > MAX_WASM_PARAM_LENGTH {
1416        return HostError::DataFieldTooLarge as i32;
1417    }
1418
1419    // TokenAmount STAmount format is always 48 bytes
1420    const TOKEN_AMOUNT_SIZE: usize = 48;
1421    if amount_len != TOKEN_AMOUNT_SIZE {
1422        return HostError::InvalidParams as i32;
1423    }
1424
1425    debug!(
1426        "trace_amount() params: msg_read_ptr={:?} msg_read_len={} amount_ptr={:?} amount_len={}",
1427        msg_read_ptr, msg_read_len, amount_ptr, amount_len
1428    );
1429
1430    let Some(message) = read_utf8_from_wasm(msg_read_ptr, msg_read_len) else {
1431        return HostError::InvalidDecoding as i32;
1432    };
1433
1434    let amount_bytes: [u8; TOKEN_AMOUNT_SIZE] = unsafe {
1435        match std::slice::from_raw_parts(amount_ptr, amount_len).try_into() {
1436            Ok(arr) => arr,
1437            Err(_) => return HostError::InvalidParams as i32,
1438        }
1439    };
1440
1441    // Parse the STAmount format to determine token type and display appropriate info
1442    let amount_info = parse_stamount_for_display(&amount_bytes);
1443
1444    println!(
1445        "WASM TRACE: {message} ({amount_info} | {} amount bytes)",
1446        amount_len
1447    );
1448
1449    (amount_info.len() + msg_read_len + 1) as i32
1450}
1451
1452/// Parse STAmount bytes and format for display according to token type
1453/// Uses the actual logic from TokenAmount::from_bytes to ensure consistency
1454fn parse_stamount_for_display(bytes: &[u8; 48]) -> String {
1455    // Use the actual TokenAmount parsing logic from xrpl-wasm-std
1456    match TokenAmount::from_bytes(bytes) {
1457        Ok(token_amount) => format_token_amount_for_display(&token_amount),
1458        Err(_) => {
1459            // Fallback to the original logic for unknown formats
1460            format!(
1461                "Unknown amount format: 0x{}",
1462                hex::encode_upper(&bytes[0..8])
1463            )
1464        }
1465    }
1466}
1467
1468/// Format a TokenAmount for display
1469fn format_token_amount_for_display(token_amount: &TokenAmount) -> String {
1470    match token_amount {
1471        TokenAmount::XRP { num_drops } => {
1472            format!("XRP: {} drops", num_drops.abs())
1473        }
1474        TokenAmount::MPT {
1475            num_units,
1476            is_positive,
1477            mpt_id,
1478        } => {
1479            let sign_str = if *is_positive { "+" } else { "-" };
1480            let sequence = mpt_id.get_sequence_num();
1481            let issuer_bytes = mpt_id.get_issuer().0;
1482            let issuer = match encode_base58(&issuer_bytes, &[0x0], Some(20)) {
1483                Ok(addr) => addr,
1484                Err(_) => hex::encode_upper(issuer_bytes),
1485            };
1486            format!(
1487                "MPT: {}{} units, Sequence: {}, Issuer: {}",
1488                sign_str, num_units, sequence, issuer
1489            )
1490        }
1491        TokenAmount::IOU {
1492            amount,
1493            issuer,
1494            currency_code,
1495        } => {
1496            // Try to deserialize the float value for display
1497            let amount_str = match _deserialize_issued_currency_amount(amount.0) {
1498                Ok(value) => format!("{}", value),
1499                Err(_) => format!("0x{}", hex::encode_upper(amount.0)),
1500            };
1501
1502            let currency_str = format_currency_code(currency_code.as_bytes());
1503            let issuer_str = match encode_base58(&issuer.0, &[0x0], Some(20)) {
1504                Ok(addr) => addr,
1505                Err(_) => hex::encode_upper(issuer.0),
1506            };
1507
1508            format!(
1509                "IOU: {} {}, Issuer: {}",
1510                amount_str, currency_str, issuer_str
1511            )
1512        }
1513    }
1514}
1515
1516/// Format currency code for display (handles both standard 3-char codes and hex)
1517fn format_currency_code(currency_bytes: &[u8; 20]) -> String {
1518    // Check if it's a standard currency code (non-zero bytes at positions 12-14)
1519    if currency_bytes[0..12].iter().all(|&b| b == 0)
1520        && currency_bytes[15..20].iter().all(|&b| b == 0)
1521        && currency_bytes[12..15].iter().any(|&b| b != 0)
1522    {
1523        // Standard 3-character currency code
1524        let code_bytes = &currency_bytes[12..15];
1525        if let Ok(code_str) = std::str::from_utf8(code_bytes)
1526            && code_str.chars().all(|c| c.is_ascii_alphanumeric())
1527        {
1528            return code_str.to_string();
1529        }
1530    }
1531
1532    // Non-standard currency code, display as hex
1533    format!("0x{}", hex::encode_upper(currency_bytes))
1534}
1535
1536#[cfg(test)]
1537mod tests {
1538    use super::*;
1539    use xrpl_wasm_std::core::types::{
1540        account_id::AccountID,
1541        amount::{
1542            currency_code::CurrencyCode, mpt_id::MptId, opaque_float::OpaqueFloat,
1543            token_amount::TokenAmount,
1544        },
1545    };
1546
1547    #[test]
1548    fn test_parse_stamount_for_display_xrp() {
1549        // Test XRP amount using TokenAmount's to_stamount_bytes
1550        let xrp_amount = TokenAmount::XRP {
1551            num_drops: 1_000_000,
1552        };
1553        let (bytes, _) = xrp_amount.to_stamount_bytes();
1554
1555        let display_str = parse_stamount_for_display(&bytes);
1556        assert_eq!(display_str, "XRP: 1000000 drops");
1557    }
1558
1559    #[test]
1560    fn test_parse_stamount_for_display_mpt() {
1561        // Test MPT amount using TokenAmount's to_stamount_bytes
1562        let issuer = AccountID::from([0xAB; 20]);
1563        let mpt_id = MptId::new(12345, issuer);
1564        let mpt_amount = TokenAmount::MPT {
1565            num_units: 750_000,
1566            is_positive: true,
1567            mpt_id,
1568        };
1569        let (bytes, _) = mpt_amount.to_stamount_bytes();
1570
1571        let display_str = parse_stamount_for_display(&bytes);
1572        assert!(display_str.starts_with("MPT: +750000 units"));
1573        assert!(display_str.contains("Sequence: 12345"));
1574    }
1575
1576    #[test]
1577    fn test_parse_stamount_for_display_iou() {
1578        // Test IOU amount using TokenAmount's to_stamount_bytes
1579        let opaque_float = OpaqueFloat([0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
1580        let currency_code = CurrencyCode::from([0u8; 20]); // Standard currency code format
1581        let issuer = AccountID::from([0xCD; 20]);
1582
1583        let iou_amount = TokenAmount::IOU {
1584            amount: opaque_float,
1585            issuer,
1586            currency_code,
1587        };
1588        let (bytes, _) = iou_amount.to_stamount_bytes();
1589
1590        let display_str = parse_stamount_for_display(&bytes);
1591        assert!(display_str.starts_with("IOU:"));
1592        assert!(display_str.contains("Issuer:"));
1593    }
1594}