diff --git a/crates/xrpl-host-functions/src/lib.rs b/crates/xrpl-host-functions/src/lib.rs index f3c5eb34a1..e8ae0e7392 100644 --- a/crates/xrpl-host-functions/src/lib.rs +++ b/crates/xrpl-host-functions/src/lib.rs @@ -75,11 +75,306 @@ host_functions! { #[wasm_name = "ldgr_index"] fn get_ledger_sqn(&self, out: &mut [u8]) -> HostResult; + /// The close time of the parent (last-closed) ledger, as 4 little-endian bytes. + #[gas = 60] + #[wasm_name = "parent_ldgr_time"] + fn get_parent_ledger_time(&self, out: &mut [u8]) -> HostResult; + + /// The hash of the parent (last-closed) ledger, as 32 bytes. + #[gas = 60] + #[wasm_name = "parent_ldgr_hash"] + fn get_parent_ledger_hash(&self, out: &mut [u8]) -> HostResult; + + /// The base fee of the ledger being built, in drops, as 4 little-endian bytes. + #[gas = 60] + #[wasm_name = "base_fee"] + fn get_base_fee(&self, out: &mut [u8]) -> HostResult; + + /// Whether an amendment is enabled. The input is either its 32-byte id or its + /// name; the answer is `1` if enabled and `0` if not. Unlike the getters, this + /// reads an input region and returns the flag directly rather than writing bytes. + #[gas = 100] + #[wasm_name = "amendment_enabled"] + fn is_amendment_enabled(&self, amendment: &[u8]) -> HostResult; + + /// Load the ledger object with the given 32-byte id into a cache slot, so later + /// calls can read its fields. `cache_idx` selects the slot (1-based); `0` asks the + /// host to assign a free one. Returns the slot used, or a negative error. + #[gas = 5000] + #[wasm_name = "cache_le"] + fn cache_ledger_obj(&self, obj_id: &[u8], cache_idx: i32) -> HostResult; + + /// The serialized bytes of one field of the transaction being executed, selected + /// by its `SField` code. + #[gas = 70] + #[wasm_name = "tx_field"] + fn get_tx_field(&self, field: i32, out: &mut [u8]) -> HostResult; + /// The serialized bytes of one field of the current (escrow) ledger object. #[gas = 70] #[wasm_name = "home_le_field"] fn get_current_ledger_obj_field(&self, field: i32, out: &mut [u8]) -> HostResult; + /// The serialized bytes of one field of a previously cached ledger object, + /// selected by its cache slot and the field's `SField` code. + #[gas = 70] + #[wasm_name = "le_field"] + fn get_ledger_obj_field(&self, cache_idx: i32, field: i32, out: &mut [u8]) -> HostResult; + + /// The serialized bytes of a nested field of the transaction, reached by a + /// `locator`: a path of little-endian `i32` steps (so its byte length is a + /// non-zero multiple of 4). Reads the locator region and writes the field bytes. + #[gas = 110] + #[wasm_name = "tx_inner"] + fn get_tx_nested_field(&self, locator: &[u8], out: &mut [u8]) -> HostResult; + + /// The serialized bytes of a nested field of the current (escrow) ledger object, + /// reached by a `locator`, as with [`Self::get_tx_nested_field`]. + #[gas = 110] + #[wasm_name = "home_le_inner"] + fn get_current_ledger_obj_nested_field( + &self, + locator: &[u8], + out: &mut [u8], + ) -> HostResult; + + /// The serialized bytes of a nested field of a previously cached ledger object, + /// selected by its cache slot and reached by a `locator`. + #[gas = 110] + #[wasm_name = "le_inner"] + fn get_ledger_obj_nested_field( + &self, + cache_idx: i32, + locator: &[u8], + out: &mut [u8], + ) -> HostResult; + + /// The number of elements in an array field of the transaction, selected by its + /// `SField` code. Answers the count directly, or a negative error (`NoArray` if + /// the field is not an array). Reads and writes no memory. + #[gas = 40] + #[wasm_name = "tx_arr_len"] + fn get_tx_array_len(&self, field: i32) -> HostResult; + + /// The number of elements in an array field of the current (escrow) ledger + /// object, as with [`Self::get_tx_array_len`]. + #[gas = 40] + #[wasm_name = "home_le_arr_len"] + fn get_current_ledger_obj_array_len(&self, field: i32) -> HostResult; + + /// The number of elements in an array field of a previously cached ledger object, + /// selected by its cache slot and `SField` code. + #[gas = 40] + #[wasm_name = "le_arr_len"] + fn get_ledger_obj_array_len(&self, cache_idx: i32, field: i32) -> HostResult; + + /// The number of elements in a nested array field of the transaction, reached by a + /// `locator`. Reads the locator region and answers the count directly. + #[gas = 70] + #[wasm_name = "tx_inner_arr_len"] + fn get_tx_nested_array_len(&self, locator: &[u8]) -> HostResult; + + /// The number of elements in a nested array field of the current (escrow) ledger + /// object, reached by a `locator`, as with [`Self::get_tx_nested_array_len`]. + #[gas = 70] + #[wasm_name = "home_le_inner_arr_len"] + fn get_current_ledger_obj_nested_array_len(&self, locator: &[u8]) -> HostResult; + + /// The number of elements in a nested array field of a previously cached ledger + /// object, selected by its cache slot and reached by a `locator`. + #[gas = 70] + #[wasm_name = "le_inner_arr_len"] + fn get_ledger_obj_nested_array_len(&self, cache_idx: i32, locator: &[u8]) -> HostResult; + + /// Verify `signature` over `message` under `pubkey`. Reads the three regions and + /// answers `1` if the signature is valid, `0` if not, or a negative error. + /// + /// GAS DISCREPANCY: this 300 is the value the C-ABI fork registered + /// (`rippled-wasm-host-functions`, WasmVM.cpp), which this port follows. The + /// prior C++ integration in this tree charged 35000 for the same call — 100x + /// more, and closer to the real cost of signature verification. The value is + /// consensus-critical, so confirm which is intended before this ships. + #[gas = 300] + #[wasm_name = "check_sig"] + fn check_signature( + &self, + message: &[u8], + signature: &[u8], + pubkey: &[u8], + ) -> HostResult; + + /// The 32-byte ledger key (keylet) of an account's `AccountRoot`, computed from a + /// 20-byte account id. Reads the account region and writes the keylet. + #[gas = 350] + #[wasm_name = "accountroot_id"] + fn account_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult; + + /// The 32-byte keylet of an AMM, computed from its two assets. Each asset is a + /// byte slice whose length selects its kind (24 = MPT, 20 = XRP, 40 = issued + /// currency + issuer). Reads both asset regions and writes the keylet. + #[gas = 450] + #[wasm_name = "amm_id"] + fn amm_keylet(&self, asset1: &[u8], asset2: &[u8], out: &mut [u8]) -> HostResult; + + /// The 32-byte keylet of a `Check`, computed from a 20-byte account id and its + /// sequence number. `seq` is the guest's `u32` carried as its `i32` bit pattern. + /// Reads the account region and writes the keylet. + #[gas = 350] + #[wasm_name = "check_id"] + fn check_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult; + + /// The 32-byte keylet of a `Credential`, computed from the 20-byte subject and + /// issuer account ids and a credential-type byte string. Reads all three regions + /// and writes the keylet. + #[gas = 350] + #[wasm_name = "credential_id"] + fn credential_keylet( + &self, + subject: &[u8], + issuer: &[u8], + credential_type: &[u8], + out: &mut [u8], + ) -> HostResult; + + /// The 32-byte keylet of a `Delegate` object, computed from the 20-byte account + /// and the account it authorizes. Reads both account regions and writes the keylet. + #[gas = 350] + #[wasm_name = "delegate_id"] + fn delegate_keylet( + &self, + account: &[u8], + authorize: &[u8], + out: &mut [u8], + ) -> HostResult; + + /// The 32-byte keylet of a `DepositPreauth`, computed from the 20-byte account and + /// the account it authorizes to deposit. Reads both account regions and writes the + /// keylet. + #[gas = 350] + #[wasm_name = "deposit_preauth_id"] + fn deposit_preauth_keylet( + &self, + account: &[u8], + authorize: &[u8], + out: &mut [u8], + ) -> HostResult; + + /// The 32-byte keylet of an account's `DID`, computed from its 20-byte account id. + /// Reads the account region and writes the keylet. + #[gas = 350] + #[wasm_name = "did_id"] + fn did_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult; + + /// The 32-byte keylet of an `Escrow`, computed from the 20-byte owner account and + /// its sequence number. `seq` is the guest's `u32` carried as its `i32` bit + /// pattern. Reads the account region and writes the keylet. + #[gas = 350] + #[wasm_name = "escrow_id"] + fn escrow_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult; + + /// The 32-byte keylet of a `RippleState` (trust line), computed from two 20-byte + /// account ids and a 20-byte currency. Reads all three regions and writes the + /// keylet. + #[gas = 400] + #[wasm_name = "trustline_id"] + fn trust_line_keylet( + &self, + account1: &[u8], + account2: &[u8], + currency: &[u8], + out: &mut [u8], + ) -> HostResult; + + /// The 32-byte keylet of an `MPTokenIssuance`, computed from the 20-byte issuer + /// account and its sequence number. `seq` is the guest's `u32` carried as its + /// `i32` bit pattern. Reads the account region and writes the keylet. + #[gas = 350] + #[wasm_name = "mpt_issuance_id"] + fn mptoken_issuance_keylet( + &self, + issuer: &[u8], + seq: i32, + out: &mut [u8], + ) -> HostResult; + + /// The 32-byte keylet of an `MPToken`, computed from a 24-byte MPT issuance id and + /// the 20-byte holder account. Reads both regions and writes the keylet. + #[gas = 500] + #[wasm_name = "mptoken_id"] + fn mptoken_keylet(&self, mptid: &[u8], holder: &[u8], out: &mut [u8]) -> HostResult; + + /// The 32-byte keylet of an `NFTokenOffer`, computed from the 20-byte owner account + /// and its sequence number. `seq` is the guest's `u32` carried as its `i32` bit + /// pattern. Reads the account region and writes the keylet. + #[gas = 350] + #[wasm_name = "nft_offer_id"] + fn nftoken_offer_keylet( + &self, + account: &[u8], + seq: i32, + out: &mut [u8], + ) -> HostResult; + + /// The 32-byte keylet of an `Offer`, computed from the 20-byte owner account and + /// its sequence number. `seq` is the guest's `u32` carried as its `i32` bit + /// pattern. Reads the account region and writes the keylet. + #[gas = 350] + #[wasm_name = "offer_id"] + fn offer_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult; + + /// The 32-byte keylet of an `Oracle`, computed from the 20-byte owner account and + /// its document id. `doc_id` is the guest's `u32` carried as its `i32` bit pattern. + /// Reads the account region and writes the keylet. + #[gas = 350] + #[wasm_name = "oracle_id"] + fn oracle_keylet(&self, account: &[u8], doc_id: i32, out: &mut [u8]) -> HostResult; + + /// The 32-byte keylet of a `PayChannel`, computed from the 20-byte source account, + /// the 20-byte destination account, and the channel's sequence number. `seq` is the + /// guest's `u32` carried as its `i32` bit pattern. Reads both account regions and + /// writes the keylet. + #[gas = 350] + #[wasm_name = "paychan_id"] + fn paychannel_keylet( + &self, + account: &[u8], + destination: &[u8], + seq: i32, + out: &mut [u8], + ) -> HostResult; + + /// The 32-byte keylet of a `PermissionedDomain`, computed from the 20-byte owner + /// account and its sequence number. `seq` is the guest's `u32` carried as its `i32` + /// bit pattern. Reads the account region and writes the keylet. + #[gas = 350] + #[wasm_name = "permissioned_domain_id"] + fn permissioned_domain_keylet( + &self, + account: &[u8], + seq: i32, + out: &mut [u8], + ) -> HostResult; + + /// The 32-byte keylet of a `SignerList`, computed from its 20-byte owner account. + /// Reads the account region and writes the keylet. + #[gas = 350] + #[wasm_name = "signers_id"] + fn signer_list_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult; + + /// The 32-byte keylet of a `Ticket`, computed from the 20-byte owner account and its + /// ticket sequence number. `seq` is the guest's `u32` carried as its `i32` bit + /// pattern. Reads the account region and writes the keylet. + #[gas = 350] + #[wasm_name = "ticket_id"] + fn ticket_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult; + + /// The 32-byte keylet of a `Vault`, computed from the 20-byte owner account and its + /// sequence number. `seq` is the guest's `u32` carried as its `i32` bit pattern. + /// Reads the account region and writes the keylet. + #[gas = 350] + #[wasm_name = "vault_id"] + fn vault_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult; + /// The XRPL `sha512Half` of `data`: the first [`HASH_LEN`] bytes of its SHA-512. #[gas = 2000] #[wasm_name = "sha512_half"] @@ -98,4 +393,147 @@ host_functions! { #[gas = 30] #[wasm_name = "trace"] fn trace(&self, msg: &str, data: &[u8], data_type: TraceDataType) -> HostResult<()>; + + /// Stores `data` as the current object's data field, replacing whatever was there, + /// and returns the number of bytes stored. Reads the data region; `DataFieldTooLarge` + /// if it exceeds the host's limit. + #[gas = 1000] + #[wasm_name = "set_data"] + fn update_data(&self, data: &[u8]) -> HostResult; + + /// The URI of the `NFToken` with id `nft_id` (32 bytes) held by the 20-byte + /// `account`. Reads both regions and writes the URI bytes. + #[gas = 5000] + #[wasm_name = "nft_uri"] + fn get_nft(&self, account: &[u8], nft_id: &[u8], out: &mut [u8]) -> HostResult; + + /// The 20-byte issuer account encoded in the `NFToken` id `nft_id` (32 bytes). + /// Reads the id region and writes the issuer bytes. + #[gas = 70] + #[wasm_name = "nft_issuer"] + fn get_nft_issuer(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult; + + /// The taxon encoded in the `NFToken` id `nft_id` (32 bytes). Reads the id region + /// and writes the taxon as its four little-endian bytes. + #[gas = 60] + #[wasm_name = "nft_taxon"] + fn get_nft_taxon(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult; + + /// The flags encoded in the `NFToken` id `nft_id` (32 bytes). Reads the id region + /// and returns the flags as the call's scalar result. + #[gas = 60] + #[wasm_name = "nft_flags"] + fn get_nft_flags(&self, nft_id: &[u8]) -> HostResult; + + /// The transfer fee encoded in the `NFToken` id `nft_id` (32 bytes). Reads the id + /// region and returns the fee as the call's scalar result. + #[gas = 60] + #[wasm_name = "nft_xfer_fee"] + fn get_nft_transfer_fee(&self, nft_id: &[u8]) -> HostResult; + + /// The sequence number encoded in the `NFToken` id `nft_id` (32 bytes). Reads the + /// id region and writes the sequence as its four little-endian bytes. + #[gas = 60] + #[wasm_name = "nft_serial"] + fn get_nft_sequence(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult; + + // A "float" here is an XRPL `Number` in its serialized form: a byte blob the guest + // holds opaquely and hands back to these functions. Inputs and outputs that are + // floats are byte regions; `mode` is the rounding mode, a scalar the guest chooses. + + /// A float built from the signed integer `x` under rounding `mode`. Writes the + /// float bytes; no input region. + #[gas = 100] + #[wasm_name = "float_from_int"] + fn float_from_int(&self, x: i64, mode: i32, out: &mut [u8]) -> HostResult; + + /// A float built from the unsigned integer in the 8-byte region `x` under rounding + /// `mode`. Reads the integer region and writes the float bytes. + #[gas = 130] + #[wasm_name = "float_from_uint"] + fn float_from_uint(&self, x: &[u8], mode: i32, out: &mut [u8]) -> HostResult; + + /// A float built from the serialized `STAmount` in `amount` under rounding `mode`. + /// Reads the amount region and writes the float bytes. + #[gas = 150] + #[wasm_name = "float_from_stamount"] + fn float_from_stamount(&self, amount: &[u8], mode: i32, out: &mut [u8]) -> HostResult; + + /// A float built from the serialized `STNumber` in `number` under rounding `mode`. + /// Reads the number region and writes the float bytes. + #[gas = 150] + #[wasm_name = "float_from_stnumber"] + fn float_from_stnumber(&self, number: &[u8], mode: i32, out: &mut [u8]) -> HostResult; + + /// The float `x` rounded to a signed integer under rounding `mode`. Reads the float + /// region and writes the integer as its eight little-endian bytes. + #[gas = 130] + #[wasm_name = "float_to_int"] + fn float_to_int(&self, x: &[u8], mode: i32, out: &mut [u8]) -> HostResult; + + /// The float `x` split into its mantissa and exponent. Reads the float region and + /// writes the mantissa (eight little-endian bytes) and the exponent (four little- + /// endian bytes) to two separate output regions. + #[gas = 130] + #[wasm_name = "float_to_mant_exp"] + fn float_to_mant_exp( + &self, + x: &[u8], + mantissa_out: &mut [u8], + exponent_out: &mut [u8], + ) -> HostResult; + + /// A float built from `mantissa` and `exponent` under rounding `mode`. Writes the + /// float bytes; no input region. + #[gas = 100] + #[wasm_name = "float_from_mant_exp"] + fn float_from_mant_exp( + &self, + mantissa: i64, + exponent: i32, + mode: i32, + out: &mut [u8], + ) -> HostResult; + + /// Compares floats `x` and `y`, returning a negative, zero, or positive scalar as + /// `x` is less than, equal to, or greater than `y`. Reads both float regions. + #[gas = 80] + #[wasm_name = "float_cmp"] + fn float_compare(&self, x: &[u8], y: &[u8]) -> HostResult; + + /// The float sum `x + y` under rounding `mode`. Reads both float regions and writes + /// the result bytes. + #[gas = 160] + #[wasm_name = "float_add"] + fn float_add(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult; + + /// The float difference `x - y` under rounding `mode`. Reads both float regions and + /// writes the result bytes. + #[gas = 160] + #[wasm_name = "float_sub"] + fn float_subtract(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult; + + /// The float product `x * y` under rounding `mode`. Reads both float regions and + /// writes the result bytes. + #[gas = 300] + #[wasm_name = "float_mult"] + fn float_multiply(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult; + + /// The float quotient `x / y` under rounding `mode`. Reads both float regions and + /// writes the result bytes. + #[gas = 300] + #[wasm_name = "float_div"] + fn float_divide(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult; + + /// The `n`-th root of the float `x` under rounding `mode`. Reads the float region + /// and writes the result bytes. + #[gas = 5500] + #[wasm_name = "float_root"] + fn float_root(&self, x: &[u8], n: i32, mode: i32, out: &mut [u8]) -> HostResult; + + /// The float `x` raised to the power `n` under rounding `mode`. Reads the float + /// region and writes the result bytes. + #[gas = 5500] + #[wasm_name = "float_pow"] + fn float_power(&self, x: &[u8], n: i32, mode: i32, out: &mut [u8]) -> HostResult; } diff --git a/crates/xrpl-host-functions/tests/generated_abi.rs b/crates/xrpl-host-functions/tests/generated_abi.rs index c0327f86c7..a7b087a85c 100644 --- a/crates/xrpl-host-functions/tests/generated_abi.rs +++ b/crates/xrpl-host-functions/tests/generated_abi.rs @@ -33,6 +33,36 @@ impl HostFunctions for FakeHost { put(out, &7u32.to_le_bytes()) } + fn get_parent_ledger_time(&self, out: &mut [u8]) -> HostResult { + put(out, &9u32.to_le_bytes()) + } + + fn get_parent_ledger_hash(&self, out: &mut [u8]) -> HostResult { + put(out, &[0xab; HASH_LEN]) + } + + fn get_base_fee(&self, out: &mut [u8]) -> HostResult { + put(out, &10u32.to_le_bytes()) + } + + /// Returns a flag rather than bytes, and reads its input: enabled unless empty. + fn is_amendment_enabled(&self, amendment: &[u8]) -> HostResult { + Ok(i32::from(!amendment.is_empty())) + } + + /// Returns a slot: the requested one, or slot 1 when asked to pick. + fn cache_ledger_obj(&self, _obj_id: &[u8], cache_idx: i32) -> HostResult { + Ok(if cache_idx == 0 { 1 } else { cache_idx }) + } + + /// A field getter over the transaction; fails on a negative selector. + fn get_tx_field(&self, field: i32, out: &mut [u8]) -> HostResult { + if field < 0 { + return Err(HostError::FieldNotFound); + } + put(out, &[field as u8]) + } + /// Fails on a field it doesn't know, so the error channel is exercised too. fn get_current_ledger_obj_field(&self, field: i32, out: &mut [u8]) -> HostResult { if field < 0 { @@ -41,6 +71,321 @@ impl HostFunctions for FakeHost { put(out, &[field as u8]) } + /// A field getter over a cached object, keyed by slot and selector. + fn get_ledger_obj_field( + &self, + cache_idx: i32, + field: i32, + out: &mut [u8], + ) -> HostResult { + if cache_idx <= 0 || field < 0 { + return Err(HostError::FieldNotFound); + } + put(out, &[cache_idx as u8, field as u8]) + } + + /// A nested-field getter over the transaction, keyed by the locator bytes. + fn get_tx_nested_field(&self, locator: &[u8], out: &mut [u8]) -> HostResult { + if locator.is_empty() { + return Err(HostError::LocatorMalformed); + } + put(out, &[locator[0], locator.len() as u8]) + } + + /// The same, over the current ledger object. + fn get_current_ledger_obj_nested_field( + &self, + locator: &[u8], + out: &mut [u8], + ) -> HostResult { + if locator.is_empty() { + return Err(HostError::LocatorMalformed); + } + put(out, &[locator.len() as u8, locator[0]]) + } + + /// The same, over a cached object keyed by slot. + fn get_ledger_obj_nested_field( + &self, + cache_idx: i32, + locator: &[u8], + out: &mut [u8], + ) -> HostResult { + if cache_idx <= 0 || locator.is_empty() { + return Err(HostError::LocatorMalformed); + } + put(out, &[cache_idx as u8, locator[0]]) + } + + /// A scalar-in, scalar-out count; `NoArray` on a negative selector. + fn get_tx_array_len(&self, field: i32) -> HostResult { + if field < 0 { + return Err(HostError::NoArray); + } + Ok(field) + } + + /// The same, over the current ledger object. + fn get_current_ledger_obj_array_len(&self, field: i32) -> HostResult { + if field < 0 { + return Err(HostError::NoArray); + } + Ok(field + 1) + } + + /// The same, over a cached object keyed by slot. + fn get_ledger_obj_array_len(&self, cache_idx: i32, field: i32) -> HostResult { + if cache_idx <= 0 || field < 0 { + return Err(HostError::NoArray); + } + Ok(cache_idx + field) + } + + /// A nested array-length getter, keyed by the locator bytes. + fn get_tx_nested_array_len(&self, locator: &[u8]) -> HostResult { + if locator.is_empty() { + return Err(HostError::LocatorMalformed); + } + Ok(locator.len() as i32) + } + + /// The same, over the current ledger object. + fn get_current_ledger_obj_nested_array_len(&self, locator: &[u8]) -> HostResult { + if locator.is_empty() { + return Err(HostError::LocatorMalformed); + } + Ok(locator.len() as i32 + 1) + } + + /// The same, over a cached object keyed by slot. + fn get_ledger_obj_nested_array_len(&self, cache_idx: i32, locator: &[u8]) -> HostResult { + if cache_idx <= 0 || locator.is_empty() { + return Err(HostError::LocatorMalformed); + } + Ok(cache_idx + locator.len() as i32) + } + + /// Reads three regions and returns a verdict: valid unless the signature is empty. + fn check_signature( + &self, + _message: &[u8], + signature: &[u8], + _pubkey: &[u8], + ) -> HostResult { + Ok(i32::from(!signature.is_empty())) + } + + /// A keylet getter: reads an account, writes a 32-byte keylet; `InvalidAccount` + /// on an empty account. + fn account_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult { + if account.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// A two-asset keylet getter; `InvalidParams` if the two assets are equal. + fn amm_keylet(&self, asset1: &[u8], asset2: &[u8], out: &mut [u8]) -> HostResult { + if asset1 == asset2 { + return Err(HostError::InvalidParams); + } + put(out, &[asset1.len() as u8; HASH_LEN]) + } + + /// A keylet from an account and a sequence; `InvalidAccount` on an empty account. + fn check_keylet(&self, account: &[u8], _seq: i32, out: &mut [u8]) -> HostResult { + if account.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// A keylet from subject, issuer, and credential type; `InvalidAccount` if either + /// account is empty, `InvalidParams` if the type is empty. + fn credential_keylet( + &self, + subject: &[u8], + issuer: &[u8], + credential_type: &[u8], + out: &mut [u8], + ) -> HostResult { + if subject.is_empty() || issuer.is_empty() { + return Err(HostError::InvalidAccount); + } + if credential_type.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[subject[0]; HASH_LEN]) + } + + /// A keylet from two accounts; `InvalidAccount` if either is empty, `InvalidParams` + /// if they are equal. + fn delegate_keylet( + &self, + account: &[u8], + authorize: &[u8], + out: &mut [u8], + ) -> HostResult { + if account.is_empty() || authorize.is_empty() { + return Err(HostError::InvalidAccount); + } + if account == authorize { + return Err(HostError::InvalidParams); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// The same two-account shape, for a `DepositPreauth`. + fn deposit_preauth_keylet( + &self, + account: &[u8], + authorize: &[u8], + out: &mut [u8], + ) -> HostResult { + if account.is_empty() || authorize.is_empty() { + return Err(HostError::InvalidAccount); + } + if account == authorize { + return Err(HostError::InvalidParams); + } + put(out, &[authorize[0]; HASH_LEN]) + } + + /// A single-account keylet, for a `DID`. + fn did_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult { + if account.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// The account-and-sequence shape, for an `Escrow`. + fn escrow_keylet(&self, account: &[u8], _seq: i32, out: &mut [u8]) -> HostResult { + if account.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// A keylet from two accounts and a currency; `InvalidAccount` if either account + /// is empty, `InvalidParams` if they are equal or the currency is empty. + fn trust_line_keylet( + &self, + account1: &[u8], + account2: &[u8], + currency: &[u8], + out: &mut [u8], + ) -> HostResult { + if account1.is_empty() || account2.is_empty() { + return Err(HostError::InvalidAccount); + } + if account1 == account2 || currency.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[account1[0]; HASH_LEN]) + } + + /// The issuer-and-sequence shape, for an `MPTokenIssuance`. + fn mptoken_issuance_keylet( + &self, + issuer: &[u8], + _seq: i32, + out: &mut [u8], + ) -> HostResult { + if issuer.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[issuer[0]; HASH_LEN]) + } + + /// A keylet from an MPT id and a holder; `InvalidParams` if the id is empty, + /// `InvalidAccount` if the holder is empty. + fn mptoken_keylet(&self, mptid: &[u8], holder: &[u8], out: &mut [u8]) -> HostResult { + if mptid.is_empty() { + return Err(HostError::InvalidParams); + } + if holder.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[mptid[0]; HASH_LEN]) + } + + /// The account-and-sequence shape, for an `NFTokenOffer`. + fn nftoken_offer_keylet(&self, account: &[u8], _seq: i32, out: &mut [u8]) -> HostResult { + if account.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// The same account-and-sequence shape, for an `Offer`. + fn offer_keylet(&self, account: &[u8], _seq: i32, out: &mut [u8]) -> HostResult { + if account.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// The same account-and-scalar shape, for an `Oracle` keyed by document id. + fn oracle_keylet(&self, account: &[u8], _doc_id: i32, out: &mut [u8]) -> HostResult { + if account.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// A two-account-and-sequence shape, for a `PayChannel`; `InvalidAccount` if + /// either account is empty. + fn paychannel_keylet( + &self, + account: &[u8], + destination: &[u8], + _seq: i32, + out: &mut [u8], + ) -> HostResult { + if account.is_empty() || destination.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// The same account-and-sequence shape, for a `PermissionedDomain`. + fn permissioned_domain_keylet( + &self, + account: &[u8], + _seq: i32, + out: &mut [u8], + ) -> HostResult { + if account.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// The account-only shape, for a `SignerList`. + fn signer_list_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult { + if account.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// The same account-and-sequence shape, for a `Ticket`. + fn ticket_keylet(&self, account: &[u8], _seq: i32, out: &mut [u8]) -> HostResult { + if account.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// The same account-and-sequence shape, for a `Vault`. + fn vault_keylet(&self, account: &[u8], _seq: i32, out: &mut [u8]) -> HostResult { + if account.is_empty() { + return Err(HostError::InvalidAccount); + } + put(out, &[account[0]; HASH_LEN]) + } + fn sha512_half(&self, data: &[u8], out: &mut [u8]) -> HostResult { let mut digest = [0; HASH_LEN]; digest[0] = data.len() as u8; @@ -53,6 +398,180 @@ impl HostFunctions for FakeHost { .push(format!("{msg}/{data_type:?}/{}", data.len())); Ok(()) } + + /// Reads a data blob and returns the count of bytes stored. + fn update_data(&self, data: &[u8]) -> HostResult { + Ok(data.len() as i32) + } + + /// Reads an account and an nft id, writes a byte value; `InvalidParams` if either + /// is empty. + fn get_nft(&self, account: &[u8], nft_id: &[u8], out: &mut [u8]) -> HostResult { + if account.is_empty() || nft_id.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[account[0]; HASH_LEN]) + } + + /// Reads an nft id, writes a byte value; `InvalidParams` on an empty id. + fn get_nft_issuer(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult { + if nft_id.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[nft_id[0]; HASH_LEN]) + } + + /// The same, for the taxon. + fn get_nft_taxon(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult { + if nft_id.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &nft_id[0].to_le_bytes()) + } + + /// Reads an nft id and returns a scalar; `InvalidParams` on an empty id. + fn get_nft_flags(&self, nft_id: &[u8]) -> HostResult { + if nft_id.is_empty() { + return Err(HostError::InvalidParams); + } + Ok(i32::from(nft_id[0])) + } + + /// The same, for the transfer fee. + fn get_nft_transfer_fee(&self, nft_id: &[u8]) -> HostResult { + if nft_id.is_empty() { + return Err(HostError::InvalidParams); + } + Ok(i32::from(nft_id[0])) + } + + /// The same byte-output shape, for the sequence number. + fn get_nft_sequence(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult { + if nft_id.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &nft_id[0].to_le_bytes()) + } + + /// A scalar-in float: writes the low byte of `x` as a stand-in float. + fn float_from_int(&self, x: i64, _mode: i32, out: &mut [u8]) -> HostResult { + put(out, &[x as u8]) + } + + /// A byte-in float; `InvalidParams` on an empty region. + fn float_from_uint(&self, x: &[u8], _mode: i32, out: &mut [u8]) -> HostResult { + if x.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[x[0]]) + } + + /// The same, for a serialized amount. + fn float_from_stamount(&self, amount: &[u8], _mode: i32, out: &mut [u8]) -> HostResult { + if amount.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[amount[0]]) + } + + /// The same, for a serialized number. + fn float_from_stnumber(&self, number: &[u8], _mode: i32, out: &mut [u8]) -> HostResult { + if number.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[number[0]]) + } + + /// A float rounded to an integer, written as bytes. + fn float_to_int(&self, x: &[u8], _mode: i32, out: &mut [u8]) -> HostResult { + if x.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[x[0]]) + } + + /// Writes a mantissa (its first byte) and an exponent (its first byte) to two + /// regions, returning their combined length. + fn float_to_mant_exp( + &self, + x: &[u8], + mantissa_out: &mut [u8], + exponent_out: &mut [u8], + ) -> HostResult { + if x.is_empty() { + return Err(HostError::InvalidParams); + } + let m = put(mantissa_out, &[x[0]])?; + let e = put(exponent_out, &[x[0]])?; + Ok(m + e) + } + + /// A two-scalar-in float. + fn float_from_mant_exp( + &self, + mantissa: i64, + _exponent: i32, + _mode: i32, + out: &mut [u8], + ) -> HostResult { + put(out, &[mantissa as u8]) + } + + /// Reads two floats and returns a scalar; `InvalidParams` if either is empty. + fn float_compare(&self, x: &[u8], y: &[u8]) -> HostResult { + if x.is_empty() || y.is_empty() { + return Err(HostError::InvalidParams); + } + Ok(i32::from(x[0]) - i32::from(y[0])) + } + + /// A binary float operator; `InvalidParams` if either operand is empty. + fn float_add(&self, x: &[u8], y: &[u8], _mode: i32, out: &mut [u8]) -> HostResult { + if x.is_empty() || y.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[x[0]]) + } + + /// The same shape, for subtraction. + fn float_subtract(&self, x: &[u8], y: &[u8], _mode: i32, out: &mut [u8]) -> HostResult { + if x.is_empty() || y.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[x[0]]) + } + + /// The same shape, for multiplication. + fn float_multiply(&self, x: &[u8], y: &[u8], _mode: i32, out: &mut [u8]) -> HostResult { + if x.is_empty() || y.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[x[0]]) + } + + /// The same shape, for division. + fn float_divide(&self, x: &[u8], y: &[u8], _mode: i32, out: &mut [u8]) -> HostResult { + if x.is_empty() || y.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[x[0]]) + } + + /// A one-float-and-integer operator; `InvalidParams` on an empty operand. + fn float_root(&self, x: &[u8], _n: i32, _mode: i32, out: &mut [u8]) -> HostResult { + if x.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[x[0]]) + } + + /// The same shape, for exponentiation. + fn float_power(&self, x: &[u8], _n: i32, _mode: i32, out: &mut [u8]) -> HostResult { + if x.is_empty() { + return Err(HostError::InvalidParams); + } + put(out, &[x[0]]) + } } #[test] @@ -62,11 +581,248 @@ fn the_trait_is_implementable() { assert_eq!(host.get_ledger_sqn(&mut out), Ok(4)); assert_eq!(out[..4], [7, 0, 0, 0]); + assert_eq!(host.get_parent_ledger_time(&mut out), Ok(4)); + assert_eq!(out[..4], [9, 0, 0, 0]); + assert_eq!(host.get_parent_ledger_hash(&mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 0xab); + assert_eq!(host.get_base_fee(&mut out), Ok(4)); + assert_eq!(out[..4], [10, 0, 0, 0]); + assert_eq!(host.is_amendment_enabled(&[1; 32]), Ok(1)); + assert_eq!(host.is_amendment_enabled(&[]), Ok(0)); + assert_eq!(host.cache_ledger_obj(&[1; 32], 0), Ok(1)); + assert_eq!(host.cache_ledger_obj(&[1; 32], 5), Ok(5)); + assert_eq!(host.get_tx_field(5, &mut out), Ok(1)); + assert_eq!(out[0], 5); assert_eq!(host.get_current_ledger_obj_field(3, &mut out), Ok(1)); assert_eq!(out[0], 3); + assert_eq!(host.get_ledger_obj_field(2, 4, &mut out), Ok(2)); + assert_eq!(out[..2], [2, 4]); + assert_eq!(host.get_tx_nested_field(&[9, 0, 0, 0], &mut out), Ok(2)); + assert_eq!(out[..2], [9, 4]); + assert_eq!( + host.get_current_ledger_obj_nested_field(&[9, 0, 0, 0], &mut out), + Ok(2) + ); + assert_eq!(out[..2], [4, 9]); + assert_eq!( + host.get_ledger_obj_nested_field(3, &[9, 0, 0, 0], &mut out), + Ok(2) + ); + assert_eq!(out[..2], [3, 9]); + assert_eq!(host.get_tx_array_len(3), Ok(3)); + assert_eq!(host.get_tx_array_len(-1), Err(HostError::NoArray)); + assert_eq!(host.get_current_ledger_obj_array_len(3), Ok(4)); + assert_eq!( + host.get_current_ledger_obj_array_len(-1), + Err(HostError::NoArray) + ); + assert_eq!(host.get_ledger_obj_array_len(2, 3), Ok(5)); + assert_eq!(host.get_ledger_obj_array_len(0, 3), Err(HostError::NoArray)); + assert_eq!(host.get_tx_nested_array_len(&[9, 0, 0, 0]), Ok(4)); + assert_eq!( + host.get_tx_nested_array_len(&[]), + Err(HostError::LocatorMalformed) + ); + assert_eq!( + host.get_current_ledger_obj_nested_array_len(&[9, 0, 0, 0]), + Ok(5) + ); + assert_eq!( + host.get_current_ledger_obj_nested_array_len(&[]), + Err(HostError::LocatorMalformed) + ); + assert_eq!( + host.get_ledger_obj_nested_array_len(2, &[9, 0, 0, 0]), + Ok(6) + ); + assert_eq!( + host.get_ledger_obj_nested_array_len(0, &[9, 0, 0, 0]), + Err(HostError::LocatorMalformed) + ); + assert_eq!(host.check_signature(b"msg", b"sig", b"pk"), Ok(1)); + assert_eq!(host.check_signature(b"msg", b"", b"pk"), Ok(0)); + assert_eq!(host.account_keylet(&[7; 20], &mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 7); + assert_eq!( + host.account_keylet(&[], &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!(host.amm_keylet(&[1; 20], &[2; 40], &mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 20); + assert_eq!( + host.amm_keylet(&[1; 20], &[1; 20], &mut out), + Err(HostError::InvalidParams) + ); + assert_eq!(host.check_keylet(&[7; 20], 5, &mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 7); + assert_eq!( + host.check_keylet(&[], 5, &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!( + host.credential_keylet(&[7; 20], &[8; 20], b"cred", &mut out), + Ok(HASH_LEN) + ); + assert_eq!(out[0], 7); + assert_eq!( + host.credential_keylet(&[], &[8; 20], b"cred", &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!( + host.delegate_keylet(&[7; 20], &[8; 20], &mut out), + Ok(HASH_LEN) + ); + assert_eq!(out[0], 7); + assert_eq!( + host.delegate_keylet(&[], &[8; 20], &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!( + host.deposit_preauth_keylet(&[7; 20], &[8; 20], &mut out), + Ok(HASH_LEN) + ); + assert_eq!(out[0], 8); + assert_eq!( + host.deposit_preauth_keylet(&[7; 20], &[7; 20], &mut out), + Err(HostError::InvalidParams) + ); + assert_eq!(host.did_keylet(&[7; 20], &mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 7); + assert_eq!( + host.did_keylet(&[], &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!(host.escrow_keylet(&[7; 20], 5, &mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 7); + assert_eq!( + host.escrow_keylet(&[], 5, &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!( + host.trust_line_keylet(&[7; 20], &[8; 20], &[1; 20], &mut out), + Ok(HASH_LEN) + ); + assert_eq!(out[0], 7); + assert_eq!( + host.trust_line_keylet(&[7; 20], &[7; 20], &[1; 20], &mut out), + Err(HostError::InvalidParams) + ); + assert_eq!( + host.mptoken_issuance_keylet(&[7; 20], 5, &mut out), + Ok(HASH_LEN) + ); + assert_eq!(out[0], 7); + assert_eq!( + host.mptoken_issuance_keylet(&[], 5, &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!( + host.mptoken_keylet(&[9; 24], &[8; 20], &mut out), + Ok(HASH_LEN) + ); + assert_eq!(out[0], 9); + assert_eq!( + host.mptoken_keylet(&[], &[8; 20], &mut out), + Err(HostError::InvalidParams) + ); + assert_eq!( + host.nftoken_offer_keylet(&[7; 20], 5, &mut out), + Ok(HASH_LEN) + ); + assert_eq!(out[0], 7); + assert_eq!( + host.nftoken_offer_keylet(&[], 5, &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!(host.offer_keylet(&[7; 20], 5, &mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 7); + assert_eq!( + host.offer_keylet(&[], 5, &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!(host.oracle_keylet(&[7; 20], 5, &mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 7); + assert_eq!( + host.oracle_keylet(&[], 5, &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!( + host.paychannel_keylet(&[7; 20], &[8; 20], 5, &mut out), + Ok(HASH_LEN) + ); + assert_eq!(out[0], 7); + assert_eq!( + host.paychannel_keylet(&[7; 20], &[], 5, &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!( + host.permissioned_domain_keylet(&[7; 20], 5, &mut out), + Ok(HASH_LEN) + ); + assert_eq!(out[0], 7); + assert_eq!( + host.permissioned_domain_keylet(&[], 5, &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!(host.signer_list_keylet(&[7; 20], &mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 7); + assert_eq!( + host.signer_list_keylet(&[], &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!(host.ticket_keylet(&[7; 20], 5, &mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 7); + assert_eq!( + host.ticket_keylet(&[], 5, &mut out), + Err(HostError::InvalidAccount) + ); + assert_eq!(host.vault_keylet(&[7; 20], 5, &mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 7); + assert_eq!( + host.vault_keylet(&[], 5, &mut out), + Err(HostError::InvalidAccount) + ); assert_eq!(host.sha512_half(b"abc", &mut out), Ok(HASH_LEN)); assert_eq!(out[0], 3); assert_eq!(host.trace("hello", b"xy", TraceDataType::AsHex), Ok(())); + assert_eq!(host.update_data(b"abcd"), Ok(4)); + assert_eq!(host.get_nft(&[7; 20], &[9; 32], &mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 7); + assert_eq!( + host.get_nft(&[], &[9; 32], &mut out), + Err(HostError::InvalidParams) + ); + assert_eq!(host.get_nft_issuer(&[9; 32], &mut out), Ok(HASH_LEN)); + assert_eq!(out[0], 9); + assert_eq!( + host.get_nft_issuer(&[], &mut out), + Err(HostError::InvalidParams) + ); + assert_eq!(host.get_nft_taxon(&[9; 32], &mut out), Ok(1)); + assert_eq!(host.get_nft_flags(&[9; 32]), Ok(9)); + assert_eq!(host.get_nft_flags(&[]), Err(HostError::InvalidParams)); + assert_eq!(host.get_nft_transfer_fee(&[9; 32]), Ok(9)); + assert_eq!(host.get_nft_sequence(&[9; 32], &mut out), Ok(1)); + assert_eq!(host.float_from_int(5, 0, &mut out), Ok(1)); + assert_eq!(host.float_from_uint(&[3; 8], 0, &mut out), Ok(1)); + assert_eq!(host.float_from_stamount(&[3; 8], 0, &mut out), Ok(1)); + assert_eq!(host.float_from_stnumber(&[3; 8], 0, &mut out), Ok(1)); + assert_eq!(host.float_to_int(&[3; 8], 0, &mut out), Ok(1)); + let mut mant = [0u8; 8]; + let mut exp = [0u8; 4]; + assert_eq!(host.float_to_mant_exp(&[3; 8], &mut mant, &mut exp), Ok(2)); + assert_eq!(host.float_from_mant_exp(5, 0, 0, &mut out), Ok(1)); + assert_eq!(host.float_compare(&[9; 8], &[4; 8]), Ok(5)); + assert_eq!( + host.float_compare(&[], &[4; 8]), + Err(HostError::InvalidParams) + ); + assert_eq!(host.float_add(&[3; 8], &[4; 8], 0, &mut out), Ok(1)); + assert_eq!(host.float_subtract(&[3; 8], &[4; 8], 0, &mut out), Ok(1)); + assert_eq!(host.float_multiply(&[3; 8], &[4; 8], 0, &mut out), Ok(1)); + assert_eq!(host.float_divide(&[3; 8], &[4; 8], 0, &mut out), Ok(1)); + assert_eq!(host.float_root(&[3; 8], 2, 0, &mut out), Ok(1)); + assert_eq!(host.float_power(&[3; 8], 2, 0, &mut out), Ok(1)); assert_eq!(*host.traced.borrow(), ["hello/AsHex/2"]); } @@ -134,9 +890,66 @@ fn the_spec_table_matches_the_declarations() { table, [ ("ldgr_index", 60), + ("parent_ldgr_time", 60), + ("parent_ldgr_hash", 60), + ("base_fee", 60), + ("amendment_enabled", 100), + ("cache_le", 5000), + ("tx_field", 70), ("home_le_field", 70), + ("le_field", 70), + ("tx_inner", 110), + ("home_le_inner", 110), + ("le_inner", 110), + ("tx_arr_len", 40), + ("home_le_arr_len", 40), + ("le_arr_len", 40), + ("tx_inner_arr_len", 70), + ("home_le_inner_arr_len", 70), + ("le_inner_arr_len", 70), + ("check_sig", 300), + ("accountroot_id", 350), + ("amm_id", 450), + ("check_id", 350), + ("credential_id", 350), + ("delegate_id", 350), + ("deposit_preauth_id", 350), + ("did_id", 350), + ("escrow_id", 350), + ("trustline_id", 400), + ("mpt_issuance_id", 350), + ("mptoken_id", 500), + ("nft_offer_id", 350), + ("offer_id", 350), + ("oracle_id", 350), + ("paychan_id", 350), + ("permissioned_domain_id", 350), + ("signers_id", 350), + ("ticket_id", 350), + ("vault_id", 350), ("sha512_half", 2000), ("trace", 30), + ("set_data", 1000), + ("nft_uri", 5000), + ("nft_issuer", 70), + ("nft_taxon", 60), + ("nft_flags", 60), + ("nft_xfer_fee", 60), + ("nft_serial", 60), + ("float_from_int", 100), + ("float_from_uint", 130), + ("float_from_stamount", 150), + ("float_from_stnumber", 150), + ("float_to_int", 130), + ("float_to_mant_exp", 130), + ("float_from_mant_exp", 100), + ("float_cmp", 80), + ("float_add", 160), + ("float_sub", 160), + ("float_mult", 300), + ("float_div", 300), + ("float_root", 5500), + ("float_pow", 5500), ] ); } diff --git a/crates/xrpl-wasm-vm-ffi/src/lib.rs b/crates/xrpl-wasm-vm-ffi/src/lib.rs index 1a358c036e..fc49626613 100644 --- a/crates/xrpl-wasm-vm-ffi/src/lib.rs +++ b/crates/xrpl-wasm-vm-ffi/src/lib.rs @@ -185,10 +185,226 @@ mod ffi { #[cxx_name = "getLedgerSqn"] fn get_ledger_sqn(self: &HostContext, out: &mut [u8]) -> i32; + #[namespace = "xrpl"] + #[cxx_name = "getParentLedgerTime"] + fn get_parent_ledger_time(self: &HostContext, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getParentLedgerHash"] + fn get_parent_ledger_hash(self: &HostContext, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getBaseFee"] + fn get_base_fee(self: &HostContext, out: &mut [u8]) -> i32; + + /// Reads the amendment (id or name) and answers `1`/`0`, or a negative + /// `HostError` code. + #[namespace = "xrpl"] + #[cxx_name = "isAmendmentEnabled"] + fn is_amendment_enabled(self: &HostContext, amendment: &[u8]) -> i32; + + /// Caches the object with `obj_id` in slot `cache_idx` (`0` = pick one) and + /// answers the slot used, or a negative `HostError` code. + #[namespace = "xrpl"] + #[cxx_name = "cacheLedgerObj"] + fn cache_ledger_obj(self: &HostContext, obj_id: &[u8], cache_idx: i32) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getTxField"] + fn get_tx_field(self: &HostContext, field: i32, out: &mut [u8]) -> i32; + #[namespace = "xrpl"] #[cxx_name = "getCurrentLedgerObjField"] fn get_current_ledger_obj_field(self: &HostContext, field: i32, out: &mut [u8]) -> i32; + #[namespace = "xrpl"] + #[cxx_name = "getLedgerObjField"] + fn get_ledger_obj_field( + self: &HostContext, + cache_idx: i32, + field: i32, + out: &mut [u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getTxNestedField"] + fn get_tx_nested_field(self: &HostContext, locator: &[u8], out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getCurrentLedgerObjNestedField"] + fn get_current_ledger_obj_nested_field( + self: &HostContext, + locator: &[u8], + out: &mut [u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getLedgerObjNestedField"] + fn get_ledger_obj_nested_field( + self: &HostContext, + cache_idx: i32, + locator: &[u8], + out: &mut [u8], + ) -> i32; + + /// Answers the array's element count directly, or a negative `HostError` code. + #[namespace = "xrpl"] + #[cxx_name = "getTxArrayLen"] + fn get_tx_array_len(self: &HostContext, field: i32) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getCurrentLedgerObjArrayLen"] + fn get_current_ledger_obj_array_len(self: &HostContext, field: i32) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getLedgerObjArrayLen"] + fn get_ledger_obj_array_len(self: &HostContext, cache_idx: i32, field: i32) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getTxNestedArrayLen"] + fn get_tx_nested_array_len(self: &HostContext, locator: &[u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getCurrentLedgerObjNestedArrayLen"] + fn get_current_ledger_obj_nested_array_len(self: &HostContext, locator: &[u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getLedgerObjNestedArrayLen"] + fn get_ledger_obj_nested_array_len( + self: &HostContext, + cache_idx: i32, + locator: &[u8], + ) -> i32; + + /// Answers `1`/`0` for a valid/invalid signature, or a negative `HostError`. + #[namespace = "xrpl"] + #[cxx_name = "checkSignature"] + fn check_signature( + self: &HostContext, + message: &[u8], + signature: &[u8], + pubkey: &[u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "accountKeylet"] + fn account_keylet(self: &HostContext, account: &[u8], out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "ammKeylet"] + fn amm_keylet(self: &HostContext, asset1: &[u8], asset2: &[u8], out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "checkKeylet"] + fn check_keylet(self: &HostContext, account: &[u8], seq: i32, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "credentialKeylet"] + fn credential_keylet( + self: &HostContext, + subject: &[u8], + issuer: &[u8], + credential_type: &[u8], + out: &mut [u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "delegateKeylet"] + fn delegate_keylet( + self: &HostContext, + account: &[u8], + authorize: &[u8], + out: &mut [u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "depositPreauthKeylet"] + fn deposit_preauth_keylet( + self: &HostContext, + account: &[u8], + authorize: &[u8], + out: &mut [u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "didKeylet"] + fn did_keylet(self: &HostContext, account: &[u8], out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "escrowKeylet"] + fn escrow_keylet(self: &HostContext, account: &[u8], seq: i32, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "trustLineKeylet"] + fn trust_line_keylet( + self: &HostContext, + account1: &[u8], + account2: &[u8], + currency: &[u8], + out: &mut [u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "mptokenIssuanceKeylet"] + fn mptoken_issuance_keylet( + self: &HostContext, + issuer: &[u8], + seq: i32, + out: &mut [u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "mptokenKeylet"] + fn mptoken_keylet(self: &HostContext, mptid: &[u8], holder: &[u8], out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "nftokenOfferKeylet"] + fn nftoken_offer_keylet( + self: &HostContext, + account: &[u8], + seq: i32, + out: &mut [u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "offerKeylet"] + fn offer_keylet(self: &HostContext, account: &[u8], seq: i32, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "oracleKeylet"] + fn oracle_keylet(self: &HostContext, account: &[u8], doc_id: i32, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "paychannelKeylet"] + fn paychannel_keylet( + self: &HostContext, + account: &[u8], + destination: &[u8], + seq: i32, + out: &mut [u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "permissionedDomainKeylet"] + fn permissioned_domain_keylet( + self: &HostContext, + account: &[u8], + seq: i32, + out: &mut [u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "signerListKeylet"] + fn signer_list_keylet(self: &HostContext, account: &[u8], out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "ticketKeylet"] + fn ticket_keylet(self: &HostContext, account: &[u8], seq: i32, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "vaultKeylet"] + fn vault_keylet(self: &HostContext, account: &[u8], seq: i32, out: &mut [u8]) -> i32; + #[namespace = "xrpl"] #[cxx_name = "sha512Half"] fn sha512_half(self: &HostContext, data: &[u8], out: &mut [u8]) -> i32; @@ -202,6 +418,105 @@ mod ffi { /// here is always one of the variants. #[namespace = "xrpl"] fn trace(self: &HostContext, msg: &str, data: &[u8], data_type: TraceDataType); + + #[namespace = "xrpl"] + #[cxx_name = "updateData"] + fn update_data(self: &HostContext, data: &[u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getNFT"] + fn get_nft(self: &HostContext, account: &[u8], nft_id: &[u8], out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getNFTIssuer"] + fn get_nft_issuer(self: &HostContext, nft_id: &[u8], out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getNFTTaxon"] + fn get_nft_taxon(self: &HostContext, nft_id: &[u8], out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getNFTFlags"] + fn get_nft_flags(self: &HostContext, nft_id: &[u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getNFTTransferFee"] + fn get_nft_transfer_fee(self: &HostContext, nft_id: &[u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "getNFTSequence"] + fn get_nft_sequence(self: &HostContext, nft_id: &[u8], out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatFromInt"] + fn float_from_int(self: &HostContext, x: i64, mode: i32, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatFromUint"] + fn float_from_uint(self: &HostContext, x: &[u8], mode: i32, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatFromSTAmount"] + fn float_from_stamount(self: &HostContext, amount: &[u8], mode: i32, out: &mut [u8]) + -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatFromSTNumber"] + fn float_from_stnumber(self: &HostContext, number: &[u8], mode: i32, out: &mut [u8]) + -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatToInt"] + fn float_to_int(self: &HostContext, x: &[u8], mode: i32, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatToMantExp"] + fn float_to_mant_exp( + self: &HostContext, + x: &[u8], + mantissa_out: &mut [u8], + exponent_out: &mut [u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatFromMantExp"] + fn float_from_mant_exp( + self: &HostContext, + mantissa: i64, + exponent: i32, + mode: i32, + out: &mut [u8], + ) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatCompare"] + fn float_compare(self: &HostContext, x: &[u8], y: &[u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatAdd"] + fn float_add(self: &HostContext, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatSubtract"] + fn float_subtract(self: &HostContext, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) + -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatMultiply"] + fn float_multiply(self: &HostContext, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) + -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatDivide"] + fn float_divide(self: &HostContext, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatRoot"] + fn float_root(self: &HostContext, x: &[u8], n: i32, mode: i32, out: &mut [u8]) -> i32; + + #[namespace = "xrpl"] + #[cxx_name = "floatPower"] + fn float_power(self: &HostContext, x: &[u8], n: i32, mode: i32, out: &mut [u8]) -> i32; } } @@ -242,15 +557,229 @@ fn crossed(data_type: TraceDataType) -> ffi::TraceDataType { } } +/// A call whose answer is a scalar the guest reads directly (a flag, a slot index): +/// a non-negative value is that answer, a negative one its error code. +fn scalar(n: i32) -> HostResult { + if n < 0 { + return Err(HostError::from_code(n)); + } + Ok(n) +} + impl HostFunctions for CxxHost<'_> { fn get_ledger_sqn(&self, out: &mut [u8]) -> HostResult { bytes_written(self.ctx.get_ledger_sqn(out)) } + fn get_parent_ledger_time(&self, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.get_parent_ledger_time(out)) + } + + fn get_parent_ledger_hash(&self, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.get_parent_ledger_hash(out)) + } + + fn get_base_fee(&self, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.get_base_fee(out)) + } + + fn is_amendment_enabled(&self, amendment: &[u8]) -> HostResult { + scalar(self.ctx.is_amendment_enabled(amendment)) + } + + fn cache_ledger_obj(&self, obj_id: &[u8], cache_idx: i32) -> HostResult { + scalar(self.ctx.cache_ledger_obj(obj_id, cache_idx)) + } + + fn get_tx_field(&self, field: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.get_tx_field(field, out)) + } + fn get_current_ledger_obj_field(&self, field: i32, out: &mut [u8]) -> HostResult { bytes_written(self.ctx.get_current_ledger_obj_field(field, out)) } + fn get_ledger_obj_field( + &self, + cache_idx: i32, + field: i32, + out: &mut [u8], + ) -> HostResult { + bytes_written(self.ctx.get_ledger_obj_field(cache_idx, field, out)) + } + + fn get_tx_nested_field(&self, locator: &[u8], out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.get_tx_nested_field(locator, out)) + } + + fn get_current_ledger_obj_nested_field( + &self, + locator: &[u8], + out: &mut [u8], + ) -> HostResult { + bytes_written(self.ctx.get_current_ledger_obj_nested_field(locator, out)) + } + + fn get_ledger_obj_nested_field( + &self, + cache_idx: i32, + locator: &[u8], + out: &mut [u8], + ) -> HostResult { + bytes_written( + self.ctx + .get_ledger_obj_nested_field(cache_idx, locator, out), + ) + } + + fn get_tx_array_len(&self, field: i32) -> HostResult { + scalar(self.ctx.get_tx_array_len(field)) + } + + fn get_current_ledger_obj_array_len(&self, field: i32) -> HostResult { + scalar(self.ctx.get_current_ledger_obj_array_len(field)) + } + + fn get_ledger_obj_array_len(&self, cache_idx: i32, field: i32) -> HostResult { + scalar(self.ctx.get_ledger_obj_array_len(cache_idx, field)) + } + + fn get_tx_nested_array_len(&self, locator: &[u8]) -> HostResult { + scalar(self.ctx.get_tx_nested_array_len(locator)) + } + + fn get_current_ledger_obj_nested_array_len(&self, locator: &[u8]) -> HostResult { + scalar(self.ctx.get_current_ledger_obj_nested_array_len(locator)) + } + + fn get_ledger_obj_nested_array_len(&self, cache_idx: i32, locator: &[u8]) -> HostResult { + scalar(self.ctx.get_ledger_obj_nested_array_len(cache_idx, locator)) + } + + fn check_signature(&self, message: &[u8], signature: &[u8], pubkey: &[u8]) -> HostResult { + scalar(self.ctx.check_signature(message, signature, pubkey)) + } + + fn account_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.account_keylet(account, out)) + } + + fn amm_keylet(&self, asset1: &[u8], asset2: &[u8], out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.amm_keylet(asset1, asset2, out)) + } + + fn check_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.check_keylet(account, seq, out)) + } + + fn credential_keylet( + &self, + subject: &[u8], + issuer: &[u8], + credential_type: &[u8], + out: &mut [u8], + ) -> HostResult { + bytes_written( + self.ctx + .credential_keylet(subject, issuer, credential_type, out), + ) + } + + fn delegate_keylet( + &self, + account: &[u8], + authorize: &[u8], + out: &mut [u8], + ) -> HostResult { + bytes_written(self.ctx.delegate_keylet(account, authorize, out)) + } + + fn deposit_preauth_keylet( + &self, + account: &[u8], + authorize: &[u8], + out: &mut [u8], + ) -> HostResult { + bytes_written(self.ctx.deposit_preauth_keylet(account, authorize, out)) + } + + fn did_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.did_keylet(account, out)) + } + + fn escrow_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.escrow_keylet(account, seq, out)) + } + + fn trust_line_keylet( + &self, + account1: &[u8], + account2: &[u8], + currency: &[u8], + out: &mut [u8], + ) -> HostResult { + bytes_written( + self.ctx + .trust_line_keylet(account1, account2, currency, out), + ) + } + + fn mptoken_issuance_keylet( + &self, + issuer: &[u8], + seq: i32, + out: &mut [u8], + ) -> HostResult { + bytes_written(self.ctx.mptoken_issuance_keylet(issuer, seq, out)) + } + + fn mptoken_keylet(&self, mptid: &[u8], holder: &[u8], out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.mptoken_keylet(mptid, holder, out)) + } + + fn nftoken_offer_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.nftoken_offer_keylet(account, seq, out)) + } + + fn offer_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.offer_keylet(account, seq, out)) + } + + fn oracle_keylet(&self, account: &[u8], doc_id: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.oracle_keylet(account, doc_id, out)) + } + + fn paychannel_keylet( + &self, + account: &[u8], + destination: &[u8], + seq: i32, + out: &mut [u8], + ) -> HostResult { + bytes_written(self.ctx.paychannel_keylet(account, destination, seq, out)) + } + + fn permissioned_domain_keylet( + &self, + account: &[u8], + seq: i32, + out: &mut [u8], + ) -> HostResult { + bytes_written(self.ctx.permissioned_domain_keylet(account, seq, out)) + } + + fn signer_list_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.signer_list_keylet(account, out)) + } + + fn ticket_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.ticket_keylet(account, seq, out)) + } + + fn vault_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.vault_keylet(account, seq, out)) + } + fn sha512_half(&self, data: &[u8], out: &mut [u8]) -> HostResult { bytes_written(self.ctx.sha512_half(data, out)) } @@ -259,6 +788,101 @@ impl HostFunctions for CxxHost<'_> { self.ctx.trace(msg, data, crossed(data_type)); Ok(()) } + + fn update_data(&self, data: &[u8]) -> HostResult { + scalar(self.ctx.update_data(data)) + } + + fn get_nft(&self, account: &[u8], nft_id: &[u8], out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.get_nft(account, nft_id, out)) + } + + fn get_nft_issuer(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.get_nft_issuer(nft_id, out)) + } + + fn get_nft_taxon(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.get_nft_taxon(nft_id, out)) + } + + fn get_nft_flags(&self, nft_id: &[u8]) -> HostResult { + scalar(self.ctx.get_nft_flags(nft_id)) + } + + fn get_nft_transfer_fee(&self, nft_id: &[u8]) -> HostResult { + scalar(self.ctx.get_nft_transfer_fee(nft_id)) + } + + fn get_nft_sequence(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.get_nft_sequence(nft_id, out)) + } + + fn float_from_int(&self, x: i64, mode: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.float_from_int(x, mode, out)) + } + + fn float_from_uint(&self, x: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.float_from_uint(x, mode, out)) + } + + fn float_from_stamount(&self, amount: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.float_from_stamount(amount, mode, out)) + } + + fn float_from_stnumber(&self, number: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.float_from_stnumber(number, mode, out)) + } + + fn float_to_int(&self, x: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.float_to_int(x, mode, out)) + } + + fn float_to_mant_exp( + &self, + x: &[u8], + mantissa_out: &mut [u8], + exponent_out: &mut [u8], + ) -> HostResult { + bytes_written(self.ctx.float_to_mant_exp(x, mantissa_out, exponent_out)) + } + + fn float_from_mant_exp( + &self, + mantissa: i64, + exponent: i32, + mode: i32, + out: &mut [u8], + ) -> HostResult { + bytes_written(self.ctx.float_from_mant_exp(mantissa, exponent, mode, out)) + } + + fn float_compare(&self, x: &[u8], y: &[u8]) -> HostResult { + scalar(self.ctx.float_compare(x, y)) + } + + fn float_add(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.float_add(x, y, mode, out)) + } + + fn float_subtract(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.float_subtract(x, y, mode, out)) + } + + fn float_multiply(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.float_multiply(x, y, mode, out)) + } + + fn float_divide(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.float_divide(x, y, mode, out)) + } + + fn float_root(&self, x: &[u8], n: i32, mode: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.float_root(x, n, mode, out)) + } + + fn float_power(&self, x: &[u8], n: i32, mode: i32, out: &mut [u8]) -> HostResult { + bytes_written(self.ctx.float_power(x, n, mode, out)) + } } fn run_escrow( @@ -559,16 +1183,11 @@ mod tests { assert_eq!(bytes_written(0), Ok(0)); assert_eq!(bytes_written(-3), Err(HostError::BufferTooSmall)); assert_eq!(bytes_written(-14), Err(HostError::NoMemExported)); + assert_eq!(scalar(1), Ok(1)); + assert_eq!(scalar(0), Ok(0)); + assert_eq!(scalar(-2), Err(HostError::FieldNotFound)); } - /// An exception caught on the C++ side arrives as `InternalFatal`, the code - /// `HostContext` answers with when a body throws. The engine stops the run on it and - /// the transaction is `tecINTERNAL`, rather than the contract being handed a code to - /// interpret. - /// - /// It arrives through the sign test like any other code, which is the point of - /// choosing a negative sentinel: `usize::try_from` rejects it, so this needs no case - /// of its own here and a positive length cannot be mistaken for it. #[test] fn a_caught_cxx_exception_arrives_as_internal_fatal() { assert_eq!(bytes_written(i32::MIN), Err(HostError::InternalFatal)); diff --git a/crates/xrpl-wasm-vm/src/abi.rs b/crates/xrpl-wasm-vm/src/abi.rs index 5a8cedd348..57e9dd6a36 100644 --- a/crates/xrpl-wasm-vm/src/abi.rs +++ b/crates/xrpl-wasm-vm/src/abi.rs @@ -160,6 +160,17 @@ pub(crate) fn read_borrowed<'a>( Ok(input.read(mem.data(caller))?) } +/// Decode a guest `u32` argument — a keylet's sequence number or document id — from +/// its four little-endian bytes, carried on to the host as its `i32` bit pattern. +/// +/// The ABI transports these as a 4-byte region rather than a wasm scalar (the guest +/// SDK passes `seq.to_le_bytes()`), so the region must be exactly four bytes; +/// `InvalidParams` otherwise, matching the C-ABI wrapper's `getDataUInt32`. +pub(crate) fn read_u32_arg(bytes: &[u8]) -> HostResult { + let arr: [u8; 4] = bytes.try_into().map_err(|_| HostError::InvalidParams)?; + Ok(i32::from_le_bytes(arr)) +} + /// Service a call whose answer is bytes, written straight into the guest's output /// region. /// @@ -256,6 +267,69 @@ pub(crate) fn write_buffered( Ok(n) } +/// The mantissa and exponent widths `float_to_mant_exp` writes: an `i64` and an `i32`. +/// Fixed by the ABI, not the guest, so the split is a constant rather than a reported +/// length. +const MANTISSA_BYTES: usize = 8; +const EXPONENT_BYTES: usize = 4; + +/// Service `float_to_mant_exp`, the one call that writes two output regions: the host +/// fills the run's output buffer with the mantissa followed by the exponent, and each +/// is copied to its own guest region once every rule has passed. +/// +/// Like [`write_buffered`], the host reads its input from the guest's memory and writes +/// to a scratch buffer, so the input stays borrowed rather than copied. The two output +/// regions are judged after the input, and the mantissa's region before the exponent's, +/// so the first fault reported is the leftmost. +pub(crate) fn write_mant_exp( + caller: &mut Caller<'_, VmState<'_>>, + mantissa_out: Region, + exponent_out: Region, + call: impl FnOnce(&dyn HostFunctions, &[u8], &mut [u8], &mut [u8]) -> HostResult, +) -> CallResult { + let mem = memory(caller)?; + let (data, state) = mem.data_and_store_mut(&mut *caller); + let host: &dyn HostFunctions = state.host; + + // The scratch buffer is split at the fixed mantissa width: the host fills the first + // eight bytes with the mantissa and the next four with the exponent. + let (mant_buf, exp_buf) = state.out_buffer.split_at_mut(MANTISSA_BYTES); + let mant_buf = &mut mant_buf[..MANTISSA_BYTES]; + let exp_buf = &mut exp_buf[..EXPONENT_BYTES]; + + let total = call(host, data, mant_buf, exp_buf)?; + + // Copy the mantissa, then the exponent, each only if its whole value fits its + // region — a region too small is `BufferTooSmall`, with nothing written. + let mant_range = mantissa_out.range()?; + let mant_dst = data + .get_mut(mant_range) + .ok_or(HostError::PointerOutOfBounds)?; + if mant_dst.len() < MANTISSA_BYTES { + return Err(HostError::BufferTooSmall.into()); + } + mant_dst[..MANTISSA_BYTES].copy_from_slice(&state.out_buffer[..MANTISSA_BYTES]); + + let exp_range = exponent_out.range()?; + let exp_dst = data + .get_mut(exp_range) + .ok_or(HostError::PointerOutOfBounds)?; + if exp_dst.len() < EXPONENT_BYTES { + return Err(HostError::BufferTooSmall.into()); + } + exp_dst[..EXPONENT_BYTES] + .copy_from_slice(&state.out_buffer[MANTISSA_BYTES..MANTISSA_BYTES + EXPONENT_BYTES]); + + charge_transfer(state, MANTISSA_BYTES + EXPONENT_BYTES)?; + #[expect( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + reason = "the total is 12, far inside i32" + )] + let total = total as i32; + Ok(total) +} + #[cfg(test)] mod tests { use super::*; @@ -271,15 +345,313 @@ mod tests { fn get_ledger_sqn(&self, _out: &mut [u8]) -> HostResult { unreachable!("no unit test in this module calls the host") } + fn get_parent_ledger_time(&self, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_parent_ledger_hash(&self, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_base_fee(&self, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn is_amendment_enabled(&self, _amendment: &[u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn cache_ledger_obj(&self, _obj_id: &[u8], _cache_idx: i32) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_tx_field(&self, _field: i32, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } fn get_current_ledger_obj_field(&self, _field: i32, _out: &mut [u8]) -> HostResult { unreachable!("no unit test in this module calls the host") } + fn get_ledger_obj_field( + &self, + _cache_idx: i32, + _field: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_tx_nested_field(&self, _locator: &[u8], _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_current_ledger_obj_nested_field( + &self, + _locator: &[u8], + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_ledger_obj_nested_field( + &self, + _cache_idx: i32, + _locator: &[u8], + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_tx_array_len(&self, _field: i32) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_current_ledger_obj_array_len(&self, _field: i32) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_ledger_obj_array_len(&self, _cache_idx: i32, _field: i32) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_tx_nested_array_len(&self, _locator: &[u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_current_ledger_obj_nested_array_len(&self, _locator: &[u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_ledger_obj_nested_array_len( + &self, + _cache_idx: i32, + _locator: &[u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn check_signature( + &self, + _message: &[u8], + _signature: &[u8], + _pubkey: &[u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn account_keylet(&self, _account: &[u8], _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn amm_keylet(&self, _asset1: &[u8], _asset2: &[u8], _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn check_keylet(&self, _account: &[u8], _seq: i32, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn credential_keylet( + &self, + _subject: &[u8], + _issuer: &[u8], + _credential_type: &[u8], + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn delegate_keylet( + &self, + _account: &[u8], + _authorize: &[u8], + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn deposit_preauth_keylet( + &self, + _account: &[u8], + _authorize: &[u8], + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn did_keylet(&self, _account: &[u8], _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn escrow_keylet(&self, _account: &[u8], _seq: i32, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn trust_line_keylet( + &self, + _account1: &[u8], + _account2: &[u8], + _currency: &[u8], + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn mptoken_issuance_keylet( + &self, + _issuer: &[u8], + _seq: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn mptoken_keylet( + &self, + _mptid: &[u8], + _holder: &[u8], + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn nftoken_offer_keylet( + &self, + _account: &[u8], + _seq: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn offer_keylet(&self, _account: &[u8], _seq: i32, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn oracle_keylet( + &self, + _account: &[u8], + _doc_id: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn paychannel_keylet( + &self, + _account: &[u8], + _destination: &[u8], + _seq: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn permissioned_domain_keylet( + &self, + _account: &[u8], + _seq: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn signer_list_keylet(&self, _account: &[u8], _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn ticket_keylet(&self, _account: &[u8], _seq: i32, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn vault_keylet(&self, _account: &[u8], _seq: i32, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } fn sha512_half(&self, _data: &[u8], _out: &mut [u8]) -> HostResult { unreachable!("no unit test in this module calls the host") } fn trace(&self, _msg: &str, _data: &[u8], _data_type: TraceDataType) -> HostResult<()> { unreachable!("no unit test in this module calls the host") } + fn update_data(&self, _data: &[u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_nft(&self, _account: &[u8], _nft_id: &[u8], _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_nft_issuer(&self, _nft_id: &[u8], _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_nft_taxon(&self, _nft_id: &[u8], _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_nft_flags(&self, _nft_id: &[u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_nft_transfer_fee(&self, _nft_id: &[u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn get_nft_sequence(&self, _nft_id: &[u8], _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_from_int(&self, _x: i64, _mode: i32, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_from_uint(&self, _x: &[u8], _mode: i32, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_from_stamount( + &self, + _amount: &[u8], + _mode: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_from_stnumber( + &self, + _number: &[u8], + _mode: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_to_int(&self, _x: &[u8], _mode: i32, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_to_mant_exp( + &self, + _x: &[u8], + _mantissa_out: &mut [u8], + _exponent_out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_from_mant_exp( + &self, + _mantissa: i64, + _exponent: i32, + _mode: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_compare(&self, _x: &[u8], _y: &[u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_add( + &self, + _x: &[u8], + _y: &[u8], + _mode: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_subtract( + &self, + _x: &[u8], + _y: &[u8], + _mode: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_multiply( + &self, + _x: &[u8], + _y: &[u8], + _mode: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_divide( + &self, + _x: &[u8], + _y: &[u8], + _mode: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_root(&self, _x: &[u8], _n: i32, _mode: i32, _out: &mut [u8]) -> HostResult { + unreachable!("no unit test in this module calls the host") + } + fn float_power( + &self, + _x: &[u8], + _n: i32, + _mode: i32, + _out: &mut [u8], + ) -> HostResult { + unreachable!("no unit test in this module calls the host") + } } fn state(budget: u64) -> VmState<'static> { diff --git a/crates/xrpl-wasm-vm/src/register.rs b/crates/xrpl-wasm-vm/src/register.rs index 99bec20a20..7a31a34b9c 100644 --- a/crates/xrpl-wasm-vm/src/register.rs +++ b/crates/xrpl-wasm-vm/src/register.rs @@ -1,4 +1,7 @@ -use crate::abi::{charged, charged_unreported, read_borrowed, write_buffered, write_into}; +use crate::abi::{ + charged, charged_unreported, read_borrowed, read_u32_arg, write_buffered, write_into, + write_mant_exp, +}; use crate::region::Region; use crate::vm::VmState; use wasmi::{Caller, Linker}; @@ -35,6 +38,88 @@ pub(crate) fn register_host_functions( }) }, ), + HostFunctionSpec::GetParentLedgerTime => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetParentLedgerTime, |c| { + let out = Region::new(out_ptr, out_len); + write_into(c, out, |host, out| host.get_parent_ledger_time(out)) + }) + }, + ), + HostFunctionSpec::GetParentLedgerHash => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetParentLedgerHash, |c| { + let out = Region::new(out_ptr, out_len); + write_into(c, out, |host, out| host.get_parent_ledger_hash(out)) + }) + }, + ), + HostFunctionSpec::GetBaseFee => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetBaseFee, |c| { + let out = Region::new(out_ptr, out_len); + write_into(c, out, |host, out| host.get_base_fee(out)) + }) + }, + ), + HostFunctionSpec::IsAmendmentEnabled => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + ptr: i32, + len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::IsAmendmentEnabled, |c| { + let host = c.data().host; + let amendment = read_borrowed(c, Region::new(ptr, len))?; + Ok(host.is_amendment_enabled(amendment)?) + }) + }, + ), + HostFunctionSpec::CacheLedgerObj => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + id_ptr: i32, + id_len: i32, + cache_idx: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::CacheLedgerObj, |c| { + let host = c.data().host; + let obj_id = read_borrowed(c, Region::new(id_ptr, id_len))?; + Ok(host.cache_ledger_obj(obj_id, cache_idx)?) + }) + }, + ), + HostFunctionSpec::GetTxField => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + field: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetTxField, |c| { + let out = Region::new(out_ptr, out_len); + write_into(c, out, |host, out| host.get_tx_field(field, out)) + }) + }, + ), HostFunctionSpec::GetCurrentLedgerObjField => linker.func_wrap( HOST_MODULE, op.wasm_name(), @@ -55,6 +140,633 @@ pub(crate) fn register_host_functions( ) }, ), + HostFunctionSpec::GetLedgerObjField => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + cache_idx: i32, + field: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetLedgerObjField, |c| { + let out = Region::new(out_ptr, out_len); + write_into(c, out, |host, out| { + host.get_ledger_obj_field(cache_idx, field, out) + }) + }) + }, + ), + HostFunctionSpec::GetTxNestedField => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + loc_ptr: i32, + loc_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetTxNestedField, |c| { + let out = Region::new(out_ptr, out_len); + let locator = Region::new(loc_ptr, loc_len); + write_buffered(c, out, |host, data, buf| { + host.get_tx_nested_field(locator.read(data)?, buf) + }) + }) + }, + ), + HostFunctionSpec::GetCurrentLedgerObjNestedField => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + loc_ptr: i32, + loc_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged( + &mut caller, + HostFunctionSpec::GetCurrentLedgerObjNestedField, + |c| { + let out = Region::new(out_ptr, out_len); + let locator = Region::new(loc_ptr, loc_len); + write_buffered(c, out, |host, data, buf| { + host.get_current_ledger_obj_nested_field(locator.read(data)?, buf) + }) + }, + ) + }, + ), + HostFunctionSpec::GetLedgerObjNestedField => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + cache_idx: i32, + loc_ptr: i32, + loc_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged( + &mut caller, + HostFunctionSpec::GetLedgerObjNestedField, + |c| { + let out = Region::new(out_ptr, out_len); + let locator = Region::new(loc_ptr, loc_len); + write_buffered(c, out, |host, data, buf| { + host.get_ledger_obj_nested_field( + cache_idx, + locator.read(data)?, + buf, + ) + }) + }, + ) + }, + ), + HostFunctionSpec::GetTxArrayLen => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, field: i32| -> Result { + charged(&mut caller, HostFunctionSpec::GetTxArrayLen, |c| { + Ok(c.data().host.get_tx_array_len(field)?) + }) + }, + ), + HostFunctionSpec::GetCurrentLedgerObjArrayLen => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, field: i32| -> Result { + charged( + &mut caller, + HostFunctionSpec::GetCurrentLedgerObjArrayLen, + |c| Ok(c.data().host.get_current_ledger_obj_array_len(field)?), + ) + }, + ), + HostFunctionSpec::GetLedgerObjArrayLen => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + cache_idx: i32, + field: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetLedgerObjArrayLen, |c| { + Ok(c.data().host.get_ledger_obj_array_len(cache_idx, field)?) + }) + }, + ), + HostFunctionSpec::GetTxNestedArrayLen => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + loc_ptr: i32, + loc_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetTxNestedArrayLen, |c| { + let host = c.data().host; + let locator = read_borrowed(c, Region::new(loc_ptr, loc_len))?; + Ok(host.get_tx_nested_array_len(locator)?) + }) + }, + ), + HostFunctionSpec::GetCurrentLedgerObjNestedArrayLen => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + loc_ptr: i32, + loc_len: i32| + -> Result { + charged( + &mut caller, + HostFunctionSpec::GetCurrentLedgerObjNestedArrayLen, + |c| { + let host = c.data().host; + let locator = read_borrowed(c, Region::new(loc_ptr, loc_len))?; + Ok(host.get_current_ledger_obj_nested_array_len(locator)?) + }, + ) + }, + ), + HostFunctionSpec::GetLedgerObjNestedArrayLen => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + cache_idx: i32, + loc_ptr: i32, + loc_len: i32| + -> Result { + charged( + &mut caller, + HostFunctionSpec::GetLedgerObjNestedArrayLen, + |c| { + let host = c.data().host; + let locator = read_borrowed(c, Region::new(loc_ptr, loc_len))?; + Ok(host.get_ledger_obj_nested_array_len(cache_idx, locator)?) + }, + ) + }, + ), + HostFunctionSpec::CheckSignature => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + msg_ptr: i32, + msg_len: i32, + sig_ptr: i32, + sig_len: i32, + pk_ptr: i32, + pk_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::CheckSignature, |c| { + let host = c.data().host; + let message = read_borrowed(c, Region::new(msg_ptr, msg_len))?; + let signature = read_borrowed(c, Region::new(sig_ptr, sig_len))?; + let pubkey = read_borrowed(c, Region::new(pk_ptr, pk_len))?; + Ok(host.check_signature(message, signature, pubkey)?) + }) + }, + ), + HostFunctionSpec::AccountKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::AccountKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + write_buffered(c, out, |host, data, buf| { + host.account_keylet(account.read(data)?, buf) + }) + }) + }, + ), + HostFunctionSpec::AmmKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + a1_ptr: i32, + a1_len: i32, + a2_ptr: i32, + a2_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::AmmKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let asset1 = Region::new(a1_ptr, a1_len); + let asset2 = Region::new(a2_ptr, a2_len); + write_buffered(c, out, |host, data, buf| { + host.amm_keylet(asset1.read(data)?, asset2.read(data)?, buf) + }) + }) + }, + ), + HostFunctionSpec::CheckKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + seq_ptr: i32, + seq_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::CheckKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + let seq = Region::new(seq_ptr, seq_len); + write_buffered(c, out, |host, data, buf| { + let account = account.read(data)?; + let seq = read_u32_arg(seq.read(data)?)?; + host.check_keylet(account, seq, buf) + }) + }) + }, + ), + HostFunctionSpec::CredentialKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + subj_ptr: i32, + subj_len: i32, + iss_ptr: i32, + iss_len: i32, + ct_ptr: i32, + ct_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::CredentialKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let subject = Region::new(subj_ptr, subj_len); + let issuer = Region::new(iss_ptr, iss_len); + let cred_type = Region::new(ct_ptr, ct_len); + write_buffered(c, out, |host, data, buf| { + host.credential_keylet( + subject.read(data)?, + issuer.read(data)?, + cred_type.read(data)?, + buf, + ) + }) + }) + }, + ), + HostFunctionSpec::DelegateKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + auth_ptr: i32, + auth_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::DelegateKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + let authorize = Region::new(auth_ptr, auth_len); + write_buffered(c, out, |host, data, buf| { + host.delegate_keylet(account.read(data)?, authorize.read(data)?, buf) + }) + }) + }, + ), + HostFunctionSpec::DepositPreauthKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + auth_ptr: i32, + auth_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::DepositPreauthKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + let authorize = Region::new(auth_ptr, auth_len); + write_buffered(c, out, |host, data, buf| { + host.deposit_preauth_keylet( + account.read(data)?, + authorize.read(data)?, + buf, + ) + }) + }) + }, + ), + HostFunctionSpec::DidKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::DidKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + write_buffered(c, out, |host, data, buf| { + host.did_keylet(account.read(data)?, buf) + }) + }) + }, + ), + HostFunctionSpec::EscrowKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + seq_ptr: i32, + seq_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::EscrowKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + let seq = Region::new(seq_ptr, seq_len); + write_buffered(c, out, |host, data, buf| { + let account = account.read(data)?; + let seq = read_u32_arg(seq.read(data)?)?; + host.escrow_keylet(account, seq, buf) + }) + }) + }, + ), + HostFunctionSpec::TrustLineKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + a1_ptr: i32, + a1_len: i32, + a2_ptr: i32, + a2_len: i32, + cur_ptr: i32, + cur_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::TrustLineKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account1 = Region::new(a1_ptr, a1_len); + let account2 = Region::new(a2_ptr, a2_len); + let currency = Region::new(cur_ptr, cur_len); + write_buffered(c, out, |host, data, buf| { + host.trust_line_keylet( + account1.read(data)?, + account2.read(data)?, + currency.read(data)?, + buf, + ) + }) + }) + }, + ), + HostFunctionSpec::MptokenIssuanceKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + seq_ptr: i32, + seq_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::MptokenIssuanceKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let issuer = Region::new(acc_ptr, acc_len); + let seq = Region::new(seq_ptr, seq_len); + write_buffered(c, out, |host, data, buf| { + let issuer = issuer.read(data)?; + let seq = read_u32_arg(seq.read(data)?)?; + host.mptoken_issuance_keylet(issuer, seq, buf) + }) + }) + }, + ), + HostFunctionSpec::MptokenKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + mpt_ptr: i32, + mpt_len: i32, + holder_ptr: i32, + holder_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::MptokenKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let mptid = Region::new(mpt_ptr, mpt_len); + let holder = Region::new(holder_ptr, holder_len); + write_buffered(c, out, |host, data, buf| { + host.mptoken_keylet(mptid.read(data)?, holder.read(data)?, buf) + }) + }) + }, + ), + HostFunctionSpec::NftokenOfferKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + seq_ptr: i32, + seq_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::NftokenOfferKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + let seq = Region::new(seq_ptr, seq_len); + write_buffered(c, out, |host, data, buf| { + let account = account.read(data)?; + let seq = read_u32_arg(seq.read(data)?)?; + host.nftoken_offer_keylet(account, seq, buf) + }) + }) + }, + ), + HostFunctionSpec::OfferKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + seq_ptr: i32, + seq_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::OfferKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + let seq = Region::new(seq_ptr, seq_len); + write_buffered(c, out, |host, data, buf| { + let account = account.read(data)?; + let seq = read_u32_arg(seq.read(data)?)?; + host.offer_keylet(account, seq, buf) + }) + }) + }, + ), + HostFunctionSpec::OracleKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + doc_ptr: i32, + doc_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::OracleKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + let doc_id = Region::new(doc_ptr, doc_len); + write_buffered(c, out, |host, data, buf| { + let account = account.read(data)?; + let doc_id = read_u32_arg(doc_id.read(data)?)?; + host.oracle_keylet(account, doc_id, buf) + }) + }) + }, + ), + HostFunctionSpec::PaychannelKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + dst_ptr: i32, + dst_len: i32, + seq_ptr: i32, + seq_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::PaychannelKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + let destination = Region::new(dst_ptr, dst_len); + let seq = Region::new(seq_ptr, seq_len); + write_buffered(c, out, |host, data, buf| { + host.paychannel_keylet( + account.read(data)?, + destination.read(data)?, + read_u32_arg(seq.read(data)?)?, + buf, + ) + }) + }) + }, + ), + HostFunctionSpec::PermissionedDomainKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + seq_ptr: i32, + seq_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged( + &mut caller, + HostFunctionSpec::PermissionedDomainKeylet, + |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + let seq = Region::new(seq_ptr, seq_len); + write_buffered(c, out, |host, data, buf| { + let account = account.read(data)?; + let seq = read_u32_arg(seq.read(data)?)?; + host.permissioned_domain_keylet(account, seq, buf) + }) + }, + ) + }, + ), + HostFunctionSpec::SignerListKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::SignerListKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + write_buffered(c, out, |host, data, buf| { + host.signer_list_keylet(account.read(data)?, buf) + }) + }) + }, + ), + HostFunctionSpec::TicketKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + seq_ptr: i32, + seq_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::TicketKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + let seq = Region::new(seq_ptr, seq_len); + write_buffered(c, out, |host, data, buf| { + let account = account.read(data)?; + let seq = read_u32_arg(seq.read(data)?)?; + host.ticket_keylet(account, seq, buf) + }) + }) + }, + ), + HostFunctionSpec::VaultKeylet => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + seq_ptr: i32, + seq_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::VaultKeylet, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + let seq = Region::new(seq_ptr, seq_len); + write_buffered(c, out, |host, data, buf| { + let account = account.read(data)?; + let seq = read_u32_arg(seq.read(data)?)?; + host.vault_keylet(account, seq, buf) + }) + }) + }, + ), HostFunctionSpec::Sha512Half => linker.func_wrap( HOST_MODULE, op.wasm_name(), @@ -105,6 +817,398 @@ pub(crate) fn register_host_functions( }) }, ), + HostFunctionSpec::UpdateData => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + ptr: i32, + len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::UpdateData, |c| { + let host = c.data().host; + let data = read_borrowed(c, Region::new(ptr, len))?; + Ok(host.update_data(data)?) + }) + }, + ), + HostFunctionSpec::GetNft => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + acc_ptr: i32, + acc_len: i32, + nft_ptr: i32, + nft_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetNft, |c| { + let out = Region::new(out_ptr, out_len); + let account = Region::new(acc_ptr, acc_len); + let nft_id = Region::new(nft_ptr, nft_len); + write_buffered(c, out, |host, data, buf| { + host.get_nft(account.read(data)?, nft_id.read(data)?, buf) + }) + }) + }, + ), + HostFunctionSpec::GetNftIssuer => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + nft_ptr: i32, + nft_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetNftIssuer, |c| { + let out = Region::new(out_ptr, out_len); + let nft_id = Region::new(nft_ptr, nft_len); + write_buffered(c, out, |host, data, buf| { + host.get_nft_issuer(nft_id.read(data)?, buf) + }) + }) + }, + ), + HostFunctionSpec::GetNftTaxon => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + nft_ptr: i32, + nft_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetNftTaxon, |c| { + let out = Region::new(out_ptr, out_len); + let nft_id = Region::new(nft_ptr, nft_len); + write_buffered(c, out, |host, data, buf| { + host.get_nft_taxon(nft_id.read(data)?, buf) + }) + }) + }, + ), + HostFunctionSpec::GetNftFlags => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + nft_ptr: i32, + nft_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetNftFlags, |c| { + let host = c.data().host; + let nft_id = read_borrowed(c, Region::new(nft_ptr, nft_len))?; + Ok(host.get_nft_flags(nft_id)?) + }) + }, + ), + HostFunctionSpec::GetNftTransferFee => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + nft_ptr: i32, + nft_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetNftTransferFee, |c| { + let host = c.data().host; + let nft_id = read_borrowed(c, Region::new(nft_ptr, nft_len))?; + Ok(host.get_nft_transfer_fee(nft_id)?) + }) + }, + ), + HostFunctionSpec::GetNftSequence => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + nft_ptr: i32, + nft_len: i32, + out_ptr: i32, + out_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::GetNftSequence, |c| { + let out = Region::new(out_ptr, out_len); + let nft_id = Region::new(nft_ptr, nft_len); + write_buffered(c, out, |host, data, buf| { + host.get_nft_sequence(nft_id.read(data)?, buf) + }) + }) + }, + ), + HostFunctionSpec::FloatFromInt => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + x: i64, + out_ptr: i32, + out_len: i32, + mode: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatFromInt, |c| { + let out = Region::new(out_ptr, out_len); + write_into(c, out, |host, out| host.float_from_int(x, mode, out)) + }) + }, + ), + HostFunctionSpec::FloatFromUint => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + in_ptr: i32, + in_len: i32, + out_ptr: i32, + out_len: i32, + mode: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatFromUint, |c| { + let out = Region::new(out_ptr, out_len); + let x = Region::new(in_ptr, in_len); + write_buffered(c, out, |host, data, buf| { + host.float_from_uint(x.read(data)?, mode, buf) + }) + }) + }, + ), + HostFunctionSpec::FloatFromStamount => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + in_ptr: i32, + in_len: i32, + out_ptr: i32, + out_len: i32, + mode: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatFromStamount, |c| { + let out = Region::new(out_ptr, out_len); + let amount = Region::new(in_ptr, in_len); + write_buffered(c, out, |host, data, buf| { + host.float_from_stamount(amount.read(data)?, mode, buf) + }) + }) + }, + ), + HostFunctionSpec::FloatFromStnumber => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + in_ptr: i32, + in_len: i32, + out_ptr: i32, + out_len: i32, + mode: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatFromStnumber, |c| { + let out = Region::new(out_ptr, out_len); + let number = Region::new(in_ptr, in_len); + write_buffered(c, out, |host, data, buf| { + host.float_from_stnumber(number.read(data)?, mode, buf) + }) + }) + }, + ), + HostFunctionSpec::FloatToInt => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + in_ptr: i32, + in_len: i32, + out_ptr: i32, + out_len: i32, + mode: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatToInt, |c| { + let out = Region::new(out_ptr, out_len); + let x = Region::new(in_ptr, in_len); + write_buffered(c, out, |host, data, buf| { + host.float_to_int(x.read(data)?, mode, buf) + }) + }) + }, + ), + HostFunctionSpec::FloatToMantExp => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + in_ptr: i32, + in_len: i32, + mant_ptr: i32, + mant_len: i32, + exp_ptr: i32, + exp_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatToMantExp, |c| { + let mantissa = Region::new(mant_ptr, mant_len); + let exponent = Region::new(exp_ptr, exp_len); + let x = Region::new(in_ptr, in_len); + write_mant_exp(c, mantissa, exponent, |host, data, mant, exp| { + host.float_to_mant_exp(x.read(data)?, mant, exp) + }) + }) + }, + ), + HostFunctionSpec::FloatFromMantExp => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + mantissa: i64, + exponent: i32, + out_ptr: i32, + out_len: i32, + mode: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatFromMantExp, |c| { + let out = Region::new(out_ptr, out_len); + write_into(c, out, |host, out| { + host.float_from_mant_exp(mantissa, exponent, mode, out) + }) + }) + }, + ), + HostFunctionSpec::FloatCompare => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + x_ptr: i32, + x_len: i32, + y_ptr: i32, + y_len: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatCompare, |c| { + let host = c.data().host; + let x = read_borrowed(c, Region::new(x_ptr, x_len))?; + let y = read_borrowed(c, Region::new(y_ptr, y_len))?; + Ok(host.float_compare(x, y)?) + }) + }, + ), + HostFunctionSpec::FloatAdd => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + x_ptr: i32, + x_len: i32, + y_ptr: i32, + y_len: i32, + out_ptr: i32, + out_len: i32, + mode: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatAdd, |c| { + let out = Region::new(out_ptr, out_len); + let x = Region::new(x_ptr, x_len); + let y = Region::new(y_ptr, y_len); + write_buffered(c, out, |host, data, buf| { + host.float_add(x.read(data)?, y.read(data)?, mode, buf) + }) + }) + }, + ), + HostFunctionSpec::FloatSubtract => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + x_ptr: i32, + x_len: i32, + y_ptr: i32, + y_len: i32, + out_ptr: i32, + out_len: i32, + mode: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatSubtract, |c| { + let out = Region::new(out_ptr, out_len); + let x = Region::new(x_ptr, x_len); + let y = Region::new(y_ptr, y_len); + write_buffered(c, out, |host, data, buf| { + host.float_subtract(x.read(data)?, y.read(data)?, mode, buf) + }) + }) + }, + ), + HostFunctionSpec::FloatMultiply => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + x_ptr: i32, + x_len: i32, + y_ptr: i32, + y_len: i32, + out_ptr: i32, + out_len: i32, + mode: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatMultiply, |c| { + let out = Region::new(out_ptr, out_len); + let x = Region::new(x_ptr, x_len); + let y = Region::new(y_ptr, y_len); + write_buffered(c, out, |host, data, buf| { + host.float_multiply(x.read(data)?, y.read(data)?, mode, buf) + }) + }) + }, + ), + HostFunctionSpec::FloatDivide => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + x_ptr: i32, + x_len: i32, + y_ptr: i32, + y_len: i32, + out_ptr: i32, + out_len: i32, + mode: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatDivide, |c| { + let out = Region::new(out_ptr, out_len); + let x = Region::new(x_ptr, x_len); + let y = Region::new(y_ptr, y_len); + write_buffered(c, out, |host, data, buf| { + host.float_divide(x.read(data)?, y.read(data)?, mode, buf) + }) + }) + }, + ), + HostFunctionSpec::FloatRoot => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + in_ptr: i32, + in_len: i32, + n: i32, + out_ptr: i32, + out_len: i32, + mode: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatRoot, |c| { + let out = Region::new(out_ptr, out_len); + let x = Region::new(in_ptr, in_len); + write_buffered(c, out, |host, data, buf| { + host.float_root(x.read(data)?, n, mode, buf) + }) + }) + }, + ), + HostFunctionSpec::FloatPower => linker.func_wrap( + HOST_MODULE, + op.wasm_name(), + |mut caller: Caller<'_, VmState<'_>>, + in_ptr: i32, + in_len: i32, + n: i32, + out_ptr: i32, + out_len: i32, + mode: i32| + -> Result { + charged(&mut caller, HostFunctionSpec::FloatPower, |c| { + let out = Region::new(out_ptr, out_len); + let x = Region::new(in_ptr, in_len); + write_buffered(c, out, |host, data, buf| { + host.float_power(x.read(data)?, n, mode, buf) + }) + }) + }, + ), }?; } Ok(()) diff --git a/crates/xrpl-wasm-vm/tests/budgets.rs b/crates/xrpl-wasm-vm/tests/budgets.rs index 5b5f4a9965..b03d8b3174 100644 --- a/crates/xrpl-wasm-vm/tests/budgets.rs +++ b/crates/xrpl-wasm-vm/tests/budgets.rs @@ -94,11 +94,189 @@ fn call_for(op: HostFunctionSpec) -> Call { "(call $ldgr_index (i32.const 0) (i32.const 4))", 2, ), + HostFunctionSpec::GetParentLedgerTime => ( + import::PARENT_LDGR_TIME, + "(call $parent_ldgr_time (i32.const 0) (i32.const 4))", + 2, + ), + HostFunctionSpec::GetParentLedgerHash => ( + import::PARENT_LDGR_HASH, + "(call $parent_ldgr_hash (i32.const 0) (i32.const 32))", + 2, + ), + HostFunctionSpec::GetBaseFee => ( + import::BASE_FEE, + "(call $base_fee (i32.const 0) (i32.const 4))", + 2, + ), + HostFunctionSpec::IsAmendmentEnabled => ( + import::AMENDMENT_ENABLED, + "(call $amendment_enabled (i32.const 0) (i32.const 32))", + 2, + ), + HostFunctionSpec::CacheLedgerObj => ( + import::CACHE_LE, + "(call $cache_le (i32.const 0) (i32.const 32) (i32.const 0))", + 3, + ), + HostFunctionSpec::GetTxField => ( + import::TX_FIELD, + "(call $tx_field (i32.const 1) (i32.const 0) (i32.const 4))", + 3, + ), HostFunctionSpec::GetCurrentLedgerObjField => ( import::HOME_LE_FIELD, "(call $home_le_field (i32.const 1) (i32.const 0) (i32.const 4))", 3, ), + HostFunctionSpec::GetLedgerObjField => ( + import::LE_FIELD, + "(call $le_field (i32.const 1) (i32.const 1) (i32.const 0) (i32.const 4))", + 4, + ), + HostFunctionSpec::GetTxNestedField => ( + import::TX_INNER, + "(call $tx_inner (i32.const 0) (i32.const 4) (i32.const 8) (i32.const 4))", + 4, + ), + HostFunctionSpec::GetCurrentLedgerObjNestedField => ( + import::HOME_LE_INNER, + "(call $home_le_inner (i32.const 0) (i32.const 4) (i32.const 8) (i32.const 4))", + 4, + ), + HostFunctionSpec::GetLedgerObjNestedField => ( + import::LE_INNER, + "(call $le_inner (i32.const 1) (i32.const 0) (i32.const 4) (i32.const 8) (i32.const 4))", + 5, + ), + HostFunctionSpec::GetTxArrayLen => { + (import::TX_ARR_LEN, "(call $tx_arr_len (i32.const 1))", 1) + } + HostFunctionSpec::GetCurrentLedgerObjArrayLen => ( + import::HOME_LE_ARR_LEN, + "(call $home_le_arr_len (i32.const 1))", + 1, + ), + HostFunctionSpec::GetLedgerObjArrayLen => ( + import::LE_ARR_LEN, + "(call $le_arr_len (i32.const 1) (i32.const 1))", + 2, + ), + HostFunctionSpec::GetTxNestedArrayLen => ( + import::TX_INNER_ARR_LEN, + "(call $tx_inner_arr_len (i32.const 0) (i32.const 4))", + 2, + ), + HostFunctionSpec::GetCurrentLedgerObjNestedArrayLen => ( + import::HOME_LE_INNER_ARR_LEN, + "(call $home_le_inner_arr_len (i32.const 0) (i32.const 4))", + 2, + ), + HostFunctionSpec::GetLedgerObjNestedArrayLen => ( + import::LE_INNER_ARR_LEN, + "(call $le_inner_arr_len (i32.const 1) (i32.const 0) (i32.const 4))", + 3, + ), + HostFunctionSpec::CheckSignature => ( + import::CHECK_SIG, + "(call $check_sig (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0))", + 6, + ), + HostFunctionSpec::AccountKeylet => ( + import::ACCOUNTROOT_ID, + "(call $accountroot_id (i32.const 0) (i32.const 20) (i32.const 32) (i32.const 32))", + 4, + ), + HostFunctionSpec::AmmKeylet => ( + import::AMM_ID, + "(call $amm_id (i32.const 0) (i32.const 20) (i32.const 24) (i32.const 40) (i32.const 0) (i32.const 32))", + 6, + ), + HostFunctionSpec::CheckKeylet => ( + import::CHECK_ID, + "(call $check_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))", + 6, + ), + HostFunctionSpec::CredentialKeylet => ( + import::CREDENTIAL_ID, + "(call $credential_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 20) (i32.const 40) (i32.const 4) (i32.const 44) (i32.const 20))", + 8, + ), + HostFunctionSpec::DelegateKeylet => ( + import::DELEGATE_ID, + "(call $delegate_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 20) (i32.const 40) (i32.const 32))", + 6, + ), + HostFunctionSpec::DepositPreauthKeylet => ( + import::DEPOSIT_PREAUTH_ID, + "(call $deposit_preauth_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 20) (i32.const 40) (i32.const 32))", + 6, + ), + HostFunctionSpec::DidKeylet => ( + import::DID_ID, + "(call $did_id (i32.const 0) (i32.const 20) (i32.const 32) (i32.const 32))", + 4, + ), + HostFunctionSpec::EscrowKeylet => ( + import::ESCROW_ID, + "(call $escrow_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))", + 6, + ), + HostFunctionSpec::TrustLineKeylet => ( + import::TRUSTLINE_ID, + "(call $trustline_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 20) (i32.const 40) (i32.const 20) (i32.const 60) (i32.const 32))", + 8, + ), + HostFunctionSpec::MptokenIssuanceKeylet => ( + import::MPT_ISSUANCE_ID, + "(call $mpt_issuance_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))", + 6, + ), + HostFunctionSpec::MptokenKeylet => ( + import::MPTOKEN_ID, + "(call $mptoken_id (i32.const 0) (i32.const 24) (i32.const 24) (i32.const 20) (i32.const 44) (i32.const 20))", + 6, + ), + HostFunctionSpec::NftokenOfferKeylet => ( + import::NFT_OFFER_ID, + "(call $nft_offer_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))", + 6, + ), + HostFunctionSpec::OfferKeylet => ( + import::OFFER_ID, + "(call $offer_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))", + 6, + ), + HostFunctionSpec::OracleKeylet => ( + import::ORACLE_ID, + "(call $oracle_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))", + 6, + ), + HostFunctionSpec::PaychannelKeylet => ( + import::PAYCHAN_ID, + "(call $paychan_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 40) (i32.const 20))", + 8, + ), + HostFunctionSpec::PermissionedDomainKeylet => ( + import::PERMISSIONED_DOMAIN_ID, + "(call $permissioned_domain_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))", + 6, + ), + HostFunctionSpec::SignerListKeylet => ( + import::SIGNERS_ID, + "(call $signers_id (i32.const 0) (i32.const 20) (i32.const 32) (i32.const 32))", + 4, + ), + HostFunctionSpec::TicketKeylet => ( + import::TICKET_ID, + "(call $ticket_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))", + 6, + ), + HostFunctionSpec::VaultKeylet => ( + import::VAULT_ID, + "(call $vault_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))", + 6, + ), HostFunctionSpec::Sha512Half => ( import::SHA512_HALF, "(call $sha512_half (i32.const 0) (i32.const 4) (i32.const 0) (i32.const 32))", @@ -109,6 +287,111 @@ fn call_for(op: HostFunctionSpec) -> Call { "(call $trace (i32.const 0) (i32.const 0) (i32.const 1) (i32.const 0) (i32.const 0))", 5, ), + HostFunctionSpec::UpdateData => ( + import::SET_DATA, + "(call $set_data (i32.const 0) (i32.const 8))", + 2, + ), + HostFunctionSpec::GetNft => ( + import::NFT_URI, + "(call $nft_uri (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 32) (i32.const 52) (i32.const 12))", + 6, + ), + HostFunctionSpec::GetNftIssuer => ( + import::NFT_ISSUER, + "(call $nft_issuer (i32.const 0) (i32.const 32) (i32.const 32) (i32.const 20))", + 4, + ), + HostFunctionSpec::GetNftTaxon => ( + import::NFT_TAXON, + "(call $nft_taxon (i32.const 0) (i32.const 32) (i32.const 32) (i32.const 4))", + 4, + ), + HostFunctionSpec::GetNftFlags => ( + import::NFT_FLAGS, + "(call $nft_flags (i32.const 0) (i32.const 32))", + 2, + ), + HostFunctionSpec::GetNftTransferFee => ( + import::NFT_XFER_FEE, + "(call $nft_xfer_fee (i32.const 0) (i32.const 32))", + 2, + ), + HostFunctionSpec::GetNftSequence => ( + import::NFT_SERIAL, + "(call $nft_serial (i32.const 0) (i32.const 32) (i32.const 32) (i32.const 4))", + 4, + ), + HostFunctionSpec::FloatFromInt => ( + import::FLOAT_FROM_INT, + "(call $float_from_int (i64.const 0) (i32.const 0) (i32.const 8) (i32.const 0))", + 4, + ), + HostFunctionSpec::FloatFromUint => ( + import::FLOAT_FROM_UINT, + "(call $float_from_uint (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 0))", + 5, + ), + HostFunctionSpec::FloatFromStamount => ( + import::FLOAT_FROM_STAMOUNT, + "(call $float_from_stamount (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 0))", + 5, + ), + HostFunctionSpec::FloatFromStnumber => ( + import::FLOAT_FROM_STNUMBER, + "(call $float_from_stnumber (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 0))", + 5, + ), + HostFunctionSpec::FloatToInt => ( + import::FLOAT_TO_INT, + "(call $float_to_int (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 0))", + 5, + ), + HostFunctionSpec::FloatToMantExp => ( + import::FLOAT_TO_MANT_EXP, + "(call $float_to_mant_exp (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 16) (i32.const 4))", + 6, + ), + HostFunctionSpec::FloatFromMantExp => ( + import::FLOAT_FROM_MANT_EXP, + "(call $float_from_mant_exp (i64.const 0) (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 0))", + 5, + ), + HostFunctionSpec::FloatCompare => ( + import::FLOAT_CMP, + "(call $float_cmp (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8))", + 4, + ), + HostFunctionSpec::FloatAdd => ( + import::FLOAT_ADD, + "(call $float_add (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 16) (i32.const 8) (i32.const 0))", + 7, + ), + HostFunctionSpec::FloatSubtract => ( + import::FLOAT_SUB, + "(call $float_sub (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 16) (i32.const 8) (i32.const 0))", + 7, + ), + HostFunctionSpec::FloatMultiply => ( + import::FLOAT_MULT, + "(call $float_mult (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 16) (i32.const 8) (i32.const 0))", + 7, + ), + HostFunctionSpec::FloatDivide => ( + import::FLOAT_DIV, + "(call $float_div (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 16) (i32.const 8) (i32.const 0))", + 7, + ), + HostFunctionSpec::FloatRoot => ( + import::FLOAT_ROOT, + "(call $float_root (i32.const 0) (i32.const 8) (i32.const 2) (i32.const 8) (i32.const 8) (i32.const 0))", + 6, + ), + HostFunctionSpec::FloatPower => ( + import::FLOAT_POW, + "(call $float_pow (i32.const 0) (i32.const 8) (i32.const 2) (i32.const 8) (i32.const 8) (i32.const 0))", + 6, + ), }; Call { import, diff --git a/crates/xrpl-wasm-vm/tests/host_calls.rs b/crates/xrpl-wasm-vm/tests/host_calls.rs index 4ae880758f..0d8abfda50 100644 --- a/crates/xrpl-wasm-vm/tests/host_calls.rs +++ b/crates/xrpl-wasm-vm/tests/host_calls.rs @@ -31,6 +31,118 @@ fn ldgr_index_writes_the_sequence_number_where_the_guest_asked() { assert_eq!(status(&wat, &host), 4, "the byte count"); } +/// A second scalar getter travels the same path: the value the host supplies lands +/// where the guest asked, and the status is the byte count. The default parent +/// ledger time is distinct from the sequence number, so this cannot pass by reading +/// the wrong one. +#[test] +fn parent_ldgr_time_writes_the_close_time_where_the_guest_asked() { + let host = FakeHost::new(); + + let wat = module( + &[import::PARENT_LDGR_TIME, ONE_PAGE], + "(drop (call $parent_ldgr_time (i32.const 64) (i32.const 4))) + (i32.load (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 9, "the 4 LE bytes the host wrote"); + + let wat = module( + &[import::PARENT_LDGR_TIME, ONE_PAGE], + "(call $parent_ldgr_time (i32.const 64) (i32.const 4))", + ); + assert_eq!(status(&wat, &host), 4, "the byte count"); +} + +/// A 32-byte value (a ledger hash) travels the same getter path as the 4-byte +/// scalars: every byte lands where the guest asked, and the status is the length. +#[test] +fn parent_ldgr_hash_writes_all_32_bytes_where_the_guest_asked() { + let host = FakeHost::new(); + + let wat = module( + &[import::PARENT_LDGR_HASH, ONE_PAGE], + "(call $parent_ldgr_hash (i32.const 64) (i32.const 32))", + ); + assert_eq!(status(&wat, &host), 32, "the byte count"); + + // The default hash is 0, 1, 2, ..., so its first four bytes load as 0x03020100. + let wat = module( + &[import::PARENT_LDGR_HASH, ONE_PAGE], + "(drop (call $parent_ldgr_hash (i32.const 64) (i32.const 32))) + (i32.load (i32.const 64))", + ); + assert_eq!( + status(&wat, &host), + 0x03020100, + "the first four bytes the host wrote" + ); +} + +/// A third scalar getter, to pin the pattern rather than a single instance of it. +#[test] +fn base_fee_writes_the_fee_where_the_guest_asked() { + let host = FakeHost::new(); + + let wat = module( + &[import::BASE_FEE, ONE_PAGE], + "(drop (call $base_fee (i32.const 64) (i32.const 4))) + (i32.load (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 10, "the 4 LE bytes the host wrote"); + + let wat = module( + &[import::BASE_FEE, ONE_PAGE], + "(call $base_fee (i32.const 64) (i32.const 4))", + ); + assert_eq!(status(&wat, &host), 4, "the byte count"); +} + +/// A call that reads an input region and returns a scalar flag, rather than writing +/// bytes to an output region: the amendment reaches the host, and its verdict comes +/// back as the call's status. +#[test] +fn amendment_enabled_reads_the_input_and_returns_the_flag() { + let host = FakeHost::new(); // enabled by default + + let wat = module( + &[import::AMENDMENT_ENABLED, ONE_PAGE], + "(call $amendment_enabled (i32.const 64) (i32.const 32))", + ); + assert_eq!(status(&wat, &host), 1, "the enabled flag"); + assert_eq!( + *host.amendments_asked.borrow(), + [vec![0u8; 32]], + "the 32-byte region reached the host" + ); + + // A host that reports the amendment disabled answers 0 — a value, not an error. + let host = FakeHost::new().answering_amendment_enabled(Ok(0)); + let wat = module( + &[import::AMENDMENT_ENABLED, ONE_PAGE], + "(call $amendment_enabled (i32.const 0) (i32.const 32))", + ); + assert_eq!(status(&wat, &host), 0, "the disabled flag"); +} + +/// A call that reads an input region and takes a second scalar arg: both the object +/// id and the requested slot reach the host, and the slot it chose comes back as the +/// status. +#[test] +fn cache_le_passes_the_object_id_and_slot_through() { + let host = FakeHost::new().answering_cache_slot(Ok(4)); + + let wat = module( + &[import::CACHE_LE, ONE_PAGE], + "(call $cache_le (i32.const 64) (i32.const 32) (i32.const 7))", + ); + assert_eq!(status(&wat, &host), 4, "the slot the host chose"); + assert_eq!( + *host.cached.borrow(), + [(vec![0u8; 32], 7)], + "the id region and the requested slot reached the host" + ); +} + /// The output region is wherever the guest points, not a fixed address. #[test] fn the_output_region_is_the_pointer_the_guest_gave() { @@ -48,6 +160,832 @@ fn the_output_region_is_the_pointer_the_guest_gave() { } } +/// A field getter over the transaction: the selector reaches the host, and the bytes +/// it answers land where the guest asked. It has its own answer set, distinct from +/// the current-object field getter's. +#[test] +fn tx_field_passes_the_selector_and_writes_the_field() { + let host = FakeHost::new().answering_tx_field(17, support::Answer::bytes([0xab, 0xcd])); + + let wat = module( + &[import::TX_FIELD, ONE_PAGE], + "(call $tx_field (i32.const 17) (i32.const 0) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 2); + assert_eq!(*host.tx_fields_asked.borrow(), vec![17]); +} + +/// A field getter over a cached object: both the slot and the selector reach the +/// host, keyed together, and the answered bytes land where the guest asked. +#[test] +fn le_field_passes_the_slot_and_selector_through() { + let host = + FakeHost::new().answering_le_field(2, 17, support::Answer::bytes([0xab, 0xcd, 0xef])); + + let wat = module( + &[import::LE_FIELD, ONE_PAGE], + "(call $le_field (i32.const 2) (i32.const 17) (i32.const 0) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 3); + assert_eq!(*host.le_fields_asked.borrow(), vec![(2, 17)]); +} + +/// A nested-field getter: the locator is read from one region and the answer written +/// to another — the read-input-write-output path. The guest lays the locator down in +/// memory, and the bytes the host answers land where it asked. +#[test] +fn tx_inner_reads_the_locator_and_writes_the_field() { + // An eight-byte, two-step locator, as it lands in little-endian guest memory. + let locator = vec![17u8, 0, 0, 0, 2, 0, 0, 0]; + let host = + FakeHost::new().answering_tx_nested(locator.clone(), support::Answer::bytes([0xaa, 0xbb])); + + let wat = module( + &[import::TX_INNER, ONE_PAGE], + "(i32.store (i32.const 0) (i32.const 17)) + (i32.store (i32.const 4) (i32.const 2)) + (call $tx_inner (i32.const 0) (i32.const 8) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 2, "the field bytes the host wrote"); + assert_eq!(*host.tx_nested_asked.borrow(), vec![locator]); +} + +/// The same read-input-write-output path over the current object, with its own +/// answer set distinct from the transaction's nested getter. +#[test] +fn home_le_inner_reads_the_locator_and_writes_the_field() { + let locator = vec![5u8, 0, 0, 0]; + let host = FakeHost::new() + .answering_home_le_nested(locator.clone(), support::Answer::bytes([0xcc, 0xdd, 0xee])); + + let wat = module( + &[import::HOME_LE_INNER, ONE_PAGE], + "(i32.store (i32.const 0) (i32.const 5)) + (call $home_le_inner (i32.const 0) (i32.const 4) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 3, "the field bytes the host wrote"); + assert_eq!(*host.home_le_nested_asked.borrow(), vec![locator]); +} + +/// The nested getter over a cached object: the slot leads, the locator is read from +/// memory, and the two reach the host keyed together. +#[test] +fn le_inner_reads_the_slot_and_locator_and_writes_the_field() { + let locator = vec![5u8, 0, 0, 0]; + let host = FakeHost::new().answering_le_nested( + 3, + locator.clone(), + support::Answer::bytes([0x11, 0x22]), + ); + + let wat = module( + &[import::LE_INNER, ONE_PAGE], + "(i32.store (i32.const 0) (i32.const 5)) + (call $le_inner (i32.const 3) (i32.const 0) (i32.const 4) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 2, "the field bytes the host wrote"); + assert_eq!(*host.le_nested_asked.borrow(), vec![(3, locator)]); +} + +/// A scalar-in, scalar-out call — no memory regions at all: the field selector +/// reaches the host and the array length comes back as the status. +#[test] +fn tx_arr_len_passes_the_selector_and_returns_the_count() { + let host = FakeHost::new().answering_tx_arr_len(17, 5); + + let wat = module( + &[import::TX_ARR_LEN, ONE_PAGE], + "(call $tx_arr_len (i32.const 17))", + ); + assert_eq!(status(&wat, &host), 5, "the array length"); + assert_eq!(*host.tx_arr_lens_asked.borrow(), vec![17]); +} + +/// The same scalar-in, scalar-out count over the current object, with its own answer +/// set distinct from the transaction's. +#[test] +fn home_le_arr_len_passes_the_selector_and_returns_the_count() { + let host = FakeHost::new().answering_home_le_arr_len(17, 8); + + let wat = module( + &[import::HOME_LE_ARR_LEN, ONE_PAGE], + "(call $home_le_arr_len (i32.const 17))", + ); + assert_eq!(status(&wat, &host), 8, "the array length"); + assert_eq!(*host.home_le_arr_lens_asked.borrow(), vec![17]); +} + +/// The scalar count over a cached object: the slot leads, and both it and the +/// selector reach the host keyed together. +#[test] +fn le_arr_len_passes_the_slot_and_selector_and_returns_the_count() { + let host = FakeHost::new().answering_le_arr_len(2, 17, 9); + + let wat = module( + &[import::LE_ARR_LEN, ONE_PAGE], + "(call $le_arr_len (i32.const 2) (i32.const 17))", + ); + assert_eq!(status(&wat, &host), 9, "the array length"); + assert_eq!(*host.le_arr_lens_asked.borrow(), vec![(2, 17)]); +} + +/// A nested array-length getter: the locator is read from memory and the count comes +/// back as the status — read-input, scalar-out, no output buffer. +#[test] +fn tx_inner_arr_len_reads_the_locator_and_returns_the_count() { + let locator = vec![5u8, 0, 0, 0]; + let host = FakeHost::new().answering_tx_nested_arr_len(locator.clone(), 6); + + let wat = module( + &[import::TX_INNER_ARR_LEN, ONE_PAGE], + "(i32.store (i32.const 0) (i32.const 5)) + (call $tx_inner_arr_len (i32.const 0) (i32.const 4))", + ); + assert_eq!(status(&wat, &host), 6, "the array length"); + assert_eq!(*host.tx_nested_arr_lens_asked.borrow(), vec![locator]); +} + +/// The same read-input, scalar-out count over the current object, with its own answer +/// set distinct from the transaction's. +#[test] +fn home_le_inner_arr_len_reads_the_locator_and_returns_the_count() { + let locator = vec![5u8, 0, 0, 0]; + let host = FakeHost::new().answering_home_le_nested_arr_len(locator.clone(), 7); + + let wat = module( + &[import::HOME_LE_INNER_ARR_LEN, ONE_PAGE], + "(i32.store (i32.const 0) (i32.const 5)) + (call $home_le_inner_arr_len (i32.const 0) (i32.const 4))", + ); + assert_eq!(status(&wat, &host), 7, "the array length"); + assert_eq!(*host.home_le_nested_arr_lens_asked.borrow(), vec![locator]); +} + +/// The nested array-length getter over a cached object: the slot leads, the locator +/// is read from memory, and the two reach the host keyed together. +#[test] +fn le_inner_arr_len_reads_the_slot_and_locator_and_returns_the_count() { + let locator = vec![5u8, 0, 0, 0]; + let host = FakeHost::new().answering_le_nested_arr_len(3, locator.clone(), 8); + + let wat = module( + &[import::LE_INNER_ARR_LEN, ONE_PAGE], + "(i32.store (i32.const 0) (i32.const 5)) + (call $le_inner_arr_len (i32.const 3) (i32.const 0) (i32.const 4))", + ); + assert_eq!(status(&wat, &host), 8, "the array length"); + assert_eq!(*host.le_nested_arr_lens_asked.borrow(), vec![(3, locator)]); +} + +/// A call that reads three input regions and returns a scalar verdict: the message, +/// signature, and pubkey all reach the host, and the verdict comes back as the status. +#[test] +fn check_sig_reads_all_three_regions_and_returns_the_verdict() { + let host = FakeHost::new(); // valid by default + + // message @0 len 3, signature @8 len 4, pubkey @16 len 5 — memory is zeroed. + let wat = module( + &[import::CHECK_SIG, ONE_PAGE], + "(call $check_sig + (i32.const 0) (i32.const 3) + (i32.const 8) (i32.const 4) + (i32.const 16) (i32.const 5))", + ); + assert_eq!(status(&wat, &host), 1, "the valid verdict"); + assert_eq!( + *host.sigs_checked.borrow(), + [(vec![0u8; 3], vec![0u8; 4], vec![0u8; 5])], + "the three regions reached the host at their declared lengths" + ); + + // An invalid signature comes back as 0 — a value, not an error. + let host = FakeHost::new().answering_check_sig(Ok(0)); + assert_eq!(status(&wat, &host), 0, "the invalid verdict"); +} + +/// A keylet getter: reads an account region and writes a 32-byte keylet back — the +/// read-input-write-output path. The account reaches the host and the keylet lands +/// where the guest asked. +#[test] +fn accountroot_id_reads_the_account_and_writes_the_keylet() { + // Guest memory is zeroed, so a 20-byte account read is all zeros. + let account = vec![0u8; 20]; + let host = + FakeHost::new().answering_account_keylet(account.clone(), support::Answer::filler(32)); + + let wat = module( + &[import::ACCOUNTROOT_ID, ONE_PAGE], + "(call $accountroot_id (i32.const 0) (i32.const 20) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.account_keylets_asked.borrow(), vec![account]); + + // The keylet bytes land at the output pointer: filler is 0, 1, 2, ..., so the + // first four load as 0x03020100. + let wat = module( + &[import::ACCOUNTROOT_ID, ONE_PAGE], + "(drop (call $accountroot_id (i32.const 0) (i32.const 20) (i32.const 64) (i32.const 64))) + (i32.load (i32.const 64))", + ); + assert_eq!( + status(&wat, &host), + 0x03020100, + "the first four keylet bytes" + ); +} + +/// A keylet getter that reads two input regions: both assets reach the host as a +/// pair, and the keylet lands where the guest asked. +#[test] +fn amm_id_reads_two_assets_and_writes_the_keylet() { + // Two distinct all-zero assets of different lengths (20 and 40 bytes). + let asset1 = vec![0u8; 20]; + let asset2 = vec![0u8; 40]; + let host = FakeHost::new().answering_amm_keylet( + asset1.clone(), + asset2.clone(), + support::Answer::filler(32), + ); + + let wat = module( + &[import::AMM_ID, ONE_PAGE], + "(call $amm_id + (i32.const 0) (i32.const 20) + (i32.const 64) (i32.const 40) + (i32.const 128) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.amm_keylets_asked.borrow(), vec![(asset1, asset2)]); +} + +/// A keylet getter that reads an account region and also takes a scalar seq: both +/// reach the host keyed together, and the keylet lands where the guest asked. +#[test] +fn check_id_reads_the_account_and_seq_and_writes_the_keylet() { + // Guest memory is zeroed, so a 20-byte account read is all zeros. + let account = vec![0u8; 20]; + let host = + FakeHost::new().answering_check_keylet(account.clone(), 5, support::Answer::filler(32)); + + let wat = module( + &[import::CHECK_ID, ONE_PAGE], + "(i32.store (i32.const 20) (i32.const 5)) + (call $check_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 4) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.check_keylets_asked.borrow(), vec![(account, 5)]); +} + +/// A keylet getter that reads three input regions — two accounts and a credential +/// type: all three reach the host keyed together, and the keylet lands where asked. +#[test] +fn credential_id_reads_subject_issuer_and_type() { + // Guest memory is zeroed, so the two 20-byte accounts and the 4-byte type read + // as zeros of their declared lengths. + let subject = vec![0u8; 20]; + let issuer = vec![0u8; 20]; + let cred_type = vec![0u8; 4]; + let host = FakeHost::new().answering_credential_keylet( + subject.clone(), + issuer.clone(), + cred_type.clone(), + support::Answer::filler(32), + ); + + let wat = module( + &[import::CREDENTIAL_ID, ONE_PAGE], + "(call $credential_id + (i32.const 0) (i32.const 20) + (i32.const 20) (i32.const 20) + (i32.const 40) (i32.const 4) + (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!( + *host.credential_keylets_asked.borrow(), + vec![(subject, issuer, cred_type)] + ); +} + +/// A two-account keylet getter: both accounts reach the host as a pair, and the +/// keylet lands where the guest asked. +#[test] +fn delegate_id_reads_both_accounts_and_writes_the_keylet() { + let account = vec![0u8; 20]; + let authorize = vec![0u8; 20]; + let host = FakeHost::new().answering_delegate_keylet( + account.clone(), + authorize.clone(), + support::Answer::filler(32), + ); + + let wat = module( + &[import::DELEGATE_ID, ONE_PAGE], + "(call $delegate_id + (i32.const 0) (i32.const 20) + (i32.const 20) (i32.const 20) + (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!( + *host.delegate_keylets_asked.borrow(), + vec![(account, authorize)] + ); +} + +/// The same two-account keylet shape as delegate, with its own answer set. +#[test] +fn deposit_preauth_id_reads_both_accounts_and_writes_the_keylet() { + let account = vec![0u8; 20]; + let authorize = vec![0u8; 20]; + let host = FakeHost::new().answering_deposit_preauth_keylet( + account.clone(), + authorize.clone(), + support::Answer::filler(32), + ); + + let wat = module( + &[import::DEPOSIT_PREAUTH_ID, ONE_PAGE], + "(call $deposit_preauth_id + (i32.const 0) (i32.const 20) + (i32.const 20) (i32.const 20) + (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!( + *host.deposit_preauth_keylets_asked.borrow(), + vec![(account, authorize)] + ); +} + +/// A single-account keylet getter (like accountroot), with its own answer set. +#[test] +fn did_id_reads_the_account_and_writes_the_keylet() { + let account = vec![0u8; 20]; + let host = FakeHost::new().answering_did_keylet(account.clone(), support::Answer::filler(32)); + + let wat = module( + &[import::DID_ID, ONE_PAGE], + "(call $did_id (i32.const 0) (i32.const 20) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.did_keylets_asked.borrow(), vec![account]); +} + +/// The account-and-sequence keylet shape (like check), with its own answer set. +#[test] +fn escrow_id_reads_the_account_and_seq_and_writes_the_keylet() { + let account = vec![0u8; 20]; + let host = + FakeHost::new().answering_escrow_keylet(account.clone(), 5, support::Answer::filler(32)); + + let wat = module( + &[import::ESCROW_ID, ONE_PAGE], + "(i32.store (i32.const 20) (i32.const 5)) + (call $escrow_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 4) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.escrow_keylets_asked.borrow(), vec![(account, 5)]); +} + +/// A keylet getter reading three regions — two accounts and a currency: all three +/// reach the host as a triple, and the keylet lands where the guest asked. +#[test] +fn trustline_id_reads_two_accounts_and_a_currency() { + let account1 = vec![0u8; 20]; + let account2 = vec![0u8; 20]; + let currency = vec![0u8; 20]; + let host = FakeHost::new().answering_trust_line_keylet( + account1.clone(), + account2.clone(), + currency.clone(), + support::Answer::filler(32), + ); + + let wat = module( + &[import::TRUSTLINE_ID, ONE_PAGE], + "(call $trustline_id + (i32.const 0) (i32.const 20) + (i32.const 20) (i32.const 20) + (i32.const 40) (i32.const 20) + (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!( + *host.trust_line_keylets_asked.borrow(), + vec![(account1, account2, currency)] + ); +} + +/// The issuer-and-sequence keylet shape (like escrow), with its own answer set. +#[test] +fn mpt_issuance_id_reads_the_issuer_and_seq() { + let issuer = vec![0u8; 20]; + let host = FakeHost::new().answering_mpt_issuance_keylet( + issuer.clone(), + 5, + support::Answer::filler(32), + ); + + let wat = module( + &[import::MPT_ISSUANCE_ID, ONE_PAGE], + "(i32.store (i32.const 20) (i32.const 5)) + (call $mpt_issuance_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 4) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.mpt_issuance_keylets_asked.borrow(), vec![(issuer, 5)]); +} + +/// A keylet from a 24-byte MPT id and a 20-byte holder: both reach the host as a +/// pair, and the keylet lands where the guest asked. +#[test] +fn mptoken_id_reads_the_mptid_and_holder() { + let mptid = vec![0u8; 24]; + let holder = vec![0u8; 20]; + let host = FakeHost::new().answering_mptoken_keylet( + mptid.clone(), + holder.clone(), + support::Answer::filler(32), + ); + + let wat = module( + &[import::MPTOKEN_ID, ONE_PAGE], + "(call $mptoken_id + (i32.const 0) (i32.const 24) + (i32.const 24) (i32.const 20) + (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.mptoken_keylets_asked.borrow(), vec![(mptid, holder)]); +} + +/// Another account-and-sequence keylet, with its own answer set. +#[test] +fn nft_offer_id_reads_the_account_and_seq() { + let account = vec![0u8; 20]; + let host = + FakeHost::new().answering_nft_offer_keylet(account.clone(), 5, support::Answer::filler(32)); + + let wat = module( + &[import::NFT_OFFER_ID, ONE_PAGE], + "(i32.store (i32.const 20) (i32.const 5)) + (call $nft_offer_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 4) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.nft_offer_keylets_asked.borrow(), vec![(account, 5)]); +} + +/// A third account-and-sequence keylet, distinct from the NFT-offer set, to pin the +/// pattern rather than a single instance of it. +#[test] +fn offer_id_reads_the_account_and_seq() { + let account = vec![0u8; 20]; + let host = + FakeHost::new().answering_offer_keylet(account.clone(), 5, support::Answer::filler(32)); + + let wat = module( + &[import::OFFER_ID, ONE_PAGE], + "(i32.store (i32.const 20) (i32.const 5)) + (call $offer_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 4) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.offer_keylets_asked.borrow(), vec![(account, 5)]); +} + +/// The account-and-scalar keylet, keyed on a document id rather than a sequence; its +/// own answer set, to keep it distinct from the other account-and-scalar getters. +#[test] +fn oracle_id_reads_the_account_and_doc_id() { + let account = vec![0u8; 20]; + let host = + FakeHost::new().answering_oracle_keylet(account.clone(), 5, support::Answer::filler(32)); + + let wat = module( + &[import::ORACLE_ID, ONE_PAGE], + "(i32.store (i32.const 20) (i32.const 5)) + (call $oracle_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 4) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.oracle_keylets_asked.borrow(), vec![(account, 5)]); +} + +/// A keylet that reads two account regions and a scalar: both accounts and the +/// sequence reach the host, keyed together, and the answered bytes land where asked. +#[test] +fn paychan_id_reads_both_accounts_and_the_seq() { + let account = vec![0u8; 20]; + let destination = vec![0u8; 20]; + let host = FakeHost::new().answering_paychannel_keylet( + account.clone(), + destination.clone(), + 5, + support::Answer::filler(32), + ); + + let wat = module( + &[import::PAYCHAN_ID, ONE_PAGE], + "(i32.store (i32.const 24) (i32.const 5)) + (call $paychan_id (i32.const 0) (i32.const 20) (i32.const 32) (i32.const 20) (i32.const 24) (i32.const 4) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!( + *host.paychannel_keylets_asked.borrow(), + vec![(account, destination, 5)] + ); +} + +/// Another account-and-sequence keylet, with its own answer set, for a permissioned +/// domain. +#[test] +fn permissioned_domain_id_reads_the_account_and_seq() { + let account = vec![0u8; 20]; + let host = FakeHost::new().answering_permissioned_domain_keylet( + account.clone(), + 5, + support::Answer::filler(32), + ); + + let wat = module( + &[import::PERMISSIONED_DOMAIN_ID, ONE_PAGE], + "(i32.store (i32.const 20) (i32.const 5)) + (call $permissioned_domain_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 4) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.domain_keylets_asked.borrow(), vec![(account, 5)]); +} + +/// An account-only keylet: the account reaches the host and the answered bytes land +/// where the guest asked, with no scalar in the shape. +#[test] +fn signers_id_reads_the_account() { + let account = vec![0u8; 20]; + let host = + FakeHost::new().answering_signer_list_keylet(account.clone(), support::Answer::filler(32)); + + let wat = module( + &[import::SIGNERS_ID, ONE_PAGE], + "(call $signers_id (i32.const 0) (i32.const 20) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.signer_list_keylets_asked.borrow(), vec![account]); +} + +/// Another account-and-sequence keylet, with its own answer set, for a ticket. +#[test] +fn ticket_id_reads_the_account_and_seq() { + let account = vec![0u8; 20]; + let host = + FakeHost::new().answering_ticket_keylet(account.clone(), 5, support::Answer::filler(32)); + + let wat = module( + &[import::TICKET_ID, ONE_PAGE], + "(i32.store (i32.const 20) (i32.const 5)) + (call $ticket_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 4) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.ticket_keylets_asked.borrow(), vec![(account, 5)]); +} + +/// The last account-and-sequence keylet, with its own answer set, for a vault. +#[test] +fn vault_id_reads_the_account_and_seq() { + let account = vec![0u8; 20]; + let host = + FakeHost::new().answering_vault_keylet(account.clone(), 5, support::Answer::filler(32)); + + let wat = module( + &[import::VAULT_ID, ONE_PAGE], + "(i32.store (i32.const 20) (i32.const 5)) + (call $vault_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 4) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 32, "the 32-byte keylet length"); + assert_eq!(*host.vault_keylets_asked.borrow(), vec![(account, 5)]); +} + +/// A call that reads an input region and returns a scalar rather than writing bytes: +/// the data blob reaches the host, and the byte count it reports comes back as the +/// call's status. +#[test] +fn set_data_passes_the_data_through_and_returns_the_count() { + let host = FakeHost::new().answering_update_data(Ok(8)); + + let wat = module( + &[import::SET_DATA, ONE_PAGE], + "(call $set_data (i32.const 64) (i32.const 8))", + ); + assert_eq!(status(&wat, &host), 8, "the byte count the host reported"); + assert_eq!( + *host.update_data_asked.borrow(), + [vec![0u8; 8]], + "the 8-byte region reached the host" + ); +} + +/// A getter that reads two input regions — an account and an nft id — and writes the +/// answer to a third: both inputs reach the host, keyed together, and the bytes it +/// answers land where the guest asked. +#[test] +fn nft_uri_reads_the_account_and_id_and_writes_the_uri() { + let account = vec![0u8; 20]; + let nft_id = vec![0u8; 32]; + let host = FakeHost::new().answering_get_nft( + account.clone(), + nft_id.clone(), + support::Answer::bytes([0xab, 0xcd, 0xef]), + ); + + let wat = module( + &[import::NFT_URI, ONE_PAGE], + "(call $nft_uri (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 32) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 3, "the uri length"); + assert_eq!(*host.nfts_asked.borrow(), vec![(account, nft_id)]); +} + +/// A single-input byte getter: the nft id reaches the host and the issuer bytes it +/// answers land where the guest asked. +#[test] +fn nft_issuer_reads_the_id_and_writes_the_issuer() { + let nft_id = vec![0u8; 32]; + let host = FakeHost::new().answering_nft_issuer(nft_id.clone(), support::Answer::filler(20)); + + let wat = module( + &[import::NFT_ISSUER, ONE_PAGE], + "(call $nft_issuer (i32.const 0) (i32.const 32) (i32.const 64) (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 20, "the issuer length"); + assert_eq!(*host.nft_issuers_asked.borrow(), vec![nft_id]); +} + +/// A u32-valued getter whose four bytes the host writes to the output region: the id +/// reaches the host, and the little-endian bytes land where the guest asked. +#[test] +fn nft_taxon_reads_the_id_and_writes_four_bytes() { + let nft_id = vec![0u8; 32]; + let host = + FakeHost::new().answering_nft_taxon(nft_id.clone(), support::Answer::bytes([7, 0, 0, 0])); + + let wat = module( + &[import::NFT_TAXON, ONE_PAGE], + "(drop (call $nft_taxon (i32.const 0) (i32.const 32) (i32.const 64) (i32.const 4))) + (i32.load (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 7, "the taxon the host wrote"); + assert_eq!(*host.nft_taxons_asked.borrow(), vec![nft_id]); +} + +/// A single-input scalar getter: the nft id reaches the host and the flags it reports +/// come back as the call's status, no output region involved. +#[test] +fn nft_flags_reads_the_id_and_returns_the_flags() { + let nft_id = vec![0u8; 32]; + let host = FakeHost::new().answering_nft_flags(Ok(11)); + + let wat = module( + &[import::NFT_FLAGS, ONE_PAGE], + "(call $nft_flags (i32.const 0) (i32.const 32))", + ); + assert_eq!(status(&wat, &host), 11, "the flags the host reported"); + assert_eq!(*host.nft_flags_asked.borrow(), vec![nft_id]); +} + +/// A second scalar getter, to pin the pattern: the transfer fee comes back as the +/// status. +#[test] +fn nft_xfer_fee_reads_the_id_and_returns_the_fee() { + let nft_id = vec![0u8; 32]; + let host = FakeHost::new().answering_nft_transfer_fee(Ok(314)); + + let wat = module( + &[import::NFT_XFER_FEE, ONE_PAGE], + "(call $nft_xfer_fee (i32.const 0) (i32.const 32))", + ); + assert_eq!(status(&wat, &host), 314, "the fee the host reported"); + assert_eq!(*host.nft_fee_asked.borrow(), vec![nft_id]); +} + +/// The last NFT getter, a u32 sequence written to the output region. +#[test] +fn nft_serial_reads_the_id_and_writes_four_bytes() { + let nft_id = vec![0u8; 32]; + let host = FakeHost::new() + .answering_nft_sequence(nft_id.clone(), support::Answer::bytes([42, 0, 0, 0])); + + let wat = module( + &[import::NFT_SERIAL, ONE_PAGE], + "(drop (call $nft_serial (i32.const 0) (i32.const 32) (i32.const 64) (i32.const 4))) + (i32.load (i32.const 64))", + ); + assert_eq!(status(&wat, &host), 42, "the sequence the host wrote"); + assert_eq!(*host.nft_sequences_asked.borrow(), vec![nft_id]); +} + +/// A float built from an i64 scalar and no input region: the value and mode reach the +/// host, and the float bytes it answers land where the guest asked. `float_from_int` +/// carries a genuine `i64` parameter, so this pins that the wide scalar survives. +#[test] +fn float_from_int_passes_the_value_and_writes_the_float() { + let host = FakeHost::new().answering_float(support::Answer::filler(8)); + + let wat = module( + &[import::FLOAT_FROM_INT, ONE_PAGE], + "(call $float_from_int (i64.const 42) (i32.const 64) (i32.const 8) (i32.const 3))", + ); + assert_eq!(status(&wat, &host), 8, "the float length"); + assert_eq!(*host.float_from_int_asked.borrow(), vec![(42, 3)]); +} + +/// A float built from an 8-byte input region: the integer bytes and mode reach the +/// host, and the float bytes it answers land where the guest asked. +#[test] +fn float_from_uint_reads_the_input_and_writes_the_float() { + let host = FakeHost::new().answering_float(support::Answer::filler(8)); + + let wat = module( + &[import::FLOAT_FROM_UINT, ONE_PAGE], + "(call $float_from_uint (i32.const 0) (i32.const 8) (i32.const 64) (i32.const 8) (i32.const 1))", + ); + assert_eq!(status(&wat, &host), 8, "the float length"); + assert_eq!( + *host.float_from_uint_asked.borrow(), + vec![(vec![0u8; 8], 1)] + ); +} + +/// The one call that writes two output regions: the mantissa lands in the first, the +/// exponent in the second, and the status is their combined length. +#[test] +fn float_to_mant_exp_writes_both_regions() { + let host = + FakeHost::new().answering_float_mant_exp(vec![1, 2, 3, 4, 5, 6, 7, 8], vec![9, 10, 11, 12]); + + // Mantissa to offset 64, exponent to offset 80; read the first byte of each back. + let wat = module( + &[import::FLOAT_TO_MANT_EXP, ONE_PAGE], + "(call $float_to_mant_exp (i32.const 0) (i32.const 8) (i32.const 64) (i32.const 8) (i32.const 80) (i32.const 4))", + ); + assert_eq!(status(&wat, &host), 12, "the mantissa and exponent lengths"); + assert_eq!(*host.float_to_mant_exp_asked.borrow(), vec![vec![0u8; 8]]); + + let wat = module( + &[import::FLOAT_TO_MANT_EXP, ONE_PAGE], + "(drop (call $float_to_mant_exp (i32.const 0) (i32.const 8) (i32.const 64) (i32.const 8) (i32.const 80) (i32.const 4))) + (i32.load8_u (i32.const 80))", + ); + assert_eq!(status(&wat, &host), 9, "the exponent's first byte"); +} + +/// A comparison that reads two float regions and returns a scalar verdict, no output +/// region involved. +#[test] +fn float_cmp_reads_both_and_returns_the_verdict() { + let host = FakeHost::new().answering_float_compare(Ok(-1)); + + let wat = module( + &[import::FLOAT_CMP, ONE_PAGE], + "(call $float_cmp (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8))", + ); + assert_eq!(status(&wat, &host), -1, "the comparison verdict"); + assert_eq!( + *host.float_compare_asked.borrow(), + vec![(vec![0u8; 8], vec![0u8; 8])] + ); +} + +/// A binary operator that reads two float regions and a mode, and writes the result: +/// both operands and the mode reach the host, tagged by operator. +#[test] +fn float_add_reads_both_operands_and_the_mode() { + let host = FakeHost::new().answering_float(support::Answer::filler(8)); + + let wat = module( + &[import::FLOAT_ADD, ONE_PAGE], + "(call $float_add (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 64) (i32.const 8) (i32.const 2))", + ); + assert_eq!(status(&wat, &host), 8, "the result length"); + assert_eq!( + *host.float_binops_asked.borrow(), + vec![("add", vec![0u8; 8], vec![0u8; 8], 2)] + ); +} + +/// A unary operator that reads one float region, an integer, and a mode: all three +/// reach the host, tagged by operator. +#[test] +fn float_root_reads_the_float_the_degree_and_the_mode() { + let host = FakeHost::new().answering_float(support::Answer::filler(8)); + + let wat = module( + &[import::FLOAT_ROOT, ONE_PAGE], + "(call $float_root (i32.const 0) (i32.const 8) (i32.const 3) (i32.const 64) (i32.const 8) (i32.const 1))", + ); + assert_eq!(status(&wat, &host), 8, "the result length"); + assert_eq!( + *host.float_unops_asked.borrow(), + vec![("root", vec![0u8; 8], 3, 1)] + ); +} + /// A leading scalar parameter reaches the host as declared. #[test] fn home_le_field_passes_the_field_selector_through() { diff --git a/crates/xrpl-wasm-vm/tests/preflight.rs b/crates/xrpl-wasm-vm/tests/preflight.rs index 8c30390fcf..4a9badb1d0 100644 --- a/crates/xrpl-wasm-vm/tests/preflight.rs +++ b/crates/xrpl-wasm-vm/tests/preflight.rs @@ -98,11 +98,68 @@ fn a_disabled_feature_does_not_pass() { /// Every host function the ABI declares, spelled as a guest imports it. The count /// is asserted against the ABI so a function added to it cannot be left out here. -const ALL_IMPORTS: [&str; 4] = [ +const ALL_IMPORTS: [&str; 61] = [ import::LDGR_INDEX, + import::PARENT_LDGR_TIME, + import::PARENT_LDGR_HASH, + import::BASE_FEE, + import::AMENDMENT_ENABLED, + import::CACHE_LE, + import::TX_FIELD, import::HOME_LE_FIELD, + import::LE_FIELD, + import::TX_INNER, + import::HOME_LE_INNER, + import::LE_INNER, + import::TX_ARR_LEN, + import::HOME_LE_ARR_LEN, + import::LE_ARR_LEN, + import::TX_INNER_ARR_LEN, + import::HOME_LE_INNER_ARR_LEN, + import::LE_INNER_ARR_LEN, + import::CHECK_SIG, + import::ACCOUNTROOT_ID, + import::AMM_ID, + import::CHECK_ID, + import::CREDENTIAL_ID, + import::DELEGATE_ID, + import::DEPOSIT_PREAUTH_ID, + import::DID_ID, + import::ESCROW_ID, + import::TRUSTLINE_ID, + import::MPT_ISSUANCE_ID, + import::MPTOKEN_ID, + import::NFT_OFFER_ID, + import::OFFER_ID, + import::ORACLE_ID, + import::PAYCHAN_ID, + import::PERMISSIONED_DOMAIN_ID, + import::SIGNERS_ID, + import::TICKET_ID, + import::VAULT_ID, import::SHA512_HALF, import::TRACE, + import::SET_DATA, + import::NFT_URI, + import::NFT_ISSUER, + import::NFT_TAXON, + import::NFT_FLAGS, + import::NFT_XFER_FEE, + import::NFT_SERIAL, + import::FLOAT_FROM_INT, + import::FLOAT_FROM_UINT, + import::FLOAT_FROM_STAMOUNT, + import::FLOAT_FROM_STNUMBER, + import::FLOAT_TO_INT, + import::FLOAT_TO_MANT_EXP, + import::FLOAT_FROM_MANT_EXP, + import::FLOAT_CMP, + import::FLOAT_ADD, + import::FLOAT_SUB, + import::FLOAT_MULT, + import::FLOAT_DIV, + import::FLOAT_ROOT, + import::FLOAT_POW, ]; #[test] diff --git a/crates/xrpl-wasm-vm/tests/support/mod.rs b/crates/xrpl-wasm-vm/tests/support/mod.rs index 32b9dfe83a..780322b772 100644 --- a/crates/xrpl-wasm-vm/tests/support/mod.rs +++ b/crates/xrpl-wasm-vm/tests/support/mod.rs @@ -97,9 +97,177 @@ pub struct Trace { pub struct FakeHost { /// What `get_ledger_sqn` answers. pub ledger_sqn: Answer, + /// What `get_parent_ledger_time` answers. + pub parent_ledger_time: Answer, + /// What `get_parent_ledger_hash` answers. + pub parent_ledger_hash: Answer, + /// What `get_base_fee` answers. + pub base_fee: Answer, + /// What `is_amendment_enabled` answers, whatever amendment it is given. + pub amendment_enabled: HostResult, + /// Every amendment `is_amendment_enabled` was asked about. + pub amendments_asked: RefCell>>, + /// What `cache_ledger_obj` answers: the slot it "used". + pub cache_slot: HostResult, + /// Every (object id, requested slot) `cache_ledger_obj` was asked to cache. + pub cached: RefCell, i32)>>, + /// What `get_tx_field` answers, by field selector. An unlisted selector answers + /// `FieldNotFound`. + pub tx_fields: HashMap, + /// Every field selector `get_tx_field` was asked for. + pub tx_fields_asked: RefCell>, /// What `get_current_ledger_obj_field` answers, by field selector. An /// unlisted selector answers `FieldNotFound`. pub fields: HashMap, + /// What `get_ledger_obj_field` answers, by (cache slot, field selector). An + /// unlisted key answers `FieldNotFound`. + pub le_fields: HashMap<(i32, i32), Answer>, + /// Every (cache slot, field selector) `get_ledger_obj_field` was asked for. + pub le_fields_asked: RefCell>, + /// What `get_tx_nested_field` answers, by locator bytes. An unlisted locator + /// answers `FieldNotFound`. + pub tx_nested: HashMap, Answer>, + /// Every locator `get_tx_nested_field` was asked for. + pub tx_nested_asked: RefCell>>, + /// What `get_current_ledger_obj_nested_field` answers, by locator bytes. An + /// unlisted locator answers `FieldNotFound`. + pub home_le_nested: HashMap, Answer>, + /// Every locator `get_current_ledger_obj_nested_field` was asked for. + pub home_le_nested_asked: RefCell>>, + /// What `get_ledger_obj_nested_field` answers, by (cache slot, locator bytes). An + /// unlisted key answers `FieldNotFound`. + pub le_nested: HashMap<(i32, Vec), Answer>, + /// Every (cache slot, locator) `get_ledger_obj_nested_field` was asked for. + pub le_nested_asked: RefCell)>>, + /// What `get_tx_array_len` answers, by field selector. An unlisted selector + /// answers `NoArray`. + pub tx_arr_lens: HashMap, + /// Every field selector `get_tx_array_len` was asked for. + pub tx_arr_lens_asked: RefCell>, + /// What `get_current_ledger_obj_array_len` answers, by field selector. An + /// unlisted selector answers `NoArray`. + pub home_le_arr_lens: HashMap, + /// Every field selector `get_current_ledger_obj_array_len` was asked for. + pub home_le_arr_lens_asked: RefCell>, + /// What `get_ledger_obj_array_len` answers, by (cache slot, field selector). An + /// unlisted key answers `NoArray`. + pub le_arr_lens: HashMap<(i32, i32), i32>, + /// Every (cache slot, field selector) `get_ledger_obj_array_len` was asked for. + pub le_arr_lens_asked: RefCell>, + /// What `get_tx_nested_array_len` answers, by locator bytes. An unlisted locator + /// answers `NoArray`. + pub tx_nested_arr_lens: HashMap, i32>, + /// Every locator `get_tx_nested_array_len` was asked for. + pub tx_nested_arr_lens_asked: RefCell>>, + /// What `get_current_ledger_obj_nested_array_len` answers, by locator bytes. An + /// unlisted locator answers `NoArray`. + pub home_le_nested_arr_lens: HashMap, i32>, + /// Every locator `get_current_ledger_obj_nested_array_len` was asked for. + pub home_le_nested_arr_lens_asked: RefCell>>, + /// What `get_ledger_obj_nested_array_len` answers, by (cache slot, locator bytes). + /// An unlisted key answers `NoArray`. + pub le_nested_arr_lens: HashMap<(i32, Vec), i32>, + /// Every (cache slot, locator) `get_ledger_obj_nested_array_len` was asked for. + pub le_nested_arr_lens_asked: RefCell)>>, + /// What `check_signature` answers, whatever it is given. + pub sig_valid: HostResult, + /// Every (message, signature, pubkey) `check_signature` was asked to verify. + pub sigs_checked: RefCell, Vec, Vec)>>, + /// What `account_keylet` answers, by account bytes. An unlisted account answers + /// `InvalidAccount`. + pub account_keylets: HashMap, Answer>, + /// Every account `account_keylet` was asked for. + pub account_keylets_asked: RefCell>>, + /// What `amm_keylet` answers, by (asset1, asset2) bytes. An unlisted pair answers + /// `InvalidParams`. + pub amm_keylets: HashMap<(Vec, Vec), Answer>, + /// Every (asset1, asset2) pair `amm_keylet` was asked for. + pub amm_keylets_asked: RefCell, Vec)>>, + /// What `check_keylet` answers, by (account bytes, seq). An unlisted key answers + /// `InvalidAccount`. + pub check_keylets: HashMap<(Vec, i32), Answer>, + /// Every (account, seq) `check_keylet` was asked for. + pub check_keylets_asked: RefCell, i32)>>, + /// What `credential_keylet` answers, by (subject, issuer, type) bytes. An unlisted + /// key answers `InvalidAccount`. + pub credential_keylets: HashMap<(Vec, Vec, Vec), Answer>, + /// Every (subject, issuer, type) `credential_keylet` was asked for. + pub credential_keylets_asked: RefCell, Vec, Vec)>>, + /// What `delegate_keylet` answers, by (account, authorize) bytes. An unlisted key + /// answers `InvalidAccount`. + pub delegate_keylets: HashMap<(Vec, Vec), Answer>, + /// Every (account, authorize) `delegate_keylet` was asked for. + pub delegate_keylets_asked: RefCell, Vec)>>, + /// What `deposit_preauth_keylet` answers, by (account, authorize) bytes. An + /// unlisted key answers `InvalidAccount`. + pub deposit_preauth_keylets: HashMap<(Vec, Vec), Answer>, + /// Every (account, authorize) `deposit_preauth_keylet` was asked for. + pub deposit_preauth_keylets_asked: RefCell, Vec)>>, + /// What `did_keylet` answers, by account bytes. An unlisted account answers + /// `InvalidAccount`. + pub did_keylets: HashMap, Answer>, + /// Every account `did_keylet` was asked for. + pub did_keylets_asked: RefCell>>, + /// What `escrow_keylet` answers, by (account bytes, seq). An unlisted key answers + /// `InvalidAccount`. + pub escrow_keylets: HashMap<(Vec, i32), Answer>, + /// Every (account, seq) `escrow_keylet` was asked for. + pub escrow_keylets_asked: RefCell, i32)>>, + /// What `trust_line_keylet` answers, by (account1, account2, currency) bytes. An + /// unlisted key answers `InvalidAccount`. + pub trust_line_keylets: HashMap<(Vec, Vec, Vec), Answer>, + /// Every (account1, account2, currency) `trust_line_keylet` was asked for. + pub trust_line_keylets_asked: RefCell, Vec, Vec)>>, + /// What `mptoken_issuance_keylet` answers, by (issuer bytes, seq). An unlisted key + /// answers `InvalidAccount`. + pub mpt_issuance_keylets: HashMap<(Vec, i32), Answer>, + /// Every (issuer, seq) `mptoken_issuance_keylet` was asked for. + pub mpt_issuance_keylets_asked: RefCell, i32)>>, + /// What `mptoken_keylet` answers, by (mptid, holder) bytes. An unlisted key answers + /// `InvalidParams`. + pub mptoken_keylets: HashMap<(Vec, Vec), Answer>, + /// Every (mptid, holder) `mptoken_keylet` was asked for. + pub mptoken_keylets_asked: RefCell, Vec)>>, + /// What `nftoken_offer_keylet` answers, by (account bytes, seq). An unlisted key + /// answers `InvalidAccount`. + pub nft_offer_keylets: HashMap<(Vec, i32), Answer>, + /// Every (account, seq) `nftoken_offer_keylet` was asked for. + pub nft_offer_keylets_asked: RefCell, i32)>>, + /// What `offer_keylet` answers, by (account bytes, seq). An unlisted key + /// answers `InvalidAccount`. + pub offer_keylets: HashMap<(Vec, i32), Answer>, + /// Every (account, seq) `offer_keylet` was asked for. + pub offer_keylets_asked: RefCell, i32)>>, + /// What `oracle_keylet` answers, by (account bytes, doc id). An unlisted key + /// answers `InvalidAccount`. + pub oracle_keylets: HashMap<(Vec, i32), Answer>, + /// Every (account, doc id) `oracle_keylet` was asked for. + pub oracle_keylets_asked: RefCell, i32)>>, + /// What `paychannel_keylet` answers, by (account, destination, seq). An unlisted + /// key answers `InvalidAccount`. + pub paychannel_keylets: HashMap<(Vec, Vec, i32), Answer>, + /// Every (account, destination, seq) `paychannel_keylet` was asked for. + pub paychannel_keylets_asked: RefCell, Vec, i32)>>, + /// What `permissioned_domain_keylet` answers, by (account bytes, seq). An unlisted + /// key answers `InvalidAccount`. + pub domain_keylets: HashMap<(Vec, i32), Answer>, + /// Every (account, seq) `permissioned_domain_keylet` was asked for. + pub domain_keylets_asked: RefCell, i32)>>, + /// What `signer_list_keylet` answers, by account bytes. An unlisted account + /// answers `InvalidAccount`. + pub signer_list_keylets: HashMap, Answer>, + /// Every account `signer_list_keylet` was asked for. + pub signer_list_keylets_asked: RefCell>>, + /// What `ticket_keylet` answers, by (account bytes, seq). An unlisted key answers + /// `InvalidAccount`. + pub ticket_keylets: HashMap<(Vec, i32), Answer>, + /// Every (account, seq) `ticket_keylet` was asked for. + pub ticket_keylets_asked: RefCell, i32)>>, + /// What `vault_keylet` answers, by (account bytes, seq). An unlisted key answers + /// `InvalidAccount`. + pub vault_keylets: HashMap<(Vec, i32), Answer>, + /// Every (account, seq) `vault_keylet` was asked for. + pub vault_keylets_asked: RefCell, i32)>>, /// What `sha512_half` answers, whatever it is given. pub digest: Answer, /// Every field selector `get_current_ledger_obj_field` was asked for. @@ -112,6 +280,65 @@ pub struct FakeHost { /// so this is how a test reaches what the engine does with an error it cannot /// report. pub trace_failure: Option, + /// What `update_data` answers, whatever data it is given. + pub update_data_answer: HostResult, + /// Every data blob `update_data` was given. + pub update_data_asked: RefCell>>, + /// What `get_nft` answers, by (account, nft id) bytes. An unlisted key answers + /// `InvalidParams`. + pub nfts: HashMap<(Vec, Vec), Answer>, + /// Every (account, nft id) `get_nft` was asked for. + pub nfts_asked: RefCell, Vec)>>, + /// What `get_nft_issuer` answers, by nft id. An unlisted id answers `InvalidParams`. + pub nft_issuers: HashMap, Answer>, + /// Every nft id `get_nft_issuer` was asked for. + pub nft_issuers_asked: RefCell>>, + /// What `get_nft_taxon` answers, by nft id. An unlisted id answers `InvalidParams`. + pub nft_taxons: HashMap, Answer>, + /// Every nft id `get_nft_taxon` was asked for. + pub nft_taxons_asked: RefCell>>, + /// What `get_nft_flags` answers, whatever nft id it is given. + pub nft_flags_answer: HostResult, + /// Every nft id `get_nft_flags` was asked for. + pub nft_flags_asked: RefCell>>, + /// What `get_nft_transfer_fee` answers, whatever nft id it is given. + pub nft_fee_answer: HostResult, + /// Every nft id `get_nft_transfer_fee` was asked for. + pub nft_fee_asked: RefCell>>, + /// What `get_nft_sequence` answers, by nft id. An unlisted id answers + /// `InvalidParams`. + pub nft_sequences: HashMap, Answer>, + /// Every nft id `get_nft_sequence` was asked for. + pub nft_sequences_asked: RefCell>>, + + /// What every float-producing call writes. + pub float_answer: Answer, + /// Every `(x, mode)` `float_from_int` was asked for. + pub float_from_int_asked: RefCell>, + /// Every `(x, mode)` `float_from_uint` was asked for. + pub float_from_uint_asked: RefCell, i32)>>, + /// Every `(amount, mode)` `float_from_stamount` was asked for. + pub float_from_stamount_asked: RefCell, i32)>>, + /// Every `(number, mode)` `float_from_stnumber` was asked for. + pub float_from_stnumber_asked: RefCell, i32)>>, + /// Every `(x, mode)` `float_to_int` was asked for. + pub float_to_int_asked: RefCell, i32)>>, + /// The mantissa and exponent bytes `float_to_mant_exp` writes to its two regions. + pub float_mant_exp_answer: (Vec, Vec), + /// Every float `float_to_mant_exp` was asked for. + pub float_to_mant_exp_asked: RefCell>>, + /// Every `(mantissa, exponent, mode)` `float_from_mant_exp` was asked for. + pub float_from_mant_exp_asked: RefCell>, + /// What `float_compare` answers, whatever floats it is given. + pub float_compare_answer: HostResult, + /// Every `(x, y)` `float_compare` was asked for. + pub float_compare_asked: RefCell, Vec)>>, + /// Every `(x, y, mode)` the four binary float operators were asked for, tagged by + /// operator name. + pub float_binops_asked: RefCell, Vec, i32)>>, + /// Every `(x, n, mode)` `float_root` and `float_power` were asked for, tagged by + /// operator name. + pub float_unops_asked: RefCell, i32, i32)>>, } impl Default for FakeHost { @@ -119,12 +346,115 @@ impl Default for FakeHost { FakeHost { // 4 little-endian bytes, as the declaration's doc comment specifies. ledger_sqn: Answer::bytes(7u32.to_le_bytes()), + // A distinct value from the sequence number, so a test cannot pass by + // reading one where it meant the other. + parent_ledger_time: Answer::bytes(9u32.to_le_bytes()), + // 32 bytes counting up from 0, the length of a real ledger hash. + parent_ledger_hash: Answer::filler(32), + // A distinct value again, so no getter can pass by reading another's answer. + base_fee: Answer::bytes(10u32.to_le_bytes()), + // Enabled by default; the id-or-name dispatch is the host's job, not the ABI's. + amendment_enabled: Ok(1), + amendments_asked: RefCell::new(Vec::new()), + // Slot 1 by default; slot assignment is the host's job, not the ABI's. + cache_slot: Ok(1), + cached: RefCell::new(Vec::new()), + tx_fields: HashMap::new(), + tx_fields_asked: RefCell::new(Vec::new()), fields: HashMap::new(), + le_fields: HashMap::new(), + le_fields_asked: RefCell::new(Vec::new()), + tx_nested: HashMap::new(), + tx_nested_asked: RefCell::new(Vec::new()), + home_le_nested: HashMap::new(), + home_le_nested_asked: RefCell::new(Vec::new()), + le_nested: HashMap::new(), + le_nested_asked: RefCell::new(Vec::new()), + tx_arr_lens: HashMap::new(), + tx_arr_lens_asked: RefCell::new(Vec::new()), + home_le_arr_lens: HashMap::new(), + home_le_arr_lens_asked: RefCell::new(Vec::new()), + le_arr_lens: HashMap::new(), + le_arr_lens_asked: RefCell::new(Vec::new()), + tx_nested_arr_lens: HashMap::new(), + tx_nested_arr_lens_asked: RefCell::new(Vec::new()), + home_le_nested_arr_lens: HashMap::new(), + home_le_nested_arr_lens_asked: RefCell::new(Vec::new()), + le_nested_arr_lens: HashMap::new(), + le_nested_arr_lens_asked: RefCell::new(Vec::new()), + // Valid by default; the verification itself is the host's job, not the ABI's. + sig_valid: Ok(1), + sigs_checked: RefCell::new(Vec::new()), + account_keylets: HashMap::new(), + account_keylets_asked: RefCell::new(Vec::new()), + amm_keylets: HashMap::new(), + amm_keylets_asked: RefCell::new(Vec::new()), + check_keylets: HashMap::new(), + check_keylets_asked: RefCell::new(Vec::new()), + credential_keylets: HashMap::new(), + credential_keylets_asked: RefCell::new(Vec::new()), + delegate_keylets: HashMap::new(), + delegate_keylets_asked: RefCell::new(Vec::new()), + deposit_preauth_keylets: HashMap::new(), + deposit_preauth_keylets_asked: RefCell::new(Vec::new()), + did_keylets: HashMap::new(), + did_keylets_asked: RefCell::new(Vec::new()), + escrow_keylets: HashMap::new(), + escrow_keylets_asked: RefCell::new(Vec::new()), + trust_line_keylets: HashMap::new(), + trust_line_keylets_asked: RefCell::new(Vec::new()), + mpt_issuance_keylets: HashMap::new(), + mpt_issuance_keylets_asked: RefCell::new(Vec::new()), + mptoken_keylets: HashMap::new(), + mptoken_keylets_asked: RefCell::new(Vec::new()), + nft_offer_keylets: HashMap::new(), + nft_offer_keylets_asked: RefCell::new(Vec::new()), + offer_keylets: HashMap::new(), + offer_keylets_asked: RefCell::new(Vec::new()), + oracle_keylets: HashMap::new(), + oracle_keylets_asked: RefCell::new(Vec::new()), + paychannel_keylets: HashMap::new(), + paychannel_keylets_asked: RefCell::new(Vec::new()), + domain_keylets: HashMap::new(), + domain_keylets_asked: RefCell::new(Vec::new()), + signer_list_keylets: HashMap::new(), + signer_list_keylets_asked: RefCell::new(Vec::new()), + ticket_keylets: HashMap::new(), + ticket_keylets_asked: RefCell::new(Vec::new()), + vault_keylets: HashMap::new(), + vault_keylets_asked: RefCell::new(Vec::new()), digest: Answer::filler(32), fields_asked: RefCell::new(Vec::new()), digested: RefCell::new(Vec::new()), traces: RefCell::new(Vec::new()), trace_failure: None, + update_data_answer: Ok(0), + update_data_asked: RefCell::new(Vec::new()), + nfts: HashMap::new(), + nfts_asked: RefCell::new(Vec::new()), + nft_issuers: HashMap::new(), + nft_issuers_asked: RefCell::new(Vec::new()), + nft_taxons: HashMap::new(), + nft_taxons_asked: RefCell::new(Vec::new()), + nft_flags_answer: Ok(0), + nft_flags_asked: RefCell::new(Vec::new()), + nft_fee_answer: Ok(0), + nft_fee_asked: RefCell::new(Vec::new()), + nft_sequences: HashMap::new(), + nft_sequences_asked: RefCell::new(Vec::new()), + float_answer: Answer::filler(8), + float_from_int_asked: RefCell::new(Vec::new()), + float_from_uint_asked: RefCell::new(Vec::new()), + float_from_stamount_asked: RefCell::new(Vec::new()), + float_from_stnumber_asked: RefCell::new(Vec::new()), + float_to_int_asked: RefCell::new(Vec::new()), + float_mant_exp_answer: (vec![0u8; 8], vec![0u8; 4]), + float_to_mant_exp_asked: RefCell::new(Vec::new()), + float_from_mant_exp_asked: RefCell::new(Vec::new()), + float_compare_answer: Ok(0), + float_compare_asked: RefCell::new(Vec::new()), + float_binops_asked: RefCell::new(Vec::new()), + float_unops_asked: RefCell::new(Vec::new()), } } } @@ -139,16 +469,348 @@ impl FakeHost { self } + pub fn answering_parent_ledger_time(mut self, answer: Answer) -> FakeHost { + self.parent_ledger_time = answer; + self + } + + pub fn answering_parent_ledger_hash(mut self, answer: Answer) -> FakeHost { + self.parent_ledger_hash = answer; + self + } + + pub fn answering_base_fee(mut self, answer: Answer) -> FakeHost { + self.base_fee = answer; + self + } + + pub fn answering_amendment_enabled(mut self, answer: HostResult) -> FakeHost { + self.amendment_enabled = answer; + self + } + + pub fn answering_cache_slot(mut self, answer: HostResult) -> FakeHost { + self.cache_slot = answer; + self + } + + pub fn answering_tx_field(mut self, field: i32, answer: Answer) -> FakeHost { + self.tx_fields.insert(field, answer); + self + } + pub fn answering_field(mut self, field: i32, answer: Answer) -> FakeHost { self.fields.insert(field, answer); self } + pub fn answering_le_field(mut self, cache_idx: i32, field: i32, answer: Answer) -> FakeHost { + self.le_fields.insert((cache_idx, field), answer); + self + } + + pub fn answering_tx_nested(mut self, locator: Vec, answer: Answer) -> FakeHost { + self.tx_nested.insert(locator, answer); + self + } + + pub fn answering_home_le_nested(mut self, locator: Vec, answer: Answer) -> FakeHost { + self.home_le_nested.insert(locator, answer); + self + } + + pub fn answering_le_nested( + mut self, + cache_idx: i32, + locator: Vec, + answer: Answer, + ) -> FakeHost { + self.le_nested.insert((cache_idx, locator), answer); + self + } + + pub fn answering_tx_arr_len(mut self, field: i32, len: i32) -> FakeHost { + self.tx_arr_lens.insert(field, len); + self + } + + pub fn answering_home_le_arr_len(mut self, field: i32, len: i32) -> FakeHost { + self.home_le_arr_lens.insert(field, len); + self + } + + pub fn answering_le_arr_len(mut self, cache_idx: i32, field: i32, len: i32) -> FakeHost { + self.le_arr_lens.insert((cache_idx, field), len); + self + } + + pub fn answering_tx_nested_arr_len(mut self, locator: Vec, len: i32) -> FakeHost { + self.tx_nested_arr_lens.insert(locator, len); + self + } + + pub fn answering_home_le_nested_arr_len(mut self, locator: Vec, len: i32) -> FakeHost { + self.home_le_nested_arr_lens.insert(locator, len); + self + } + + pub fn answering_le_nested_arr_len( + mut self, + cache_idx: i32, + locator: Vec, + len: i32, + ) -> FakeHost { + self.le_nested_arr_lens.insert((cache_idx, locator), len); + self + } + + pub fn answering_check_sig(mut self, answer: HostResult) -> FakeHost { + self.sig_valid = answer; + self + } + + pub fn answering_account_keylet(mut self, account: Vec, answer: Answer) -> FakeHost { + self.account_keylets.insert(account, answer); + self + } + + pub fn answering_amm_keylet( + mut self, + asset1: Vec, + asset2: Vec, + answer: Answer, + ) -> FakeHost { + self.amm_keylets.insert((asset1, asset2), answer); + self + } + + pub fn answering_check_keylet( + mut self, + account: Vec, + seq: i32, + answer: Answer, + ) -> FakeHost { + self.check_keylets.insert((account, seq), answer); + self + } + + pub fn answering_credential_keylet( + mut self, + subject: Vec, + issuer: Vec, + credential_type: Vec, + answer: Answer, + ) -> FakeHost { + self.credential_keylets + .insert((subject, issuer, credential_type), answer); + self + } + + pub fn answering_delegate_keylet( + mut self, + account: Vec, + authorize: Vec, + answer: Answer, + ) -> FakeHost { + self.delegate_keylets.insert((account, authorize), answer); + self + } + + pub fn answering_deposit_preauth_keylet( + mut self, + account: Vec, + authorize: Vec, + answer: Answer, + ) -> FakeHost { + self.deposit_preauth_keylets + .insert((account, authorize), answer); + self + } + + pub fn answering_did_keylet(mut self, account: Vec, answer: Answer) -> FakeHost { + self.did_keylets.insert(account, answer); + self + } + + pub fn answering_escrow_keylet( + mut self, + account: Vec, + seq: i32, + answer: Answer, + ) -> FakeHost { + self.escrow_keylets.insert((account, seq), answer); + self + } + + pub fn answering_trust_line_keylet( + mut self, + account1: Vec, + account2: Vec, + currency: Vec, + answer: Answer, + ) -> FakeHost { + self.trust_line_keylets + .insert((account1, account2, currency), answer); + self + } + + pub fn answering_mpt_issuance_keylet( + mut self, + issuer: Vec, + seq: i32, + answer: Answer, + ) -> FakeHost { + self.mpt_issuance_keylets.insert((issuer, seq), answer); + self + } + + pub fn answering_mptoken_keylet( + mut self, + mptid: Vec, + holder: Vec, + answer: Answer, + ) -> FakeHost { + self.mptoken_keylets.insert((mptid, holder), answer); + self + } + + pub fn answering_nft_offer_keylet( + mut self, + account: Vec, + seq: i32, + answer: Answer, + ) -> FakeHost { + self.nft_offer_keylets.insert((account, seq), answer); + self + } + + pub fn answering_offer_keylet( + mut self, + account: Vec, + seq: i32, + answer: Answer, + ) -> FakeHost { + self.offer_keylets.insert((account, seq), answer); + self + } + + pub fn answering_oracle_keylet( + mut self, + account: Vec, + doc_id: i32, + answer: Answer, + ) -> FakeHost { + self.oracle_keylets.insert((account, doc_id), answer); + self + } + + pub fn answering_paychannel_keylet( + mut self, + account: Vec, + destination: Vec, + seq: i32, + answer: Answer, + ) -> FakeHost { + self.paychannel_keylets + .insert((account, destination, seq), answer); + self + } + + pub fn answering_permissioned_domain_keylet( + mut self, + account: Vec, + seq: i32, + answer: Answer, + ) -> FakeHost { + self.domain_keylets.insert((account, seq), answer); + self + } + + pub fn answering_signer_list_keylet(mut self, account: Vec, answer: Answer) -> FakeHost { + self.signer_list_keylets.insert(account, answer); + self + } + + pub fn answering_ticket_keylet( + mut self, + account: Vec, + seq: i32, + answer: Answer, + ) -> FakeHost { + self.ticket_keylets.insert((account, seq), answer); + self + } + + pub fn answering_vault_keylet( + mut self, + account: Vec, + seq: i32, + answer: Answer, + ) -> FakeHost { + self.vault_keylets.insert((account, seq), answer); + self + } + pub fn answering_digest(mut self, answer: Answer) -> FakeHost { self.digest = answer; self } + pub fn answering_update_data(mut self, answer: HostResult) -> FakeHost { + self.update_data_answer = answer; + self + } + + pub fn answering_get_nft( + mut self, + account: Vec, + nft_id: Vec, + answer: Answer, + ) -> FakeHost { + self.nfts.insert((account, nft_id), answer); + self + } + + pub fn answering_nft_issuer(mut self, nft_id: Vec, answer: Answer) -> FakeHost { + self.nft_issuers.insert(nft_id, answer); + self + } + + pub fn answering_nft_taxon(mut self, nft_id: Vec, answer: Answer) -> FakeHost { + self.nft_taxons.insert(nft_id, answer); + self + } + + pub fn answering_nft_flags(mut self, answer: HostResult) -> FakeHost { + self.nft_flags_answer = answer; + self + } + + pub fn answering_nft_transfer_fee(mut self, answer: HostResult) -> FakeHost { + self.nft_fee_answer = answer; + self + } + + pub fn answering_nft_sequence(mut self, nft_id: Vec, answer: Answer) -> FakeHost { + self.nft_sequences.insert(nft_id, answer); + self + } + + pub fn answering_float(mut self, answer: Answer) -> FakeHost { + self.float_answer = answer; + self + } + + pub fn answering_float_mant_exp(mut self, mantissa: Vec, exponent: Vec) -> FakeHost { + self.float_mant_exp_answer = (mantissa, exponent); + self + } + + pub fn answering_float_compare(mut self, answer: HostResult) -> FakeHost { + self.float_compare_answer = answer; + self + } + pub fn failing_trace(mut self, error: HostError) -> FakeHost { self.trace_failure = Some(error); self @@ -164,6 +826,36 @@ impl HostFunctions for FakeHost { self.ledger_sqn.fill(out) } + fn get_parent_ledger_time(&self, out: &mut [u8]) -> HostResult { + self.parent_ledger_time.fill(out) + } + + fn get_parent_ledger_hash(&self, out: &mut [u8]) -> HostResult { + self.parent_ledger_hash.fill(out) + } + + fn get_base_fee(&self, out: &mut [u8]) -> HostResult { + self.base_fee.fill(out) + } + + fn is_amendment_enabled(&self, amendment: &[u8]) -> HostResult { + self.amendments_asked.borrow_mut().push(amendment.to_vec()); + self.amendment_enabled + } + + fn cache_ledger_obj(&self, obj_id: &[u8], cache_idx: i32) -> HostResult { + self.cached.borrow_mut().push((obj_id.to_vec(), cache_idx)); + self.cache_slot + } + + fn get_tx_field(&self, field: i32, out: &mut [u8]) -> HostResult { + self.tx_fields_asked.borrow_mut().push(field); + match self.tx_fields.get(&field) { + Some(answer) => answer.fill(out), + None => Err(HostError::FieldNotFound), + } + } + fn get_current_ledger_obj_field(&self, field: i32, out: &mut [u8]) -> HostResult { self.fields_asked.borrow_mut().push(field); match self.fields.get(&field) { @@ -172,6 +864,335 @@ impl HostFunctions for FakeHost { } } + fn get_ledger_obj_field( + &self, + cache_idx: i32, + field: i32, + out: &mut [u8], + ) -> HostResult { + self.le_fields_asked.borrow_mut().push((cache_idx, field)); + match self.le_fields.get(&(cache_idx, field)) { + Some(answer) => answer.fill(out), + None => Err(HostError::FieldNotFound), + } + } + + fn get_tx_nested_field(&self, locator: &[u8], out: &mut [u8]) -> HostResult { + self.tx_nested_asked.borrow_mut().push(locator.to_vec()); + match self.tx_nested.get(locator) { + Some(answer) => answer.fill(out), + None => Err(HostError::FieldNotFound), + } + } + + fn get_current_ledger_obj_nested_field( + &self, + locator: &[u8], + out: &mut [u8], + ) -> HostResult { + self.home_le_nested_asked + .borrow_mut() + .push(locator.to_vec()); + match self.home_le_nested.get(locator) { + Some(answer) => answer.fill(out), + None => Err(HostError::FieldNotFound), + } + } + + fn get_ledger_obj_nested_field( + &self, + cache_idx: i32, + locator: &[u8], + out: &mut [u8], + ) -> HostResult { + self.le_nested_asked + .borrow_mut() + .push((cache_idx, locator.to_vec())); + match self.le_nested.get(&(cache_idx, locator.to_vec())) { + Some(answer) => answer.fill(out), + None => Err(HostError::FieldNotFound), + } + } + + fn get_tx_array_len(&self, field: i32) -> HostResult { + self.tx_arr_lens_asked.borrow_mut().push(field); + match self.tx_arr_lens.get(&field) { + Some(&len) => Ok(len), + None => Err(HostError::NoArray), + } + } + + fn get_current_ledger_obj_array_len(&self, field: i32) -> HostResult { + self.home_le_arr_lens_asked.borrow_mut().push(field); + match self.home_le_arr_lens.get(&field) { + Some(&len) => Ok(len), + None => Err(HostError::NoArray), + } + } + + fn get_ledger_obj_array_len(&self, cache_idx: i32, field: i32) -> HostResult { + self.le_arr_lens_asked.borrow_mut().push((cache_idx, field)); + match self.le_arr_lens.get(&(cache_idx, field)) { + Some(&len) => Ok(len), + None => Err(HostError::NoArray), + } + } + + fn get_tx_nested_array_len(&self, locator: &[u8]) -> HostResult { + self.tx_nested_arr_lens_asked + .borrow_mut() + .push(locator.to_vec()); + match self.tx_nested_arr_lens.get(locator) { + Some(&len) => Ok(len), + None => Err(HostError::NoArray), + } + } + + fn get_current_ledger_obj_nested_array_len(&self, locator: &[u8]) -> HostResult { + self.home_le_nested_arr_lens_asked + .borrow_mut() + .push(locator.to_vec()); + match self.home_le_nested_arr_lens.get(locator) { + Some(&len) => Ok(len), + None => Err(HostError::NoArray), + } + } + + fn get_ledger_obj_nested_array_len(&self, cache_idx: i32, locator: &[u8]) -> HostResult { + self.le_nested_arr_lens_asked + .borrow_mut() + .push((cache_idx, locator.to_vec())); + match self.le_nested_arr_lens.get(&(cache_idx, locator.to_vec())) { + Some(&len) => Ok(len), + None => Err(HostError::NoArray), + } + } + + fn check_signature(&self, message: &[u8], signature: &[u8], pubkey: &[u8]) -> HostResult { + self.sigs_checked.borrow_mut().push(( + message.to_vec(), + signature.to_vec(), + pubkey.to_vec(), + )); + self.sig_valid + } + + fn account_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult { + self.account_keylets_asked + .borrow_mut() + .push(account.to_vec()); + match self.account_keylets.get(account) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn amm_keylet(&self, asset1: &[u8], asset2: &[u8], out: &mut [u8]) -> HostResult { + self.amm_keylets_asked + .borrow_mut() + .push((asset1.to_vec(), asset2.to_vec())); + match self.amm_keylets.get(&(asset1.to_vec(), asset2.to_vec())) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidParams), + } + } + + fn check_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult { + self.check_keylets_asked + .borrow_mut() + .push((account.to_vec(), seq)); + match self.check_keylets.get(&(account.to_vec(), seq)) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn credential_keylet( + &self, + subject: &[u8], + issuer: &[u8], + credential_type: &[u8], + out: &mut [u8], + ) -> HostResult { + let key = (subject.to_vec(), issuer.to_vec(), credential_type.to_vec()); + self.credential_keylets_asked.borrow_mut().push(key.clone()); + match self.credential_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn delegate_keylet( + &self, + account: &[u8], + authorize: &[u8], + out: &mut [u8], + ) -> HostResult { + let key = (account.to_vec(), authorize.to_vec()); + self.delegate_keylets_asked.borrow_mut().push(key.clone()); + match self.delegate_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn deposit_preauth_keylet( + &self, + account: &[u8], + authorize: &[u8], + out: &mut [u8], + ) -> HostResult { + let key = (account.to_vec(), authorize.to_vec()); + self.deposit_preauth_keylets_asked + .borrow_mut() + .push(key.clone()); + match self.deposit_preauth_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn did_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult { + self.did_keylets_asked.borrow_mut().push(account.to_vec()); + match self.did_keylets.get(account) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn escrow_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult { + let key = (account.to_vec(), seq); + self.escrow_keylets_asked.borrow_mut().push(key.clone()); + match self.escrow_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn trust_line_keylet( + &self, + account1: &[u8], + account2: &[u8], + currency: &[u8], + out: &mut [u8], + ) -> HostResult { + let key = (account1.to_vec(), account2.to_vec(), currency.to_vec()); + self.trust_line_keylets_asked.borrow_mut().push(key.clone()); + match self.trust_line_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn mptoken_issuance_keylet( + &self, + issuer: &[u8], + seq: i32, + out: &mut [u8], + ) -> HostResult { + let key = (issuer.to_vec(), seq); + self.mpt_issuance_keylets_asked + .borrow_mut() + .push(key.clone()); + match self.mpt_issuance_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn mptoken_keylet(&self, mptid: &[u8], holder: &[u8], out: &mut [u8]) -> HostResult { + let key = (mptid.to_vec(), holder.to_vec()); + self.mptoken_keylets_asked.borrow_mut().push(key.clone()); + match self.mptoken_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidParams), + } + } + + fn nftoken_offer_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult { + let key = (account.to_vec(), seq); + self.nft_offer_keylets_asked.borrow_mut().push(key.clone()); + match self.nft_offer_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn offer_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult { + let key = (account.to_vec(), seq); + self.offer_keylets_asked.borrow_mut().push(key.clone()); + match self.offer_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn oracle_keylet(&self, account: &[u8], doc_id: i32, out: &mut [u8]) -> HostResult { + let key = (account.to_vec(), doc_id); + self.oracle_keylets_asked.borrow_mut().push(key.clone()); + match self.oracle_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn paychannel_keylet( + &self, + account: &[u8], + destination: &[u8], + seq: i32, + out: &mut [u8], + ) -> HostResult { + let key = (account.to_vec(), destination.to_vec(), seq); + self.paychannel_keylets_asked.borrow_mut().push(key.clone()); + match self.paychannel_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn permissioned_domain_keylet( + &self, + account: &[u8], + seq: i32, + out: &mut [u8], + ) -> HostResult { + let key = (account.to_vec(), seq); + self.domain_keylets_asked.borrow_mut().push(key.clone()); + match self.domain_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn signer_list_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult { + self.signer_list_keylets_asked + .borrow_mut() + .push(account.to_vec()); + match self.signer_list_keylets.get(account) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn ticket_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult { + let key = (account.to_vec(), seq); + self.ticket_keylets_asked.borrow_mut().push(key.clone()); + match self.ticket_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + + fn vault_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult { + let key = (account.to_vec(), seq); + self.vault_keylets_asked.borrow_mut().push(key.clone()); + match self.vault_keylets.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidAccount), + } + } + fn sha512_half(&self, data: &[u8], out: &mut [u8]) -> HostResult { self.digested.borrow_mut().push(data.to_vec()); self.digest.fill(out) @@ -190,6 +1211,162 @@ impl HostFunctions for FakeHost { None => Ok(()), } } + + fn update_data(&self, data: &[u8]) -> HostResult { + self.update_data_asked.borrow_mut().push(data.to_vec()); + self.update_data_answer + } + + fn get_nft(&self, account: &[u8], nft_id: &[u8], out: &mut [u8]) -> HostResult { + let key = (account.to_vec(), nft_id.to_vec()); + self.nfts_asked.borrow_mut().push(key.clone()); + match self.nfts.get(&key) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidParams), + } + } + + fn get_nft_issuer(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult { + self.nft_issuers_asked.borrow_mut().push(nft_id.to_vec()); + match self.nft_issuers.get(nft_id) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidParams), + } + } + + fn get_nft_taxon(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult { + self.nft_taxons_asked.borrow_mut().push(nft_id.to_vec()); + match self.nft_taxons.get(nft_id) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidParams), + } + } + + fn get_nft_flags(&self, nft_id: &[u8]) -> HostResult { + self.nft_flags_asked.borrow_mut().push(nft_id.to_vec()); + self.nft_flags_answer + } + + fn get_nft_transfer_fee(&self, nft_id: &[u8]) -> HostResult { + self.nft_fee_asked.borrow_mut().push(nft_id.to_vec()); + self.nft_fee_answer + } + + fn get_nft_sequence(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult { + self.nft_sequences_asked.borrow_mut().push(nft_id.to_vec()); + match self.nft_sequences.get(nft_id) { + Some(answer) => answer.fill(out), + None => Err(HostError::InvalidParams), + } + } + + fn float_from_int(&self, x: i64, mode: i32, out: &mut [u8]) -> HostResult { + self.float_from_int_asked.borrow_mut().push((x, mode)); + self.float_answer.fill(out) + } + + fn float_from_uint(&self, x: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + self.float_from_uint_asked + .borrow_mut() + .push((x.to_vec(), mode)); + self.float_answer.fill(out) + } + + fn float_from_stamount(&self, amount: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + self.float_from_stamount_asked + .borrow_mut() + .push((amount.to_vec(), mode)); + self.float_answer.fill(out) + } + + fn float_from_stnumber(&self, number: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + self.float_from_stnumber_asked + .borrow_mut() + .push((number.to_vec(), mode)); + self.float_answer.fill(out) + } + + fn float_to_int(&self, x: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + self.float_to_int_asked + .borrow_mut() + .push((x.to_vec(), mode)); + self.float_answer.fill(out) + } + + fn float_to_mant_exp( + &self, + x: &[u8], + mantissa_out: &mut [u8], + exponent_out: &mut [u8], + ) -> HostResult { + self.float_to_mant_exp_asked.borrow_mut().push(x.to_vec()); + let (mantissa, exponent) = &self.float_mant_exp_answer; + mantissa_out[..mantissa.len()].copy_from_slice(mantissa); + exponent_out[..exponent.len()].copy_from_slice(exponent); + Ok(mantissa.len() + exponent.len()) + } + + fn float_from_mant_exp( + &self, + mantissa: i64, + exponent: i32, + mode: i32, + out: &mut [u8], + ) -> HostResult { + self.float_from_mant_exp_asked + .borrow_mut() + .push((mantissa, exponent, mode)); + self.float_answer.fill(out) + } + + fn float_compare(&self, x: &[u8], y: &[u8]) -> HostResult { + self.float_compare_asked + .borrow_mut() + .push((x.to_vec(), y.to_vec())); + self.float_compare_answer + } + + fn float_add(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + self.float_binops_asked + .borrow_mut() + .push(("add", x.to_vec(), y.to_vec(), mode)); + self.float_answer.fill(out) + } + + fn float_subtract(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + self.float_binops_asked + .borrow_mut() + .push(("sub", x.to_vec(), y.to_vec(), mode)); + self.float_answer.fill(out) + } + + fn float_multiply(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + self.float_binops_asked + .borrow_mut() + .push(("mult", x.to_vec(), y.to_vec(), mode)); + self.float_answer.fill(out) + } + + fn float_divide(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult { + self.float_binops_asked + .borrow_mut() + .push(("div", x.to_vec(), y.to_vec(), mode)); + self.float_answer.fill(out) + } + + fn float_root(&self, x: &[u8], n: i32, mode: i32, out: &mut [u8]) -> HostResult { + self.float_unops_asked + .borrow_mut() + .push(("root", x.to_vec(), n, mode)); + self.float_answer.fill(out) + } + + fn float_power(&self, x: &[u8], n: i32, mode: i32, out: &mut [u8]) -> HostResult { + self.float_unops_asked + .borrow_mut() + .push(("pow", x.to_vec(), n, mode)); + self.float_answer.fill(out) + } } // --------------------------------------------------------------------------- @@ -202,11 +1379,82 @@ impl HostFunctions for FakeHost { pub mod import { pub const LDGR_INDEX: &str = r#"(import "host_lib" "ldgr_index" (func $ldgr_index (param i32 i32) (result i32)))"#; + pub const PARENT_LDGR_TIME: &str = r#"(import "host_lib" "parent_ldgr_time" (func $parent_ldgr_time (param i32 i32) (result i32)))"#; + pub const PARENT_LDGR_HASH: &str = r#"(import "host_lib" "parent_ldgr_hash" (func $parent_ldgr_hash (param i32 i32) (result i32)))"#; + pub const BASE_FEE: &str = + r#"(import "host_lib" "base_fee" (func $base_fee (param i32 i32) (result i32)))"#; + pub const AMENDMENT_ENABLED: &str = r#"(import "host_lib" "amendment_enabled" (func $amendment_enabled (param i32 i32) (result i32)))"#; + pub const CACHE_LE: &str = + r#"(import "host_lib" "cache_le" (func $cache_le (param i32 i32 i32) (result i32)))"#; + pub const TX_FIELD: &str = + r#"(import "host_lib" "tx_field" (func $tx_field (param i32 i32 i32) (result i32)))"#; pub const HOME_LE_FIELD: &str = r#"(import "host_lib" "home_le_field" (func $home_le_field (param i32 i32 i32) (result i32)))"#; + pub const LE_FIELD: &str = + r#"(import "host_lib" "le_field" (func $le_field (param i32 i32 i32 i32) (result i32)))"#; + pub const TX_INNER: &str = + r#"(import "host_lib" "tx_inner" (func $tx_inner (param i32 i32 i32 i32) (result i32)))"#; + pub const HOME_LE_INNER: &str = r#"(import "host_lib" "home_le_inner" (func $home_le_inner (param i32 i32 i32 i32) (result i32)))"#; + pub const LE_INNER: &str = r#"(import "host_lib" "le_inner" (func $le_inner (param i32 i32 i32 i32 i32) (result i32)))"#; + pub const TX_ARR_LEN: &str = + r#"(import "host_lib" "tx_arr_len" (func $tx_arr_len (param i32) (result i32)))"#; + pub const HOME_LE_ARR_LEN: &str = + r#"(import "host_lib" "home_le_arr_len" (func $home_le_arr_len (param i32) (result i32)))"#; + pub const LE_ARR_LEN: &str = + r#"(import "host_lib" "le_arr_len" (func $le_arr_len (param i32 i32) (result i32)))"#; + pub const TX_INNER_ARR_LEN: &str = r#"(import "host_lib" "tx_inner_arr_len" (func $tx_inner_arr_len (param i32 i32) (result i32)))"#; + pub const HOME_LE_INNER_ARR_LEN: &str = r#"(import "host_lib" "home_le_inner_arr_len" (func $home_le_inner_arr_len (param i32 i32) (result i32)))"#; + pub const LE_INNER_ARR_LEN: &str = r#"(import "host_lib" "le_inner_arr_len" (func $le_inner_arr_len (param i32 i32 i32) (result i32)))"#; + pub const CHECK_SIG: &str = r#"(import "host_lib" "check_sig" (func $check_sig (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const ACCOUNTROOT_ID: &str = r#"(import "host_lib" "accountroot_id" (func $accountroot_id (param i32 i32 i32 i32) (result i32)))"#; + pub const AMM_ID: &str = r#"(import "host_lib" "amm_id" (func $amm_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const CHECK_ID: &str = r#"(import "host_lib" "check_id" (func $check_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const CREDENTIAL_ID: &str = r#"(import "host_lib" "credential_id" (func $credential_id (param i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const DELEGATE_ID: &str = r#"(import "host_lib" "delegate_id" (func $delegate_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const DEPOSIT_PREAUTH_ID: &str = r#"(import "host_lib" "deposit_preauth_id" (func $deposit_preauth_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const DID_ID: &str = + r#"(import "host_lib" "did_id" (func $did_id (param i32 i32 i32 i32) (result i32)))"#; + pub const ESCROW_ID: &str = r#"(import "host_lib" "escrow_id" (func $escrow_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const TRUSTLINE_ID: &str = r#"(import "host_lib" "trustline_id" (func $trustline_id (param i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const MPT_ISSUANCE_ID: &str = r#"(import "host_lib" "mpt_issuance_id" (func $mpt_issuance_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const MPTOKEN_ID: &str = r#"(import "host_lib" "mptoken_id" (func $mptoken_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const NFT_OFFER_ID: &str = r#"(import "host_lib" "nft_offer_id" (func $nft_offer_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const OFFER_ID: &str = r#"(import "host_lib" "offer_id" (func $offer_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const ORACLE_ID: &str = r#"(import "host_lib" "oracle_id" (func $oracle_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const PAYCHAN_ID: &str = r#"(import "host_lib" "paychan_id" (func $paychan_id (param i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const PERMISSIONED_DOMAIN_ID: &str = r#"(import "host_lib" "permissioned_domain_id" (func $permissioned_domain_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const SIGNERS_ID: &str = r#"(import "host_lib" "signers_id" (func $signers_id (param i32 i32 i32 i32) (result i32)))"#; + pub const TICKET_ID: &str = r#"(import "host_lib" "ticket_id" (func $ticket_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const VAULT_ID: &str = r#"(import "host_lib" "vault_id" (func $vault_id (param i32 i32 i32 i32 i32 i32) (result i32)))"#; pub const SHA512_HALF: &str = r#"(import "host_lib" "sha512_half" (func $sha512_half (param i32 i32 i32 i32) (result i32)))"#; /// No result, unlike every other import here: `trace` answers the guest nothing. pub const TRACE: &str = r#"(import "host_lib" "trace" (func $trace (param i32 i32 i32 i32 i32)))"#; + pub const SET_DATA: &str = + r#"(import "host_lib" "set_data" (func $set_data (param i32 i32) (result i32)))"#; + pub const NFT_URI: &str = r#"(import "host_lib" "nft_uri" (func $nft_uri (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const NFT_ISSUER: &str = r#"(import "host_lib" "nft_issuer" (func $nft_issuer (param i32 i32 i32 i32) (result i32)))"#; + pub const NFT_TAXON: &str = + r#"(import "host_lib" "nft_taxon" (func $nft_taxon (param i32 i32 i32 i32) (result i32)))"#; + pub const NFT_FLAGS: &str = + r#"(import "host_lib" "nft_flags" (func $nft_flags (param i32 i32) (result i32)))"#; + pub const NFT_XFER_FEE: &str = + r#"(import "host_lib" "nft_xfer_fee" (func $nft_xfer_fee (param i32 i32) (result i32)))"#; + pub const NFT_SERIAL: &str = r#"(import "host_lib" "nft_serial" (func $nft_serial (param i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_FROM_INT: &str = r#"(import "host_lib" "float_from_int" (func $float_from_int (param i64 i32 i32 i32) (result i32)))"#; + pub const FLOAT_FROM_UINT: &str = r#"(import "host_lib" "float_from_uint" (func $float_from_uint (param i32 i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_FROM_STAMOUNT: &str = r#"(import "host_lib" "float_from_stamount" (func $float_from_stamount (param i32 i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_FROM_STNUMBER: &str = r#"(import "host_lib" "float_from_stnumber" (func $float_from_stnumber (param i32 i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_TO_INT: &str = r#"(import "host_lib" "float_to_int" (func $float_to_int (param i32 i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_TO_MANT_EXP: &str = r#"(import "host_lib" "float_to_mant_exp" (func $float_to_mant_exp (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_FROM_MANT_EXP: &str = r#"(import "host_lib" "float_from_mant_exp" (func $float_from_mant_exp (param i64 i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_CMP: &str = + r#"(import "host_lib" "float_cmp" (func $float_cmp (param i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_ADD: &str = r#"(import "host_lib" "float_add" (func $float_add (param i32 i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_SUB: &str = r#"(import "host_lib" "float_sub" (func $float_sub (param i32 i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_MULT: &str = r#"(import "host_lib" "float_mult" (func $float_mult (param i32 i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_DIV: &str = r#"(import "host_lib" "float_div" (func $float_div (param i32 i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_ROOT: &str = r#"(import "host_lib" "float_root" (func $float_root (param i32 i32 i32 i32 i32 i32) (result i32)))"#; + pub const FLOAT_POW: &str = r#"(import "host_lib" "float_pow" (func $float_pow (param i32 i32 i32 i32 i32 i32) (result i32)))"#; } /// One page of linear memory, exported under the name the engine looks for. diff --git a/include/xrpl/tx/wasm/HostContext.h b/include/xrpl/tx/wasm/HostContext.h index 7ba70e0b6e..e6150391eb 100644 --- a/include/xrpl/tx/wasm/HostContext.h +++ b/include/xrpl/tx/wasm/HostContext.h @@ -54,9 +54,226 @@ public: [[nodiscard]] std::int32_t getLedgerSqn(rust::Slice out) const noexcept; + [[nodiscard]] std::int32_t + getParentLedgerTime(rust::Slice out) const noexcept; + + [[nodiscard]] std::int32_t + getParentLedgerHash(rust::Slice out) const noexcept; + + [[nodiscard]] std::int32_t + getBaseFee(rust::Slice out) const noexcept; + + // The amendment is either a 32-byte id or a name; a 32-byte input is tried as an + // id first and falls back to a name lookup. Answers 1 or 0, or a negative + // `HostFunctionError` code. + [[nodiscard]] std::int32_t + isAmendmentEnabled(rust::Slice amendment) const noexcept; + + // The object id must be a 32-byte uint256, else `InvalidParams`. `cacheIdx` selects + // the slot (0 = pick a free one). Answers the slot used, or a negative + // `HostFunctionError` code. + [[nodiscard]] std::int32_t + cacheLedgerObj(rust::Slice objId, std::int32_t cacheIdx) const noexcept; + + [[nodiscard]] std::int32_t + getTxField(std::int32_t field, rust::Slice out) const noexcept; + [[nodiscard]] std::int32_t getCurrentLedgerObjField(std::int32_t field, rust::Slice out) const noexcept; + [[nodiscard]] std::int32_t + getLedgerObjField(std::int32_t cacheIdx, std::int32_t field, rust::Slice out) + const noexcept; + + // The locator is a path of little-endian i32 steps, so its byte length must be a + // non-zero multiple of 4, else `LocatorMalformed`. + [[nodiscard]] std::int32_t + getTxNestedField(rust::Slice locator, rust::Slice out) + const noexcept; + + [[nodiscard]] std::int32_t + getCurrentLedgerObjNestedField( + rust::Slice locator, + rust::Slice out) const noexcept; + + [[nodiscard]] std::int32_t + getLedgerObjNestedField( + std::int32_t cacheIdx, + rust::Slice locator, + rust::Slice out) const noexcept; + + // Answers the array's element count directly, or a negative `HostFunctionError` + // code (`NoArray` if the field is not an array). + [[nodiscard]] std::int32_t + getTxArrayLen(std::int32_t field) const noexcept; + + [[nodiscard]] std::int32_t + getCurrentLedgerObjArrayLen(std::int32_t field) const noexcept; + + [[nodiscard]] std::int32_t + getLedgerObjArrayLen(std::int32_t cacheIdx, std::int32_t field) const noexcept; + + [[nodiscard]] std::int32_t + getTxNestedArrayLen(rust::Slice locator) const noexcept; + + [[nodiscard]] std::int32_t + getCurrentLedgerObjNestedArrayLen(rust::Slice locator) const noexcept; + + [[nodiscard]] std::int32_t + getLedgerObjNestedArrayLen(std::int32_t cacheIdx, rust::Slice locator) + const noexcept; + + // Answers 1/0 for a valid/invalid signature, or a negative `HostFunctionError`. + [[nodiscard]] std::int32_t + checkSignature( + rust::Slice message, + rust::Slice signature, + rust::Slice pubkey) const noexcept; + + // The account id must be 20 bytes, else `InvalidParams`. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + accountKeylet(rust::Slice account, rust::Slice out) + const noexcept; + + // Each asset is decoded by length (24 = MPT, 20 = XRP, 40 = issue), else + // `InvalidParams`. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + ammKeylet( + rust::Slice asset1, + rust::Slice asset2, + rust::Slice out) const noexcept; + + // The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's + // u32 as its i32 bit pattern. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + checkKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept; + + // Subject and issuer must each be 20 bytes, else `InvalidParams`. Writes the + // 32-byte keylet. + [[nodiscard]] std::int32_t + credentialKeylet( + rust::Slice subject, + rust::Slice issuer, + rust::Slice credentialType, + rust::Slice out) const noexcept; + + // Both accounts must be 20 bytes, else `InvalidParams`. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + delegateKeylet( + rust::Slice account, + rust::Slice authorize, + rust::Slice out) const noexcept; + + // Both accounts must be 20 bytes, else `InvalidParams`. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + depositPreauthKeylet( + rust::Slice account, + rust::Slice authorize, + rust::Slice out) const noexcept; + + // The account id must be 20 bytes, else `InvalidParams`. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + didKeylet(rust::Slice account, rust::Slice out) + const noexcept; + + // The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's + // u32 as its i32 bit pattern. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + escrowKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept; + + // Both accounts and the currency must each be 20 bytes, else `InvalidParams`. + // Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + trustLineKeylet( + rust::Slice account1, + rust::Slice account2, + rust::Slice currency, + rust::Slice out) const noexcept; + + // The issuer id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's + // u32 as its i32 bit pattern. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + mptokenIssuanceKeylet( + rust::Slice issuer, + std::int32_t seq, + rust::Slice out) const noexcept; + + // The MPT id must be 24 bytes and the holder 20, else `InvalidParams`. Writes the + // 32-byte keylet. + [[nodiscard]] std::int32_t + mptokenKeylet( + rust::Slice mptid, + rust::Slice holder, + rust::Slice out) const noexcept; + + // The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's + // u32 as its i32 bit pattern. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + nftokenOfferKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept; + + // The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's + // u32 as its i32 bit pattern. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + offerKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept; + + // The account id must be 20 bytes, else `InvalidParams`. `docId` carries the + // guest's u32 as its i32 bit pattern. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + oracleKeylet( + rust::Slice account, + std::int32_t docId, + rust::Slice out) const noexcept; + + // Both account ids must be 20 bytes, else `InvalidParams`. `seq` carries the + // guest's u32 as its i32 bit pattern. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + paychannelKeylet( + rust::Slice account, + rust::Slice destination, + std::int32_t seq, + rust::Slice out) const noexcept; + + // The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's + // u32 as its i32 bit pattern. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + permissionedDomainKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept; + + // The account id must be 20 bytes, else `InvalidParams`. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + signerListKeylet(rust::Slice account, rust::Slice out) + const noexcept; + + // The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's + // u32 as its i32 bit pattern. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + ticketKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept; + + // The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's + // u32 as its i32 bit pattern. Writes the 32-byte keylet. + [[nodiscard]] std::int32_t + vaultKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept; + [[nodiscard]] std::int32_t sha512Half(rust::Slice data, rust::Slice out) const noexcept; @@ -69,6 +286,142 @@ public: void trace(rust::Str msg, rust::Slice data, TraceDataType dataType) const noexcept; + + // Stores `data` as the current object's data field and returns the number of bytes + // stored, or a negative `HostFunctionError` code. + [[nodiscard]] std::int32_t + updateData(rust::Slice data) const noexcept; + + // The account id must be 20 bytes and the nft id 32 bytes, else `InvalidParams`. + // Writes the token's URI bytes. + [[nodiscard]] std::int32_t + getNFT( + rust::Slice account, + rust::Slice nftId, + rust::Slice out) const noexcept; + + // The nft id must be 32 bytes, else `InvalidParams`. Writes the 20-byte issuer + // account encoded in the id. + [[nodiscard]] std::int32_t + getNFTIssuer(rust::Slice nftId, rust::Slice out) + const noexcept; + + // The nft id must be 32 bytes, else `InvalidParams`. Writes the taxon as its four + // little-endian bytes. + [[nodiscard]] std::int32_t + getNFTTaxon(rust::Slice nftId, rust::Slice out) + const noexcept; + + // The nft id must be 32 bytes, else `InvalidParams`. Returns the flags, or a + // negative `HostFunctionError` code. + [[nodiscard]] std::int32_t + getNFTFlags(rust::Slice nftId) const noexcept; + + // The nft id must be 32 bytes, else `InvalidParams`. Returns the transfer fee, or a + // negative `HostFunctionError` code. + [[nodiscard]] std::int32_t + getNFTTransferFee(rust::Slice nftId) const noexcept; + + // The nft id must be 32 bytes, else `InvalidParams`. Writes the sequence number as + // its four little-endian bytes. + [[nodiscard]] std::int32_t + getNFTSequence(rust::Slice nftId, rust::Slice out) + const noexcept; + + // Float / number arithmetic. A float is an XRPL `Number` in serialized form; + // `mode` is a rounding mode. Each writes the result float bytes unless noted. + + [[nodiscard]] std::int32_t + floatFromInt(std::int64_t x, std::int32_t mode, rust::Slice out) const noexcept; + + // The integer region must be eight bytes, else `InvalidParams`. + [[nodiscard]] std::int32_t + floatFromUint( + rust::Slice x, + std::int32_t mode, + rust::Slice out) const noexcept; + + // `amount` must be a serialized `STAmount`, else `InvalidParams`. + [[nodiscard]] std::int32_t + floatFromSTAmount( + rust::Slice amount, + std::int32_t mode, + rust::Slice out) const noexcept; + + // `number` must be a serialized `STNumber`, else `InvalidParams`. + [[nodiscard]] std::int32_t + floatFromSTNumber( + rust::Slice number, + std::int32_t mode, + rust::Slice out) const noexcept; + + // Rounds the float to an integer, written as its eight little-endian bytes. + [[nodiscard]] std::int32_t + floatToInt(rust::Slice x, std::int32_t mode, rust::Slice out) + const noexcept; + + // Writes the mantissa (eight little-endian bytes) and the exponent (four little- + // endian bytes) to two output regions; returns their total size. + [[nodiscard]] std::int32_t + floatToMantExp( + rust::Slice x, + rust::Slice mantissaOut, + rust::Slice exponentOut) const noexcept; + + [[nodiscard]] std::int32_t + floatFromMantExp( + std::int64_t mantissa, + std::int32_t exponent, + std::int32_t mode, + rust::Slice out) const noexcept; + + // Returns a negative, zero, or positive scalar as `x` is less than, equal to, or + // greater than `y`, or a negative `HostFunctionError` code on failure. + [[nodiscard]] std::int32_t + floatCompare(rust::Slice x, rust::Slice y) + const noexcept; + + [[nodiscard]] std::int32_t + floatAdd( + rust::Slice x, + rust::Slice y, + std::int32_t mode, + rust::Slice out) const noexcept; + + [[nodiscard]] std::int32_t + floatSubtract( + rust::Slice x, + rust::Slice y, + std::int32_t mode, + rust::Slice out) const noexcept; + + [[nodiscard]] std::int32_t + floatMultiply( + rust::Slice x, + rust::Slice y, + std::int32_t mode, + rust::Slice out) const noexcept; + + [[nodiscard]] std::int32_t + floatDivide( + rust::Slice x, + rust::Slice y, + std::int32_t mode, + rust::Slice out) const noexcept; + + [[nodiscard]] std::int32_t + floatRoot( + rust::Slice x, + std::int32_t n, + std::int32_t mode, + rust::Slice out) const noexcept; + + [[nodiscard]] std::int32_t + floatPower( + rust::Slice x, + std::int32_t n, + std::int32_t mode, + rust::Slice out) const noexcept; }; } // namespace xrpl diff --git a/src/libxrpl/tx/wasm/HostContext.cpp b/src/libxrpl/tx/wasm/HostContext.cpp index 5bd96984c2..23c4ee63cc 100644 --- a/src/libxrpl/tx/wasm/HostContext.cpp +++ b/src/libxrpl/tx/wasm/HostContext.cpp @@ -5,10 +5,14 @@ #include #include #include +#include +#include #include #include #include +#include #include +#include #include #include @@ -20,10 +24,13 @@ #include #include #include +#include #include #include #include #include +#include +#include namespace xrpl { @@ -40,7 +47,9 @@ std::int32_t answer(rust::Slice out, std::uint8_t const* value, std::size_t size) { if (size <= out.size()) + { std::memcpy(out.data(), value, size); + } return static_cast(size); } @@ -59,6 +68,283 @@ answerScalar(rust::Slice out, T value) return answer(out, reinterpret_cast(&wire), sizeof(wire)); } +// Decode an asset from its wire bytes, whose length selects the kind: an MPT id, a +// bare currency (which must be XRP), or a currency followed by an issuer (which must +// not be XRP). Any other length is malformed. This mirrors `getDataAsset` in the +// C-ABI wrapper the wasm engine replaces. +std::expected +parseAsset(rust::Slice bytes) +{ + if (bytes.size() == MPTID::size()) + { + return Asset{MPTID::fromVoid(bytes.data())}; + } + + if (bytes.size() == Currency::size()) + { + auto const issue = Issue{Currency::fromVoid(bytes.data()), xrpAccount()}; + if (!issue.native()) + { + return std::unexpected(HostFunctionError::InvalidParams); + } + return Asset{issue}; + } + + if (bytes.size() == Currency::size() + AccountID::size()) + { + auto const issue = Issue{ + Currency::fromVoid(bytes.data()), AccountID::fromVoid(bytes.data() + Currency::size())}; + if (issue.native()) + { + return std::unexpected(HostFunctionError::InvalidParams); + } + return Asset{issue}; + } + + return std::unexpected(HostFunctionError::InvalidParams); +} + +// Decode a `uint64` from its eight wire bytes, in the wire's byte order. The region +// must be exactly eight bytes, mirroring `getDataUnsigned` in the C-ABI wrapper. +std::expected +parseUint64(rust::Slice bytes) +{ + if (bytes.size() != sizeof(std::uint64_t)) + { + return std::unexpected(HostFunctionError::InvalidParams); + } + + auto x = std::uint64_t{}; + std::memcpy(&x, bytes.data(), sizeof(x)); + return adjustWasmEndianess(x); +} + +// Deserialize an `ST` object from its wire bytes; `InvalidParams` if the bytes are not +// a well-formed one. Mirrors the try/catch around `SerialIter` in the C-ABI wrapper. +template +std::expected +parseST(rust::Slice bytes) +{ + try + { + auto sit = SerialIter{Slice{bytes.data(), bytes.size()}}; + return T{sit, sfGeneric}; + } + catch (std::exception const&) + { + return std::unexpected(HostFunctionError::InvalidParams); + } +} + +template +std::int32_t +invokeWithLocator( + rust::Slice locator, + rust::Slice out, + Functor&& functor) +{ + if (locator.empty() || (locator.size() & 3) != 0) + { + return hfErrorToInt(HostFunctionError::LocatorMalformed); + } + + std::uint32_t const steps = locator.size() / sizeof(std::int32_t); + auto locBuf = std::vector(steps); + std::memcpy(locBuf.data(), locator.data(), locator.size()); + auto const fl = FieldLocator{std::move(locBuf)}; + + auto const value = functor(fl); + if (!value) + { + return hfErrorToInt(value.error()); + } + + return answer(out, value->data(), value->size()); +} + +template +std::int32_t +invokeWithLocator(rust::Slice locator, Functor&& functor) +{ + if (locator.empty() || (locator.size() & 3) != 0) + { + return hfErrorToInt(HostFunctionError::LocatorMalformed); + } + + std::uint32_t const steps = locator.size() / sizeof(std::int32_t); + auto locBuf = std::vector(steps); + std::memcpy(locBuf.data(), locator.data(), locator.size()); + auto const fl = FieldLocator{std::move(locBuf)}; + + auto const value = functor(fl); + if (!value) + { + return hfErrorToInt(value.error()); + } + + return *value; +} + +template +std::int32_t +invokeWithField(std::int32_t field, rust::Slice out, Functor&& functor) +{ + auto const& knownSFields = SField::getKnownCodeToField(); + auto const it = knownSFields.find(field); + if (it == std::end(knownSFields)) + { + return hfErrorToInt(HostFunctionError::InvalidField); + } + + auto const value = functor(*it->second); + if (!value) + { + return hfErrorToInt(value.error()); + } + + return answer(out, value->data(), value->size()); +} + +template +std::int32_t +invokeWithField(std::int32_t field, Functor&& functor) +{ + auto const& knownSFields = SField::getKnownCodeToField(); + auto const it = knownSFields.find(field); + if (it == std::end(knownSFields)) + { + return hfErrorToInt(HostFunctionError::InvalidField); + } + + auto const len = functor(*it->second); + if (!len) + { + return hfErrorToInt(len.error()); + } + + return *len; +} + +template +std::int32_t +invokeWithAccount( + rust::Slice account, + rust::Slice out, + Functor&& functor) +{ + if (account.size() != AccountID::size()) + { + return hfErrorToInt(HostFunctionError::InvalidParams); + } + + auto const value = functor(AccountID::fromVoid(account.data())); + if (!value) + { + return hfErrorToInt(value.error()); + } + + return answer(out, value->data(), value->size()); +} + +template +std::int32_t +invokeWithAccounts( + rust::Slice account1, + rust::Slice account2, + rust::Slice out, + Functor&& functor) +{ + if (account1.size() != AccountID::size() || account2.size() != AccountID::size()) + { + return hfErrorToInt(HostFunctionError::InvalidParams); + } + + auto const value = + functor(AccountID::fromVoid(account1.data()), AccountID::fromVoid(account2.data())); + if (!value) + { + return hfErrorToInt(value.error()); + } + + return answer(out, value->data(), value->size()); +} + +template +std::int32_t +invokeNFT(rust::Slice nftId, rust::Slice out, Functor&& functor) +{ + if (nftId.size() != uint256::size()) + { + return hfErrorToInt(HostFunctionError::InvalidParams); + } + + auto const value = functor(uint256::fromVoid(nftId.data())); + if (!value) + { + return hfErrorToInt(value.error()); + } + + if constexpr (Scalar) + { + return answerScalar(out, *value); + } + else + { + return answer(out, value->data(), value->size()); + } +} + +template +std::int32_t +invokeNFT(rust::Slice nftId, Functor&& functor) +{ + if (nftId.size() != uint256::size()) + { + return hfErrorToInt(HostFunctionError::InvalidParams); + } + + auto const value = functor(uint256::fromVoid(nftId.data())); + if (!value) + { + return hfErrorToInt(value.error()); + } + + return *value; +} + +template +std::int32_t +invoke(rust::Slice out, Functor&& functor) +{ + auto const value = functor(); + if (!value) + { + return hfErrorToInt(value.error()); + } + + if constexpr (Scalar) + { + return answerScalar(out, *value); + } + else + { + return answer(out, value->data(), value->size()); + } +} + +template +std::int32_t +invoke(Functor&& functor) +{ + auto const value = functor(); + if (!value) + { + return hfErrorToInt(value.error()); + } + + return *value; +} + // A traced integer, which the guest sends as bytes rather than as a wasm scalar so that one // import serves every type. `std::nullopt` if the buffer is not the width the type needs. // @@ -129,7 +415,7 @@ traceFormat(TraceDataType type, Slice const& data) } // namespace -HostContext::HostContext(HostFunctions& hostFunctions) : hostFunctions_(hostFunctions) +HostContext::HostContext(HostFunctions& hostFunctions) : hostFunctions_{hostFunctions} { } @@ -137,11 +423,85 @@ std::int32_t HostContext::getLedgerSqn(rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { - auto const sqn = hostFunctions_.getLedgerSqn(); - if (!sqn) - return hfErrorToInt(sqn.error()); + return invoke(out, [&] { return hostFunctions_.getLedgerSqn(); }); + }); +} - return answerScalar(out, *sqn); +std::int32_t +HostContext::getParentLedgerTime(rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke(out, [&] { return hostFunctions_.getParentLedgerTime(); }); + }); +} + +std::int32_t +HostContext::getParentLedgerHash(rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke(out, [&] { return hostFunctions_.getParentLedgerHash(); }); + }); +} + +std::int32_t +HostContext::getBaseFee(rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke(out, [&] { return hostFunctions_.getBaseFee(); }); + }); +} + +std::int32_t +HostContext::isAmendmentEnabled(rust::Slice amendment) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + // A 32-byte input may be an amendment id; try that first and fall through to + // a name lookup if it is not an enabled amendment - the 32 bytes could spell + // a name instead. + if (amendment.size() == uint256::size()) + { + auto const enabled = + hostFunctions_.isAmendmentEnabled(uint256::fromVoid(amendment.data())); + if (enabled && *enabled == 1) + { + return *enabled; + } + } + + static constexpr auto kMaxAmendmentSize = 64UZ; + if (amendment.size() > kMaxAmendmentSize) + { + return hfErrorToInt(HostFunctionError::DataFieldTooLarge); + } + + auto const name = + std::string_view{reinterpret_cast(amendment.data()), amendment.size()}; + return invoke([&] { return hostFunctions_.isAmendmentEnabled(name); }); + }); +} + +std::int32_t +HostContext::cacheLedgerObj(rust::Slice objId, std::int32_t cacheIdx) + const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + if (objId.size() != uint256::size()) + { + return hfErrorToInt(HostFunctionError::InvalidParams); + } + return invoke([&] { + return hostFunctions_.cacheLedgerObj(uint256::fromVoid(objId.data()), cacheIdx); + }); + }); +} + +std::int32_t +HostContext::getTxField(std::int32_t field, rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithField(field, out, [&](auto const& innerField) { + return hostFunctions_.getTxField(innerField); + }); }); } @@ -150,16 +510,411 @@ HostContext::getCurrentLedgerObjField(std::int32_t field, rust::Slicesecond); - if (!value) - return hfErrorToInt(value.error()); +std::int32_t +HostContext::getLedgerObjField( + std::int32_t cacheIdx, + std::int32_t field, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithField(field, out, [&](auto const& innerField) { + return hostFunctions_.getLedgerObjField(cacheIdx, innerField); + }); + }); +} - return answer(out, value->data(), value->size()); +std::int32_t +HostContext::getTxNestedField( + rust::Slice locator, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithLocator(locator, out, [&](FieldLocator const& fl) { + return hostFunctions_.getTxNestedField(fl); + }); + }); +} + +std::int32_t +HostContext::getCurrentLedgerObjNestedField( + rust::Slice locator, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithLocator(locator, out, [&](FieldLocator const& fl) { + return hostFunctions_.getCurrentLedgerObjNestedField(fl); + }); + }); +} + +std::int32_t +HostContext::getLedgerObjNestedField( + std::int32_t cacheIdx, + rust::Slice locator, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithLocator(locator, out, [&](FieldLocator const& fl) { + return hostFunctions_.getLedgerObjNestedField(cacheIdx, fl); + }); + }); +} + +std::int32_t +HostContext::getTxArrayLen(std::int32_t field) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithField(field, [&](auto const& innerField) { + return hostFunctions_.getTxArrayLen(innerField); + }); + }); +} + +std::int32_t +HostContext::getCurrentLedgerObjArrayLen(std::int32_t field) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithField(field, [&](auto const& innerField) { + return hostFunctions_.getCurrentLedgerObjArrayLen(innerField); + }); + }); +} + +std::int32_t +HostContext::getLedgerObjArrayLen(std::int32_t cacheIdx, std::int32_t field) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithField(field, [&](auto const& innerField) { + return hostFunctions_.getLedgerObjArrayLen(cacheIdx, innerField); + }); + }); +} + +std::int32_t +HostContext::getTxNestedArrayLen(rust::Slice locator) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithLocator(locator, [&](FieldLocator const& fl) { + return hostFunctions_.getTxNestedArrayLen(fl); + }); + }); +} + +std::int32_t +HostContext::getCurrentLedgerObjNestedArrayLen( + rust::Slice locator) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithLocator(locator, [&](FieldLocator const& fl) { + return hostFunctions_.getCurrentLedgerObjNestedArrayLen(fl); + }); + }); +} + +std::int32_t +HostContext::getLedgerObjNestedArrayLen( + std::int32_t cacheIdx, + rust::Slice locator) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithLocator(locator, [&](FieldLocator const& fl) { + return hostFunctions_.getLedgerObjNestedArrayLen(cacheIdx, fl); + }); + }); +} + +std::int32_t +HostContext::checkSignature( + rust::Slice message, + rust::Slice signature, + rust::Slice pubkey) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke([&] { + return hostFunctions_.checkSignature( + Slice{message.data(), message.size()}, + Slice{signature.data(), signature.size()}, + Slice{pubkey.data(), pubkey.size()}); + }); + }); +} + +std::int32_t +HostContext::accountKeylet(rust::Slice account, rust::Slice out) + const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccount(account, out, [&](auto const& accountId) { + return hostFunctions_.accountKeylet(accountId); + }); + }); +} + +std::int32_t +HostContext::ammKeylet( + rust::Slice asset1, + rust::Slice asset2, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + auto const a1 = parseAsset(asset1); + if (!a1) + { + return hfErrorToInt(a1.error()); + } + + auto const a2 = parseAsset(asset2); + if (!a2) + { + return hfErrorToInt(a2.error()); + } + return invoke(out, [&] { return hostFunctions_.ammKeylet(*a1, *a2); }); + }); +} + +std::int32_t +HostContext::checkKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccount(account, out, [&](auto const& accountId) { + return hostFunctions_.checkKeylet(accountId, static_cast(seq)); + }); + }); +} + +std::int32_t +HostContext::credentialKeylet( + rust::Slice subject, + rust::Slice issuer, + rust::Slice credentialType, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccounts( + subject, issuer, out, [&](auto const& account1, auto const& account2) { + return hostFunctions_.credentialKeylet( + account1, account2, Slice{credentialType.data(), credentialType.size()}); + }); + }); +} + +std::int32_t +HostContext::delegateKeylet( + rust::Slice account, + rust::Slice authorize, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccounts( + account, authorize, out, [&](auto const& account1, auto const& account2) { + return hostFunctions_.delegateKeylet(account1, account2); + }); + }); +} + +std::int32_t +HostContext::depositPreauthKeylet( + rust::Slice account, + rust::Slice authorize, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccounts( + account, authorize, out, [&](auto const& account1, auto const& account2) { + return hostFunctions_.depositPreauthKeylet(account1, account2); + }); + }); +} + +std::int32_t +HostContext::didKeylet(rust::Slice account, rust::Slice out) + const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccount(account, out, [&](auto const& accountId) { + return hostFunctions_.didKeylet(accountId); + }); + }); +} + +std::int32_t +HostContext::escrowKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccount(account, out, [&](auto const& accountId) { + return hostFunctions_.escrowKeylet(accountId, static_cast(seq)); + }); + }); +} + +std::int32_t +HostContext::trustLineKeylet( + rust::Slice account1, + rust::Slice account2, + rust::Slice currency, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + if (currency.size() != Currency::size()) + { + return hfErrorToInt(HostFunctionError::InvalidParams); + } + + return invokeWithAccounts( + account1, account2, out, [&](auto const& innerAccount1, auto const& innerAccount2) { + return hostFunctions_.trustLineKeylet( + innerAccount1, innerAccount2, Currency::fromVoid(currency.data())); + }); + }); +} + +std::int32_t +HostContext::mptokenIssuanceKeylet( + rust::Slice issuer, + std::int32_t seq, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccount(issuer, out, [&](auto const& accountId) { + return hostFunctions_.mptokenIssuanceKeylet(accountId, static_cast(seq)); + }); + }); +} + +std::int32_t +HostContext::mptokenKeylet( + rust::Slice mptid, + rust::Slice holder, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + if (mptid.size() != MPTID::size() || holder.size() != AccountID::size()) + { + return hfErrorToInt(HostFunctionError::InvalidParams); + } + return invoke(out, [&] { + return hostFunctions_.mptokenKeylet( + MPTID::fromVoid(mptid.data()), AccountID::fromVoid(holder.data())); + }); + }); +} + +std::int32_t +HostContext::nftokenOfferKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccount(account, out, [&](auto const& accountId) { + return hostFunctions_.nftokenOfferKeylet(accountId, static_cast(seq)); + }); + }); +} + +std::int32_t +HostContext::offerKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccount(account, out, [&](auto const& accountId) { + return hostFunctions_.offerKeylet(accountId, static_cast(seq)); + }); + }); +} + +std::int32_t +HostContext::oracleKeylet( + rust::Slice account, + std::int32_t docId, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccount(account, out, [&](auto const& accountId) { + return hostFunctions_.oracleKeylet(accountId, static_cast(docId)); + }); + }); +} + +std::int32_t +HostContext::paychannelKeylet( + rust::Slice account, + rust::Slice destination, + std::int32_t seq, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccounts( + account, destination, out, [&](auto const& account1, auto const& account2) { + return hostFunctions_.paychannelKeylet( + account1, account2, static_cast(seq)); + }); + }); +} + +std::int32_t +HostContext::permissionedDomainKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccount(account, out, [&](auto const& accountId) { + return hostFunctions_.permissionedDomainKeylet( + accountId, static_cast(seq)); + }); + }); +} + +std::int32_t +HostContext::signerListKeylet( + rust::Slice account, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccount(account, out, [&](auto const& accountId) { + return hostFunctions_.signerListKeylet(accountId); + }); + }); +} + +std::int32_t +HostContext::ticketKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccount(account, out, [&](auto const& accountId) { + return hostFunctions_.ticketKeylet(accountId, static_cast(seq)); + }); + }); +} + +std::int32_t +HostContext::vaultKeylet( + rust::Slice account, + std::int32_t seq, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeWithAccount(account, out, [&](auto const& accountId) { + return hostFunctions_.vaultKeylet(accountId, static_cast(seq)); + }); }); } @@ -168,11 +923,9 @@ HostContext::sha512Half(rust::Slice data, rust::Slicedata(), digest->size()); + return invoke(out, [&] { + return hostFunctions_.computeSha512HalfHash(Slice{data.data(), data.size()}); + }); }); } @@ -215,4 +968,277 @@ HostContext::trace(rust::Str msg, rust::Slice data, TraceDat } } +std::int32_t +HostContext::updateData(rust::Slice data) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke([&] { return hostFunctions_.updateData(Slice{data.data(), data.size()}); }); + }); +} + +std::int32_t +HostContext::getNFT( + rust::Slice account, + rust::Slice nftId, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + if (account.size() != AccountID::size()) + { + return hfErrorToInt(HostFunctionError::InvalidParams); + } + return invokeNFT(nftId, out, [&](auto const& nft) { + return hostFunctions_.getNFT(AccountID::fromVoid(account.data()), nft); + }); + }); +} + +std::int32_t +HostContext::getNFTIssuer(rust::Slice nftId, rust::Slice out) + const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeNFT( + nftId, out, [&](auto const& nft) { return hostFunctions_.getNFTIssuer(nft); }); + }); +} + +std::int32_t +HostContext::getNFTTaxon(rust::Slice nftId, rust::Slice out) + const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeNFT( + nftId, out, [&](auto const& nft) { return hostFunctions_.getNFTTaxon(nft); }); + }); +} + +std::int32_t +HostContext::getNFTFlags(rust::Slice nftId) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeNFT(nftId, [&](auto const& nft) { return hostFunctions_.getNFTFlags(nft); }); + }); +} + +std::int32_t +HostContext::getNFTTransferFee(rust::Slice nftId) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeNFT( + nftId, [&](auto const& nft) { return hostFunctions_.getNFTTransferFee(nft); }); + }); +} + +std::int32_t +HostContext::getNFTSequence(rust::Slice nftId, rust::Slice out) + const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invokeNFT( + nftId, out, [&](auto const& nft) { return hostFunctions_.getNFTSequence(nft); }); + }); +} + +std::int32_t +HostContext::floatFromInt(std::int64_t x, std::int32_t mode, rust::Slice out) + const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke(out, [&] { return hostFunctions_.floatFromInt(x, mode); }); + }); +} + +std::int32_t +HostContext::floatFromUint( + rust::Slice x, + std::int32_t mode, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + auto const parsed = parseUint64(x); + if (!parsed) + { + return hfErrorToInt(parsed.error()); + } + return invoke(out, [&] { return hostFunctions_.floatFromUint(*parsed, mode); }); + }); +} + +std::int32_t +HostContext::floatFromSTAmount( + rust::Slice amount, + std::int32_t mode, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + auto const parsed = parseST(amount); + if (!parsed) + { + return hfErrorToInt(parsed.error()); + } + return invoke(out, [&] { return hostFunctions_.floatFromSTAmount(*parsed, mode); }); + }); +} + +std::int32_t +HostContext::floatFromSTNumber( + rust::Slice number, + std::int32_t mode, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + auto const parsed = parseST(number); + if (!parsed) + { + return hfErrorToInt(parsed.error()); + } + return invoke(out, [&] { return hostFunctions_.floatFromSTNumber(*parsed, mode); }); + }); +} + +std::int32_t +HostContext::floatToInt( + rust::Slice x, + std::int32_t mode, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke( + out, [&] { return hostFunctions_.floatToInt(Slice{x.data(), x.size()}, mode); }); + }); +} + +std::int32_t +HostContext::floatToMantExp( + rust::Slice x, + rust::Slice mantissaOut, + rust::Slice exponentOut) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + auto const value = hostFunctions_.floatToMantExp(Slice{x.data(), x.size()}); + if (!value) + { + return hfErrorToInt(value.error()); + } + + // The engine copies each region only if the whole value fits, so writing the + // true lengths here and summing them matches its accounting. + auto const r1 = answerScalar(mantissaOut, value->first); + auto const r2 = answerScalar(exponentOut, value->second); + return r1 + r2; + }); +} + +std::int32_t +HostContext::floatFromMantExp( + std::int64_t mantissa, + std::int32_t exponent, + std::int32_t mode, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke( + out, [&] { return hostFunctions_.floatFromMantExp(mantissa, exponent, mode); }); + }); +} + +std::int32_t +HostContext::floatCompare(rust::Slice x, rust::Slice y) + const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke([&] { + return hostFunctions_.floatCompare( + Slice{x.data(), x.size()}, Slice{y.data(), y.size()}); + }); + }); +} + +std::int32_t +HostContext::floatAdd( + rust::Slice x, + rust::Slice y, + std::int32_t mode, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke(out, [&] { + return hostFunctions_.floatAdd( + Slice{x.data(), x.size()}, Slice{y.data(), y.size()}, mode); + }); + }); +} + +std::int32_t +HostContext::floatSubtract( + rust::Slice x, + rust::Slice y, + std::int32_t mode, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke(out, [&] { + return hostFunctions_.floatSubtract( + Slice{x.data(), x.size()}, Slice{y.data(), y.size()}, mode); + }); + }); +} + +std::int32_t +HostContext::floatMultiply( + rust::Slice x, + rust::Slice y, + std::int32_t mode, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke(out, [&] { + return hostFunctions_.floatMultiply( + Slice{x.data(), x.size()}, Slice{y.data(), y.size()}, mode); + }); + }); +} + +std::int32_t +HostContext::floatDivide( + rust::Slice x, + rust::Slice y, + std::int32_t mode, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke(out, [&] { + return hostFunctions_.floatDivide( + Slice{x.data(), x.size()}, Slice{y.data(), y.size()}, mode); + }); + }); +} + +std::int32_t +HostContext::floatRoot( + rust::Slice x, + std::int32_t n, + std::int32_t mode, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke( + out, [&] { return hostFunctions_.floatRoot(Slice{x.data(), x.size()}, n, mode); }); + }); +} + +std::int32_t +HostContext::floatPower( + rust::Slice x, + std::int32_t n, + std::int32_t mode, + rust::Slice out) const noexcept +{ + return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { + return invoke( + out, [&] { return hostFunctions_.floatPower(Slice{x.data(), x.size()}, n, mode); }); + }); +} + } // namespace xrpl diff --git a/src/tests/libxrpl/tx/wasm/host_calls/Trace.cpp b/src/tests/libxrpl/tx/wasm/host_calls/Trace.cpp index fcf28cb638..349785e1fa 100644 --- a/src/tests/libxrpl/tx/wasm/host_calls/Trace.cpp +++ b/src/tests/libxrpl/tx/wasm/host_calls/Trace.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include