feat: Hook up set_data host function

This commit is contained in:
TimothyBanks
2026-08-10 21:58:24 -04:00
parent 98abdef208
commit 98cf3a0532
11 changed files with 100 additions and 1 deletions

View File

@@ -420,4 +420,11 @@ host_functions! {
#[gas = 500]
#[wasm_name = "trace_num"]
fn trace_num(&self, msg: &str, number: i64) -> HostResult<()>;
/// Stores `data` as the current object's data field, replacing whatever was there,
/// and returns the number of bytes stored. Reads the data region; `DataFieldTooLarge`
/// if it exceeds the host's limit.
#[gas = 1000]
#[wasm_name = "set_data"]
fn update_data(&self, data: &[u8]) -> HostResult<i32>;
}

View File

@@ -401,6 +401,11 @@ impl HostFunctions for FakeHost {
self.traced.borrow_mut().push(format!("{msg}={number}"));
Ok(())
}
/// Reads a data blob and returns the count of bytes stored.
fn update_data(&self, data: &[u8]) -> HostResult<i32> {
Ok(data.len() as i32)
}
}
#[test]
@@ -615,6 +620,7 @@ fn the_trait_is_implementable() {
assert_eq!(out[0], 3);
assert_eq!(host.trace("hello", b"xy", true), Ok(()));
assert_eq!(host.trace_num("count", -1), Ok(()));
assert_eq!(host.update_data(b"abcd"), Ok(4));
assert_eq!(*host.traced.borrow(), ["hello/2/true", "count=-1"]);
}
@@ -719,6 +725,7 @@ fn the_spec_table_matches_the_declarations() {
("sha512_half", 2000),
("trace", 500),
("trace_num", 500),
("set_data", 1000),
]
);
}

View File

@@ -391,6 +391,10 @@ mod ffi {
#[namespace = "xrpl"]
#[cxx_name = "traceNum"]
fn trace_num(self: &HostContext, msg: &str, number: i64) -> i32;
#[namespace = "xrpl"]
#[cxx_name = "updateData"]
fn update_data(self: &HostContext, data: &[u8]) -> i32;
}
}
@@ -657,6 +661,10 @@ impl HostFunctions for CxxHost<'_> {
fn trace_num(&self, msg: &str, number: i64) -> HostResult<()> {
reported(self.ctx.trace_num(msg, number))
}
fn update_data(&self, data: &[u8]) -> HostResult<i32> {
scalar(self.ctx.update_data(data))
}
}
fn run_escrow(

View File

@@ -384,6 +384,9 @@ mod tests {
fn trace_num(&self, _msg: &str, _number: i64) -> HostResult<()> {
unreachable!("no unit test in this module calls the host")
}
fn update_data(&self, _data: &[u8]) -> HostResult<i32> {
unreachable!("no unit test in this module calls the host")
}
}
fn state(budget: u64) -> VmState<'static> {

View File

@@ -781,6 +781,20 @@ pub(crate) fn register_host_functions(
})
},
),
HostFunctionSpec::UpdateData => linker.func_wrap(
HOST_MODULE,
op.wasm_name(),
|mut caller: Caller<'_, VmState<'_>>,
ptr: i32,
len: i32|
-> Result<i32, wasmi::Error> {
charged(&mut caller, HostFunctionSpec::UpdateData, |c| {
let host = c.data().host;
let data = read_borrowed(c, Region::new(ptr, len))?;
host.update_data(data)
})
},
),
}?;
}
Ok(())

View File

