mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-26 23:19:07 +00:00
chore: Merge upstream branch
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
17
crates/Cargo.lock
generated
@@ -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]]
|
||||
|
||||
@@ -9,3 +9,4 @@ crate-type = ["staticlib", "rlib"]
|
||||
[dependencies]
|
||||
cxx.workspace = true
|
||||
wat = "1"
|
||||
xrpl-host-functions = { path = "../xrpl-host-functions" }
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(_));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(_));
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
175
src/benchmarks/libxrpl/wasm/BenchFixtures.cpp
Normal file
175
src/benchmarks/libxrpl/wasm/BenchFixtures.cpp
Normal 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
|
||||
111
src/benchmarks/libxrpl/wasm/BenchFixtures.h
Normal file
111
src/benchmarks/libxrpl/wasm/BenchFixtures.h
Normal 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
|
||||
44
src/benchmarks/libxrpl/wasm/Crossing.cpp
Normal file
44
src/benchmarks/libxrpl/wasm/Crossing.cpp
Normal 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
|
||||
180
src/benchmarks/libxrpl/wasm/README.md
Normal file
180
src/benchmarks/libxrpl/wasm/README.md
Normal 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_`.
|
||||
149
src/benchmarks/libxrpl/wasm/Vm.cpp
Normal file
149
src/benchmarks/libxrpl/wasm/Vm.cpp
Normal 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
|
||||
397
src/benchmarks/libxrpl/wasm/WasmBench.cpp
Normal file
397
src/benchmarks/libxrpl/wasm/WasmBench.cpp
Normal 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
|
||||
369
src/benchmarks/libxrpl/wasm/WasmBench.h
Normal file
369
src/benchmarks/libxrpl/wasm/WasmBench.h
Normal 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
|
||||
24
src/benchmarks/libxrpl/wasm/host_functions/AccountKeylet.cpp
Normal file
24
src/benchmarks/libxrpl/wasm/host_functions/AccountKeylet.cpp
Normal 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
|
||||
31
src/benchmarks/libxrpl/wasm/host_functions/AmmKeylet.cpp
Normal file
31
src/benchmarks/libxrpl/wasm/host_functions/AmmKeylet.cpp
Normal 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
|
||||
24
src/benchmarks/libxrpl/wasm/host_functions/BaseFee.cpp
Normal file
24
src/benchmarks/libxrpl/wasm/host_functions/BaseFee.cpp
Normal 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
|
||||
@@ -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
|
||||
26
src/benchmarks/libxrpl/wasm/host_functions/CheckKeylet.cpp
Normal file
26
src/benchmarks/libxrpl/wasm/host_functions/CheckKeylet.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
24
src/benchmarks/libxrpl/wasm/host_functions/DidKeylet.cpp
Normal file
24
src/benchmarks/libxrpl/wasm/host_functions/DidKeylet.cpp
Normal 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
|
||||
55
src/benchmarks/libxrpl/wasm/host_functions/EscrowKeylet.cpp
Normal file
55
src/benchmarks/libxrpl/wasm/host_functions/EscrowKeylet.cpp
Normal 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
|
||||
45
src/benchmarks/libxrpl/wasm/host_functions/FloatAdd.cpp
Normal file
45
src/benchmarks/libxrpl/wasm/host_functions/FloatAdd.cpp
Normal 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
|
||||
24
src/benchmarks/libxrpl/wasm/host_functions/FloatCompare.cpp
Normal file
24
src/benchmarks/libxrpl/wasm/host_functions/FloatCompare.cpp
Normal 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
|
||||
27
src/benchmarks/libxrpl/wasm/host_functions/FloatDivide.cpp
Normal file
27
src/benchmarks/libxrpl/wasm/host_functions/FloatDivide.cpp
Normal 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
|
||||
24
src/benchmarks/libxrpl/wasm/host_functions/FloatFromInt.cpp
Normal file
24
src/benchmarks/libxrpl/wasm/host_functions/FloatFromInt.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
24
src/benchmarks/libxrpl/wasm/host_functions/FloatFromUint.cpp
Normal file
24
src/benchmarks/libxrpl/wasm/host_functions/FloatFromUint.cpp
Normal 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
|
||||
27
src/benchmarks/libxrpl/wasm/host_functions/FloatMultiply.cpp
Normal file
27
src/benchmarks/libxrpl/wasm/host_functions/FloatMultiply.cpp
Normal 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
|
||||
42
src/benchmarks/libxrpl/wasm/host_functions/FloatPower.cpp
Normal file
42
src/benchmarks/libxrpl/wasm/host_functions/FloatPower.cpp
Normal 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
|
||||
27
src/benchmarks/libxrpl/wasm/host_functions/FloatSubtract.cpp
Normal file
27
src/benchmarks/libxrpl/wasm/host_functions/FloatSubtract.cpp
Normal 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
|
||||
24
src/benchmarks/libxrpl/wasm/host_functions/FloatToInt.cpp
Normal file
24
src/benchmarks/libxrpl/wasm/host_functions/FloatToInt.cpp
Normal 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
|
||||
@@ -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
|
||||
34
src/benchmarks/libxrpl/wasm/host_functions/GetNFT.cpp
Normal file
34
src/benchmarks/libxrpl/wasm/host_functions/GetNFT.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
38
src/benchmarks/libxrpl/wasm/host_functions/LedgerSqn.cpp
Normal file
38
src/benchmarks/libxrpl/wasm/host_functions/LedgerSqn.cpp
Normal 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
|
||||
@@ -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
|
||||
31
src/benchmarks/libxrpl/wasm/host_functions/MptokenKeylet.cpp
Normal file
31
src/benchmarks/libxrpl/wasm/host_functions/MptokenKeylet.cpp
Normal 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
|
||||
24
src/benchmarks/libxrpl/wasm/host_functions/NFTFlags.cpp
Normal file
24
src/benchmarks/libxrpl/wasm/host_functions/NFTFlags.cpp
Normal 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
|
||||
24
src/benchmarks/libxrpl/wasm/host_functions/NFTIssuer.cpp
Normal file
24
src/benchmarks/libxrpl/wasm/host_functions/NFTIssuer.cpp
Normal 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
|
||||
24
src/benchmarks/libxrpl/wasm/host_functions/NFTSequence.cpp
Normal file
24
src/benchmarks/libxrpl/wasm/host_functions/NFTSequence.cpp
Normal 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
|
||||
24
src/benchmarks/libxrpl/wasm/host_functions/NFTTaxon.cpp
Normal file
24
src/benchmarks/libxrpl/wasm/host_functions/NFTTaxon.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
26
src/benchmarks/libxrpl/wasm/host_functions/OfferKeylet.cpp
Normal file
26
src/benchmarks/libxrpl/wasm/host_functions/OfferKeylet.cpp
Normal 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
|
||||
26
src/benchmarks/libxrpl/wasm/host_functions/OracleKeylet.cpp
Normal file
26
src/benchmarks/libxrpl/wasm/host_functions/OracleKeylet.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
69
src/benchmarks/libxrpl/wasm/host_functions/Sha512Half.cpp
Normal file
69
src/benchmarks/libxrpl/wasm/host_functions/Sha512Half.cpp
Normal 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
|
||||
@@ -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
|
||||
26
src/benchmarks/libxrpl/wasm/host_functions/TicketKeylet.cpp
Normal file
26
src/benchmarks/libxrpl/wasm/host_functions/TicketKeylet.cpp
Normal 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
|
||||
40
src/benchmarks/libxrpl/wasm/host_functions/Trace.cpp
Normal file
40
src/benchmarks/libxrpl/wasm/host_functions/Trace.cpp
Normal 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
|
||||
@@ -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(); },
|
||||
[¤cy](auto& host) {
|
||||
return host.trustLineKeylet(
|
||||
Fixtures::instance().alice().id(), Fixtures::instance().bob().id(), currency);
|
||||
});
|
||||
}
|
||||
BENCHMARK(trustLineKeyletImpl)->UseManualTime()->Iterations(kBenchIterations);
|
||||
|
||||
} // namespace
|
||||
} // namespace xrpl::test::bench
|
||||
26
src/benchmarks/libxrpl/wasm/host_functions/TxArrayLen.cpp
Normal file
26
src/benchmarks/libxrpl/wasm/host_functions/TxArrayLen.cpp
Normal 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
|
||||
26
src/benchmarks/libxrpl/wasm/host_functions/TxField.cpp
Normal file
26
src/benchmarks/libxrpl/wasm/host_functions/TxField.cpp
Normal 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
|
||||
@@ -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
|
||||
55
src/benchmarks/libxrpl/wasm/host_functions/TxNestedField.cpp
Normal file
55
src/benchmarks/libxrpl/wasm/host_functions/TxNestedField.cpp
Normal 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
|
||||
62
src/benchmarks/libxrpl/wasm/host_functions/UpdateData.cpp
Normal file
62
src/benchmarks/libxrpl/wasm/host_functions/UpdateData.cpp
Normal 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
|
||||
26
src/benchmarks/libxrpl/wasm/host_functions/VaultKeylet.cpp
Normal file
26
src/benchmarks/libxrpl/wasm/host_functions/VaultKeylet.cpp
Normal 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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
3
src/test/app/wasm_fixtures/.gitignore
vendored
3
src/test/app/wasm_fixtures/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
**/target
|
||||
**/debug
|
||||
*.wasm
|
||||
180
src/test/app/wasm_fixtures/all_host_functions/Cargo.lock
generated
180
src/test/app/wasm_fixtures/all_host_functions/Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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:", ¤t_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:",
|
||||
¤t_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
|
||||
}
|
||||
171
src/test/app/wasm_fixtures/all_keylets/Cargo.lock
generated
171
src/test/app/wasm_fixtures/all_keylets/Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
@@ -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"
|
||||
@@ -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, ¤cy);
|
||||
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.
|
||||
}
|
||||
@@ -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(); }
|
||||
180
src/test/app/wasm_fixtures/codecov_tests/Cargo.lock
generated
180
src/test/app/wasm_fixtures/codecov_tests/Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
@@ -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" }
|
||||
@@ -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
@@ -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.")
|
||||
@@ -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)))
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
extern std::string const kLedgerSqnWasmHex;
|
||||
extern std::string const kAllHostFunctionsWasmHex;
|
||||
extern std::string const kAllKeyletsWasmHex;
|
||||
extern std::string const kCodecovTestsWasmHex;
|
||||
|
||||
extern std::string const kFibWasmHex;
|
||||
|
||||
extern std::string const kFloatTestsWasmHex;
|
||||
extern std::string const kFloat0Hex;
|
||||
extern std::string const kDisabledFloatHex;
|
||||
|
||||
extern std::string const kMemoryPointerAtLimitHex;
|
||||
extern std::string const kMemoryPointerOverLimitHex;
|
||||
extern std::string const kMemoryOffsetOverLimitHex;
|
||||
extern std::string const kMemoryEndOfWordOverLimitHex;
|
||||
extern std::string const kMemoryGrow0To1PageHex;
|
||||
extern std::string const kMemoryGrow1To0PageHex;
|
||||
extern std::string const kMemoryLastByteOf8MbHex;
|
||||
extern std::string const kMemoryGrow1MoreThan8MbHex;
|
||||
extern std::string const kMemoryGrow0MoreThan8MbHex;
|
||||
extern std::string const kMemoryInit1MoreThan8MbHex;
|
||||
extern std::string const kMemoryNegativeAddressHex;
|
||||
|
||||
extern std::string const kTable64ElementsHex;
|
||||
extern std::string const kTable65ElementsHex;
|
||||
extern std::string const kTable2TablesHex;
|
||||
extern std::string const kTable0ElementsHex;
|
||||
extern std::string const kTableUintMaxHex;
|
||||
|
||||
extern std::string const kProposalMutableGlobalHex;
|
||||
extern std::string const kProposalGcStructNewHex;
|
||||
extern std::string const kProposalMultiValueHex;
|
||||
extern std::string const kProposalSignExtHex;
|
||||
extern std::string const kProposalFloatToIntHex;
|
||||
extern std::string const kProposalBulkMemoryHex;
|
||||
extern std::string const kProposalRefTypesHex;
|
||||
extern std::string const kProposalTailCallHex;
|
||||
extern std::string const kProposalExtendedConstHex;
|
||||
extern std::string const kProposalMultiMemoryHex;
|
||||
extern std::string const kProposalCustomPageSizesHex;
|
||||
extern std::string const kProposalMemory64Hex;
|
||||
extern std::string const kProposalWideArithmeticHex;
|
||||
|
||||
extern std::string const kTrapDivideBy0Hex;
|
||||
extern std::string const kTrapIntOverflowHex;
|
||||
extern std::string const kTrapUnreachableHex;
|
||||
extern std::string const kTrapNullCallHex;
|
||||
extern std::string const kTrapFuncSigMismatchHex;
|
||||
|
||||
extern std::string const kWasiGetTimeHex;
|
||||
extern std::string const kWasiPrintHex;
|
||||
|
||||
extern std::string const kBadMagicNumberHex;
|
||||
extern std::string const kBadVersionNumberHex;
|
||||
extern std::string const kLyingHeaderHex;
|
||||
extern std::string const kNeverEndingNumberHex;
|
||||
extern std::string const kVectorLieHex;
|
||||
extern std::string const kSectionOrderingHex;
|
||||
extern std::string const kGhostPayloadHex;
|
||||
extern std::string const kJunkAfterSectionHex;
|
||||
extern std::string const kInvalidSectionIdHex;
|
||||
extern std::string const kLocalVariableBombHex;
|
||||
|
||||
extern std::string const kDeepRecursionHex;
|
||||
extern std::string const kInfiniteLoopWasmHex;
|
||||
extern std::string const kStartLoopHex;
|
||||
|
||||
extern std::string const kBadAlignWasmHex;
|
||||
|
||||
extern std::string const kThousandParamsHex;
|
||||
extern std::string const kThousand1ParamsHex;
|
||||
extern std::string const kLocals10kHex;
|
||||
extern std::string const kFunctions5kHex;
|
||||
|
||||
extern std::string const kOpcReservedHex;
|
||||
|
||||
extern std::string const kImpExpHex;
|
||||
171
src/test/app/wasm_fixtures/float_0/Cargo.lock
generated
171
src/test/app/wasm_fixtures/float_0/Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user