Merge branch 'ripple/smart-escrow' into ripple/se/supported

This commit is contained in:
TimothyBanks
2026-09-08 15:55:32 -04:00
303 changed files with 4868 additions and 11972 deletions

View File

@@ -7,8 +7,6 @@ ignorePaths:
- cmake/**
- LICENSE.md
- .clang-tidy
- src/test/app/wasm_fixtures/**/*.wat
- src/test/app/wasm_fixtures/*.c
- nix/check-tools/*.txt # generated, and full of Nix store hashes
language: en
allowCompoundWords: true # TODO (#6334)

View File

@@ -1,6 +1,9 @@
benchmarks.libxrpl > xrpl.basics
benchmarks.libxrpl > xrpl.config
benchmarks.libxrpl > xrpl.nodestore
benchmarks.libxrpl > xrpl.protocol
benchmarks.libxrpl > xrpl.protocol_autogen
benchmarks.libxrpl > xrpl.tx
libxrpl.basics > xrpl.basics
libxrpl.conditions > xrpl.basics
libxrpl.conditions > xrpl.conditions

View File

@@ -29,6 +29,14 @@ function(xrpl_add_benchmark name)
# XrplCore.cmake. Each file compiles fine on its own.
set_target_properties(${target} PROPERTIES UNITY_BUILD OFF)
# Land next to `xrpl_tests` in the build root rather than buried under
# `src/benchmarks/libxrpl/`. A benchmark is something a person runs by hand,
# repeatedly, and comparing two of them should not mean typing two long paths.
set_target_properties(
${target}
PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
)
isolate_headers(
${target}
"${CMAKE_SOURCE_DIR}/src"

17
crates/Cargo.lock generated
View File

@@ -348,9 +348,9 @@ dependencies = [
[[package]]
name = "wasmi"
version = "2.0.0-beta.10"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab57cbb8db5ee46c6667b642544d7664adfbc0ea6a1ab219c92d734b795f36b1"
checksum = "78693fcdd618e0fc34af59c6b8efa9ac5d58c68df940beff4bedddb6acfe7c27"
dependencies = [
"spin",
"wasmi_collections",
@@ -361,27 +361,27 @@ dependencies = [
[[package]]
name = "wasmi_collections"
version = "2.0.0-beta.10"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ea3ee266456966465c55a1f440e33116caf2b05a4fc30da36cb0c9813059d5"
checksum = "8a8be2aa467cf2d29e96ff759472c36eeb44a3c81c67fc9cb76c9a24c519c557"
dependencies = [
"string-interner",
]
[[package]]
name = "wasmi_core"
version = "2.0.0-beta.10"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f8285efe48a9e1afbcdfcc19cd807b3eb20129b7e199c7a99efd30ba192926b"
checksum = "69372d5fda3ea3d1e0aa6603c7888110e0187e88ea17cd8fc2e2df0a0e1f37fa"
dependencies = [
"libm",
]
[[package]]
name = "wasmi_ir"
version = "2.0.0-beta.10"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6227be1aebba39b4815ab6a312d0528590f0db2473621ec9285606410889b0a6"
checksum = "8f17b774caa13c618c7244f1ee51fe23c5e7b8538a471fa46d9949779758aed6"
dependencies = [
"wasmi_core",
]
@@ -476,6 +476,7 @@ version = "0.1.0"
dependencies = [
"cxx",
"wat",
"xrpl-host-functions",
]
[[package]]

View File

@@ -9,3 +9,4 @@ crate-type = ["staticlib", "rlib"]
[dependencies]
cxx.workspace = true
wat = "1"
xrpl-host-functions = { path = "../xrpl-host-functions" }

View File

@@ -19,6 +19,18 @@ mod ffi {
/// Throws `rust::Error` on invalid input, which is what a test wants: a typo in a
/// fixture should fail the test that holds it, at the line that holds it.
fn compile_wat(wat: &str) -> Result<Vec<u8>>;
/// The gas a host function is charged before it runs, by its guest import name.
///
/// For the C++ gas benchmarks, which measure what a host call actually costs and
/// report it against what the table says it costs. Reading the declaration through
/// here rather than copying the numbers into C++ is the point: 61 transcribed
/// constants would drift from `lib.rs` the first time a price changed, and drift
/// silently, because a benchmark has nothing to fail.
///
/// Throws `rust::Error` on an unknown name — a typo should fail loudly rather than
/// quietly compare against zero.
fn host_function_gas(wasm_name: &str) -> Result<u64>;
}
}
@@ -26,9 +38,28 @@ fn compile_wat(wat: &str) -> Result<Vec<u8>, wat::Error> {
wat::parse_str(wat)
}
fn host_function_gas(wasm_name: &str) -> Result<u64, UnknownHostFunction> {
xrpl_host_functions::HostFunctionSpec::ALL
.iter()
.find(|op| op.wasm_name() == wasm_name)
.map(|op| op.gas())
.ok_or_else(|| UnknownHostFunction(wasm_name.to_owned()))
}
#[derive(Debug)]
struct UnknownHostFunction(String);
impl std::fmt::Display for UnknownHostFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "no host function is imported as `{}`", self.0)
}
}
impl std::error::Error for UnknownHostFunction {}
#[cfg(test)]
mod tests {
use super::compile_wat;
use super::*;
#[test]
fn a_module_assembles_to_something_beginning_with_the_wasm_magic() {
@@ -37,6 +68,33 @@ mod tests {
assert_eq!(&wasm[..4], b"\0asm");
}
#[test]
fn a_host_function_reports_the_gas_its_declaration_gives_it() {
// `trace` is the cheapest declaration in the table; the point is not the number but
// that the lookup reaches the same constant the engine charges from.
assert_eq!(
host_function_gas("trace").expect("trace is a host function"),
xrpl_host_functions::HostFunctionSpec::Trace.gas()
);
}
#[test]
fn every_host_function_is_reachable_by_its_import_name() {
for op in xrpl_host_functions::HostFunctionSpec::ALL {
assert_eq!(
host_function_gas(op.wasm_name()).expect("declared"),
op.gas(),
"{} must be reachable by name",
op.wasm_name()
);
}
}
#[test]
fn an_unknown_name_is_an_error_rather_than_zero_gas() {
host_function_gas("not_a_host_function").expect_err("must not resolve");
}
#[test]
fn a_typo_is_an_error_rather_than_a_module() {
let error = compile_wat("(module (func (export").expect_err("must not assemble");

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition.workspace = true
[dependencies]
wasmi = { version = "2.0.0-beta.10", default-features = false, features = ["std", "validate", "portable-dispatch"] }
wasmi = { version = "2.0.0", default-features = false, features = ["std", "validate", "portable-dispatch"] }
xrpl-host-functions = { path = "../xrpl-host-functions" }
[dev-dependencies]

View File

@@ -1247,3 +1247,142 @@ fn the_outcome_carries_whatever_the_guest_returned() {
assert_eq!(outcome.result, value);
}
}
/// The remaining binary operators marshal like `float_add`: both operands and the mode reach
/// the host, tagged by operator.
#[test]
fn float_sub_reads_both_operands_and_the_mode() {
let host = FakeHost::new().answering_float(support::Answer::filler(8));
let wat = module(
&[import::FLOAT_SUB, ONE_PAGE],
"(call $float_sub (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);
assert_eq!(
*host.float_binary_ops_asked.borrow(),
vec![("sub", vec![0u8; 8], vec![0u8; 8], 2)]
);
}
#[test]
fn float_mult_reads_both_operands_and_the_mode() {
let host = FakeHost::new().answering_float(support::Answer::filler(8));
let wat = module(
&[import::FLOAT_MULT, ONE_PAGE],
"(call $float_mult (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);
assert_eq!(
*host.float_binary_ops_asked.borrow(),
vec![("mult", vec![0u8; 8], vec![0u8; 8], 2)]
);
}
#[test]
fn float_div_reads_both_operands_and_the_mode() {
let host = FakeHost::new().answering_float(support::Answer::filler(8));
let wat = module(
&[import::FLOAT_DIV, ONE_PAGE],
"(call $float_div (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);
assert_eq!(
*host.float_binary_ops_asked.borrow(),
vec![("div", vec![0u8; 8], vec![0u8; 8], 2)]
);
}
#[test]
fn float_pow_reads_the_float_the_degree_and_the_mode() {
let host = FakeHost::new().answering_float(support::Answer::filler(8));
let wat = module(
&[import::FLOAT_POW, ONE_PAGE],
"(call $float_pow (i32.const 0) (i32.const 8) (i32.const 3) (i32.const 64) (i32.const 8) (i32.const 1))",
);
assert_eq!(status(&wat, &host), 8);
assert_eq!(
*host.float_unary_ops_asked.borrow(),
vec![("pow", vec![0u8; 8], 3, 1)]
);
}
/// The `ST*`-in operators read one serialized region and a mode, and write the float.
#[test]
fn float_from_stamount_reads_the_amount_and_the_mode() {
let host = FakeHost::new().answering_float(support::Answer::filler(8));
let wat = module(
&[import::FLOAT_FROM_STAMOUNT, ONE_PAGE],
"(call $float_from_stamount (i32.const 0) (i32.const 8) (i32.const 64) (i32.const 8) (i32.const 2))",
);
assert_eq!(status(&wat, &host), 8);
assert_eq!(
*host.float_from_stamount_asked.borrow(),
vec![(vec![0u8; 8], 2)]
);
}
#[test]
fn float_from_stnumber_reads_the_number_and_the_mode() {
let host = FakeHost::new().answering_float(support::Answer::filler(8));
let wat = module(
&[import::FLOAT_FROM_STNUMBER, ONE_PAGE],
"(call $float_from_stnumber (i32.const 0) (i32.const 8) (i32.const 64) (i32.const 8) (i32.const 2))",
);
assert_eq!(status(&wat, &host), 8);
assert_eq!(
*host.float_from_stnumber_asked.borrow(),
vec![(vec![0u8; 8], 2)]
);
}
/// `float_to_int` reads a float and a mode, writing the integer.
#[test]
fn float_to_int_reads_the_float_and_the_mode() {
let host = FakeHost::new().answering_float(support::Answer::filler(8));
let wat = module(
&[import::FLOAT_TO_INT, ONE_PAGE],
"(call $float_to_int (i32.const 0) (i32.const 8) (i32.const 64) (i32.const 8) (i32.const 2))",
);
assert_eq!(status(&wat, &host), 8);
assert_eq!(*host.float_to_int_asked.borrow(), vec![(vec![0u8; 8], 2)]);
}
/// `float_from_mant_exp` passes two scalars (an i64 mantissa and an i32 exponent) and a mode.
#[test]
fn float_from_mant_exp_passes_the_mantissa_exponent_and_mode() {
let host = FakeHost::new().answering_float(support::Answer::filler(8));
let wat = module(
&[import::FLOAT_FROM_MANT_EXP, ONE_PAGE],
"(call $float_from_mant_exp (i64.const 42) (i32.const 3) (i32.const 64) (i32.const 8) (i32.const 1))",
);
assert_eq!(status(&wat, &host), 8);
assert_eq!(*host.float_from_mant_exp_asked.borrow(), vec![(42, 3, 1)]);
}
/// A host input read from an unaligned pointer arrives intact: the host reads a byte slice, not
/// an aligned word, so a contract that hands it an odd offset is served the right bytes. (Ports
/// the old `bad_align` fixture, which exercised unaligned reads in the C-ABI wrapper.)
#[test]
fn an_unaligned_input_is_read_intact() {
let host = FakeHost::new().answering_float(support::Answer::filler(8));
let stores: String = (0..8u8)
.map(|i| {
format!(
"(i32.store8 (i32.const {}) (i32.const {}))",
i + 1,
0xa0 + i
)
})
.collect();
let wat = module(
&[import::FLOAT_FROM_STAMOUNT, ONE_PAGE],
&format!(
"{stores} (call $float_from_stamount (i32.const 1) (i32.const 8) (i32.const 64) (i32.const 8) (i32.const 0))"
),
);
assert_eq!(status(&wat, &host), 8);
assert_eq!(
*host.float_from_stamount_asked.borrow(),
vec![((0xa0u8..0xa8u8).collect::<Vec<u8>>(), 0)]
);
}

View File

@@ -593,3 +593,61 @@ fn a_memory64_memory_is_refused_by_screening() {
"{refusal}"
);
}
/// The corruption fixtures below are written as hex strings, which is how the old Beast suite
/// carried them — the bytes are deliberately malformed, so there is nothing to assemble them
/// from.
fn hex(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
/// Malformed modules crafted to abuse the parser rather than merely be invalid — a vector
/// length that lies about its size, a section that overruns its payload, a locals-count bomb,
/// and a non-terminating LEB128 — are refused at compile like any other garbage. These guard
/// the parser against resource-exhaustion shapes (ported from the old Beast section-corruption
/// fixtures); the plainer "bad magic / wrong version" shapes are covered by `garbage_does_not_pass`.
#[test]
fn parser_abuse_shapes_are_refused() {
let cases = [
("vector length lies", "0061736d010000000105ffffffff0f"),
("section overruns its payload", "0061736d01000000010a0160"),
(
"locals-count bomb",
"0061736d01000000010401600000030201000a0f010d01ffffffff0f7f0b",
),
(
"non-terminating LEB128",
"0061736d0100000001058080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080",
),
];
for (label, h) in cases {
let refusal = xrpl_wasm_vm::check(&hex(h), ENTRY).expect_err(label);
assert_stage!(refusal, CheckError::Compile(_));
}
}
/// The plain structurally-malformed modules from the old section-corruption fixtures — a
/// corrupt magic, a wrong version, a lying section length, sections out of order, junk after
/// the last section, an unknown section id — are all refused at compile. Belt-and-suspenders
/// alongside `garbage_does_not_pass`: guards against a wasmi upgrade loosening the validator.
#[test]
fn structurally_malformed_modules_are_refused() {
let cases = [
("corrupt magic number", "0161736d01000000"),
("wrong version", "0061736d02000000"),
("lying section length", "0061736d01000000018080808008"),
("sections out of order", "0061736d010000000a02000b03020000"),
(
"junk after last section",
"0061736d01000000010a01600000000000000000",
),
("unknown section id", "0061736d01000000ff0100"),
];
for (label, h) in cases {
let refusal = xrpl_wasm_vm::check(&hex(h), ENTRY).expect_err(label);
assert_stage!(refusal, CheckError::Compile(_));
}
}

View File

@@ -598,3 +598,114 @@ fn a_memory64_module_is_rejected_at_compile() {
"rejected before instantiation, so nothing is charged: {failure}"
);
}
/// A function declaring more parameters than wasm allows (1000) is refused at compile, so a
/// contract cannot smuggle an unbounded signature past screening.
#[test]
fn a_function_with_too_many_params_is_refused() {
let host = FakeHost::new();
let params = " i32".repeat(1001);
let wat = format!(
"(module {ONE_PAGE} (func (param{params}) (result i32) (i32.const 0)) \
(func (export \"finish\") (result i32) (i32.const 0)))"
);
assert_stage!(failure(&wat, &host), RunError::Compile(_));
}
/// A function declaring more locals than wasm allows (50 000) is refused at compile.
#[test]
fn a_function_with_too_many_locals_is_refused() {
let host = FakeHost::new();
let locals = format!("(local{})", " i32".repeat(50_001));
let wat = module(&[ONE_PAGE], &format!("{locals} (i32.const 0)"));
assert_stage!(failure(&wat, &host), RunError::Compile(_));
}
/// Below the compile cap but past the engine's register frame, a locals-heavy function is
/// refused when the frame is built rather than at compile — still refused, just later.
#[test]
fn a_function_past_the_register_frame_is_refused() {
let host = FakeHost::new();
let locals = format!("(local{})", " i32".repeat(40_000));
let wat = module(&[ONE_PAGE], &format!("{locals} (i32.const 0)"));
assert_stage!(failure(&wat, &host), RunError::Trap(_));
}
/// Unbounded recursion is stopped by the engine's call-stack limit — it traps rather than
/// running the host's native stack off the end (the portable dispatcher makes loops safe;
/// this pins that guest *calls* are bounded too).
#[test]
fn unbounded_recursion_is_stopped_by_the_call_stack_limit() {
let host = FakeHost::new();
let wat = format!(
"(module {ONE_PAGE} \
(func $rec (param i32) (result i32) \
(if (result i32) (i32.eqz (local.get 0)) (then (i32.const 0)) \
(else (call $rec (i32.sub (local.get 0) (i32.const 1)))))) \
(func (export \"finish\") (result i32) (call $rec (i32.const 1000000))))"
);
assert_stage!(failure(&wat, &host), RunError::Trap(_));
}
/// A module with many functions currently compiles and runs: wasmi's only cap is its
/// 1,000,000 hard limit, so the ticket's ~24k-function module — a CodeMap-growth DoS, since
/// every validation appends to the engine's append-only CodeMap — is not refused here.
/// Enforcing a tighter bound (a function-count / average-bytes-per-function limit) belongs in
/// a future preflight pass that parses the module before the engine sees it. Ignored until
/// then, so this documents the gap without asserting it is acceptable.
#[test]
#[ignore = "CodeMap-DoS unmitigated; a function-count limit is deferred to preflight parsing"]
fn many_functions_currently_run_unbounded() {
let host = FakeHost::new();
let funcs: String = (0..24_000)
.map(|i| format!("(func $f{i} (result i32) (i32.const {}))", i % 7))
.collect();
let wat =
format!("(module {ONE_PAGE} {funcs} (func (export \"finish\") (result i32) (call $f0)))");
assert!(
run(&wat, &host).is_ok(),
"a large-function module currently compiles and runs"
);
}
/// The trap *kinds* wasmi distinguishes all reach the caller identically — a guest trap
/// charged as the contract's fault — so the `unreachable` representative pins the mapping.
/// These pin the individual kinds too, guarding against a wasmi upgrade reclassifying any of
/// them as something other than a trap.
#[test]
fn a_division_by_zero_traps() {
let host = FakeHost::new();
let wat = module(&[ONE_PAGE], "(i32.div_s (i32.const 1) (i32.const 0))");
assert_stage!(failure(&wat, &host), RunError::Trap(_));
}
#[test]
fn a_signed_integer_overflow_traps() {
let host = FakeHost::new();
let wat = module(
&[ONE_PAGE],
"(i32.div_s (i32.const 0x80000000) (i32.const -1))",
);
assert_stage!(failure(&wat, &host), RunError::Trap(_));
}
#[test]
fn an_indirect_call_to_a_null_table_entry_traps() {
let host = FakeHost::new();
let wat = format!(
"(module {ONE_PAGE} (type $t (func (result i32))) (table 1 funcref) \
(func (export \"finish\") (result i32) (call_indirect (type $t) (i32.const 0))))"
);
assert_stage!(failure(&wat, &host), RunError::Trap(_));
}
#[test]
fn an_indirect_call_with_a_mismatched_signature_traps() {
let host = FakeHost::new();
let wat = format!(
"(module {ONE_PAGE} (type $void (func)) (type $i32 (func (result i32))) \
(table 1 funcref) (elem (i32.const 0) $f) (func $f (type $void)) \
(func (export \"finish\") (result i32) (call_indirect (type $i32) (i32.const 0))))"
);
assert_stage!(failure(&wat, &host), RunError::Trap(_));
}

View File

@@ -20,3 +20,20 @@ target_link_libraries(
xrpl_add_benchmark(nodestore)
target_link_libraries(xrpl.bench.nodestore PRIVATE xrpl.imports.bench)
add_dependencies(xrpl.benchmarks xrpl.bench.nodestore)
# Gas calibration for the wasm host functions. The ledger and real host come from
# `xrpl.testkit.wasm` (built with the tests, but framework-free), so this target links
# no GTest and no GMock.
if(TARGET xrpl.testkit.wasm)
xrpl_add_benchmark(wasm)
target_link_libraries(
xrpl.bench.wasm
PRIVATE xrpl.imports.bench xrpl.testkit.wasm
)
add_dependencies(xrpl.benchmarks xrpl.bench.wasm)
else()
message(
STATUS
"xrpl.testkit.wasm not built (tests disabled); skipping xrpl.bench.wasm."
)
endif()

View File

@@ -0,0 +1,175 @@
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol_autogen/transactions/EscrowCreate.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <helpers/Account.h>
#include <helpers/TxTest.h>
#include <tx/wasm/fixtures/FloatConstants.h>
#include <tx/wasm/fixtures/NftSetup.h>
#include <tx/wasm/fixtures/WasmLedger.h>
#include <string>
#include <string_view>
#include <utility>
namespace xrpl::test::bench {
Fixtures::Fixtures()
: alice_{ledger_.fund("benchAlice")}
, bob_{ledger_.fund("benchBob")}
, signerListOwner_{ledger_.fund("benchSigners")}
, escrow_{keylet::account(AccountID{})}
, signedMessage_{signMessage("the quick brown fox jumps over the lazy dog")}
, nftId_{NftIds::makeNftId(alice_.id())}
{
ledger_.makeSignerList(signerListOwner_, 2, {{alice_, 1}, {bob_, 1}});
// The escrow has to be submitted after the accounts exist, which is why it is built here
// rather than in the initializer list: its keylet depends on the owner's sequence number at
// submission time.
auto const ownerSeq = ledger_.ledger.getAccountRoot(alice_.id()).getSequence();
auto const created = ledger_.ledger.submit(
transactions::EscrowCreateBuilder{alice_.id(), bob_.id(), XRP(100)}.setFinishAfter(
900'000'000),
alice_);
if (created.ter != tesSUCCESS)
{
fixtureFailed(std::string{"creating the escrow: "} + transToken(created.ter));
}
ledger_.ledger.close();
escrow_ = keylet::escrow(alice_.id(), SeqProxy::rawSequence(ownerSeq));
}
Account const&
Fixtures::alice() const
{
return alice_;
}
Account const&
Fixtures::bob() const
{
return bob_;
}
TxAssembler
Fixtures::memoTx()
{
auto assembler = escrowFinishTx(ledger_.ledger, alice_);
assembler.build = [inner = std::move(assembler.build)](STObject& obj) {
inner(obj);
auto memos = STArray{};
memos.push_back(makeMemo(WasmLedger::toBytes("hello")));
memos.push_back(makeMemo(WasmLedger::toBytes("world")));
obj.setFieldArray(sfMemos, memos);
};
return assembler;
}
FieldLocator
Fixtures::memoLocator()
{
return FieldLocator{{sfMemos.getCode(), 0, sfMemoData.getCode()}};
}
WasmHost
Fixtures::host()
{
auto assembler = memoTx();
return ledger_.makeHost(
keylet::account(alice_.id()), assembler.type, std::move(assembler.build));
}
WasmHost
Fixtures::cachedHost()
{
auto wasmHost = host();
if (!wasmHost->cacheLedgerObj(keylet::account(alice_.id()).key, 1).has_value())
{
fixtureFailed("caching the account root into slot 1");
}
return wasmHost;
}
WasmHost
Fixtures::signerListHost()
{
auto assembler = bareTx();
return ledger_.makeHost(
keylet::signerList(signerListOwner_.id()), assembler.type, std::move(assembler.build));
}
WasmHost
Fixtures::cachedSignerListHost()
{
auto assembler = bareTx();
auto wasmHost =
ledger_.makeHost(keylet::account(AccountID{}), assembler.type, std::move(assembler.build));
if (!wasmHost->cacheLedgerObj(keylet::signerList(signerListOwner_.id()).key, 1).has_value())
{
fixtureFailed("caching the signer list into slot 1");
}
return wasmHost;
}
WasmHost
Fixtures::tracingHost()
{
return ledger_.makeTracingHost();
}
Keylet const&
Fixtures::escrow() const
{
return escrow_;
}
WasmHost
Fixtures::escrowHost()
{
return ledger_.makeHost(escrow_);
}
Slice
Fixtures::floatX()
{
return FloatConstants::slice(FloatConstants::kPi);
}
Slice
Fixtures::floatY()
{
return FloatConstants::slice(FloatConstants::kTwo);
}
SignedMessage const&
Fixtures::signedMessage() const
{
return signedMessage_;
}
uint256 const&
Fixtures::nftId() const
{
return nftId_;
}
Fixtures&
Fixtures::instance()
{
static Fixtures kValue;
return kValue;
}
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,111 @@
#pragma once
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <helpers/Account.h>
#include <tx/wasm/fixtures/WasmLedger.h>
#include <cstdint>
namespace xrpl::test::bench {
// The ledger and canned inputs every `*.bench.cpp` measures against.
class Fixtures
{
public:
// The one set of fixtures every benchmark shares, built on first use.
static Fixtures&
instance();
// A sequence number for the keylets that take one. Arbitrary — a keylet hashes whatever it
// is given, so the value cannot change the cost.
static constexpr std::uint32_t kSeq = 42;
// Rounding mode 0 throughout the float family: modes select a tie-breaking rule, not a
// different algorithm, so they do not move the cost, and pinning one keeps the fourteen
// comparable.
static constexpr std::int32_t kRoundingMode = 0;
// Two funded accounts, enough for every keylet shape and every object below.
[[nodiscard]] Account const&
alice() const;
[[nodiscard]] Account const&
bob() const;
// The default host: its transaction carries a two-element memo array (something for the
// nested getters to walk to and the array-length getters to count) and its current object is
// Alice's account root.
[[nodiscard]] WasmHost
host();
// The same, with Alice's account root pinned to slot 1, for the `le_*` getters that read
// through a cache slot rather than the current object.
[[nodiscard]] WasmHost
cachedHost();
// An account root has no arrays, so the array-length getters that read a *ledger object*
// need a different one. A signer list has `sfSignerEntries`; without it those calls would
// answer `FieldNotFound` and the benchmark would time the rejection instead of the work.
[[nodiscard]] WasmHost
signerListHost();
[[nodiscard]] WasmHost
cachedSignerListHost();
// A host whose `trace` output is captured rather than dropped, so the log-enabled path can
// be measured against the log-disabled one that `host()` gives.
[[nodiscard]] WasmHost
tracingHost();
// A real escrow, created through the real transactor — the current object for
// `home_le_field`, the one getter whose cost depends on the object it reads rather than on
// its arguments.
[[nodiscard]] Keylet const&
escrow() const;
[[nodiscard]] WasmHost
escrowHost();
// `sfMemos[0].sfMemoData` — a two-step locator path, the shape the nested getters are priced
// for.
[[nodiscard]] static FieldLocator
memoLocator();
// Canonical float operands. Zeroed bytes decode as a non-canonical float and would be
// refused before any arithmetic ran, so the whole family shares these two known-good values.
[[nodiscard]] static Slice
floatX();
[[nodiscard]] static Slice
floatY();
// A signed message for `check_sig`. Signing is far more expensive than the verification
// being measured, so it happens once here rather than inside a timed loop.
[[nodiscard]] SignedMessage const&
signedMessage() const;
// A well-formed NFToken id with the fixture's known taxon, flags, fee and sequence baked in,
// so the id-extractor getters have real fields to pull out rather than zeros.
[[nodiscard]] uint256 const&
nftId() const;
private:
// Order matters, and that is the reason this is a constructor rather than a pile of lazy
// statics: the accounts have to be funded before the signer list and the escrow can be built
// on them.
Fixtures();
// The transaction the default host runs, carrying the memo array.
[[nodiscard]] TxAssembler
memoTx();
WasmLedger ledger_;
Account alice_;
Account bob_;
Account signerListOwner_;
Keylet escrow_;
SignedMessage signedMessage_;
uint256 nftId_;
};
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,44 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
// The harness checking itself.
//
// This file holds no host function — it belongs to the wasm directory rather than
// `host_functions/` because it measures the two reference points every per-function number is read
// against, and neither is a host call.
//
// `GuestInstruction` runs a contract whose "host call" is a couple of guest instructions. Its
// `implied_gas` and `charged_gas` are then two independent measurements of the same quantity — one
// from wall time via `secondsPerGas`, one from the engine's own fuel meter — and they should agree
// closely. When they diverge, `secondsPerGas` has measured something other than a guest
// instruction and no other number in the run is trustworthy. Read this first.
//
// The crossing floor is the other reference point, and it lives in `host_functions/LedgerSqn`:
// `ldgr_index` takes no input and answers from a header already in hand, so its impl is as close
// to nothing as a host function gets, and whatever its `ThroughVm` case costs above its `Impl`
// case is the price of leaving the guest — paid by every one of the 61 functions before any of
// them does any work.
//
// So the reading order across the suite is: this file, then `LedgerSqn`'s pair for the floor, then
// a function's own `Impl` number. Those three should account for its `ThroughVm` number; where
// they do not, the gap is size-dependent copying, which the swept cases (`Sha512Half`,
// `UpdateData`) expose.
void
guestInstruction(benchmark::State& state)
{
static constexpr std::string_view kBody = "(i32.add (local.get $r) (i32.const 1))";
// Empty import name: this case prices no host function, so there is nothing to look a
// declaration up for and it reports no `suggested_gas`.
benchmarkThroughVm(state, "", "", "", kBody, [] { return Fixtures::instance().host(); });
}
BENCHMARK(guestInstruction)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,180 @@
# WASM host functions — gas calibration
These are **not tests**: nothing asserts, and a number moving is not a build failure. They answer
the question the tests cannot — whether each `#[gas = N]` in
`crates/xrpl-host-functions/src/lib.rs` matches what the function actually costs.
```bash
cmake --build build --target xrpl.bench.wasm
./build/xrpl.bench.wasm # everything (~2 min)
./build/xrpl.bench.wasm --benchmark_filter=sha512Half
./build/xrpl.bench.wasm --benchmark_repetitions=25 --benchmark_report_aggregates_only=true
```
**Build Release.** Debug inflates the crossing far more than the impls: `Impl`-to-`Impl` ratios
survive it, `suggested_gas` does not.
## Reading the output
| Counter | Meaning |
| ------------------- | ---------------------------------------------------------------------------------- |
| `suggested_gas` | **the answer** — what this function should be priced at |
| `host_function_gas` | what `lib.rs` says today, read through the `wasm_testkit` bridge so it can't drift |
| `price_ratio` | `host_function_gas / suggested_gas`. **1.0 is correct; below 1 is underpriced** |
| `rel_error` | relative uncertainty of `suggested_gas`. **Quote this one** — ~1.2% when quiet |
| `unreliable` | `1` means do not act on this row |
| `implied_gas` | the raw measurement, before the crossing is added back |
| `charged_gas` | what the engine actually billed; confirms the right call was measured |
| `ns_per_call` | raw wall time, for debugging a suspicious ratio |
Sort by `price_ratio`. **Below 1 is the direction that matters** — an underpriced call is one a
contract can buy too cheaply, a denial-of-service vector rather than a rounding error:
```bash
./build/xrpl.bench.wasm --benchmark_format=json |
jq -r '.benchmarks[] | select(.price_ratio) | [.price_ratio, .name] | @tsv' | sort -n
```
`unreliable=1` when `rel_error` exceeds 25%, or when `suggested_gas` falls below the crossing floor
— a call whose own cost is small next to the crossing is read off the difference of two nearly
equal numbers.
### With `--benchmark_repetitions`
Adds `_mean` / `_median` / `_stddev` / `_cv` rows. One trap worth knowing:
**`cv` on `suggested_gas` for an `Impl` case is not an error bar.** The crossing floor is measured
once per process, so repetitions never resample it — and it is most of a cheap `Impl` case's value.
Over 25 repetitions on an idle machine, `Impl` `cv` reads **1.3%** against `ThroughVm`'s 2.3%, while
`rel_error` is 1.2% for both. The lowest number in the output is the least trustworthy one.
Use repetitions to confirm the machine is quiet and to get a median; quote `rel_error`. Under load
the `Impl` cases inflate first (3.0% against 1.8% at load ~10), which makes the gap between the two
families a usable load detector.
## How `suggested_gas` is measured
Gas is wasmi fuel — `set_fuel(gas)` meters guest instructions and host charges from one pool — so
one gas is about one guest instruction and the question becomes a ratio. Every step is a
subtraction, so fixed costs cancel:
```
secondsPerGas = (time_busy − time_idle) / (fuel_busy − fuel_idle) # pure-wasm loop, N vs 0
implied_gas = secondsPerCall / secondsPerGas
crossing_floor = (ldgr_index ThroughVm − ldgr_index Impl) / secondsPerGas
suggested_gas = implied_gas # ThroughVm — the guest already paid the crossing
suggested_gas = implied_gas + crossing_floor # Impl — a guest cannot call without paying it
price_ratio = host_function_gas / suggested_gas
rel_error = sqrt( ( sqrt((implied·caseErr)² + (floor·floorErr)²) / suggested )² + perGasErr² )
```
`secondsPerCall` is itself a subtraction: a `ThroughVm` case runs a contract making N host calls
against a **byte-identical** one making none, so compilation, instantiation and the guest's own loop
cancel. An `Impl` case times `kCallsPerRun` direct calls and divides.
`secondsPerGas` is measured **once** and shared by every case, deliberately — two routes to the same
price must divide by the same constant, and per-case calibration was tried and swung 6x between
runs. Its uncertainty is propagated arithmetically instead. See the comments in `WasmBench.cpp` for
why the three terms in `rel_error` do not all combine in quadrature.
**`guestInstruction` is the self-test — read it first.** It runs the calibration's own loop body, so
its `implied_gas` (wall time) and `charged_gas` (the fuel meter) are two independent measurements of
one quantity. Quiet Release machine: **≈13.7 against 13.007, ~5% high.** A persistent gap much
beyond that means every other number in the run shares it. It has already caught an estimator
mismatch worth +40%, and the memory leak below.
**`suggested_gas` for an `Impl`-only case is a lower bound** — the crossing floor is measured on a
call with no input, so a function that moves bytes pays more; `Sha512Half` and `UpdateData` sweep
that per-byte term. `ThroughVm` cases are deliberately one per crossing _shape_, not one per
function.
## VM overhead (`Vm.cpp`)
Everything above prices what a contract _asks for_. `Vm.cpp` prices getting it running at all. Of
the seven stages `runEscrowWasm` performs — compile, store, linker, instantiate, entry-point lookup,
call, fuel read — **only the call is metered**; the rest is wall time no transaction pays for.
These cases report `ns_per_op` and `gas_equivalent` (that time over the same `secondsPerGas`, so an
unpriced stage reads on the same axis as a priced one), plus `module_bytes` and `gas_per_byte` on
size sweeps.
| case | measures |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `preflightMinimal` | compile plus the import/export walk. The narrowest view of compilation reachable from C++: no host, no instance |
| `preflightRejects` | the same on a module refused at the import walk. Against `preflightMinimal`, what refusing costs versus accepting |
| `runMinimal` | a whole run of a do-nothing contract — every stage. Minus `preflightMinimal`, the stages that are not compilation |
| `compileScaling/N` | compilation against module size: `N` unreachable filler functions, preflight only |
| `runScaling/N` | the same modules through a whole run. Paired with `compileScaling` per size, separates size-dependent stages from fixed ones |
| `instantiateScaling/pages` | declared memory at constant module size, so what moves is the host allocating and zeroing pages |
Two of those pairings are the point of the file. `runMinimal − preflightMinimal` gives the fixed
cost of everything that is not compilation; `runScaling` against `compileScaling` shows compilation
appearing a second time, because the transactor validates and executes with no module cache between
them. The size sweeps matter more than the floor: a fixed cost is only a griefing concern if it is
large, but a slope against attacker-chosen module size is one at any height.
Two caveats when reading a sweep. `gas_per_byte` is an **average** carrying the case's fixed cost,
not a marginal rate — it overestimates, and falls toward the true slope as the module grows, so read
the convergence rather than any single row. And the `/4096` points get few iterations and go noisy
first; compare `rel_error` across the sweep before quoting the largest one.
The sweep stops at 4096 functions for want of a real cap to stop at — no maximum contract size is
enforced anywhere yet, the transactor not being wired.
The linker rebuild and the fuel-metering overhead are **not** separable from here — C++ sees only
`runEscrowWasm` and `preflightEscrowWasm`. Both need benchmarks inside `xrpl-wasm-vm`, where
`compile` and `wasm_engine` are `pub(crate)`.
### Compiling leaks — pin your iteration counts
`wasm_engine()` is a process-global `LazyLock<Engine>`, and what `Module::new` adds to it is never
released. Repeatedly preflighting one **60-byte** module:
| `--benchmark_repetitions` | peak RSS |
| ------------------------- | -------- |
| 1 | 0.41 GB |
| 5 | 1.46 GB |
| 15 | 4.20 GB |
Linear, at roughly **800 bytes per compile**. Within the suite this is why every `Vm.cpp` case pins
`->Iterations(...)`: automatic sizing ran `preflightMinimal` ~348k times per repetition, reaching
7.9 GB at 25 repetitions, after which every later case in the binary failed to compile — 720 errored
rows, all blaming cases that were innocent.
**Outside the suite it is worth a look.** A validator compiles twice per programmable-escrow
transaction against that same static engine. Whether that is unbounded growth in production depends
on wasmi internals not checked here — this is the C++-visible symptom, not a diagnosis.
## Gotchas, each of which has already cost someone an afternoon
- **The wasm ABI is not the trait's argument order.** `float_add(x, y, mode, out)` in Rust is
`(x_ptr, x_len, y_ptr, y_len, out_ptr, out_len, mode)` on the wire — scalars move _after_ the
output region. Check `register.rs`, not `lib.rs`, when writing WAT.
- **A soft host error still "succeeds".** The run completes and gas is charged _before_ the body, so
a wrong-argument case reports a plausible, confidently wrong number. The harness requires the
contract's result to be `>= 0`; the tell is a `ThroughVm` case coming out _faster_ than its `Impl`.
- **A host serves exactly one run** (`checkSelf` in `WasmVM.cpp`), so a benchmark builds a fresh host
per run and cannot pre-cache a slot.
- **`MAX_FIELD_BYTES` is 1024** — nothing crosses the boundary above 1 KiB, so size sweeps stop there.
- **Do not compare timings across builds at the cheap end.** Two builds whose timed regions were
byte-identical measured 2.18 ns and 2.52 ns for the same case — ~15% apart, from code layout alone.
Use `price_ratio` within one run, and reason from the code when judging whether a change costs
anything.
- **Every case pins `->Iterations(...)`, for two different reasons.** Host-function cases must
because with `UseManualTime` automatic sizing reads only the tiny reported residue and would ask
for millions of iterations; `Vm.cpp` cases must because compiling leaks.
## Layout
One `.cpp` per host function under `host_functions/`, mirroring
`src/tests/libxrpl/tx/wasm/host_functions/`, so adding a host function is a two-file checklist
rather than a judgement call. `Crossing.cpp` holds the harness's own reference points, `Vm.cpp` the
per-run overhead around them, `WasmBench.*` the measurement machinery, `BenchFixtures.*` the shared
ledger (one ledger, funded once, for the whole binary).
The ledger and real host come from `xrpl.testkit.wasm` — a framework-free library built alongside
the tests — so this target links **no GTest and no GMock**. See
`src/tests/libxrpl/tx/wasm/README.md` for how that library is split, and why its setup steps throw
rather than using `EXPECT_`.

View File

@@ -0,0 +1,149 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <tx/wasm/fixtures/WasmRun.h>
#include <cstddef>
#include <format>
#include <string>
namespace xrpl::test::bench {
namespace {
// What a run costs *around* the contract, rather than what its host calls cost. Only the guest's
// own execution is metered, so every stage measured here is wall time no transaction pays for.
// ../README.md has what each case measures and how to read `gas_equivalent`.
//
// **Every case must pin `->Iterations(...)`.** Compiling a module allocates against the
// process-global engine and is never reclaimed — roughly 800 bytes per compile — so Google
// Benchmark's automatic sizing, which targets a wall-clock budget rather than a compile count,
// reaches gigabytes resident and the whole binary stops being able to compile anything.
// The smallest module the engine accepts. Everything a run does to it is overhead by construction.
std::string
minimalWat()
{
return R"wat((module
(memory (export "memory") 1)
(func (export "escrow_finish") (result i32)
(i32.const 1)))
)wat";
}
// `count` unreachable functions on top of the minimal module: bigger without doing more.
//
// Each body is seeded with its own index so no two are identical and none folds to a constant the
// translator can drop — either would break the link between function count and byte count.
// Unreachable is fine: validation and translation visit every function a module declares, and that
// visit is exactly the cost being swept.
std::string
fillerWat(size_t count)
{
auto out = std::string{"(module\n (memory (export \"memory\") 1)\n"};
for (auto i = 0uz; i < count; ++i)
{
out += std::format(
" (func $f{0} (param i32) (result i32)\n"
" (i32.add (i32.mul (local.get 0) (i32.const {0})) (i32.const {0})))\n",
i);
}
out += " (func (export \"escrow_finish\") (result i32)\n (i32.const 1)))\n";
return out;
}
// The minimal module asking for `pages` of initial memory. `MAX_MEMORY_PAGES` is 128 (8 MiB), and a
// contract declares this for free — the host allocates and zeroes it before the first instruction.
std::string
pagesWat(size_t pages)
{
return std::format(
"(module\n"
" (memory (export \"memory\") {})\n"
" (func (export \"escrow_finish\") (result i32)\n"
" (i32.const 1)))\n",
pages);
}
// Compiles cleanly and is then refused: `env::malloc` is not a namespace the engine serves, so
// `check_imports` stops at it.
std::string
rejectedWat()
{
return R"wat((module
(import "env" "malloc" (func $malloc (param i32) (result i32)))
(memory (export "memory") 1)
(func (export "escrow_finish") (result i32)
(i32.const 1)))
)wat";
}
void
preflightMinimal(benchmark::State& state)
{
static auto const kWasm = assembleWat(minimalWat());
benchmarkPreflight(state, kWasm);
}
BENCHMARK(preflightMinimal)->UseManualTime()->Iterations(kBenchIterations);
void
preflightRejects(benchmark::State& state)
{
static auto const kWasm = assembleWat(rejectedWat());
benchmarkPreflight(state, kWasm, /*expectAccepted*/ false);
}
BENCHMARK(preflightRejects)->UseManualTime()->Iterations(kBenchIterations);
void
runMinimal(benchmark::State& state)
{
static auto const kWasm = assembleWat(minimalWat());
benchmarkRun(state, kWasm, [] { return Fixtures::instance().host(); });
}
BENCHMARK(runMinimal)->UseManualTime()->Iterations(kBenchIterations);
void
compileScaling(benchmark::State& state)
{
auto const wasm = assembleWat(fillerWat(static_cast<size_t>(state.range(0))));
benchmarkPreflight(state, wasm, /*expectAccepted*/ true, /*sizeSweep*/ true);
}
BENCHMARK(compileScaling)
->UseManualTime()
->Iterations(kBenchIterations)
->Arg(1)
->Arg(8)
->Arg(64)
->Arg(512)
->Arg(4096);
void
runScaling(benchmark::State& state)
{
auto const wasm = assembleWat(fillerWat(static_cast<size_t>(state.range(0))));
benchmarkRun(state, wasm, [] { return Fixtures::instance().host(); }, /*sizeSweep*/ true);
}
BENCHMARK(runScaling)
->UseManualTime()
->Iterations(kBenchIterations)
->Arg(1)
->Arg(8)
->Arg(64)
->Arg(512)
->Arg(4096);
void
instantiateScaling(benchmark::State& state)
{
auto const wasm = assembleWat(pagesWat(static_cast<size_t>(state.range(0))));
benchmarkRun(state, wasm, [] { return Fixtures::instance().host(); });
}
BENCHMARK(instantiateScaling)
->UseManualTime()
->Iterations(kBenchIterations)
->Arg(1)
->Arg(8)
->Arg(32)
->Arg(128);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,397 @@
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/wasm/HostFunc.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <xrpl/tx/wasm/WasmVM.h>
#include <benchmark/benchmark.h>
#include <rust/cxx.h>
#include <tx/wasm/fixtures/WasmLedger.h>
#include <tx/wasm/fixtures/WasmRun.h>
#include <xrpl_wasm_testkit_cxxbridge/lib.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <format>
#include <span>
#include <string>
#include <string_view>
namespace xrpl::test::bench {
int
callsWithinTransferBudget(std::int64_t bytesWrittenPerCall)
{
// Writes nothing back to the guest, so the budget does not apply.
if (bytesWrittenPerCall <= 0)
{
return kCallsPerRun;
}
auto const affordable = kTransferLimitBytes / bytesWrittenPerCall;
if (affordable < 1)
{
fixtureFailed("a single call would exceed the run's transfer budget");
}
return static_cast<int>(std::min<std::int64_t>(affordable, kCallsPerRun));
}
std::string
dataSegment(int offset, std::span<std::uint8_t const> bytes)
{
return std::format(" (data (i32.const {}) \"{}\")\n", offset, watEscaped(bytes));
}
std::string
dataSegment(int offset, Bytes const& bytes)
{
return dataSegment(offset, std::span<std::uint8_t const>{bytes.data(), bytes.size()});
}
std::string
makeLoopWat(std::string_view imports, std::string_view data, std::string_view body, int count)
{
static constexpr auto kTemplate = R"wat((module
{}
(memory (export "memory") 1)
{}
(func (export "escrow_finish") (result i32)
(local $i i32)
(local $r i32)
(local.set $i (i32.const {}))
(block $done
(loop $again
(br_if $done (i32.eqz (local.get $i)))
(local.set $r {})
(local.set $i (i32.sub (local.get $i) (i32.const 1)))
(br $again)))
(local.get $r)))
)wat";
return std::format(kTemplate, imports, data, count, body);
}
Timing
timeRun(HostFunctions& host, Bytes const& wasm)
{
auto const start = std::chrono::steady_clock::now();
auto outcome = runEscrowWasm(wasm, host, kBenchGas);
auto const elapsed = std::chrono::steady_clock::now() - start;
benchmark::DoNotOptimize(outcome);
return {
.seconds = std::chrono::duration<double>(elapsed).count(),
.gas = outcome.has_value() ? outcome->cost : std::int64_t{0}};
}
StageTimer::StageTimer(benchmark::State& state, std::int64_t moduleBytes)
: state_{state}, moduleBytes_{moduleBytes}
{
}
void
StageTimer::add(double seconds)
{
state_.SetIterationTime(seconds);
total_ += seconds;
sumSquares_ += seconds * seconds;
++rounds_;
}
void
StageTimer::report()
{
if (rounds_ == 0)
{
return;
}
auto const count = static_cast<double>(rounds_);
auto const mean = total_ / count;
auto const variance = std::max(0.0, (sumSquares_ / count) - (mean * mean));
auto const spread = mean > 0.0 ? std::sqrt(variance) / mean : 0.0;
auto const& calibration = Calibration::instance();
auto const perGas = calibration.secondsPerGas();
auto const equivalent = perGas > 0.0 ? mean / perGas : 0.0;
state_.counters["ns_per_op"] = mean * 1e9;
// What this stage would cost if it were charged at the rate the guest inside it is charged at.
// It is not charged, which is the point: this puts an unpriced stage in the host-function
// table's units.
state_.counters["gas_equivalent"] = equivalent;
if (moduleBytes_ > 0)
{
state_.counters["module_bytes"] = static_cast<double>(moduleBytes_);
// An average over the whole operation, not the marginal rate: it carries the case's fixed
// cost, so it overestimates and falls toward the true slope as the sweep grows.
state_.counters["gas_per_byte"] = equivalent / static_cast<double>(moduleBytes_);
}
// `gas_equivalent` divides by `secondsPerGas`, so the divisor's uncertainty is in every number
// here too.
auto const caseStdErr = spread / std::sqrt(count);
auto const perGasErr = calibration.secondsPerGasRelStdErr();
auto const totalErr = std::sqrt((caseStdErr * caseStdErr) + (perGasErr * perGasErr));
state_.counters["rel_error"] = totalErr;
state_.counters["unreliable"] = totalErr > kMaxRelativeSpread ? 1 : 0;
}
void
benchmarkPreflight(benchmark::State& state, Bytes const& wasm, bool expectAccepted, bool sizeSweep)
{
(void)Calibration::instance();
// Discarded: the reject case refuses on every iteration, and a real sink would put string
// formatting and I/O inside the measurement.
auto const journal = beast::Journal{beast::Journal::getNullSink()};
if (isTesSuccess(preflightEscrowWasm(wasm, journal)) != expectAccepted)
{
state.SkipWithError(
expectAccepted
? "the module was refused; the case would be measuring the reject path"
: "the module was accepted; the case would be measuring the accept path");
return;
}
StageTimer timer{state, sizeSweep ? static_cast<std::int64_t>(wasm.size()) : 0};
for (auto _ : state)
{
auto const start = std::chrono::steady_clock::now();
auto verdict = preflightEscrowWasm(wasm, journal);
auto const elapsed = std::chrono::steady_clock::now() - start;
benchmark::DoNotOptimize(verdict);
timer.add(std::chrono::duration<double>(elapsed).count());
}
timer.report();
}
namespace {
// Seconds of wall time one unit of gas buys on this machine.
//
// The estimator must be the *same* one the cases use — a mean, with the same clamp at zero. Since
// `implied_gas = secondsPerCall / secondsPerGas`, any difference between how divisor and dividend
// are estimated lands in every reported number: calibrating with a best-of while measuring with a
// mean once biased the whole report +40%.
//
// `guestInstruction` in Crossing.cpp is the check that this holds — it runs this exact loop body.
double
measureSecondsPerGas(double& relativeStandardError)
{
// A couple of guest instructions, no memory traffic, nothing the engine can fold away.
static constexpr auto kBody = std::string_view{"(i32.add (local.get $r) (i32.const 1))"};
auto const busy = assembleWat(makeLoopWat("", "", kBody, kCallsPerRun));
auto const idle = assembleWat(makeLoopWat("", "", kBody, 0));
auto fixture = WasmLedger{};
// Warm up, so the first-run penalty does not land on one side of the subtraction.
for (auto i = 0U; i < 8; ++i)
{
timeRun(*fixture.makeHost(), busy);
timeRun(*fixture.makeHost(), idle);
}
auto total = 0.0;
auto sumSquares = 0.0;
// Fuel is exact and deterministic, so any pair gives the same delta.
auto gasDelta = std::int64_t{1};
for (auto i = 0; i < kCalibrationPairs; ++i)
{
auto hotHost = fixture.makeHost();
auto const hot = timeRun(*hotHost, busy);
auto coldHost = fixture.makeHost();
auto const cold = timeRun(*coldHost, idle);
auto const delta = std::max(0.0, hot.seconds - cold.seconds);
total += delta;
sumSquares += delta * delta;
gasDelta = std::max(std::int64_t{1}, hot.gas - cold.gas);
}
auto const mean = total / kCalibrationPairs;
auto const variance = std::max(0.0, (sumSquares / kCalibrationPairs) - (mean * mean));
relativeStandardError = mean > 0.0 ? std::sqrt(variance / kCalibrationPairs) / mean : 0.0;
return mean / static_cast<double>(gasDelta);
}
// The crossing, in gas: `ldgr_index` through the VM minus `ldgr_index` called directly.
//
// Both halves are means, for the reason above: the VM half has to match `benchmarkThroughVm`'s
// estimator and the impl half `benchmarkImpl`'s. `secondsPerGas` is passed in rather than
// re-measured so it comes from the same snapshot.
double
measureCrossingFloorGas(double secondsPerGas, double& relativeStandardError)
{
static constexpr std::string_view kImport =
R"( (import "host_lib" "ldgr_index" (func $ldgr_index (param i32 i32) (result i32)))
)";
static constexpr std::string_view kBody = "(call $ldgr_index (i32.const 0) (i32.const 4))";
auto const loaded = assembleWat(makeLoopWat(kImport, "", kBody, kCallsPerRun));
auto const baseline = assembleWat(makeLoopWat(kImport, "", kBody, 0));
auto fixture = WasmLedger{};
auto vmTotal = 0.0;
auto vmSquares = 0.0;
auto guestOverheadGas = 0.0;
for (auto i = 0; i < kBenchIterations; ++i)
{
auto hotHost = fixture.makeHost();
auto const hot = timeRun(*hotHost, loaded);
auto coldHost = fixture.makeHost();
auto const cold = timeRun(*coldHost, baseline);
auto const perCall = std::max(0.0, hot.seconds - cold.seconds) / kCallsPerRun;
vmTotal += perCall;
vmSquares += perCall * perCall;
// Exact, from the fuel meter: what the guest burned per call beyond the call itself.
guestOverheadGas =
(static_cast<double>(hot.gas - cold.gas) / kCallsPerRun) - declaredGas("ldgr_index");
}
auto const vmSeconds = vmTotal / kBenchIterations;
// The impl side is the same call without the VM. Subtracting it leaves the crossing.
auto implTotal = 0.0;
auto implSquares = 0.0;
auto host = fixture.makeHost();
for (auto i = 0; i < kBenchIterations; ++i)
{
auto const start = std::chrono::steady_clock::now();
for (auto c = 0U; c < kCallsPerRun; ++c)
{
auto result = host->getLedgerSqn();
benchmark::DoNotOptimize(result);
}
auto const elapsed = std::chrono::steady_clock::now() - start;
auto const perCall = std::chrono::duration<double>(elapsed).count() / kCallsPerRun;
implTotal += perCall;
implSquares += perCall * perCall;
}
auto const implSeconds = implTotal / kBenchIterations;
if (secondsPerGas <= 0.0)
{
return 0.0;
}
// Take the guest's loop bookkeeping off here too: `report` removes it from every `ThroughVm`
// number, so leaving it in would make the two routes to one price disagree by that amount.
auto const crossing = std::max(0.0, vmSeconds - implSeconds) / secondsPerGas;
auto const floor = std::max(0.0, crossing - std::max(0.0, guestOverheadGas));
// Relative to the *difference*, not to either half: both contribute their error, and the
// denominator is what survives the subtraction. Not divided by `secondsPerGas` — that error is
// common-mode with the rest of `suggested_gas` and is applied once, to the sum, in `report`.
auto const vmVariance = std::max(0.0, (vmSquares / kBenchIterations) - (vmSeconds * vmSeconds));
auto const implVariance =
std::max(0.0, (implSquares / kBenchIterations) - (implSeconds * implSeconds));
auto const vmStdErr = std::sqrt(vmVariance / kBenchIterations);
auto const implStdErr = std::sqrt(implVariance / kBenchIterations);
auto const crossingSeconds = vmSeconds - implSeconds;
auto const crossingStdErr = std::sqrt((vmStdErr * vmStdErr) + (implStdErr * implStdErr));
relativeStandardError = crossingSeconds > 0.0 ? crossingStdErr / crossingSeconds : 0.0;
return floor;
}
} // namespace
Calibration const&
Calibration::instance()
{
static Calibration const kValue;
return kValue;
}
Calibration::Calibration()
: secondsPerGas_{measureSecondsPerGas(secondsPerGasRelStdErr_)}
, crossingFloorGas_{measureCrossingFloorGas(secondsPerGas_, crossingFloorRelStdErr_)}
{
}
double
declaredGas(std::string_view wasmName)
{
return static_cast<double>(
rs::wasm_testkit::host_function_gas(rust::Str{wasmName.data(), wasmName.size()}));
}
void
report(
benchmark::State& state,
double secondsPerCall,
double chargedGas,
double guestOverheadGas,
double relativeSpread,
std::int64_t rounds,
std::string_view wasmName,
bool throughVm)
{
auto const& calibration = Calibration::instance();
auto const perGas = calibration.secondsPerGas();
auto const measured = perGas > 0.0 ? secondsPerCall / perGas : 0.0;
// The timed number covers the host call *and* whatever the guest ran around it.
// `guestOverheadGas` is exact, so taking it off removes a bias rather than trading estimates.
auto const implied = std::max(0.0, measured - guestOverheadGas);
auto const suggested = throughVm ? implied : implied + calibration.crossingFloorGas();
state.counters["implied_gas"] = implied;
state.counters["ns_per_call"] = secondsPerCall * 1e9;
state.counters["charged_gas"] = chargedGas;
if (wasmName.empty())
{
return;
}
auto const declared = declaredGas(wasmName);
state.counters["host_function_gas"] = declared;
state.counters["suggested_gas"] = suggested;
// Below 1 is the direction that matters: an underpriced call is one a contract buys too
// cheaply.
state.counters["price_ratio"] = suggested > 0.0 ? declared / suggested : 0.0;
// Uncertainty **of `suggested_gas`**, not of `implied_gas` — different numbers once the floor
// is added. `implied` and the floor are independent timings, so their absolute errors add in
// quadrature over the sum; but both divide by `secondsPerGas`, so that error is common-mode and
// applies once to the total. Adding it per-term would count it twice.
//
// For `ThroughVm` the floor term is zero and `suggested == implied`, so this reduces exactly to
// the plain `sqrt(caseErr² + perGasErr²)`. Only `Impl` cases move — and for a cheap one the
// floor is most of `suggested_gas`, so an error bar describing `implied` alone described
// little.
auto const caseStdErr =
rounds > 0 ? relativeSpread / std::sqrt(static_cast<double>(rounds)) : relativeSpread;
auto const impliedErr = implied * caseStdErr;
auto const floorErr =
throughVm ? 0.0 : calibration.crossingFloorGas() * calibration.crossingFloorRelStdErr();
auto const independentErr = suggested > 0.0
? std::sqrt((impliedErr * impliedErr) + (floorErr * floorErr)) / suggested
: caseStdErr;
auto const perGasErr = calibration.secondsPerGasRelStdErr();
auto const totalErr = std::sqrt((independentErr * independentErr) + (perGasErr * perGasErr));
state.counters["rel_error"] = totalErr;
// A call whose own cost is small next to the crossing is read off the difference of two nearly
// equal numbers, so its `suggested_gas` is scatter rather than signal.
auto const floor = calibration.crossingFloorGas();
state.counters["unreliable"] =
(totalErr > kMaxRelativeSpread || (floor > 0.0 && suggested < floor)) ? 1 : 0;
}
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,369 @@
#pragma once
#include <xrpl/tx/wasm/HostFunc.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <xrpl/tx/wasm/WasmVM.h>
#include <benchmark/benchmark.h>
#include <tx/wasm/fixtures/WasmRun.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
// The gas-calibration harness. What the numbers mean and how to read a report are in ../README.md.
namespace xrpl::test::bench {
// Enough that a benchmark loop never ends early; running out would measure something shorter.
inline constexpr std::int64_t kBenchGas = 2'000'000'000;
// Host calls per run: enough that the per-call cost dominates the baseline subtraction's residue.
inline constexpr std::int32_t kCallsPerRun = 1000;
// Timed iterations per case. Pinned because automatic sizing cannot work here: a case reports a
// residue of nanoseconds while the iteration producing it ran two contracts and cost milliseconds,
// so sizing would ask for millions.
inline constexpr std::int32_t kBenchIterations = 50;
// Pairs the one-off calibration averages. Higher than `kBenchIterations` because `secondsPerGas`
// divides every reported number, and a bare wasm loop is cheap to repeat.
inline constexpr std::int32_t kCalibrationPairs = 400;
// Above this relative uncertainty, `suggested_gas` is reported as unreliable.
inline constexpr double kMaxRelativeSpread = 0.25;
// `TRANSFER_LIMIT_BYTES` in crates/xrpl-wasm-vm/src/vm.rs: what a run may write into guest memory
// before `charge_transfer` starts refusing calls.
inline constexpr std::int64_t kTransferLimitBytes = 1 << 20;
// How many calls a run can afford, given the bytes each has the host **write into guest memory**.
// Output direction only — what the guest passes in is borrowed, and costs nothing against the
// budget. Never raises the count to meet a floor; a case that cannot afford one call fails loudly.
int
callsWithinTransferBudget(std::int64_t bytesWrittenPerCall);
// One run of a contract: how long it took, and what the engine charged it.
struct Timing
{
double seconds{};
std::int64_t gas{};
};
// A `(data ...)` segment placing `bytes` at `offset` in guest memory, so the timed loop measures
// the host call rather than the guest arranging its arguments. `watEscaped` in WasmRun.h says why
// zeroed memory will not do.
std::string
dataSegment(int offset, std::span<std::uint8_t const> bytes);
std::string
dataSegment(int offset, Bytes const& bytes);
// A contract that runs `body` `count` times and returns the last result.
std::string
makeLoopWat(std::string_view imports, std::string_view data, std::string_view body, int count);
// Run pre-assembled `wasm` once through the real VM, reporting wall time and gas.
Timing
timeRun(HostFunctions& host, Bytes const& wasm);
// What this machine costs, measured once and shared by every case: two cases pricing one function
// two ways (`Impl` + crossing floor, versus `ThroughVm`) must divide by the *same* `secondsPerGas`
// or they disagree for reasons unrelated to the function.
class Calibration
{
public:
static Calibration const&
instance();
// Seconds of wall time one unit of gas buys here.
[[nodiscard]] double
secondsPerGas() const
{
return secondsPerGas_;
}
// The gas a host call costs before it does anything: region decode, bounds checks, the cxx hop.
[[nodiscard]] double
crossingFloorGas() const
{
return crossingFloorGas_;
}
// Propagated into every case's `rel_error`, which is what lets one snapshot stay shared:
// `--benchmark_repetitions` resamples per-case timings but never this divisor.
[[nodiscard]] double
secondsPerGasRelStdErr() const
{
return secondsPerGasRelStdErr_;
}
// An additive term in every `Impl` case's `suggested_gas`, and most of the cheap ones.
[[nodiscard]] double
crossingFloorRelStdErr() const
{
return crossingFloorRelStdErr_;
}
private:
Calibration();
double secondsPerGasRelStdErr_{};
double crossingFloorRelStdErr_{};
double secondsPerGas_{};
double crossingFloorGas_{};
};
// What the gas table declares for a host function, by guest import name, read through the
// `wasm_testkit` bridge so it cannot drift.
double
declaredGas(std::string_view wasmName);
// Attach the calibration counters to a finished case.
//
// `guestOverheadGas` is fuel the guest burned around the call — loop bookkeeping and the
// `i32.const`s pushing arguments. From the fuel meter, so it is exact. Zero for `Impl` cases.
void
report(
benchmark::State& state,
double secondsPerCall,
double chargedGas,
double guestOverheadGas,
double relativeSpread,
std::int64_t rounds,
std::string_view wasmName,
bool throughVm);
// Accumulates one whole-operation case and turns it into counters.
//
// The two harnesses below subtract setup away, amortizing over `kCallsPerRun` calls against a
// baseline. Here that is inverted: setup is the subject, timed whole, nothing amortized.
class StageTimer
{
public:
// Pass zero for `moduleBytes` unless the case belongs to a sweep that *varies* module size —
// the per-byte counter it enables is an average over the whole operation, meaningless where the
// module is constant.
StageTimer(benchmark::State& state, std::int64_t moduleBytes);
void
add(double seconds);
// Attach the counters. Call once, after the loop.
void
report();
private:
benchmark::State& state_;
std::int64_t moduleBytes_{};
double total_{};
double sumSquares_{};
std::int64_t rounds_{};
};
// Measure `preflightEscrowWasm`: compile, then the walk over the module's imports and exports.
//
// `expectAccepted` is what the module *should* do. A refused module stops at the first fault and is
// far cheaper, so a case that silently flipped verdict would report a confident number for an
// operation it never performed.
void
benchmarkPreflight(
benchmark::State& state,
Bytes const& wasm,
bool expectAccepted = true,
bool sizeSweep = false);
// Measure a whole `runEscrowWasm` — every stage, unamortized.
template <class SetUp>
void
benchmarkRun(benchmark::State& state, Bytes const& wasm, SetUp&& setUp, bool sizeSweep = false)
{
// Force the calibration and the engine's lazy construction before the clock starts, so the
// first case does not absorb them into its own first iteration.
[[maybe_unused]] auto const& calibration = Calibration::instance();
auto probe = setUp();
if (auto const check = runEscrowWasm(wasm, *probe, kBenchGas); !check.has_value())
{
state.SkipWithError("the benchmarked contract did not run to completion");
return;
}
auto timer = StageTimer{state, sizeSweep ? static_cast<std::int64_t>(wasm.size()) : 0};
for (auto _ : state)
{
auto host = setUp();
timer.add(timeRun(*host, wasm).seconds);
}
timer.report();
}
// Measure a host function through the whole stack — guest, VM, marshalling, real impl, real ledger
// — with everything but the host calls subtracted away.
template <class SetUp>
void
benchmarkThroughVm(
benchmark::State& state,
std::string_view wasmName,
std::string_view imports,
std::string_view data,
std::string_view body,
SetUp&& setUp,
int calls = kCallsPerRun)
{
auto const loaded = assembleWat(makeLoopWat(imports, data, body, calls));
auto const baseline = assembleWat(makeLoopWat(imports, data, body, 0));
// A host serves exactly one run — `runEscrowWasm` asserts it was handed a clean one
// (`checkSelf` in WasmVM.cpp). Hence `setUp` being a factory rather than a host.
auto probe = setUp();
// A soft host error still completes the run, so require both that it completed and that the
// last host call returned a non-negative result, which every body leaves in `$r`.
auto const check = runEscrowWasm(loaded, *probe, kBenchGas);
if (!check.has_value())
{
state.SkipWithError("the benchmarked contract did not run to completion");
return;
}
if (check->result < 0)
{
state.SkipWithError(
"the benchmarked host call returned error code " + std::to_string(check->result) +
"; the case would be measuring the rejection path, not the work");
return;
}
auto totalSeconds = 0.0;
auto sumSquares = 0.0;
auto totalGas = 0.0;
auto rounds = std::int64_t{0};
for (auto _ : state)
{
auto hotHost = setUp();
auto const hot = timeRun(*hotHost, loaded);
auto coldHost = setUp();
auto const cold = timeRun(*coldHost, baseline);
// Clamped: on a noisy machine a pair can invert, and a negative iteration time would make
// Google Benchmark's statistics meaningless.
auto const perCall = std::max(0.0, hot.seconds - cold.seconds) / calls;
state.SetIterationTime(perCall);
totalSeconds += perCall;
sumSquares += perCall * perCall;
totalGas += static_cast<double>(hot.gas - cold.gas) / calls;
++rounds;
}
if (rounds > 0)
{
auto const meanSeconds = totalSeconds / rounds;
auto const variance = std::max(0.0, (sumSquares / rounds) - (meanSeconds * meanSeconds));
auto const spread = meanSeconds > 0.0 ? std::sqrt(variance) / meanSeconds : 0.0;
// Zero for the empty-name case — `guestInstruction`, which prices no host function. There
// is no host call to separate scaffolding *from*, and subtracting the full charge would
// leave `implied_gas = measured - charged`, ~0 by construction: it would turn the harness's
// one self-test into a tautology that cannot fail.
auto const chargedPerCall = totalGas / rounds;
auto const overhead = wasmName.empty() ? 0.0 : chargedPerCall - declaredGas(wasmName);
report(
state,
meanSeconds,
chargedPerCall,
std::max(0.0, overhead),
spread,
rounds,
wasmName,
true);
}
}
// Measure a host function's impl alone — no guest, no VM, no marshalling. Against the `ThroughVm`
// case for the same function, the difference is what crossing the boundary costs.
template <class SetUp, class Call>
void
benchmarkImpl(benchmark::State& state, std::string_view wasmName, SetUp&& setUp, Call&& call)
{
auto host = setUp();
// A call that errors returns early and is far cheaper than one that works, so a subtly wrong
// argument yields a confident and always *too low* price. Probed either side of the loop rather
// than inside it, where the branch would land in the measurement: *before* catches wrong
// arguments, *after* catches a call that stopped working once the loop exhausted something.
//
// The `requires` skips host functions that answer nothing (`trace`) or answer a bare value.
auto const checkSucceeds = [&](char const* when) {
if constexpr (requires { call(*host).has_value(); })
{
if (auto const probe = call(*host); !probe.has_value())
{
state.SkipWithError(
std::string{"the benchmarked host call returned error code "} +
std::to_string(static_cast<int>(probe.error())) + " " + when +
" the timed loop; the case would be measuring the rejection path");
return false;
}
}
return true;
};
if (!checkSucceeds("before"))
{
return;
}
auto totalSeconds = 0.0;
auto sumSquares = 0.0;
auto rounds = std::int64_t{0};
for (auto _ : state)
{
auto const start = std::chrono::steady_clock::now();
for (int i = 0; i < kCallsPerRun; ++i)
{
// `trace` answers nothing, so `ClobberMemory` stands in for `DoNotOptimize`.
if constexpr (std::is_void_v<decltype(call(*host))>)
{
call(*host);
benchmark::ClobberMemory();
}
else
{
auto result = call(*host);
benchmark::DoNotOptimize(result);
}
}
auto const elapsed = std::chrono::steady_clock::now() - start;
auto const perCall = std::chrono::duration<double>(elapsed).count() / kCallsPerRun;
state.SetIterationTime(perCall);
totalSeconds += perCall;
sumSquares += perCall * perCall;
++rounds;
}
if (!checkSucceeds("after"))
{
return;
}
if (rounds > 0)
{
auto const meanSeconds = totalSeconds / rounds;
auto const variance = std::max(0.0, (sumSquares / rounds) - (meanSeconds * meanSeconds));
auto const spread = meanSeconds > 0.0 ? std::sqrt(variance) / meanSeconds : 0.0;
// Nothing charged and no guest scaffolding; `report` adds the crossing back in.
report(state, meanSeconds, 0.0, 0.0, spread, rounds, wasmName, false);
}
}
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
accountKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"accountroot_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.accountKeylet(Fixtures::instance().alice().id()); });
}
BENCHMARK(accountKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,31 @@
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/UintTypes.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
ammKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"amm_id"};
auto const usd = Asset{Issue{toCurrency("USD"), Fixtures::instance().alice().id()}};
auto const xrp = Asset{xrpIssue()};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[&usd, &xrp](auto& host) { return host.ammKeylet(usd, xrp); });
}
BENCHMARK(ammKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
baseFeeImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"base_fee"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getBaseFee(); });
}
BENCHMARK(baseFeeImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,28 @@
#include <xrpl/protocol/Indexes.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
cacheLedgerObjImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"cache_le"};
auto const key = keylet::account(Fixtures::instance().alice().id()).key;
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[&key](auto& host) { return host.cacheLedgerObj(key, 1); });
}
BENCHMARK(cacheLedgerObjImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
checkKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"check_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.checkKeylet(Fixtures::instance().alice().id(), Fixtures::kSeq);
});
}
BENCHMARK(checkKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,63 @@
#include <xrpl/basics/Slice.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <cstdint>
#include <format>
#include <string_view>
namespace xrpl::test::bench {
namespace {
constexpr std::string_view kWasmName = "check_sig";
constexpr std::string_view kImport =
R"( (import "host_lib" "check_sig" (func $check_sig (param i32 i32 i32 i32 i32 i32) (result i32)))
)";
constexpr std::int32_t kMessageOffset = 0;
constexpr std::int32_t kSignatureOffset = 256;
constexpr std::int32_t kPubkeyOffset = 512;
void
checkSignatureThroughVm(benchmark::State& state)
{
auto const& m = Fixtures::instance().signedMessage();
static auto const kData = dataSegment(kMessageOffset, m.message) +
dataSegment(kSignatureOffset, m.signature) + dataSegment(kPubkeyOffset, m.publicKey);
static auto const kBody = std::format(
"(call $check_sig (i32.const {}) (i32.const {}) (i32.const {}) (i32.const {}) "
"(i32.const {}) (i32.const {}))",
kMessageOffset,
m.message.size(),
kSignatureOffset,
m.signature.size(),
kPubkeyOffset,
m.publicKey.size());
benchmarkThroughVm(
state, kWasmName, kImport, kData, kBody, [] { return Fixtures::instance().host(); });
}
BENCHMARK(checkSignatureThroughVm)->UseManualTime()->Iterations(kBenchIterations);
void
checkSignatureImpl(benchmark::State& state)
{
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
auto const& m = Fixtures::instance().signedMessage();
return host.checkSignature(
Slice{m.message.data(), m.message.size()},
Slice{m.signature.data(), m.signature.size()},
Slice{m.publicKey.data(), m.publicKey.size()});
});
}
BENCHMARK(checkSignatureImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,32 @@
#include <xrpl/basics/Slice.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
credentialKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"credential_id"};
static constexpr auto kType = std::string_view{"termsandconditions"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.credentialKeylet(
Fixtures::instance().alice().id(),
Fixtures::instance().bob().id(),
Slice{kType.data(), kType.size()});
});
}
BENCHMARK(credentialKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <xrpl/protocol/SField.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
currentLedgerObjArrayLenImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"home_le_arr_len"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().signerListHost(); },
[](auto& host) { return host.getCurrentLedgerObjArrayLen(sfSignerEntries); });
}
BENCHMARK(currentLedgerObjArrayLenImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,41 @@
#include <xrpl/protocol/SField.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <format>
#include <string_view>
namespace xrpl::test::bench {
namespace {
constexpr std::string_view kWasmName = "home_le_field";
constexpr std::string_view kImport =
R"( (import "host_lib" "home_le_field" (func $home_le_field (param i32 i32 i32) (result i32)))
)";
void
currentLedgerObjFieldThroughVm(benchmark::State& state)
{
static auto const kBody = std::format(
"(call $home_le_field (i32.const {}) (i32.const 0) (i32.const 32))", sfAccount.getCode());
benchmarkThroughVm(
state, kWasmName, kImport, "", kBody, [] { return Fixtures::instance().escrowHost(); });
}
BENCHMARK(currentLedgerObjFieldThroughVm)->UseManualTime()->Iterations(kBenchIterations);
void
currentLedgerObjFieldImpl(benchmark::State& state)
{
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().escrowHost(); },
[](auto& host) { return host.getCurrentLedgerObjField(sfAccount); });
}
BENCHMARK(currentLedgerObjFieldImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,30 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
currentLedgerObjNestedArrayLenImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"home_le_inner_arr_len"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().signerListHost(); },
[](auto& host) {
return host.getCurrentLedgerObjNestedArrayLen(
FieldLocator{{sfSignerEntries.getCode()}});
});
}
BENCHMARK(currentLedgerObjNestedArrayLenImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,29 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
currentLedgerObjNestedFieldImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"home_le_inner"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.getCurrentLedgerObjNestedField(FieldLocator{{sfAccount.getCode()}});
});
}
BENCHMARK(currentLedgerObjNestedFieldImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,27 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
delegateKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"delegate_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.delegateKeylet(
Fixtures::instance().alice().id(), Fixtures::instance().bob().id());
});
}
BENCHMARK(delegateKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,27 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
depositPreauthKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"deposit_preauth_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.depositPreauthKeylet(
Fixtures::instance().alice().id(), Fixtures::instance().bob().id());
});
}
BENCHMARK(depositPreauthKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
didKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"did_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.didKeylet(Fixtures::instance().alice().id()); });
}
BENCHMARK(didKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,55 @@
#include <xrpl/tx/wasm/WasmCommon.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <tx/wasm/fixtures/WasmLedger.h>
#include <cstdint>
#include <string_view>
namespace xrpl::test::bench {
namespace {
constexpr std::string_view kWasmName = "escrow_id";
constexpr std::string_view kImport =
R"( (import "host_lib" "escrow_id" (func $escrow_id (param i32 i32 i32 i32 i32 i32) (result i32)))
)";
constexpr std::string_view kBody =
"(call $escrow_id (i32.const 0) (i32.const 20) (i32.const 32) (i32.const 4) "
"(i32.const 64) (i32.const 32))";
void
escrowKeyletThroughVm(benchmark::State& state)
{
static auto const kData = [] {
auto seq = Bytes(4);
for (auto i = 0U; i < 4; ++i)
{
seq[i] = static_cast<std::uint8_t>((Fixtures::kSeq >> (8 * i)) & 0xFF);
}
return dataSegment(0, WasmLedger::toBytes(Fixtures::instance().alice().id())) +
dataSegment(32, seq);
}();
benchmarkThroughVm(
state, kWasmName, kImport, kData, kBody, [] { return Fixtures::instance().host(); });
}
BENCHMARK(escrowKeyletThroughVm)->UseManualTime()->Iterations(kBenchIterations);
void
escrowKeyletImpl(benchmark::State& state)
{
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.escrowKeylet(Fixtures::instance().alice().id(), Fixtures::kSeq);
});
}
BENCHMARK(escrowKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,45 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <tx/wasm/fixtures/FloatConstants.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
constexpr std::string_view kWasmName = "float_add";
constexpr std::string_view kImport =
R"( (import "host_lib" "float_add" (func $float_add (param i32 i32 i32 i32 i32 i32 i32) (result i32)))
)";
constexpr std::string_view kBody =
"(call $float_add (i32.const 0) (i32.const 12) (i32.const 16) (i32.const 12) "
"(i32.const 64) (i32.const 12) (i32.const 0))";
void
floatAddThroughVm(benchmark::State& state)
{
static auto const kData =
dataSegment(0, FloatConstants::kPi) + dataSegment(16, FloatConstants::kTwo);
benchmarkThroughVm(
state, kWasmName, kImport, kData, kBody, [] { return Fixtures::instance().host(); });
}
BENCHMARK(floatAddThroughVm)->UseManualTime()->Iterations(kBenchIterations);
void
floatAddImpl(benchmark::State& state)
{
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.floatAdd(Fixtures::floatX(), Fixtures::floatY(), Fixtures::kRoundingMode);
});
}
BENCHMARK(floatAddImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
floatCompareImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"float_cmp"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.floatCompare(Fixtures::floatX(), Fixtures::floatY()); });
}
BENCHMARK(floatCompareImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,27 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
floatDivideImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"float_div"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.floatDivide(
Fixtures::floatX(), Fixtures::floatY(), Fixtures::kRoundingMode);
});
}
BENCHMARK(floatDivideImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
floatFromIntImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"float_from_int"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.floatFromInt(3141592653589793, Fixtures::kRoundingMode); });
}
BENCHMARK(floatFromIntImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
floatFromMantExpImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"float_from_mant_exp"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.floatFromMantExp(3141592653589793, -15, Fixtures::kRoundingMode);
});
}
BENCHMARK(floatFromMantExpImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,31 @@
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/UintTypes.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
floatFromStAmountImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"float_from_stamount"};
auto const amount =
STAmount{Issue{toCurrency("USD"), Fixtures::instance().alice().id()}, 1234567, -3};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[&amount](auto& host) { return host.floatFromSTAmount(amount, Fixtures::kRoundingMode); });
}
BENCHMARK(floatFromStAmountImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,30 @@
#include <xrpl/basics/Number.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STNumber.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
floatFromStNumberImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"float_from_stnumber"};
auto const number = STNumber{sfNumber, Number(3141592653589793, -15)};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[&number](auto& host) { return host.floatFromSTNumber(number, Fixtures::kRoundingMode); });
}
BENCHMARK(floatFromStNumberImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
floatFromUintImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"float_from_uint"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.floatFromUint(3141592653589793u, Fixtures::kRoundingMode); });
}
BENCHMARK(floatFromUintImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,27 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
floatMultiplyImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"float_mult"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.floatMultiply(
Fixtures::floatX(), Fixtures::floatY(), Fixtures::kRoundingMode);
});
}
BENCHMARK(floatMultiplyImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,42 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <tx/wasm/fixtures/FloatConstants.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
constexpr std::string_view kWasmName = "float_pow";
constexpr std::string_view kImport =
R"( (import "host_lib" "float_pow" (func $float_pow (param i32 i32 i32 i32 i32 i32) (result i32)))
)";
constexpr std::string_view kBody =
"(call $float_pow (i32.const 0) (i32.const 12) (i32.const 7) "
"(i32.const 64) (i32.const 12) (i32.const 0))";
void
floatPowerThroughVm(benchmark::State& state)
{
static auto const kData = dataSegment(0, FloatConstants::kPi);
benchmarkThroughVm(
state, kWasmName, kImport, kData, kBody, [] { return Fixtures::instance().host(); });
}
BENCHMARK(floatPowerThroughVm)->UseManualTime()->Iterations(kBenchIterations);
void
floatPowerImpl(benchmark::State& state)
{
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.floatPower(Fixtures::floatX(), 7, Fixtures::kRoundingMode); });
}
BENCHMARK(floatPowerImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,27 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
floatSubtractImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"float_sub"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.floatSubtract(
Fixtures::floatX(), Fixtures::floatY(), Fixtures::kRoundingMode);
});
}
BENCHMARK(floatSubtractImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
floatToIntImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"float_to_int"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.floatToInt(Fixtures::floatX(), Fixtures::kRoundingMode); });
}
BENCHMARK(floatToIntImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,41 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <tx/wasm/fixtures/FloatConstants.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
constexpr std::string_view kWasmName = "float_to_mant_exp";
constexpr std::string_view kImport =
R"( (import "host_lib" "float_to_mant_exp" (func $split (param i32 i32 i32 i32 i32 i32) (result i32)))
)";
constexpr std::string_view kBody =
"(call $split (i32.const 0) (i32.const 12) (i32.const 64) (i32.const 8) "
"(i32.const 128) (i32.const 4))";
void
floatToMantExpThroughVm(benchmark::State& state)
{
static auto const kData = dataSegment(0, FloatConstants::kPi);
benchmarkThroughVm(
state, kWasmName, kImport, kData, kBody, [] { return Fixtures::instance().host(); });
}
BENCHMARK(floatToMantExpThroughVm)->UseManualTime()->Iterations(kBenchIterations);
void
floatToMantExpImpl(benchmark::State& state)
{
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.floatToMantExp(Fixtures::floatX()); });
}
BENCHMARK(floatToMantExpImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,34 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <tx/wasm/fixtures/NftSetup.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
getNFTImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"nft_uri"};
// A really minted token, so the lookup walks a real page rather than failing fast — a
// not-found answer would measure the rejection instead of the work.
static constexpr auto kUri = std::string_view{"ipfs://benchmark"};
// Its own ledger rather than the shared `Fixtures`: minting mutates state, and the shared
// one is deliberately read-only after construction.
static auto nft = WasmLedger{};
static auto const kOwner = nft.fund("benchNftOwner");
static auto const kMinted = mintNft(nft, kOwner, kUri);
benchmarkImpl(
state,
kWasmName,
[] { return nft.makeHost(); },
[](auto& host) { return host.getNFT(kOwner.id(), kMinted); });
}
BENCHMARK(getNFTImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,54 @@
#include <xrpl/protocol/Feature.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string>
#include <string_view>
namespace xrpl::test::bench {
namespace {
constexpr std::string_view kWasmName = "amendment_enabled";
std::string const&
benchAmendment()
{
static auto const kValue = std::string{"TokenEscrow"};
return kValue;
}
void
isAmendmentEnabledByIdImpl(benchmark::State& state)
{
auto const feature = getRegisteredFeature(benchAmendment());
if (!feature.has_value())
{
state.SkipWithError("the benchmarked amendment is not registered");
return;
}
auto const id = *feature;
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[&id](auto& host) { return host.isAmendmentEnabled(id); });
}
BENCHMARK(isAmendmentEnabledByIdImpl)->UseManualTime()->Iterations(kBenchIterations);
void
isAmendmentEnabledByNameImpl(benchmark::State& state)
{
// The gap over the id form is precisely what the shared price of 100 asserts does not exist.
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.isAmendmentEnabled(std::string_view{benchAmendment()}); });
}
BENCHMARK(isAmendmentEnabledByNameImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <xrpl/protocol/SField.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
ledgerObjArrayLenImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"le_arr_len"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().cachedSignerListHost(); },
[](auto& host) { return host.getLedgerObjArrayLen(1, sfSignerEntries); });
}
BENCHMARK(ledgerObjArrayLenImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <xrpl/protocol/SField.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
ledgerObjFieldImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"le_field"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().cachedHost(); },
[](auto& host) { return host.getLedgerObjField(1, sfAccount); });
}
BENCHMARK(ledgerObjFieldImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,29 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
ledgerObjNestedArrayLenImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"le_inner_arr_len"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().cachedSignerListHost(); },
[](auto& host) {
return host.getLedgerObjNestedArrayLen(1, FieldLocator{{sfSignerEntries.getCode()}});
});
}
BENCHMARK(ledgerObjNestedArrayLenImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,29 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
ledgerObjNestedFieldImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"le_inner"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().cachedHost(); },
[](auto& host) {
return host.getLedgerObjNestedField(1, FieldLocator{{sfAccount.getCode()}});
});
}
BENCHMARK(ledgerObjNestedFieldImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,38 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
constexpr std::string_view kWasmName = "ldgr_index";
constexpr std::string_view kImport =
R"( (import "host_lib" "ldgr_index" (func $ldgr_index (param i32 i32) (result i32)))
)";
void
ledgerSqnThroughVm(benchmark::State& state)
{
benchmarkThroughVm(
state, kWasmName, kImport, "", "(call $ldgr_index (i32.const 0) (i32.const 4))", [] {
return Fixtures::instance().host();
});
}
BENCHMARK(ledgerSqnThroughVm)->UseManualTime()->Iterations(kBenchIterations);
void
ledgerSqnImpl(benchmark::State& state)
{
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getLedgerSqn(); });
}
BENCHMARK(ledgerSqnImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
mptokenIssuanceKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"mpt_issuance_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.mptokenIssuanceKeylet(Fixtures::instance().alice().id(), Fixtures::kSeq);
});
}
BENCHMARK(mptokenIssuanceKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,31 @@
#include <xrpl/protocol/Indexes.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
mptokenKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"mptoken_id"};
auto const mptid = makeMptID(1, Fixtures::instance().alice().id());
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[&mptid](auto& host) {
return host.mptokenKeylet(mptid, Fixtures::instance().bob().id());
});
}
BENCHMARK(mptokenKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
nftFlagsImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"nft_flags"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getNFTFlags(Fixtures::instance().nftId()); });
}
BENCHMARK(nftFlagsImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
nftIssuerImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"nft_issuer"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getNFTIssuer(Fixtures::instance().nftId()); });
}
BENCHMARK(nftIssuerImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
nftSequenceImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"nft_serial"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getNFTSequence(Fixtures::instance().nftId()); });
}
BENCHMARK(nftSequenceImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
nftTaxonImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"nft_taxon"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getNFTTaxon(Fixtures::instance().nftId()); });
}
BENCHMARK(nftTaxonImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
nftTransferFeeImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"nft_xfer_fee"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getNFTTransferFee(Fixtures::instance().nftId()); });
}
BENCHMARK(nftTransferFeeImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
nftokenOfferKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"nft_offer_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.nftokenOfferKeylet(Fixtures::instance().alice().id(), Fixtures::kSeq);
});
}
BENCHMARK(nftokenOfferKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
offerKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"offer_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.offerKeylet(Fixtures::instance().alice().id(), Fixtures::kSeq);
});
}
BENCHMARK(offerKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
oracleKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"oracle_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.oracleKeylet(Fixtures::instance().alice().id(), Fixtures::kSeq);
});
}
BENCHMARK(oracleKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
parentLedgerHashImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"parent_ldgr_hash"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getParentLedgerHash(); });
}
BENCHMARK(parentLedgerHashImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
parentLedgerTimeImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"parent_ldgr_time"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getParentLedgerTime(); });
}
BENCHMARK(parentLedgerTimeImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,27 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
paychannelKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"paychan_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.paychannelKeylet(
Fixtures::instance().alice().id(), Fixtures::instance().bob().id(), Fixtures::kSeq);
});
}
BENCHMARK(paychannelKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
permissionedDomainKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"permissioned_domain_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.permissionedDomainKeylet(Fixtures::instance().alice().id(), Fixtures::kSeq);
});
}
BENCHMARK(permissionedDomainKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,69 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <cstddef>
#include <format>
#include <string_view>
namespace xrpl::test::bench {
namespace {
constexpr std::string_view kWasmName = "sha512_half";
constexpr std::string_view kImport =
R"( (import "host_lib" "sha512_half" (func $sha512_half (param i32 i32 i32 i32) (result i32)))
)";
void
sha512HalfThroughVm(benchmark::State& state)
{
// Zeroed guest memory is a perfectly good hash input: `sha512_half` validates nothing about
// its bytes, so there is no data segment to seed.
auto const body = std::format(
"(call $sha512_half (i32.const 0) (i32.const {}) (i32.const 8192) (i32.const 32))",
state.range(0));
// A hash is 32 bytes back to the guest whatever the input length, and the transfer budget
// counts only what the host writes — so the input sweep does not shrink the call count.
benchmarkThroughVm(
state,
kWasmName,
kImport,
"",
body,
[] { return Fixtures::instance().host(); },
callsWithinTransferBudget(32));
state.SetBytesProcessed(state.iterations() * state.range(0));
}
BENCHMARK(sha512HalfThroughVm)
->RangeMultiplier(8)
->Range(8, xrpl::kMaxWasmDataLength)
->UseManualTime()
->Iterations(kBenchIterations);
void
sha512HalfImpl(benchmark::State& state)
{
auto const data = Bytes(static_cast<std::size_t>(state.range(0)), 0x42);
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[&data](auto& host) {
return host.computeSha512HalfHash(Slice{data.data(), data.size()});
});
state.SetBytesProcessed(state.iterations() * state.range(0));
}
BENCHMARK(sha512HalfImpl)
->RangeMultiplier(8)
->Range(8, xrpl::kMaxWasmDataLength)
->UseManualTime()
->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,24 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
signerListKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"signers_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.signerListKeylet(Fixtures::instance().alice().id()); });
}
BENCHMARK(signerListKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
ticketKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"ticket_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.ticketKeylet(Fixtures::instance().alice().id(), Fixtures::kSeq);
});
}
BENCHMARK(ticketKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,40 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
constexpr std::string_view kWasmName = "trace";
constexpr std::string_view kMessage = "benchmark trace message";
constexpr std::string_view kData = "0123456789abcdef";
// The path a validator actually runs: journal pointed at a null sink.
void
traceDisabledImpl(benchmark::State& state)
{
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.trace(kMessage, kData); });
}
BENCHMARK(traceDisabledImpl)->UseManualTime()->Iterations(kBenchIterations);
// The same call against a host whose sink records what it is given. The gap over the case above
// is the cost the flat 30 does not cover.
void
traceEnabledImpl(benchmark::State& state)
{
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().tracingHost(); },
[](auto& host) { return host.trace(kMessage, kData); });
}
BENCHMARK(traceEnabledImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,31 @@
#include <xrpl/protocol/UintTypes.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
trustLineKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"trustline_id"};
auto const currency = toCurrency("USD");
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[&currency](auto& host) {
return host.trustLineKeylet(
Fixtures::instance().alice().id(), Fixtures::instance().bob().id(), currency);
});
}
BENCHMARK(trustLineKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <xrpl/protocol/SField.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
txArrayLenImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"tx_arr_len"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getTxArrayLen(sfMemos); });
}
BENCHMARK(txArrayLenImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <xrpl/protocol/SField.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
txFieldImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"tx_field"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getTxField(sfAccount); });
}
BENCHMARK(txFieldImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,27 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
txNestedArrayLenImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"tx_inner_arr_len"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getTxNestedArrayLen(FieldLocator{{sfMemos.getCode()}}); });
}
BENCHMARK(txNestedArrayLenImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,55 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <cstdint>
#include <string_view>
namespace xrpl::test::bench {
namespace {
constexpr std::string_view kWasmName = "tx_inner";
constexpr std::string_view kImport =
R"( (import "host_lib" "tx_inner" (func $tx_inner (param i32 i32 i32 i32) (result i32)))
)";
constexpr std::string_view kBody =
"(call $tx_inner (i32.const 0) (i32.const 12) (i32.const 64) (i32.const 64))";
void
txNestedFieldThroughVm(benchmark::State& state)
{
// The locator bytes are seeded rather than stored by the guest, so the loop measures the host
// call and not three `i32.store`s.
static auto const kData = dataSegment(0, [] {
auto bytes = Bytes{};
for (auto const step : {sfMemos.getCode(), 0, sfMemoData.getCode()})
{
for (auto i = 0U; i < 4; ++i)
{
bytes.push_back(static_cast<std::uint8_t>((step >> (8 * i)) & 0xFF));
}
}
return bytes;
}());
benchmarkThroughVm(
state, kWasmName, kImport, kData, kBody, [] { return Fixtures::instance().host(); });
}
BENCHMARK(txNestedFieldThroughVm)->UseManualTime()->Iterations(kBenchIterations);
void
txNestedFieldImpl(benchmark::State& state)
{
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) { return host.getTxNestedField(Fixtures::memoLocator()); });
}
BENCHMARK(txNestedFieldImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,62 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <cstddef>
#include <format>
#include <string_view>
namespace xrpl::test::bench {
namespace {
constexpr std::string_view kWasmName = "set_data";
constexpr std::string_view kImport =
R"( (import "host_lib" "set_data" (func $set_data (param i32 i32) (result i32)))
)";
void
updateDataThroughVm(benchmark::State& state)
{
auto const body = std::format("(call $set_data (i32.const 0) (i32.const {}))", state.range(0));
benchmarkThroughVm(
state,
kWasmName,
kImport,
"",
body,
[] { return Fixtures::instance().host(); },
// `set_data` answers a scalar and writes nothing into guest memory, so the
// transfer budget does not constrain it however large the input gets.
callsWithinTransferBudget(0));
state.SetBytesProcessed(state.iterations() * state.range(0));
}
BENCHMARK(updateDataThroughVm)
->RangeMultiplier(4)
->Range(8, kMaxWasmDataLength)
->UseManualTime()
->Iterations(kBenchIterations);
void
updateDataImpl(benchmark::State& state)
{
auto const data = Bytes(static_cast<std::size_t>(state.range(0)), 0x42);
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[&data](auto& host) { return host.updateData(Slice{data.data(), data.size()}); });
state.SetBytesProcessed(state.iterations() * state.range(0));
}
BENCHMARK(updateDataImpl)
->RangeMultiplier(4)
->Range(8, kMaxWasmDataLength)
->UseManualTime()
->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -0,0 +1,26 @@
#include <benchmark/benchmark.h>
#include <benchmarks/libxrpl/wasm/BenchFixtures.h>
#include <benchmarks/libxrpl/wasm/WasmBench.h>
#include <string_view>
namespace xrpl::test::bench {
namespace {
void
vaultKeyletImpl(benchmark::State& state)
{
static constexpr auto kWasmName = std::string_view{"vault_id"};
benchmarkImpl(
state,
kWasmName,
[] { return Fixtures::instance().host(); },
[](auto& host) {
return host.vaultKeylet(Fixtures::instance().alice().id(), Fixtures::kSeq);
});
}
BENCHMARK(vaultKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
} // namespace
} // namespace xrpl::test::bench

View File

@@ -1,488 +0,0 @@
#pragma once
#include <test/jtx/Env.h>
#include <test/unit_test/SuiteJournal.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/detail/ApplyViewBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/tx/wasm/HostFunc.h>
#include <xrpl/tx/wasm/WasmCommon.h>
#include <cstdint>
#include <expected>
#include <string>
#include <string_view>
namespace xrpl::test {
class TestLedgerDataProvider : public HostFunctions
{
jtx::Env& env_;
public:
TestLedgerDataProvider(jtx::Env& env) : HostFunctions(env.journal), env_(env)
{
}
[[nodiscard]] std::expected<std::uint32_t, HostFunctionError>
getLedgerSqn() const override
{
return env_.current()->seq();
}
};
class TestHostFunctions : public HostFunctions
{
protected:
test::jtx::Env& env_;
AccountID accountID_;
Bytes data_;
public:
TestHostFunctions(test::jtx::Env& env) : HostFunctions(env.journal), env_(env)
{
accountID_ = env.master.id();
std::string t = "10000";
data_ = Bytes{t.begin(), t.end()};
}
[[nodiscard]] std::expected<std::uint32_t, HostFunctionError>
getLedgerSqn() const override
{
return 12345;
}
[[nodiscard]] std::expected<std::uint32_t, HostFunctionError>
getParentLedgerTime() const override
{
return 67890;
}
[[nodiscard]] std::expected<Hash, HostFunctionError>
getParentLedgerHash() const override
{
return env_.current()->header().parentHash;
}
[[nodiscard]] std::expected<std::uint32_t, HostFunctionError>
getBaseFee() const override
{
return 10;
}
[[nodiscard]] std::expected<int32_t, HostFunctionError>
isAmendmentEnabled(uint256 const& amendmentId) const override
{
return 1;
}
[[nodiscard]] std::expected<int32_t, HostFunctionError>
isAmendmentEnabled(std::string_view const& amendmentName) const override
{
return 1;
}
std::expected<int32_t, HostFunctionError>
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override
{
return 1;
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
getTxField(SField const& fname) const override
{
if (fname == sfAccount)
return Bytes(accountID_.begin(), accountID_.end());
if (fname == sfFee)
{
int64_t x = 235;
auto const* p = reinterpret_cast<uint8_t const*>(&x);
return Bytes{p, p + sizeof(x)};
}
if (fname == sfSequence)
{
auto const x = getLedgerSqn();
if (!x)
return std::unexpected(x.error());
std::uint32_t const data = x.value();
auto const* b = reinterpret_cast<uint8_t const*>(&data);
auto const* e = reinterpret_cast<uint8_t const*>(&data + 1);
return Bytes{b, e};
}
return Bytes();
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
getCurrentLedgerObjField(SField const& fname) const override
{
auto const& sn = fname.getName();
if (sn == "Destination" || sn == "Account")
return Bytes(accountID_.begin(), accountID_.end());
if (sn == "Data")
return data_;
if (sn == "FinishAfter")
{
auto t = env_.current()->parentCloseTime().time_since_epoch().count();
std::string s = std::to_string(t);
return Bytes{s.begin(), s.end()};
}
// FieldNotFound is a guest-returnable code (the contract handles a negative result);
// Unimplemented now maps to a fatal Fault::Internal (tecINTERNAL) that stops the run.
return std::unexpected(HostFunctionError::FieldNotFound);
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
getLedgerObjField(int32_t, SField const& fname) const override
{
if (fname == sfBalance)
{
int64_t x = 10'000;
auto const* p = reinterpret_cast<uint8_t const*>(&x);
return Bytes{p, p + sizeof(x)};
}
if (fname == sfAccount)
return Bytes(accountID_.begin(), accountID_.end());
return data_;
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
getTxNestedField(FieldLocator const& locator) const override
{
if (locator.size() == 1)
{
int32_t const* l = locator.data();
int32_t const sfield = l[0];
if (sfield == sfAccount.getCode())
return Bytes(accountID_.begin(), accountID_.end());
}
uint8_t const a[] = {0x2b, 0x6a, 0x23, 0x2a, 0xa4, 0xc4, 0xbe, 0x41, 0xbf, 0x49, 0xd2,
0x45, 0x9f, 0xa4, 0xa0, 0x34, 0x7e, 0x1b, 0x54, 0x3a, 0x4c, 0x92,
0xfc, 0xee, 0x08, 0x21, 0xc0, 0x20, 0x1e, 0x2e, 0x9a, 0x00};
return Bytes(&a[0], &a[sizeof(a)]);
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
getCurrentLedgerObjNestedField(FieldLocator const& locator) const override
{
if (locator.size() == 1)
{
int32_t const* l = locator.data();
int32_t const sfield = l[0];
if (sfield == sfAccount.getCode())
return Bytes(accountID_.begin(), accountID_.end());
}
uint8_t const a[] = {0x2b, 0x6a, 0x23, 0x2a, 0xa4, 0xc4, 0xbe, 0x41, 0xbf, 0x49, 0xd2,
0x45, 0x9f, 0xa4, 0xa0, 0x34, 0x7e, 0x1b, 0x54, 0x3a, 0x4c, 0x92,
0xfc, 0xee, 0x08, 0x21, 0xc0, 0x20, 0x1e, 0x2e, 0x9a, 0x00};
return Bytes(&a[0], &a[sizeof(a)]);
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const override
{
if (locator.size() == 1)
{
int32_t const* l = locator.data();
int32_t const sfield = l[0];
if (sfield == sfAccount.getCode())
return Bytes(accountID_.begin(), accountID_.end());
}
uint8_t const a[] = {0x2b, 0x6a, 0x23, 0x2a, 0xa4, 0xc4, 0xbe, 0x41, 0xbf, 0x49, 0xd2,
0x45, 0x9f, 0xa4, 0xa0, 0x34, 0x7e, 0x1b, 0x54, 0x3a, 0x4c, 0x92,
0xfc, 0xee, 0x08, 0x21, 0xc0, 0x20, 0x1e, 0x2e, 0x9a, 0x00};
return Bytes(&a[0], &a[sizeof(a)]);
}
[[nodiscard]] std::expected<int32_t, HostFunctionError>
getTxArrayLen(SField const& fname) const override
{
return 32;
}
[[nodiscard]] std::expected<int32_t, HostFunctionError>
getCurrentLedgerObjArrayLen(SField const& fname) const override
{
return 32;
}
[[nodiscard]] std::expected<int32_t, HostFunctionError>
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const override
{
return 32;
}
[[nodiscard]] std::expected<int32_t, HostFunctionError>
getTxNestedArrayLen(FieldLocator const& locator) const override
{
return 32;
}
[[nodiscard]] std::expected<int32_t, HostFunctionError>
getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const override
{
return 32;
}
[[nodiscard]] std::expected<int32_t, HostFunctionError>
getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const override
{
return 32;
}
std::expected<int32_t, HostFunctionError>
updateData(Slice const& data) override
{
return data.size();
}
[[nodiscard]] std::expected<int32_t, HostFunctionError>
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) const override
{
return 1;
}
[[nodiscard]] std::expected<Hash, HostFunctionError>
computeSha512HalfHash(Slice const& data) const override
{
return env_.current()->header().parentHash;
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
accountKeylet(AccountID const& account) const override
{
if (!account)
return std::unexpected(HostFunctionError::InvalidAccount);
auto const keylet = keylet::account(account);
return Bytes{keylet.key.begin(), keylet.key.end()};
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
ammKeylet(Asset const& issue1, Asset const& issue2) const override
{
if (issue1 == issue2)
return std::unexpected(HostFunctionError::InvalidParams);
if (issue1.holds<MPTIssue>() || issue2.holds<MPTIssue>())
return std::unexpected(HostFunctionError::InvalidParams);
auto const keylet = keylet::amm(issue1, issue2);
return Bytes{keylet.key.begin(), keylet.key.end()};
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
checkKeylet(AccountID const& account, std::uint32_t seq) const override
{
if (!account)
return std::unexpected(HostFunctionError::InvalidAccount);
auto const keylet = keylet::check(account, SeqProxy::rawSequence(seq));
return Bytes{keylet.key.begin(), keylet.key.end()};
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
const override
{
if (!subject || !issuer || credentialType.empty() ||
credentialType.size() > kMaxCredentialTypeLength)
return std::unexpected(HostFunctionError::InvalidAccount);
auto const keylet = keylet::credential(subject, issuer, credentialType);
return Bytes{keylet.key.begin(), keylet.key.end()};
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
escrowKeylet(AccountID const& account, std::uint32_t seq) const override
{
if (!account)
return std::unexpected(HostFunctionError::InvalidAccount);
auto const keylet = keylet::escrow(account, SeqProxy::rawSequence(seq));
return Bytes{keylet.key.begin(), keylet.key.end()};
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
oracleKeylet(AccountID const& account, std::uint32_t documentId) const override
{
if (!account)
return std::unexpected(HostFunctionError::InvalidAccount);
auto const keylet = keylet::oracle(account, documentId);
return Bytes{keylet.key.begin(), keylet.key.end()};
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
getNFT(AccountID const& account, uint256 const& nftId) const override
{
if (!account || !nftId)
return std::unexpected(HostFunctionError::InvalidParams);
std::string s = "https://ripple.com";
return Bytes(s.begin(), s.end());
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
getNFTIssuer(uint256 const& nftId) const override
{
return Bytes(accountID_.begin(), accountID_.end());
}
[[nodiscard]] std::expected<std::uint32_t, HostFunctionError>
getNFTTaxon(uint256 const& nftId) const override
{
return 4;
}
[[nodiscard]] std::expected<int32_t, HostFunctionError>
getNFTFlags(uint256 const& nftId) const override
{
return 8;
}
[[nodiscard]] std::expected<int32_t, HostFunctionError>
getNFTTransferFee(uint256 const& nftId) const override
{
return 10;
}
[[nodiscard]] std::expected<std::uint32_t, HostFunctionError>
getNFTSequence(uint256 const& nftId) const override
{
return 4;
}
template <typename F>
void
log(std::string_view const& msg, F&& dataFn) const
{
#ifdef DEBUG_OUTPUT
auto& j = std::cerr;
#else
if (!getJournal().active(beast::Severity::Trace))
return;
auto j = getJournal().trace();
#endif
j << "WasmTrace: " << msg << " " << dataFn();
#ifdef DEBUG_OUTPUT
j << std::endl;
#endif
}
void
trace(std::string_view const& msg, std::string_view const& data) const override
{
log(msg, [&data] { return data; });
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
floatFromInt(int64_t x, int32_t mode) const override
{
return wasm_float::floatFromIntImpl(x, mode);
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
floatFromUint(uint64_t x, int32_t mode) const override
{
return wasm_float::floatFromUintImpl(x, mode);
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
floatFromSTAmount(STAmount const& x, int32_t mode) const override
{
return wasm_float::floatFromSTAmountImpl(x, mode);
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
floatFromSTNumber(STNumber const& x, int32_t mode) const override
{
return wasm_float::floatFromSTNumberImpl(x, mode);
}
[[nodiscard]] std::expected<int64_t, HostFunctionError>
floatToInt(Slice const& x, int32_t mode) const override
{
return wasm_float::floatToIntImpl(x, mode);
}
[[nodiscard]] std::expected<FloatPair, HostFunctionError>
floatToMantExp(Slice const& x) const override
{
return wasm_float::floatToMantExpImpl(x);
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const override
{
return wasm_float::floatFromMantExpImpl(mantissa, exponent, mode);
}
[[nodiscard]] std::expected<int32_t, HostFunctionError>
floatCompare(Slice const& x, Slice const& y) const override
{
return wasm_float::floatCompareImpl(x, y);
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
floatAdd(Slice const& x, Slice const& y, int32_t mode) const override
{
return wasm_float::floatAddImpl(x, y, mode);
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const override
{
return wasm_float::floatSubtractImpl(x, y, mode);
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const override
{
return wasm_float::floatMultiplyImpl(x, y, mode);
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
floatDivide(Slice const& x, Slice const& y, int32_t mode) const override
{
return wasm_float::floatDivideImpl(x, y, mode);
}
[[nodiscard]] std::expected<Bytes, HostFunctionError>
floatPower(Slice const& x, int32_t n, int32_t mode) const override
{
return wasm_float::floatPowerImpl(x, n, mode);
}
};
class TestHostFunctionsSink : public TestHostFunctions
{
test::StreamSink sink_;
public:
explicit TestHostFunctionsSink(test::jtx::Env& env)
: TestHostFunctions(env), sink_(beast::Severity::Debug)
{
j_ = beast::Journal(sink_);
}
test::StreamSink&
getSink()
{
return sink_;
}
};
} // namespace xrpl::test

View File

@@ -1,3 +1,4 @@
#if 0
#include <expected>
#ifdef _DEBUG
// #define DEBUG_OUTPUT 1
@@ -1430,3 +1431,4 @@ struct Wasm_test : public beast::unit_test::Suite
BEAST_DEFINE_TESTSUITE(Wasm, app, xrpl);
} // namespace xrpl::test
#endif

View File

@@ -1,3 +0,0 @@
**/target
**/debug
*.wasm

View File

@@ -1,180 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "all_host_functions"
version = "0.1.0"
dependencies = [
"xrpl-common-stdlib",
"xrpl-escrow-stdlib",
]
[[package]]
name = "block-buffer"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
dependencies = [
"hybrid-array",
]
[[package]]
name = "bs58"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
dependencies = [
"tinyvec",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "const-oid"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
"hybrid-array",
]
[[package]]
name = "digest"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
"block-buffer",
"const-oid",
"crypto-common",
]
[[package]]
name = "hybrid-array"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
dependencies = [
"typenum",
]
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "sha2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tinyvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "typenum"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "xrpl-common-stdlib"
version = "0.8.0"
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=error-and-trace#b008b097237ce0d1a2dffc72ba39dd9fc50020a9"
dependencies = [
"xrpl-macros",
]
[[package]]
name = "xrpl-escrow-stdlib"
version = "0.1.0"
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=error-and-trace#b008b097237ce0d1a2dffc72ba39dd9fc50020a9"
dependencies = [
"xrpl-common-stdlib",
]
[[package]]
name = "xrpl-macros"
version = "0.1.0"
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=error-and-trace#b008b097237ce0d1a2dffc72ba39dd9fc50020a9"
dependencies = [
"bs58",
"proc-macro2",
"quote",
"sha2",
"syn",
]

View File

@@ -1,22 +0,0 @@
[package]
name = "all_host_functions"
version = "0.1.0"
edition = "2024"
# This empty workspace definition keeps this project independent of the parent workspace
[workspace]
[lib]
crate-type = ["cdylib"]
[dependencies]
xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-common-stdlib", branch = "error-and-trace" }
xrpl-escrow = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-escrow-stdlib", branch = "error-and-trace" }
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
opt-level = "z"
lto = true

View File

@@ -1,760 +0,0 @@
#![cfg_attr(target_arch = "wasm32", no_std)]
#[cfg(not(target_arch = "wasm32"))]
extern crate std;
//
// Host Functions Test
// Tests 26 host functions (across 7 categories)
//
// With craft you can run this test with:
// craft test --project host_functions_test --test-case host_functions_test
//
// Amount Format Update:
// - XRP amounts now return as 8-byte serialized rippled objects
// - IOU and MPT amounts return in variable-length serialized format
// - Format details: https://xrpl.org/docs/references/protocol/binary-format#amount-fields
//
// Error Code Ranges:
// -100 to -199: Ledger Header Functions (3 functions)
// -200 to -299: Transaction Data Functions (5 functions)
// -300 to -399: Current Ledger Object Functions (4 functions)
// -400 to -499: Any Ledger Object Functions (5 functions)
// -500 to -599: Keylet Generation Functions (4 functions)
// -600 to -699: Utility Functions (4 functions)
// -700 to -799: Data Update Functions (1 function)
//
use xrpl_escrow::current_tx::escrow_finish::EscrowFinish;
use xrpl_std::current_tx::traits::TransactionCommonFields;
use xrpl_std::host;
use xrpl_std::host::trace::TraceDataType;
use xrpl_std::host::trace::{trace, trace_acct_buf, trace_hex, trace_num};
use xrpl_std::sfield;
#[unsafe(no_mangle)]
pub extern "C" fn escrow_finish() -> i32 {
let _ = trace("=== HOST FUNCTIONS TEST ===");
let _ = trace("Testing 26 host functions");
// Category 1: Ledger Header Data Functions (3 functions)
// Error range: -100 to -199
match test_ledger_header_functions() {
0 => (),
err => return err,
}
// Category 2: Transaction Data Functions (5 functions)
// Error range: -200 to -299
match test_transaction_data_functions() {
0 => (),
err => return err,
}
// Category 3: Current Ledger Object Functions (4 functions)
// Error range: -300 to -399
match test_current_ledger_object_functions() {
0 => (),
err => return err,
}
// Category 4: Any Ledger Object Functions (5 functions)
// Error range: -400 to -499
match test_any_ledger_object_functions() {
0 => (),
err => return err,
}
// Category 5: Keylet Generation Functions (4 functions)
// Error range: -500 to -599
match test_keylet_generation_functions() {
0 => (),
err => return err,
}
// Category 6: Utility Functions (4 functions)
// Error range: -600 to -699
match test_utility_functions() {
0 => (),
err => return err,
}
// Category 7: Data Update Functions (1 function)
// Error range: -700 to -799
match test_data_update_functions() {
0 => (),
err => return err,
}
let _ = trace("SUCCESS: All host function tests passed!");
1 // Success return code for WASM finish function
}
/// Test Category 1: Ledger Header Data Functions (3 functions)
/// - get_ledger_sqn() - Get ledger sequence number
/// - get_parent_ledger_time() - Get parent ledger timestamp
/// - get_parent_ledger_hash() - Get parent ledger hash
fn test_ledger_header_functions() -> i32 {
let _ = trace("--- Category 1: Ledger Header Functions ---");
// Test 1.1: get_ledger_sqn() - should return current ledger sequence number
let mut sqn_buffer = [0u8; 4];
let sqn_result = unsafe { host::ldgr_index(sqn_buffer.as_mut_ptr(), sqn_buffer.len()) };
if sqn_result <= 0 {
let _ = trace_num("ERROR: get_ledger_sqn failed:", sqn_result as i64);
return -101; // Ledger sequence number test failed
}
let ledger_sqn = u32::from_be_bytes(sqn_buffer);
let _ = trace_num("Ledger sequence number:", ledger_sqn as i64);
// Test 1.2: get_parent_ledger_time() - should return parent ledger timestamp
let mut time_buffer = [0u8; 4];
let time_result =
unsafe { host::parent_ldgr_time(time_buffer.as_mut_ptr(), time_buffer.len()) };
if time_result <= 0 {
let _ = trace_num("ERROR: get_parent_ledger_time failed:", time_result as i64);
return -102; // Parent ledger time test failed
}
let parent_ledger_time = u32::from_be_bytes(time_buffer);
let _ = trace_num("Parent ledger time:", parent_ledger_time as i64);
// Test 1.3: get_parent_ledger_hash() - should return parent ledger hash (32 bytes)
let mut hash_buffer = [0u8; 32];
let hash_result =
unsafe { host::parent_ldgr_hash(hash_buffer.as_mut_ptr(), hash_buffer.len()) };
if hash_result != 32 {
let _ = trace_num(
"ERROR: get_parent_ledger_hash wrong length:",
hash_result as i64,
);
return -103; // Parent ledger hash test failed - should be exactly 32 bytes
}
let _ = trace_hex("Parent ledger hash:", &hash_buffer);
let _ = trace("SUCCESS: Ledger header functions");
0
}
/// Test Category 2: Transaction Data Functions (5 functions)
/// Tests all functions for accessing current transaction data
fn test_transaction_data_functions() -> i32 {
let _ = trace("--- Category 2: Transaction Data Functions ---");
// Test 2.1: get_tx_field() - Basic transaction field access
// Test with Account field (required, 20 bytes)
let mut account_buffer = [0u8; 20];
let account_len = unsafe {
host::tx_field(
sfield::Account.into(),
account_buffer.as_mut_ptr(),
account_buffer.len(),
)
};
if account_len != 20 {
let _ = trace_num(
"ERROR: get_tx_field(Account) wrong length:",
account_len as i64,
);
return -201; // Basic transaction field test failed
}
let _ = trace_acct_buf("Transaction Account:", &account_buffer);
// Test with Fee field (XRP amount - 8 bytes in new serialized format)
// New format: XRP amounts are always 8 bytes (positive: value | cPositive flag, negative: just value)
let mut fee_buffer = [0u8; 8];
let fee_len = unsafe {
host::tx_field(
sfield::Fee.into(),
fee_buffer.as_mut_ptr(),
fee_buffer.len(),
)
};
if fee_len != 8 {
let _ = trace_num(
"ERROR: get_tx_field(Fee) wrong length (expected 8 bytes for XRP):",
fee_len as i64,
);
return -202; // Fee field test failed - XRP amounts should be exactly 8 bytes
}
let _ = trace_num("Transaction Fee length:", fee_len as i64);
let _ = trace_hex("Transaction Fee (serialized XRP amount):", &fee_buffer);
// Test with Sequence field (required, 4 bytes uint32)
let mut seq_buffer = [0u8; 4];
let seq_len = unsafe {
host::tx_field(
sfield::Sequence.into(),
seq_buffer.as_mut_ptr(),
seq_buffer.len(),
)
};
if seq_len != 4 {
let _ = trace_num(
"ERROR: get_tx_field(Sequence) wrong length:",
seq_len as i64,
);
return -203; // Sequence field test failed
}
let _ = trace_hex("Transaction Sequence:", &seq_buffer);
// NOTE: get_tx_field2() through get_tx_field6() have been deprecated.
// Use get_tx_field() with appropriate parameters for all transaction field access.
// Test 2.2: get_tx_nested_field() - Nested field access with locator
let locator = [
0x01_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8,
]; // Two int32s in little-endian: [1, 0]
let mut nested_buffer = [0u8; 32];
let nested_result = unsafe {
host::tx_inner(
locator.as_ptr(),
locator.len(),
nested_buffer.as_mut_ptr(),
nested_buffer.len(),
)
};
if nested_result < 0 {
let _ = trace_num(
"INFO: get_tx_nested_field not applicable:",
nested_result as i64,
);
// Expected - locator may not match transaction structure
} else {
let _ = trace_num("Nested field length:", nested_result as i64);
let _ = trace_hex("Nested field:", &nested_buffer[..nested_result as usize]);
}
// Test 2.3: get_tx_array_len() - Get array length
let signers_len = unsafe { host::tx_arr_len(sfield::Signers.into()) };
let _ = trace_num("Signers array length:", signers_len as i64);
let memos_len = unsafe { host::tx_arr_len(sfield::Memos.into()) };
let _ = trace_num("Memos array length:", memos_len as i64);
// Test 2.4: get_tx_nested_array_len() - Get nested array length with locator
let nested_array_len = unsafe { host::tx_inner_arr_len(locator.as_ptr(), locator.len()) };
if nested_array_len < 0 {
let _ = trace_num(
"INFO: get_tx_nested_array_len not applicable:",
nested_array_len as i64,
);
} else {
let _ = trace_num("Nested array length:", nested_array_len as i64);
}
let _ = trace("SUCCESS: Transaction data functions");
0
}
/// Test Category 3: Current Ledger Object Functions (4 functions)
/// Tests functions that access the current ledger object being processed
fn test_current_ledger_object_functions() -> i32 {
let _ = trace("--- Category 3: Current Ledger Object Functions ---");
// Test 3.1: get_current_ledger_obj_field() - Access field from current ledger object
// Test with Balance field (XRP amount - 8 bytes in new serialized format)
let mut balance_buffer = [0u8; 8];
let balance_result = unsafe {
host::home_le_field(
sfield::Balance.into(),
balance_buffer.as_mut_ptr(),
balance_buffer.len(),
)
};
if balance_result <= 0 {
let _ = trace_num(
"INFO: get_current_ledger_obj_field(Balance) failed (may be expected):",
balance_result as i64,
);
// This might fail if current ledger object doesn't have balance field
} else if balance_result == 8 {
let _ = trace_num(
"Current object balance length (XRP amount):",
balance_result as i64,
);
let _ = trace_hex(
"Current object balance (serialized XRP amount):",
&balance_buffer,
);
} else {
let _ = trace_num(
"Current object balance length (non-XRP amount):",
balance_result as i64,
);
let _ = trace_hex(
"Current object balance:",
&balance_buffer[..balance_result as usize],
);
}
// Test with Account field
let mut current_account_buffer = [0u8; 20];
let current_account_result = unsafe {
host::home_le_field(
sfield::Account.into(),
current_account_buffer.as_mut_ptr(),
current_account_buffer.len(),
)
};
if current_account_result <= 0 {
let _ = trace_num(
"INFO: get_current_ledger_obj_field(Account) failed:",
current_account_result as i64,
);
} else {
let _ = trace_acct_buf("Current ledger object account:", &current_account_buffer);
}
// Test 3.2: get_current_ledger_obj_nested_field() - Nested field access
let locator = [
0x01_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8,
]; // Two int32s in little-endian: [1, 0]
let mut current_nested_buffer = [0u8; 32];
let current_nested_result = unsafe {
host::home_le_inner(
locator.as_ptr(),
locator.len(),
current_nested_buffer.as_mut_ptr(),
current_nested_buffer.len(),
)
};
if current_nested_result < 0 {
let _ = trace_num(
"INFO: get_current_ledger_obj_nested_field not applicable:",
current_nested_result as i64,
);
} else {
let _ = trace_num("Current nested field length:", current_nested_result as i64);
let _ = trace_hex(
"Current nested field:",
&current_nested_buffer[..current_nested_result as usize],
);
}
// Test 3.3: get_current_ledger_obj_array_len() - Array length in current object
let current_array_len = unsafe { host::home_le_arr_len(sfield::Signers.into()) };
let _ = trace_num(
"Current object Signers array length:",
current_array_len as i64,
);
// Test 3.4: get_current_ledger_obj_nested_array_len() - Nested array length
let current_nested_array_len =
unsafe { host::home_le_inner_arr_len(locator.as_ptr(), locator.len()) };
if current_nested_array_len < 0 {
let _ = trace_num(
"INFO: get_current_ledger_obj_nested_array_len not applicable:",
current_nested_array_len as i64,
);
} else {
let _ = trace_num(
"Current nested array length:",
current_nested_array_len as i64,
);
}
let _ = trace("SUCCESS: Current ledger object functions");
0
}
/// Test Category 4: Any Ledger Object Functions (5 functions)
/// Tests functions that work with cached ledger objects
fn test_any_ledger_object_functions() -> i32 {
let _ = trace("--- Category 4: Any Ledger Object Functions ---");
// First we need to cache a ledger object to test the other functions
// Get the account from transaction and generate its keylet
let escrow_finish = EscrowFinish;
let account_id = escrow_finish.get_account().unwrap();
// Test 4.1: cache_le() - Cache a ledger object
let mut keylet_buffer = [0u8; 32];
let keylet_result = unsafe {
host::accountroot_id(
account_id.0.as_ptr(),
account_id.0.len(),
keylet_buffer.as_mut_ptr(),
keylet_buffer.len(),
)
};
if keylet_result != 32 {
let _ = trace_num(
"ERROR: accountroot_id failed for caching test:",
keylet_result as i64,
);
return -401; // Keylet generation failed for caching test
}
let cache_result = unsafe { host::cache_le(keylet_buffer.as_ptr(), keylet_result as usize, 0) };
if cache_result <= 0 {
let _ = trace_num(
"INFO: cache_le failed (expected with test fixtures):",
cache_result as i64,
);
// Test fixtures may not contain the account object - this is expected
// We'll test the interface but expect failures
// Test 4.2-4.5 with invalid slot (should fail gracefully)
let mut test_buffer = [0u8; 32];
// Test le_field with invalid slot
let field_result = unsafe {
host::le_field(
1,
sfield::Balance.into(),
test_buffer.as_mut_ptr(),
test_buffer.len(),
)
};
if field_result < 0 {
let _ = trace_num(
"INFO: le_field failed as expected (no cached object):",
field_result as i64,
);
}
// Test le_inner_field with invalid slot
let locator = [
0x01_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8,
]; // Two int32s in little-endian: [1, 0]
let nested_result = unsafe {
host::le_inner(
1,
locator.as_ptr(),
locator.len(),
test_buffer.as_mut_ptr(),
test_buffer.len(),
)
};
if nested_result < 0 {
let _ = trace_num(
"INFO: le_inner_field failed as expected:",
nested_result as i64,
);
}
// Test le_inner_arr_len with invalid slot
let array_result = unsafe { host::le_arr_len(1, sfield::Signers.into()) };
if array_result < 0 {
let _ = trace_num(
"INFO: le_inner_arr_len failed as expected:",
array_result as i64,
);
}
// Test get_ledger_obj_nested_array_len with invalid slot
let nested_array_result =
unsafe { host::le_inner_arr_len(1, locator.as_ptr(), locator.len()) };
if nested_array_result < 0 {
let _ = trace_num(
"INFO: get_ledger_obj_nested_array_len failed as expected:",
nested_array_result as i64,
);
}
let _ = trace("SUCCESS: Any ledger object functions (interface tested)");
return 0;
}
// If we successfully cached an object, test the access functions
let slot = cache_result;
let _ = trace_num("Successfully cached object in slot:", slot as i64);
// Test 4.2: le_field() - Access field from cached object
let mut cached_balance_buffer = [0u8; 8];
let cached_balance_result = unsafe {
host::le_field(
slot,
sfield::Balance.into(),
cached_balance_buffer.as_mut_ptr(),
cached_balance_buffer.len(),
)
};
if cached_balance_result <= 0 {
let _ = trace_num(
"INFO: le_field(Balance) failed:",
cached_balance_result as i64,
);
} else if cached_balance_result == 8 {
let _ = trace_num(
"Cached object balance length (XRP amount):",
cached_balance_result as i64,
);
let _ = trace_hex(
"Cached object balance (serialized XRP amount):",
&cached_balance_buffer,
);
} else {
let _ = trace_num(
"Cached object balance length (non-XRP amount):",
cached_balance_result as i64,
);
let _ = trace_hex(
"Cached object balance:",
&cached_balance_buffer[..cached_balance_result as usize],
);
}
// Test 4.3: le_inner_field() - Nested field from cached object
let locator = [
0x01_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8,
]; // Two int32s in little-endian: [1, 0]
let mut cached_nested_buffer = [0u8; 32];
let cached_nested_result = unsafe {
host::le_inner(
slot,
locator.as_ptr(),
locator.len(),
cached_nested_buffer.as_mut_ptr(),
cached_nested_buffer.len(),
)
};
if cached_nested_result < 0 {
let _ = trace_num(
"INFO: le_inner_field not applicable:",
cached_nested_result as i64,
);
} else {
let _ = trace_num("Cached nested field length:", cached_nested_result as i64);
let _ = trace_hex(
"Cached nested field:",
&cached_nested_buffer[..cached_nested_result as usize],
);
}
// Test 4.4: le_inner_arr_len() - Array length from cached object
let cached_array_len = unsafe { host::le_arr_len(slot, sfield::Signers.into()) };
let _ = trace_num(
"Cached object Signers array length:",
cached_array_len as i64,
);
// Test 4.5: get_ledger_obj_nested_array_len() - Nested array length from cached object
let cached_nested_array_len =
unsafe { host::le_inner_arr_len(slot, locator.as_ptr(), locator.len()) };
if cached_nested_array_len < 0 {
let _ = trace_num(
"INFO: get_ledger_obj_nested_array_len not applicable:",
cached_nested_array_len as i64,
);
} else {
let _ = trace_num(
"Cached nested array length:",
cached_nested_array_len as i64,
);
}
let _ = trace("SUCCESS: Any ledger object functions");
0
}
/// Test Category 5: Keylet Generation Functions (4 functions)
/// Tests keylet generation functions for different ledger entry types
fn test_keylet_generation_functions() -> i32 {
let _ = trace("--- Category 5: Keylet Generation Functions ---");
let escrow_finish = EscrowFinish;
let account_id = escrow_finish.get_account().unwrap();
// Test 5.1: accountroot_id() - Generate keylet for account
let mut accountroot_id_buffer = [0u8; 32];
let accountroot_id_result = unsafe {
host::accountroot_id(
account_id.0.as_ptr(),
account_id.0.len(),
accountroot_id_buffer.as_mut_ptr(),
accountroot_id_buffer.len(),
)
};
if accountroot_id_result != 32 {
let _ = trace_num(
"ERROR: accountroot_id failed:",
accountroot_id_result as i64,
);
return -501; // Account keylet generation failed
}
let _ = trace_hex("Account keylet:", &accountroot_id_buffer);
// Test 5.2: credential_keylet() - Generate keylet for credential
let mut credential_keylet_buffer = [0u8; 32];
let credential_keylet_result = unsafe {
host::credential_id(
account_id.0.as_ptr(), // Subject
account_id.0.len(),
account_id.0.as_ptr(), // Issuer - same account for test
account_id.0.len(),
b"TestType".as_ptr(), // Credential type
9usize, // Length of "TestType"
credential_keylet_buffer.as_mut_ptr(),
credential_keylet_buffer.len(),
)
};
if credential_keylet_result <= 0 {
let _ = trace_num(
"INFO: credential_keylet failed (expected - interface issue):",
credential_keylet_result as i64,
);
// This is expected to fail due to unusual parameter types
} else {
let _ = trace_hex(
"Credential keylet:",
&credential_keylet_buffer[..credential_keylet_result as usize],
);
}
// Test 5.3: escrow_keylet() - Generate keylet for escrow
let mut escrow_keylet_buffer = [0u8; 32];
let sequence_number: i32 = 1000;
let sequence_number_bytes = sequence_number.to_be_bytes();
let escrow_keylet_result = unsafe {
host::escrow_id(
account_id.0.as_ptr(),
account_id.0.len(),
sequence_number_bytes.as_ptr(),
sequence_number_bytes.len(),
escrow_keylet_buffer.as_mut_ptr(),
escrow_keylet_buffer.len(),
)
};
if escrow_keylet_result != 32 {
let _ = trace_num("ERROR: escrow_keylet failed:", escrow_keylet_result as i64);
return -503; // Escrow keylet generation failed
}
let _ = trace_hex("Escrow keylet:", &escrow_keylet_buffer);
// Test 5.4: oracle_keylet() - Generate keylet for oracle
let mut oracle_keylet_buffer = [0u8; 32];
let document_id: i32 = 42;
let document_id_bytes = document_id.to_be_bytes();
let oracle_keylet_result = unsafe {
host::oracle_id(
account_id.0.as_ptr(),
account_id.0.len(),
document_id_bytes.as_ptr(),
document_id_bytes.len(),
oracle_keylet_buffer.as_mut_ptr(),
oracle_keylet_buffer.len(),
)
};
if oracle_keylet_result != 32 {
let _ = trace_num("ERROR: oracle_keylet failed:", oracle_keylet_result as i64);
return -504; // Oracle keylet generation failed
}
let _ = trace_hex("Oracle keylet:", &oracle_keylet_buffer);
let _ = trace("SUCCESS: Keylet generation functions");
0
}
/// Test Category 6: Utility Functions (4 functions)
/// Tests utility functions for hashing, NFT access, and tracing
fn test_utility_functions() -> i32 {
let _ = trace("--- Category 6: Utility Functions ---");
// Test 6.1: compute_sha512_half() - SHA512 hash computation (first 32 bytes)
let test_data = b"Hello, XRPL WASM world!";
let mut hash_output = [0u8; 32];
let hash_result = unsafe {
host::sha512_half(
test_data.as_ptr(),
test_data.len(),
hash_output.as_mut_ptr(),
hash_output.len(),
)
};
if hash_result != 32 {
let _ = trace_num("ERROR: compute_sha512_half failed:", hash_result as i64);
return -601; // SHA512 half computation failed
}
let _ = trace_hex("Input data:", test_data);
let _ = trace_hex("SHA512 half hash:", &hash_output);
// Test 6.2: get_nft() - NFT data retrieval
let escrow_finish = EscrowFinish;
let account_id = escrow_finish.get_account().unwrap();
let nft_id = [0u8; 32]; // Dummy NFT ID for testing
let mut nft_buffer = [0u8; 256];
let nft_result = unsafe {
host::nft_uri(
account_id.0.as_ptr(),
account_id.0.len(),
nft_id.as_ptr(),
nft_id.len(),
nft_buffer.as_mut_ptr(),
nft_buffer.len(),
)
};
if nft_result <= 0 {
let _ = trace_num(
"INFO: get_nft failed (expected - no such NFT):",
nft_result as i64,
);
// This is expected - test account likely doesn't own the dummy NFT
} else {
let _ = trace_num("NFT data length:", nft_result as i64);
let _ = trace_hex("NFT data:", &nft_buffer[..nft_result as usize]);
}
// Test 6.3: trace() - Debug logging with data
let trace_message = b"Test trace message";
let trace_data_payload = b"payload";
unsafe {
host::trace(
trace_message.as_ptr(),
trace_message.len(),
TraceDataType::AsHex as i32,
trace_data_payload.as_ptr(),
trace_data_payload.len(),
)
};
// Test 6.4: trace_num() - Debug logging with number
let test_number = 42i64;
trace_num("Test number trace", test_number);
let _ = trace("SUCCESS: Utility functions");
0
}
/// Test Category 7: Data Update Functions (1 function)
/// Tests the function for modifying the current ledger entry
fn test_data_update_functions() -> i32 {
let _ = trace("--- Category 7: Data Update Functions ---");
// Test 7.1: update_data() - Update current ledger entry data
let update_payload = b"Updated ledger entry data from WASM test";
let update_result = unsafe { host::set_data(update_payload.as_ptr(), update_payload.len()) };
if update_result != update_payload.len() as i32 {
let _ = trace_num("ERROR: update_data failed:", update_result as i64);
return -701; // Data update failed
}
let _ = trace_hex("Successfully updated ledger entry with:", update_payload);
let _ = trace("SUCCESS: Data update functions");
0
}

View File

@@ -1,171 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "all_keylets"
version = "0.0.1"
dependencies = [
"xrpl-wasm-stdlib",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bs58"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
dependencies = [
"tinyvec",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tinyvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "typenum"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "xrpl-macros"
version = "0.1.0"
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#21c522f34a24b460297ebb6be1822680459bf37e"
dependencies = [
"bs58",
"quote",
"sha2",
"syn",
]
[[package]]
name = "xrpl-wasm-stdlib"
version = "0.8.0"
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#21c522f34a24b460297ebb6be1822680459bf37e"
dependencies = [
"xrpl-macros",
]

View File

@@ -1,21 +0,0 @@
[package]
edition = "2024"
name = "all_keylets"
version = "0.0.1"
# This empty workspace definition keeps this project independent of the parent workspace
[workspace]
[lib]
crate-type = ["cdylib"]
[profile.release]
lto = true
opt-level = 's'
panic = "abort"
[dependencies]
xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-wasm-stdlib", branch = "renames" }
[profile.dev]
panic = "abort"

View File

@@ -1,176 +0,0 @@
#![cfg_attr(target_arch = "wasm32", no_std)]
#[cfg(not(target_arch = "wasm32"))]
extern crate std;
use crate::host::{Error, Result, Result::Err, Result::Ok};
use xrpl_std::core::keylets;
use xrpl_std::core::ledger_objects::current_escrow::get_current_escrow;
use xrpl_std::core::ledger_objects::current_escrow::CurrentEscrow;
use xrpl_std::core::ledger_objects::ledger_object;
use xrpl_std::core::ledger_objects::traits::CurrentEscrowFields;
use xrpl_std::core::ledger_objects::LedgerObjectFieldGetter;
use xrpl_std::core::types::currency::Currency;
use xrpl_std::core::types::issue::{IouIssue, Issue, XrpIssue};
use xrpl_std::core::types::mpt_id::MptId;
use xrpl_std::host;
use xrpl_std::host::trace::{trace, trace_acct, trace_data, trace_num, DataRepr};
use xrpl_std::sfield;
pub fn object_exists<T: LedgerObjectFieldGetter, const CODE: i32>(
keylet_result: Result<keylets::KeyletBytes>,
keylet_type: &str,
sfield: sfield::SField<T, CODE>,
) -> Result<bool> {
let field = CODE;
match keylet_result {
Ok(keylet) => {
let _ = trace_data(keylet_type, &keylet, DataRepr::AsHex);
let slot = unsafe { host::cache_le(keylet.as_ptr(), keylet.len(), 0) };
if slot <= 0 {
let _ = trace_num("Error: ", slot.into());
return Err(Error::from_code(slot));
}
if field == 0 {
let new_field = sfield::PreviousTxnID;
let _ = trace_num("Getting field: ", new_field.clone().into());
match ledger_object::get_field(slot, new_field) {
Ok(data) => {
let _ = trace_data("Field data: ", &data.0, DataRepr::AsHex);
}
Err(result_code) => {
let _ = trace_num("Error getting field: ", result_code.into());
return Err(result_code);
}
}
} else {
let _ = trace_num("Getting field: ", field.into());
match ledger_object::get_field(slot, sfield) {
Ok(_data) => {
let _ = trace("Field data: retrieved");
}
Err(result_code) => {
let _ = trace_num("Error getting field: ", result_code.into());
return Err(result_code);
}
}
}
Ok(true)
}
Err(error) => {
let _ = trace_num("Error getting keylet: ", error.into());
Err(error)
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn escrow_finish() -> i32 {
let _ = trace("$$$$$ STARTING WASM EXECUTION $$$$$");
let escrow: CurrentEscrow = get_current_escrow();
let account = escrow.get_account().unwrap_or_panic();
let _ = trace_acct("Account:", &account);
let destination = escrow.get_destination().unwrap_or_panic();
let _ = trace_acct("Destination:", &destination);
let mut seq = 5;
macro_rules! check_object_exists {
($keylet:expr, $type:expr, $field:expr) => {
match object_exists($keylet, $type, $field) {
Ok(_exists) => {
// false isn't returned
let _ = trace(concat!(
$type,
" object exists, proceeding with escrow finish."
));
}
Err(error) => {
let _ = trace_num("Current seq value:", seq.try_into().unwrap());
return error.code();
}
}
};
}
let accountroot_id = keylets::accountroot_id(&account);
check_object_exists!(accountroot_id, "Account", sfield::Account);
let currency_code: &[u8; 3] = b"USD";
let currency: Currency = Currency::from(*currency_code);
let trustline_id = keylets::trustline_id(&account, &destination, &currency);
check_object_exists!(trustline_id, "Trustline", sfield::Generic);
seq += 1;
let asset1 = Issue::XRP(XrpIssue {});
let asset2 = Issue::IOU(IouIssue::new(destination, currency));
check_object_exists!(keylets::amm_id(&asset1, &asset2), "AMM", sfield::Account);
let check_id = keylets::check_id(&account, seq);
check_object_exists!(check_id, "Check", sfield::Account);
seq += 1;
let cred_type: &[u8] = b"termsandconditions";
let credential_id = keylets::credential_id(&account, &account, cred_type);
check_object_exists!(credential_id, "Credential", sfield::Subject);
seq += 1;
let delegate_id = keylets::delegate_id(&account, &destination);
check_object_exists!(delegate_id, "Delegate", sfield::Account);
seq += 1;
let deposit_preauth_id = keylets::deposit_preauth_id(&account, &destination);
check_object_exists!(deposit_preauth_id, "DepositPreauth", sfield::Account);
seq += 1;
let did_id = keylets::did_id(&account);
check_object_exists!(did_id, "DID", sfield::Account);
seq += 1;
let escrow_id = keylets::escrow_id(&account, seq);
check_object_exists!(escrow_id, "Escrow", sfield::Account);
seq += 1;
let mpt_issuance_id = keylets::mpt_issuance_id(&account, seq);
let mpt_id = MptId::new(seq.try_into().unwrap(), account);
check_object_exists!(mpt_issuance_id, "MPTIssuance", sfield::Issuer);
seq += 1;
let mptoken_id = keylets::mptoken_id(&mpt_id, &destination);
check_object_exists!(mptoken_id, "MPToken", sfield::Account);
let nft_offer_id = keylets::nft_offer_id(&destination, 6);
check_object_exists!(nft_offer_id, "NFTokenOffer", sfield::Owner);
let offer_id = keylets::offer_id(&account, seq);
check_object_exists!(offer_id, "Offer", sfield::Account);
seq += 1;
let paychan_id = keylets::paychan_id(&account, &destination, seq);
check_object_exists!(paychan_id, "PayChannel", sfield::Account);
seq += 1;
let pd_id = keylets::permissioned_domain_id(&account, seq);
check_object_exists!(pd_id, "PermissionedDomain", sfield::Owner);
seq += 1;
let signers_id = keylets::signers_id(&account);
check_object_exists!(signers_id, "SignerList", sfield::Generic);
seq += 1;
seq += 1; // ticket sequence number is one greater
let ticket_id = keylets::ticket_id(&account, seq);
check_object_exists!(ticket_id, "Ticket", sfield::Account);
seq += 1;
let vault_id = keylets::vault_id(&account, seq);
check_object_exists!(vault_id, "Vault", sfield::Account);
// seq += 1;
1 // All keylets exist, finish the escrow.
}

View File

@@ -1,42 +0,0 @@
#include <stdint.h>
int32_t float_from_uint(uint8_t const *, int32_t, uint8_t *, int32_t, int32_t);
int32_t check_id(uint8_t const *, int32_t, uint8_t const *, int32_t, uint8_t *,
int32_t);
uint8_t e_data1[32 * 1024];
uint8_t e_data2[32 * 1024];
int32_t test1()
{
e_data1[1] = 0xFF;
e_data1[2] = 0xFF;
e_data1[3] = 0xFF;
e_data1[4] = 0xFF;
e_data1[5] = 0xFF;
e_data1[6] = 0xFF;
e_data1[7] = 0xFF;
e_data1[8] = 0xFF;
int32_t result = float_from_uint(&e_data1[1], 8, &e_data1[35], 12, 0);
return result >= 0 ? *((int32_t *)(&e_data1[36])) : result;
}
int32_t test2()
{
// Set up misaligned uint32 (seq) at offset 1
e_data2[1] = 0xFF;
e_data2[2] = 0xFF;
e_data2[3] = 0xFF;
e_data2[4] = 0xFF;
// Set up valid non-zero AccountID (20 bytes) at offset 10
for (int i = 0; i < 20; i++)
e_data2[10 + i] = i + 1;
// Call check_id with misaligned uint32 at &e_data2[1] to hit line 72 in
// HostFuncWrapper.cpp
int32_t result = check_id(&e_data2[10], 20, &e_data2[1], 4, &e_data2[35], 32);
// Return the misaligned value directly to validate it was read correctly (-1
// if all 0xFF)
return result >= 0 ? *((int32_t *)(&e_data2[36])) : result;
}
int32_t test() { return test1() + test2(); }

View File

@@ -1,180 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "block-buffer"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
dependencies = [
"hybrid-array",
]
[[package]]
name = "bs58"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
dependencies = [
"tinyvec",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "codecov_tests"
version = "0.0.1"
dependencies = [
"xrpl-common-stdlib",
"xrpl-escrow-stdlib",
]
[[package]]
name = "const-oid"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
"hybrid-array",
]
[[package]]
name = "digest"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
"block-buffer",
"const-oid",
"crypto-common",
]
[[package]]
name = "hybrid-array"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
dependencies = [
"typenum",
]
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "sha2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tinyvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "typenum"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "xrpl-common-stdlib"
version = "0.9.0"
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#e88dc32a48fac9d0eb6e7239c8a38693221657f6"
dependencies = [
"xrpl-macros",
]
[[package]]
name = "xrpl-escrow-stdlib"
version = "0.9.0"
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#e88dc32a48fac9d0eb6e7239c8a38693221657f6"
dependencies = [
"xrpl-common-stdlib",
]
[[package]]
name = "xrpl-macros"
version = "0.9.0"
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#e88dc32a48fac9d0eb6e7239c8a38693221657f6"
dependencies = [
"bs58",
"proc-macro2",
"quote",
"sha2",
"syn",
]

View File

@@ -1,19 +0,0 @@
[package]
edition = "2024"
name = "codecov_tests"
version = "0.0.1"
# This empty workspace definition keeps this project independent of the parent workspace
[workspace]
[lib]
crate-type = ["cdylib"]
[profile.release]
lto = true
opt-level = 's'
panic = "abort"
[dependencies]
xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-common-stdlib" }
xrpl-escrow = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-escrow-stdlib" }

View File

@@ -1,56 +0,0 @@
//TODO add docs after discussing the interface
//Note that Craft currently does not honor the rounding modes
#[allow(unused)]
pub const FLOAT_ROUNDING_MODES_TO_NEAREST: i32 = 0;
#[allow(unused)]
pub const FLOAT_ROUNDING_MODES_TOWARDS_ZERO: i32 = 1;
#[allow(unused)]
pub const FLOAT_ROUNDING_MODES_DOWNWARD: i32 = 2;
#[allow(unused)]
pub const FLOAT_ROUNDING_MODES_UPWARD: i32 = 3;
// pub enum RippledRoundingModes{
// ToNearest = 0,
// TowardsZero = 1,
// DOWNWARD = 2,
// UPWARD = 3
// }
#[allow(unused)]
#[link(wasm_import_module = "host_lib")]
unsafe extern "C" {
pub fn parent_ldgr_hash(out_buff_ptr: i32, out_buff_len: i32) -> i32;
pub fn cache_le(keylet_ptr: i32, keylet_len: i32, cache_num: i32) -> i32;
pub fn tx_inner_arr_len(locator_ptr: i32, locator_len: i32) -> i32;
pub fn accountroot_id(
account_ptr: i32,
account_len: i32,
out_buff_ptr: *mut u8,
out_buff_len: usize,
) -> i32;
pub fn trustline_id(
account1_ptr: *const u8,
account1_len: usize,
account2_ptr: *const u8,
account2_len: usize,
currency_ptr: i32,
currency_len: i32,
out_buff_ptr: *mut u8,
out_buff_len: usize,
) -> i32;
// Same wasm functype as the real binding, so this is not a second import of
// host_lib.trace. Loose i32 pointers exercise the out-of-bounds path.
#[link_name = "trace"]
pub fn trace_loose(
msg_read_ptr: i32,
msg_read_len: i32,
data_type: i32,
data_read_ptr: i32,
data_read_len: i32,
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,302 +0,0 @@
# cspell: disable
import os
import re
import shlex
import subprocess
import sys
import tempfile
import zipfile
from difflib import get_close_matches
OPT = "-Oz"
BASE_PATH = os.path.abspath(os.path.dirname(__file__))
def pascal_case(name):
return "".join(word[:1].upper() + word[1:] for word in re.split(r"[_\W]+", name))
def normalize_name(name):
name = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name)
return re.sub(r"[^a-z0-9]", "", name.lower())
def fixture_key(name):
name = normalize_name(name).removeprefix("k")
return name.removesuffix("wasmhex").removesuffix("hex")
def declared_fixtures():
h_path = os.path.join(BASE_PATH, "fixtures.h")
with open(h_path, "r", encoding="utf8") as f:
return re.findall(
r"extern std::string const ([A-Za-z_][A-Za-z0-9_]*);", f.read()
)
def find_fixture_name(project_name, suffix):
default = re.sub(r"_([a-z])", lambda m: m.group(1).upper(), project_name) + suffix
k_default = f"k{pascal_case(project_name)}{suffix}"
declarations = declared_fixtures()
normalized = {normalize_name(name): name for name in declarations}
fixture_keys = {fixture_key(name): name for name in declarations}
for name in (default, k_default):
if normalize_name(name) in normalized:
return normalized[normalize_name(name)]
project_key = normalize_name(project_name)
matches = [
name
for key, name in fixture_keys.items()
if key.endswith(project_key)
or key.startswith(project_key)
or project_key.endswith(key)
or project_key.startswith(key)
]
if len(matches) == 1:
return matches[0]
close = get_close_matches(project_key, fixture_keys.keys(), n=1, cutoff=0.82)
if close:
return fixture_keys[close[0]]
return k_default
def fixture_cpp_path(fixture_name):
pattern = rf"extern std::string const {fixture_name} ="
for file_name in os.listdir(BASE_PATH):
if not file_name.endswith(".cpp"):
continue
cpp_path = os.path.join(BASE_PATH, file_name)
with open(cpp_path, "r", encoding="utf8") as f:
if re.search(pattern, f.read()):
return cpp_path
return os.path.join(BASE_PATH, "fixtures.cpp")
def update_fixture(project_name, wasm, suffix="WasmHex"):
fixture_name = find_fixture_name(project_name, suffix)
print(f"Updating fixture: {fixture_name}")
cpp_path = fixture_cpp_path(fixture_name)
h_path = os.path.join(BASE_PATH, "fixtures.h")
with open(cpp_path, "r", encoding="utf8") as f:
cpp_content = f.read()
pattern = rf'extern std::string const {fixture_name} =[ \n]+"[^;]*;'
if re.search(pattern, cpp_content, flags=re.MULTILINE):
updated_cpp_content = re.sub(
pattern,
f'extern std::string const {fixture_name} = "{wasm}";',
cpp_content,
flags=re.MULTILINE,
)
else:
with open(h_path, "r", encoding="utf8") as f:
h_content = f.read()
updated_h_content = (
h_content.rstrip() + f"\n\nextern std::string const {fixture_name};\n"
)
with open(h_path, "w", encoding="utf8") as f:
f.write(updated_h_content)
updated_cpp_content = (
cpp_content.rstrip()
+ f'\n\nextern std::string const {fixture_name} = "{wasm}";\n'
)
with open(cpp_path, "w", encoding="utf8") as f:
f.write(updated_cpp_content)
def read_wasm_hex(path):
with open(path, "rb") as f:
return f.read().hex()
def process_rust(project_name):
project_path = os.path.join(BASE_PATH, project_name)
wasm_location = os.path.join(
project_path, "target", "wasm32v1-none", "release", f"{project_name}.wasm"
)
try:
subprocess.run(
["cargo", "build", "--target", "wasm32v1-none", "--release"],
cwd=project_path,
check=True,
)
subprocess.run(
["wasm-opt", wasm_location, OPT, "-o", wasm_location], check=True
)
print(f"WASM file for {project_name} has been built and optimized.")
except FileNotFoundError as e:
print(f"exec error: {e.filename} is required to build Rust fixtures")
sys.exit(1)
except subprocess.CalledProcessError as e:
print(f"exec error: {e}")
sys.exit(1)
update_fixture(project_name, read_wasm_hex(wasm_location))
def process_c(project_name):
project_path = os.path.join(BASE_PATH, f"{project_name}.c")
wasm_path = os.path.join(BASE_PATH, f"{project_name}.wasm")
cc = os.environ.get("CC")
sysroot = os.environ.get("SYSROOT")
if not cc or not sysroot:
print("exec error: CC and SYSROOT are required to build C fixtures")
sys.exit(1)
build_cmd = [
*shlex.split(cc),
f"--sysroot={sysroot}",
"-O3",
"-ffast-math",
"--target=wasm32",
"-fno-exceptions",
"-fno-threadsafe-statics",
"-fvisibility=default",
"-Wl,--export-all",
"-Wl,--no-entry",
"-Wl,--allow-undefined",
"-DNDEBUG",
"--no-standard-libraries",
"-fno-builtin-memset",
"-o",
wasm_path,
project_path,
]
try:
subprocess.run(build_cmd, check=True)
subprocess.run(["wasm-opt", wasm_path, OPT, "-o", wasm_path], check=True)
print(
f"WASM file for {project_name} has been built with WASI support using clang."
)
except FileNotFoundError as e:
print(f"exec error: {e.filename} is required to build C fixtures")
sys.exit(1)
except subprocess.CalledProcessError as e:
print(f"exec error: {e}")
sys.exit(1)
update_fixture(project_name, read_wasm_hex(wasm_path))
def wat_to_wasm(wat_path, wasm_path):
build_cmd = ["wat2wasm", "--enable-all", wat_path, "-o", wasm_path]
try:
subprocess.run(build_cmd, check=True)
print(f"WASM file for {os.path.basename(wat_path)} has been built.")
return
except FileNotFoundError:
print("exec error: wat2wasm is required to build WAT fixtures")
sys.exit(1)
except subprocess.CalledProcessError:
# wat2wasm (wabt) does not support some proposal text syntax such as
# the GC instructions, so fall back to wasm-tools which does.
pass
fallback_cmd = ["wasm-tools", "parse", wat_path, "-o", wasm_path]
try:
subprocess.run(fallback_cmd, check=True)
print(
f"WASM file for {os.path.basename(wat_path)} has been built with wasm-tools."
)
except FileNotFoundError:
print("exec error: wasm-tools is required to build this WAT fixture")
sys.exit(1)
except subprocess.CalledProcessError as e:
print(f"exec error: {e}")
sys.exit(1)
def process_wat_file(wat_path):
project_name = os.path.splitext(os.path.basename(wat_path))[0]
with open(wat_path, "r", encoding="utf8") as f:
if "(module" not in f.read():
print(f"Skipping WAT fixture without a module: {project_name}")
return
with tempfile.TemporaryDirectory() as tmpdir:
wasm_path = os.path.join(tmpdir, f"{project_name}.wasm")
wat_to_wasm(wat_path, wasm_path)
update_fixture(project_name, read_wasm_hex(wasm_path), "Hex")
def process_wat_zip(zip_path):
project_name = os.path.splitext(os.path.basename(zip_path))[0]
with tempfile.TemporaryDirectory() as tmpdir:
with zipfile.ZipFile(zip_path) as archive:
wat_names = [name for name in archive.namelist() if name.endswith(".wat")]
if len(wat_names) != 1:
print(f"exec error: expected one .wat file in {zip_path}")
sys.exit(1)
archive.extract(wat_names[0], tmpdir)
wasm_path = os.path.join(tmpdir, f"{project_name}.wasm")
wat_to_wasm(os.path.join(tmpdir, wat_names[0]), wasm_path)
update_fixture(project_name, read_wasm_hex(wasm_path), "Hex")
def process_wat(project_name):
candidates = [
os.path.join(BASE_PATH, f"{project_name}.wat"),
os.path.join(BASE_PATH, "wat", f"{project_name}.wat"),
os.path.join(BASE_PATH, "wat", f"{project_name}.zip"),
]
for path in candidates:
if os.path.isfile(path):
if path.endswith(".zip"):
process_wat_zip(path)
else:
process_wat_file(path)
return
print(f"exec error: fixture {project_name} not found")
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) > 2:
print("Usage: python copyFixtures.py [<project_name>]")
sys.exit(1)
if len(sys.argv) == 2:
project_name = os.path.splitext(os.path.basename(sys.argv[1]))[0]
if os.path.isfile(os.path.join(BASE_PATH, project_name, "Cargo.toml")):
process_rust(project_name)
elif os.path.isfile(os.path.join(BASE_PATH, f"{project_name}.c")):
process_c(project_name)
else:
process_wat(project_name)
print("Fixture has been processed.")
else:
dirs = [
d
for d in os.listdir(BASE_PATH)
if os.path.isfile(os.path.join(BASE_PATH, d, "Cargo.toml"))
]
c_files = [f for f in os.listdir(BASE_PATH) if f.endswith(".c")]
wat_files = [f for f in os.listdir(BASE_PATH) if f.endswith(".wat")]
wat_path = os.path.join(BASE_PATH, "wat")
wat_fixture_files = [
f
for f in (os.listdir(wat_path) if os.path.isdir(wat_path) else [])
if f.endswith((".wat", ".zip"))
]
for d in sorted(dirs):
process_rust(d)
for c in sorted(c_files):
process_c(c[:-2])
for wat in sorted(wat_files):
process_wat_file(os.path.join(BASE_PATH, wat))
for wat_fixture in sorted(wat_fixture_files):
path = os.path.join(wat_path, wat_fixture)
if wat_fixture.endswith(".zip"):
process_wat_zip(path)
else:
process_wat_file(path)
print("All fixtures have been processed.")

View File

@@ -1,34 +0,0 @@
(module
(type (;0;) (func))
(type (;1;) (func (result i32)))
(func (;0;) (type 0))
(func (;1;) (type 1) (result i32)
f32.const -2048
f32.const 2050
f32.sub
drop
i32.const 1)
(memory (;0;) 2)
(global (;0;) i32 (i32.const 1024))
(global (;1;) i32 (i32.const 1024))
(global (;2;) i32 (i32.const 2048))
(global (;3;) i32 (i32.const 2048))
(global (;4;) i32 (i32.const 67584))
(global (;5;) i32 (i32.const 1024))
(global (;6;) i32 (i32.const 67584))
(global (;7;) i32 (i32.const 131072))
(global (;8;) i32 (i32.const 0))
(global (;9;) i32 (i32.const 1))
(export "memory" (memory 0))
(export "__wasm_call_ctors" (func 0))
(export "escrow_finish" (func 1))
(export "buf" (global 0))
(export "__dso_handle" (global 1))
(export "__data_end" (global 2))
(export "__stack_low" (global 3))
(export "__stack_high" (global 4))
(export "__global_base" (global 5))
(export "__heap_base" (global 6))
(export "__heap_end" (global 7))
(export "__memory_base" (global 8))
(export "__table_base" (global 9)))

View File

@@ -1,11 +0,0 @@
// typedef long long mint;
typedef int mint;
mint fib(mint n)
{
if (!n)
return 0;
if (n <= 2)
return 1;
return fib(n - 1) + fib(n - 2);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,171 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "block-buffer"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
dependencies = [
"hybrid-array",
]
[[package]]
name = "bs58"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
dependencies = [
"tinyvec",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "const-oid"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
"hybrid-array",
]
[[package]]
name = "digest"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
"block-buffer",
"const-oid",
"crypto-common",
]
[[package]]
name = "float_0"
version = "0.0.1"
dependencies = [
"xrpl-wasm-stdlib",
]
[[package]]
name = "hybrid-array"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
dependencies = [
"typenum",
]
[[package]]
name = "libc"
version = "0.2.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "sha2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tinyvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "xrpl-macros"
version = "0.1.0"
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#6b35fe45ac70bad38914e7f319d31d7947e05e25"
dependencies = [
"bs58",
"proc-macro2",
"quote",
"sha2",
"syn",
]
[[package]]
name = "xrpl-wasm-stdlib"
version = "0.8.0"
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#6b35fe45ac70bad38914e7f319d31d7947e05e25"
dependencies = [
"xrpl-macros",
]

View File

@@ -1,21 +0,0 @@
[package]
name = "float_0"
version = "0.0.1"
edition = "2024"
# This empty workspace definition keeps this project independent of the parent workspace
[workspace]
[lib]
crate-type = ["cdylib"]
[profile.release]
lto = true
opt-level = 's'
panic = "abort"
[dependencies]
xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-wasm-stdlib", branch = "renames" }
[profile.dev]
panic = "abort"

View File

@@ -1,70 +0,0 @@
#![cfg_attr(target_arch = "wasm32", no_std)]
use xrpl_std::host::trace::trace;
use xrpl_std::host::{float_cmp, float_from_int, float_sub, FLOAT_ROUNDING_MODES_TO_NEAREST};
// Float size constant (8 bytes mantissa + 4 bytes exponent)
const FLOAT_SIZE: usize = 12;
// FLOAT_ZERO constant
const FLOAT_ZERO: [u8; FLOAT_SIZE] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
];
#[unsafe(no_mangle)]
pub extern "C" fn escrow_finish() -> i32 {
let _ = trace("\n$$$ test_float_0 $$$");
// Test: 10 - 10 should equal 0
let mut f10: [u8; FLOAT_SIZE] = [0u8; FLOAT_SIZE];
let mut f_result: [u8; FLOAT_SIZE] = [0u8; FLOAT_SIZE];
// Create float from 10
if FLOAT_SIZE as i32
!= unsafe {
float_from_int(
10,
f10.as_mut_ptr(),
FLOAT_SIZE,
FLOAT_ROUNDING_MODES_TO_NEAREST,
)
}
{
let _ = trace(" float 10-10: failed");
return 1;
}
// Subtract: 10 - 10 = 0
if FLOAT_SIZE as i32
!= unsafe {
float_sub(
f10.as_ptr(),
FLOAT_SIZE,
f10.as_ptr(),
FLOAT_SIZE,
f_result.as_mut_ptr(),
FLOAT_SIZE,
FLOAT_ROUNDING_MODES_TO_NEAREST,
)
}
{
let _ = trace(" float 10-10: failed");
return 1;
}
// Compare result with FLOAT_ZERO constant
if 0 == unsafe {
float_cmp(
f_result.as_ptr(),
FLOAT_SIZE,
FLOAT_ZERO.as_ptr(),
FLOAT_SIZE,
)
} {
let _ = trace(" FLOAT_ZERO compare: good");
} else {
let _ = trace(" FLOAT_ZERO compare: bad");
}
1
}

Some files were not shown because too many files have changed in this diff Show More