@@ -254,6 +254,11 @@ fn call_for(op: HostFunctionSpec) -> Call {
"(call $trace_num (i32.const 0) (i32.const 0) (i64.const 0))",
3,
),
HostFunctionSpec::UpdateData => (
import::SET_DATA,
"(call $set_data (i32.const 0) (i32.const 8))",
2,
),
};
Call {
import,

View File

@@ -748,6 +748,25 @@ fn vault_id_reads_the_account_and_seq() {
assert_eq!(*host.vault_keylets_asked.borrow(), vec![(account, 5)]);
}
/// A call that reads an input region and returns a scalar rather than writing bytes:
/// the data blob reaches the host, and the byte count it reports comes back as the
/// call's status.
#[test]
fn set_data_passes_the_data_through_and_returns_the_count() {
let host = FakeHost::new().answering_update_data(Ok(8));
let wat = module(
&[import::SET_DATA, ONE_PAGE],
"(call $set_data (i32.const 64) (i32.const 8))",
);
assert_eq!(status(&wat, &host), 8, "the byte count the host reported");
assert_eq!(
*host.update_data_asked.borrow(),
[vec![0u8; 8]],
"the 8-byte region reached the host"
);
}
/// A leading scalar parameter reaches the host as declared.
#[test]
fn home_le_field_passes_the_field_selector_through() {

View File

@@ -98,7 +98,7 @@ fn a_disabled_feature_does_not_pass() {
/// Every host function the ABI declares, spelled as a guest imports it. The count
/// is asserted against the ABI so a function added to it cannot be left out here.
const ALL_IMPORTS: [&str; 41] = [
const ALL_IMPORTS: [&str; 42] = [
import::LDGR_INDEX,
import::PARENT_LDGR_TIME,
import::PARENT_LDGR_HASH,
@@ -140,6 +140,7 @@ const ALL_IMPORTS: [&str; 41] = [
import::SHA512_HALF,
import::TRACE,
import::TRACE_NUM,
import::SET_DATA,
];
#[test]

View File

@@ -282,6 +282,10 @@ pub struct FakeHost {
pub digested: RefCell<Vec<Vec<u8>>>,
/// Every `trace`/`trace_num` call, in order.
pub traces: RefCell<Vec<Trace>>,
/// What `update_data` answers, whatever data it is given.
pub update_data_answer: HostResult<i32>,
/// Every data blob `update_data` was given.
pub update_data_asked: RefCell<Vec<Vec<u8>>>,
}
impl Default for FakeHost {
@@ -370,6 +374,8 @@ impl Default for FakeHost {
fields_asked: RefCell::new(Vec::new()),
digested: RefCell::new(Vec::new()),
traces: RefCell::new(Vec::new()),
update_data_answer: Ok(0),
update_data_asked: RefCell::new(Vec::new()),
}
}
}
@@ -671,6 +677,11 @@ impl FakeHost {
self
}
pub fn answering_update_data(mut self, answer: HostResult<i32>) -> FakeHost {
self.update_data_answer = answer;
self
}
pub fn traces(&self) -> Vec<Trace> {
self.traces.borrow().clone()
}
@@ -1069,6 +1080,11 @@ impl HostFunctions for FakeHost {
});
Ok(())
}
fn update_data(&self, data: &[u8]) -> HostResult<i32> {
self.update_data_asked.borrow_mut().push(data.to_vec());
self.update_data_answer
}
}
// ---------------------------------------------------------------------------
@@ -1132,6 +1148,8 @@ pub mod import {
r#"(import "host_lib" "trace" (func $trace (param i32 i32 i32 i32 i32) (result i32)))"#;
pub const TRACE_NUM: &str =
r#"(import "host_lib" "trace_num" (func $trace_num (param i32 i32 i64) (result i32)))"#;
pub const SET_DATA: &str =
r#"(import "host_lib" "set_data" (func $set_data (param i32 i32) (result i32)))"#;
}
/// One page of linear memory, exported under the name the engine looks for.

View File

@@ -273,6 +273,11 @@ public:
[[nodiscard]] std::int32_t
traceNum(rust::Str msg, std::int64_t number) const noexcept;
// Stores `data` as the current object's data field and returns the number of bytes
// stored, or a negative `HostFunctionError` code.
[[nodiscard]] std::int32_t
updateData(rust::Slice<std::uint8_t const> data) const noexcept;
};
} // namespace xrpl

View File

@@ -862,4 +862,16 @@ HostContext::traceNum(rust::Str msg, std::int64_t number) const noexcept
});
}
std::int32_t
HostContext::updateData(rust::Slice<std::uint8_t const> data) const noexcept
{
return guarded(hostFunctions_.getJournal(), kHostInternal, [&] {
auto const stored = hostFunctions_.updateData(Slice{data.data(), data.size()});
if (!stored)
return hfErrorToInt(stored.error());
return *stored;
});
}
} // namespace xrpl