mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 14:20:56 +00:00
Add self to host functions trat
This commit is contained in:
@@ -15,9 +15,9 @@ use parsed_host_function::ParsedHostFunction;
|
||||
|
||||
/// Declares the wasm host ABI once, and generates everything that follows from it.
|
||||
///
|
||||
/// The input is a block of bare `fn` declarations, each carrying the gas cost the
|
||||
/// host charges before the call and the name the guest imports it under. Doc
|
||||
/// comments are kept and appear on the generated items.
|
||||
/// The input is a block of `fn` declarations, each carrying the gas cost the host
|
||||
/// charges before the call and the name the guest imports it under. Doc comments
|
||||
/// are kept and appear on the generated items.
|
||||
///
|
||||
/// ```
|
||||
/// use xrpl_host_functions_macros::host_functions;
|
||||
@@ -26,19 +26,19 @@ use parsed_host_function::ParsedHostFunction;
|
||||
/// /// The sequence number of the ledger being built.
|
||||
/// #[gas = 60]
|
||||
/// #[wasm_name = "ldgr_index"]
|
||||
/// fn get_ledger_sqn() -> [u8; 4];
|
||||
/// fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
///
|
||||
/// /// Writes `msg` to the trace log.
|
||||
/// #[gas = 500]
|
||||
/// #[wasm_name = "trace_num"]
|
||||
/// fn trace_num(msg: &str, number: i64);
|
||||
/// fn trace_num(&self, msg: &str, number: i64);
|
||||
/// }
|
||||
///
|
||||
/// // A `HostFunctions` trait, with a `&mut self` receiver added:
|
||||
/// // A `HostFunctions` trait, holding the declarations verbatim:
|
||||
/// struct Host;
|
||||
/// impl HostFunctions for Host {
|
||||
/// fn get_ledger_sqn(&mut self) -> [u8; 4] { 7u32.to_le_bytes() }
|
||||
/// fn trace_num(&mut self, _msg: &str, _number: i64) {}
|
||||
/// fn get_ledger_sqn(&self) -> [u8; 4] { 7u32.to_le_bytes() }
|
||||
/// fn trace_num(&self, _msg: &str, _number: i64) {}
|
||||
/// }
|
||||
///
|
||||
/// // A `HostFunctionSpec` enum carrying the ABI metadata as a `const` table:
|
||||
@@ -47,9 +47,9 @@ use parsed_host_function::ParsedHostFunction;
|
||||
/// assert_eq!(HostFunctionSpec::ALL.len(), 2);
|
||||
/// ```
|
||||
///
|
||||
/// A declaration must be a plain `fn` with no receiver, no body and no generics:
|
||||
/// it maps to exactly one wasm import signature. Two declarations may not share a
|
||||
/// `wasm_name`, nor collapse to the same PascalCase variant.
|
||||
/// A declaration must be a plain `fn` taking `&self`, with no body and no
|
||||
/// generics: it maps to exactly one wasm import signature. Two declarations may
|
||||
/// not share a `wasm_name`, nor collapse to the same PascalCase variant.
|
||||
#[proc_macro]
|
||||
pub fn host_functions(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
expand(input.into())
|
||||
@@ -123,9 +123,9 @@ fn generate(functions: &[ParsedHostFunction]) -> TokenStream {
|
||||
///
|
||||
/// Implement it once per execution environment — the ledger host, a test
|
||||
/// double, a benchmark fake — and a guest module cannot tell them apart.
|
||||
/// Each method is a declaration from the `host_functions!` block with a
|
||||
/// `&mut self` receiver added; the receiver is not part of the ABI the
|
||||
/// guest sees.
|
||||
/// Each method is one declaration from the `host_functions!` block, as
|
||||
/// written; its `&self` receiver is not part of the ABI the guest sees,
|
||||
/// so a host that must mutate does so behind interior mutability.
|
||||
pub trait HostFunctions {
|
||||
#(#trait_methods)*
|
||||
}
|
||||
@@ -217,10 +217,10 @@ mod tests {
|
||||
fn reports_mistakes_from_every_function() {
|
||||
let error = expand(quote! {
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
|
||||
#[gas = 2000]
|
||||
fn sha512_half(data: &[u8]) -> [u8; 32];
|
||||
fn sha512_half(&self, data: &[u8]) -> [u8; 32];
|
||||
})
|
||||
.expect_err("expected parsing to fail");
|
||||
|
||||
@@ -249,19 +249,19 @@ mod tests {
|
||||
let generated = expand(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
|
||||
#[gas = 500]
|
||||
#[wasm_name = "trace_num"]
|
||||
fn trace_num(msg: &str, number: i64);
|
||||
fn trace_num(&self, msg: &str, number: i64);
|
||||
})
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
for expected in [
|
||||
"pub trait HostFunctions",
|
||||
"fn get_ledger_sqn (& mut self) -> [u8 ; 4] ;",
|
||||
"fn trace_num (& mut self , msg : & str , number : i64) ;",
|
||||
"fn get_ledger_sqn (& self) -> [u8 ; 4] ;",
|
||||
"fn trace_num (& self , msg : & str , number : i64) ;",
|
||||
"pub struct HostFnSpec",
|
||||
"pub name : & 'static str",
|
||||
"pub gas : u64",
|
||||
@@ -279,11 +279,11 @@ mod tests {
|
||||
let messages = messages(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace(msg: &str);
|
||||
fn trace(&self, msg: &str);
|
||||
|
||||
#[gas = 70]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace_num(msg: &str, number: i64);
|
||||
fn trace_num(&self, msg: &str, number: i64);
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
@@ -299,11 +299,11 @@ mod tests {
|
||||
let messages = messages(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "a"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
|
||||
#[gas = 70]
|
||||
#[wasm_name = "b"]
|
||||
fn get_ledger__sqn() -> [u8; 4];
|
||||
fn get_ledger__sqn(&self) -> [u8; 4];
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
Attribute, Expr, ExprLit, Ident, Lit, LitStr, Safety, Signature, TraitItemFn, parse_quote,
|
||||
Attribute, Expr, ExprLit, Ident, Lit, LitStr, ReceiverKind, Safety, Signature, TraitItemFn,
|
||||
};
|
||||
|
||||
use crate::errors;
|
||||
@@ -27,14 +27,12 @@ pub(crate) struct ParsedHostFunction {
|
||||
}
|
||||
|
||||
impl ParsedHostFunction {
|
||||
/// `#[doc …] fn get_ledger_sqn(&mut self) -> [u8; 4];`
|
||||
/// `#[doc …] fn get_ledger_sqn(&self) -> [u8; 4];`
|
||||
pub(crate) fn trait_method(&self) -> TokenStream {
|
||||
let docs = &self.docs;
|
||||
|
||||
// The receiver is not part of the wasm ABI, so declarations omit it and
|
||||
// only the trait needs one.
|
||||
let mut signature = self.signature.clone();
|
||||
signature.inputs.insert(0, parse_quote!(&self));
|
||||
// The declaration is already a trait method: emitted verbatim, so what
|
||||
// the block reads like is what the trait is.
|
||||
let signature = &self.signature;
|
||||
|
||||
quote! {
|
||||
#(#docs)*
|
||||
@@ -124,12 +122,7 @@ impl ParsedHostFunction {
|
||||
"a host function must not be generic: it maps to one wasm import signature",
|
||||
));
|
||||
}
|
||||
if let Some(receiver) = function.sig.receiver() {
|
||||
errors.push(syn::Error::new_spanned(
|
||||
receiver,
|
||||
"the receiver is added by the macro; declare only the wasm parameters",
|
||||
));
|
||||
}
|
||||
errors.extend(check_receiver(&function.sig).err());
|
||||
if let Some(name) = &wasm_name {
|
||||
errors.extend(check_wasm_name(name).err());
|
||||
}
|
||||
@@ -163,6 +156,35 @@ impl ParsedHostFunction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Every declaration carries a receiver, and it is always `&self`.
|
||||
///
|
||||
/// `&self` is the only receiver that can work: the VM reaches the host through a
|
||||
/// shared `&dyn HostFunctions` stored in the wasmi `Store`, and a host that needs
|
||||
/// to mutate does so behind interior mutability. The receiver is not part of the
|
||||
/// wasm ABI — the guest passes no `self` — so it is uniform across the block.
|
||||
fn check_receiver(signature: &Signature) -> syn::Result<()> {
|
||||
let Some(receiver) = signature.receiver() else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&signature.ident,
|
||||
format!(
|
||||
"a host function must declare its receiver: `fn {}(&self, ...)`",
|
||||
signature.ident
|
||||
),
|
||||
));
|
||||
};
|
||||
|
||||
// `&self` and nothing else: not `&mut self`, not `self`/`mut self`, not a
|
||||
// typed `self: Box<Self>`, and not a spelled-out lifetime.
|
||||
if !matches!(receiver.kind, ReceiverKind::Reference(_, None, None)) {
|
||||
return Err(syn::Error::new_spanned(
|
||||
receiver,
|
||||
"a host function's receiver must be exactly `&self`: the VM calls the host \
|
||||
through a shared `&dyn HostFunctions`",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `const`, `async`, `unsafe`/`safe` and `extern "…"` have no meaning in the
|
||||
/// wasm ABI, and would otherwise pass silently into the generated trait.
|
||||
fn reject_modifiers(signature: &Signature, errors: &mut Vec<syn::Error>) {
|
||||
@@ -339,7 +361,7 @@ mod tests {
|
||||
let parsed = ParsedHostFunction::parse(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -378,7 +400,7 @@ mod tests {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "two_factor"]
|
||||
fn _2fa();
|
||||
fn _2fa(&self);
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
@@ -410,7 +432,7 @@ mod tests {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = -5]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
@@ -422,7 +444,7 @@ mod tests {
|
||||
let empty = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = ""]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
});
|
||||
assert_eq!(empty.len(), 1, "{empty:?}");
|
||||
assert_eq!(empty[0], "the wasm name must not be empty");
|
||||
@@ -430,7 +452,7 @@ mod tests {
|
||||
let spaced = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
});
|
||||
assert_eq!(spaced.len(), 1, "{spaced:?}");
|
||||
assert!(spaced[0].contains("may only contain"), "{spaced:?}");
|
||||
@@ -439,10 +461,10 @@ mod tests {
|
||||
#[test]
|
||||
fn rejects_signature_modifiers() {
|
||||
for declaration in [
|
||||
quote! { unsafe fn get_ledger_sqn() -> [u8; 4]; },
|
||||
quote! { async fn get_ledger_sqn() -> [u8; 4]; },
|
||||
quote! { const fn get_ledger_sqn() -> [u8; 4]; },
|
||||
quote! { extern "C" fn get_ledger_sqn() -> [u8; 4]; },
|
||||
quote! { unsafe fn get_ledger_sqn(&self) -> [u8; 4]; },
|
||||
quote! { async fn get_ledger_sqn(&self) -> [u8; 4]; },
|
||||
quote! { const fn get_ledger_sqn(&self) -> [u8; 4]; },
|
||||
quote! { extern "C" fn get_ledger_sqn(&self) -> [u8; 4]; },
|
||||
] {
|
||||
let function: TraitItemFn = syn::parse2(quote! {
|
||||
#[gas = 60]
|
||||
@@ -458,12 +480,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trait_method_takes_a_receiver_and_ends_in_a_semicolon() {
|
||||
fn trait_method_keeps_the_declared_receiver_and_ends_in_a_semicolon() {
|
||||
let parsed = ParsedHostFunction::parse(parse_quote! {
|
||||
/// Hashes `data`.
|
||||
#[gas = 2000]
|
||||
#[wasm_name = "sha512_half"]
|
||||
fn sha512_half(data: &[u8]) -> [u8; 32];
|
||||
fn sha512_half(&self, data: &[u8]) -> [u8; 32];
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -475,7 +497,7 @@ mod tests {
|
||||
"{method}"
|
||||
);
|
||||
assert!(
|
||||
method.contains("fn sha512_half (& mut self , data : & [u8]) -> [u8 ; 32] ;"),
|
||||
method.contains("fn sha512_half (& self , data : & [u8]) -> [u8 ; 32] ;"),
|
||||
"{method}"
|
||||
);
|
||||
}
|
||||
@@ -485,7 +507,7 @@ mod tests {
|
||||
let parsed = ParsedHostFunction::parse(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -503,7 +525,7 @@ mod tests {
|
||||
/// Third line.
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -516,16 +538,17 @@ mod tests {
|
||||
let traced = ParsedHostFunction::parse(parse_quote! {
|
||||
#[gas = 500]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace(msg: &str, data: &[u8], as_hex: bool);
|
||||
fn trace(&self, msg: &str, data: &[u8], as_hex: bool);
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(traced.signature.inputs.len(), 3);
|
||||
// The receiver is `inputs[0]`; the three wasm parameters follow it.
|
||||
assert_eq!(traced.signature.inputs.len(), 4);
|
||||
assert!(matches!(traced.signature.output, syn::ReturnType::Default));
|
||||
|
||||
let hashed = ParsedHostFunction::parse(parse_quote! {
|
||||
#[gas = 2000]
|
||||
#[wasm_name = "sha512_half"]
|
||||
fn sha512_half(data: &[u8]) -> [u8; 32];
|
||||
fn sha512_half(&self, data: &[u8]) -> [u8; 32];
|
||||
})
|
||||
.unwrap();
|
||||
assert!(matches!(hashed.signature.output, syn::ReturnType::Type(..)));
|
||||
@@ -534,7 +557,7 @@ mod tests {
|
||||
#[test]
|
||||
fn reports_both_missing_attributes_at_once() {
|
||||
let messages = messages(parse_quote! {
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 2);
|
||||
@@ -547,7 +570,7 @@ mod tests {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wsam_name = "typo"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
});
|
||||
|
||||
// The typo'd attribute, plus the `wasm_name` it failed to be.
|
||||
@@ -563,7 +586,7 @@ mod tests {
|
||||
let gas = messages(parse_quote! {
|
||||
#[gas = "60"]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
});
|
||||
assert_eq!(gas.len(), 1, "{gas:?}");
|
||||
assert!(
|
||||
@@ -574,7 +597,7 @@ mod tests {
|
||||
let name = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = 7]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
});
|
||||
assert_eq!(name.len(), 1, "{name:?}");
|
||||
assert!(
|
||||
@@ -588,7 +611,7 @@ mod tests {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = 99999999999999999999999]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
@@ -600,7 +623,7 @@ mod tests {
|
||||
let bare = messages(parse_quote! {
|
||||
#[gas]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
});
|
||||
assert_eq!(bare.len(), 1, "{bare:?}");
|
||||
assert!(bare[0].contains("gas = ..."), "{bare:?}");
|
||||
@@ -608,7 +631,7 @@ mod tests {
|
||||
let list = messages(parse_quote! {
|
||||
#[gas(60)]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
});
|
||||
assert_eq!(list.len(), 1, "{list:?}");
|
||||
}
|
||||
@@ -620,7 +643,7 @@ mod tests {
|
||||
#[gas = 70]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 2, "{messages:?}");
|
||||
@@ -637,7 +660,7 @@ mod tests {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = "60"]
|
||||
#[wasm_name = 7]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 2, "{messages:?}");
|
||||
@@ -652,7 +675,7 @@ mod tests {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4] { [0; 4] }
|
||||
fn get_ledger_sqn(&self) -> [u8; 4] { [0; 4] }
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
@@ -664,7 +687,7 @@ mod tests {
|
||||
let parameter = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn<T>() -> T;
|
||||
fn get_ledger_sqn<T>(&self) -> T;
|
||||
});
|
||||
assert_eq!(parameter.len(), 1, "{parameter:?}");
|
||||
assert!(
|
||||
@@ -675,20 +698,50 @@ mod tests {
|
||||
let clause = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4] where Self: Sized;
|
||||
fn get_ledger_sqn(&self) -> [u8; 4] where Self: Sized;
|
||||
});
|
||||
assert_eq!(clause.len(), 1, "{clause:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_an_explicit_receiver() {
|
||||
fn requires_a_receiver() {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
assert!(messages[0].contains("receiver"), "{messages:?}");
|
||||
assert!(
|
||||
messages[0].contains("must declare its receiver: `fn get_ledger_sqn(&self, ...)`"),
|
||||
"{messages:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Anything but `&self` would need a host the VM cannot hand out: it holds
|
||||
/// one shared `&dyn HostFunctions` for the whole run.
|
||||
#[test]
|
||||
fn rejects_receivers_other_than_shared_self() {
|
||||
for receiver in [
|
||||
quote! { &mut self },
|
||||
quote! { self },
|
||||
quote! { mut self },
|
||||
quote! { self: Box<Self> },
|
||||
quote! { &'a self },
|
||||
] {
|
||||
let function: TraitItemFn = syn::parse2(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(#receiver) -> [u8; 4];
|
||||
})
|
||||
.unwrap_or_else(|_| panic!("`{receiver}` should parse"));
|
||||
|
||||
let messages = messages(function);
|
||||
assert_eq!(messages.len(), 1, "`{receiver}`: {messages:?}");
|
||||
assert!(
|
||||
messages[0].contains("must be exactly `&self`"),
|
||||
"`{receiver}`: {messages:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,21 +87,21 @@ pub const HASH_LEN: usize = 32;
|
||||
host_functions! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
fn get_ledger_sqn(&self) -> [u8; 4];
|
||||
|
||||
#[gas = 70]
|
||||
#[wasm_name = "home_le_field"]
|
||||
fn get_current_ledger_obj_field(field: i32) -> Vec<u8>;
|
||||
fn get_current_ledger_obj_field(&self, field: i32) -> Vec<u8>;
|
||||
|
||||
#[gas = 2000]
|
||||
#[wasm_name = "sha512_half"]
|
||||
fn sha512_half(data: &[u8]) -> [u8; 32];
|
||||
fn sha512_half(&self, data: &[u8]) -> [u8; 32];
|
||||
|
||||
#[gas = 500]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace(msg: &str, data: &[u8], as_hex: bool);
|
||||
fn trace(&self, msg: &str, data: &[u8], as_hex: bool);
|
||||
|
||||
#[gas = 500]
|
||||
#[wasm_name = "trace_num"]
|
||||
fn trace_num(msg: &str, number: i64);
|
||||
fn trace_num(&self, msg: &str, number: i64);
|
||||
}
|
||||
|
||||
@@ -1,41 +1,48 @@
|
||||
//! Exercises what `host_functions!` generates: the trait is implementable and
|
||||
//! the spec table agrees with the declarations in `src/lib.rs`.
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use xrpl_host_functions::{HASH_LEN, HostFnSpec, HostFunctionSpec, HostFunctions};
|
||||
|
||||
/// Records what it was asked to do; enough to prove the trait is usable.
|
||||
///
|
||||
/// Every method takes `&self`, so a host that records anything keeps it behind
|
||||
/// interior mutability.
|
||||
#[derive(Default)]
|
||||
struct FakeHost {
|
||||
traced: Vec<String>,
|
||||
traced: RefCell<Vec<String>>,
|
||||
}
|
||||
|
||||
impl HostFunctions for FakeHost {
|
||||
fn get_ledger_sqn(&mut self) -> [u8; 4] {
|
||||
fn get_ledger_sqn(&self) -> [u8; 4] {
|
||||
7u32.to_le_bytes()
|
||||
}
|
||||
|
||||
fn get_current_ledger_obj_field(&mut self, field: i32) -> Vec<u8> {
|
||||
fn get_current_ledger_obj_field(&self, field: i32) -> Vec<u8> {
|
||||
vec![field as u8]
|
||||
}
|
||||
|
||||
fn sha512_half(&mut self, data: &[u8]) -> [u8; HASH_LEN] {
|
||||
fn sha512_half(&self, data: &[u8]) -> [u8; HASH_LEN] {
|
||||
let mut digest = [0; HASH_LEN];
|
||||
digest[0] = data.len() as u8;
|
||||
digest
|
||||
}
|
||||
|
||||
fn trace(&mut self, msg: &str, data: &[u8], as_hex: bool) {
|
||||
self.traced.push(format!("{msg}/{}/{as_hex}", data.len()));
|
||||
fn trace(&self, msg: &str, data: &[u8], as_hex: bool) {
|
||||
self.traced
|
||||
.borrow_mut()
|
||||
.push(format!("{msg}/{}/{as_hex}", data.len()));
|
||||
}
|
||||
|
||||
fn trace_num(&mut self, msg: &str, number: i64) {
|
||||
self.traced.push(format!("{msg}={number}"));
|
||||
fn trace_num(&self, msg: &str, number: i64) {
|
||||
self.traced.borrow_mut().push(format!("{msg}={number}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_trait_is_implementable() {
|
||||
let mut host = FakeHost::default();
|
||||
let host = FakeHost::default();
|
||||
|
||||
assert_eq!(host.get_ledger_sqn(), [7, 0, 0, 0]);
|
||||
assert_eq!(host.get_current_ledger_obj_field(3), vec![3]);
|
||||
@@ -43,7 +50,20 @@ fn the_trait_is_implementable() {
|
||||
host.trace("hello", b"xy", true);
|
||||
host.trace_num("count", -1);
|
||||
|
||||
assert_eq!(host.traced, ["hello/2/true", "count=-1"]);
|
||||
assert_eq!(*host.traced.borrow(), ["hello/2/true", "count=-1"]);
|
||||
}
|
||||
|
||||
/// The VM reaches the host as one shared trait object held in the wasmi `Store`,
|
||||
/// which is what the `&self` receivers are for.
|
||||
#[test]
|
||||
fn the_trait_is_callable_through_a_shared_trait_object() {
|
||||
let fake = FakeHost::default();
|
||||
let host: &dyn HostFunctions = &fake;
|
||||
|
||||
assert_eq!(host.get_ledger_sqn(), [7, 0, 0, 0]);
|
||||
host.trace_num("count", 1);
|
||||
|
||||
assert_eq!(*fake.traced.borrow(), ["count=1"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -25,6 +25,10 @@ only for reference. Anything we need about the old semantics is recoverable with
|
||||
`host_functions! { ... }` generates the `HostFunctions` trait + the
|
||||
`HostFunctionSpec` enum (wasm import name + gas per function). Also `HostError`.
|
||||
**This crate is the single source of truth for the ABI.**
|
||||
Each declaration spells its receiver — always `&self`, checked by the macro, so
|
||||
a declaration reads exactly as the trait method it becomes. `&self` is what lets
|
||||
the VM hold the host as one shared `&dyn HostFunctions` in the wasmi `Store`; a
|
||||
host that needs to mutate uses interior mutability.
|
||||
- `crates/xrpl-host-functions-macros/` — the `host_functions!` proc macro.
|
||||
- `crates/xrpl-wasm-vm/` — the wasmi wrapper: `vm.rs` (engine/store/run),
|
||||
`abi.rs` (gas + transfer-limit + guest-memory marshaling), `register.rs`
|
||||
@@ -129,6 +133,7 @@ That is the entire gap.
|
||||
|
||||
```
|
||||
params:
|
||||
&self -> nothing (receiver, not part of the ABI)
|
||||
i32, bool -> i32 (bool: nonzero = true)
|
||||
i64 -> i64
|
||||
&[u8], &str -> i32 ptr, i32 len const uint8_t*, int32_t
|
||||
@@ -338,10 +343,12 @@ useful for comparison and for the gas assertions in `Wasm_test.cpp` — not gosp
|
||||
## Current state (2026-07-29)
|
||||
|
||||
`crates/` does **not** compile: the macro-generated trait (value-returning,
|
||||
infallible) and the uncommitted VM code (fill-caller's-buffer, `HostResult<usize>`,
|
||||
`&dyn`) are two different ABI shapes. Every call site in `register.rs` is affected,
|
||||
as are the macro's own doctests and `xrpl-host-functions/tests/generated_abi.rs`
|
||||
(which still use `&mut self` and value returns).
|
||||
infallible) and the VM code (fill-caller's-buffer, `HostResult<usize>`) are two
|
||||
different ABI shapes. The 8 errors are all in `xrpl-wasm-vm` — every byte-returning
|
||||
call site in `register.rs`, plus `?` on the infallible `trace`/`trace_num`.
|
||||
|
||||
`xrpl-host-functions` and `xrpl-host-functions-macros` are green (`cargo test` +
|
||||
`clippy`): 28 macro tests, 5 ABI tests, 1 doctest.
|
||||
|
||||
Resolving that shape is the immediate work. Per the mechanism note above, the
|
||||
value-returning direction (plus `HostResult<T>`) is the one that composes with
|
||||
|
||||
Reference in New Issue
Block a user