mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 14:20:56 +00:00
Implementing macro
This commit is contained in:
8
crates/Cargo.lock
generated
8
crates/Cargo.lock
generated
@@ -303,10 +303,18 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "xrpl-host-functions"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"xrpl-host-functions-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-host-functions-macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-vm"
|
||||
|
||||
@@ -3,4 +3,10 @@ name = "xrpl-host-functions-macros"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "3", features = ["full"] }
|
||||
quote = "1"
|
||||
proc-macro2 = "1"
|
||||
|
||||
@@ -1,5 +1,170 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
Attribute, Expr, ExprLit, Lit, Signature, TraitItemFn,
|
||||
parse::{Parse, ParseStream},
|
||||
parse2,
|
||||
};
|
||||
|
||||
#[proc_macro]
|
||||
pub fn host_functions(input: TokenStream) -> TokenStream {
|
||||
expand(input.into())
|
||||
.unwrap_or_else(syn::Error::into_compile_error)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn expand(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2::TokenStream> {
|
||||
let HostFunctionsInput { functions } = parse2(input)?;
|
||||
|
||||
// let mut errors = Vec::new();
|
||||
|
||||
for f in functions {}
|
||||
|
||||
Ok(quote! {
|
||||
trait HostFunctions {
|
||||
|
||||
}
|
||||
|
||||
enum HostFunctionSpec {
|
||||
|
||||
}
|
||||
|
||||
impl HostFunctionSpec
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
struct HostFunctionsInput {
|
||||
functions: Vec<TraitItemFn>,
|
||||
}
|
||||
|
||||
impl Parse for HostFunctionsInput {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let mut functions = Vec::new();
|
||||
while !input.is_empty() {
|
||||
functions.push(input.parse()?);
|
||||
}
|
||||
Ok(HostFunctionsInput { functions })
|
||||
}
|
||||
}
|
||||
|
||||
struct ParsedHostFunction {
|
||||
gas: usize,
|
||||
wasm_name: String,
|
||||
docs: Vec<Attribute>,
|
||||
signature: Signature,
|
||||
}
|
||||
|
||||
impl ParsedHostFunction {
|
||||
const GAS_PATH: &str = "gas";
|
||||
const WASM_NAME_PATH: &str = "wasm_name";
|
||||
|
||||
fn parse(value: TraitItemFn) -> Result<Self, syn::Error> {
|
||||
let mut gas = None;
|
||||
let mut wasm_name = None;
|
||||
let mut docs = Vec::new();
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for attr in &value.attrs {
|
||||
let named = match attr.meta.require_name_value() {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
errors.push(e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match &named.path {
|
||||
p if p.is_ident(Self::GAS_PATH) => {
|
||||
let parsed_value = match Self::parse_number(&named.value) {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
errors.push(e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if gas.replace(parsed_value).is_some() {
|
||||
errors.push(syn::Error::new_spanned(
|
||||
named,
|
||||
format!("duplicated {} attribute", Self::GAS_PATH),
|
||||
));
|
||||
}
|
||||
}
|
||||
p if p.is_ident(Self::WASM_NAME_PATH) => {
|
||||
let parsed_value = match Self::parse_string(&named.value) {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
errors.push(e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if wasm_name.replace(parsed_value).is_some() {
|
||||
errors.push(syn::Error::new_spanned(
|
||||
named,
|
||||
format!("duplicated {} attribute", Self::WASM_NAME_PATH),
|
||||
));
|
||||
}
|
||||
}
|
||||
p if p.is_ident("doc") => {
|
||||
docs.push(attr.clone());
|
||||
}
|
||||
_ => {
|
||||
errors.push(syn::Error::new_spanned(named, "unexpected attribute"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(errors
|
||||
.into_iter()
|
||||
.reduce(|mut l, r| {
|
||||
l.combine(r);
|
||||
l
|
||||
})
|
||||
.unwrap());
|
||||
}
|
||||
if gas.is_none() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&value.sig,
|
||||
format!("missing {} attribute", Self::GAS_PATH),
|
||||
));
|
||||
}
|
||||
|
||||
if wasm_name.is_none() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&value.sig,
|
||||
format!("missing {} attribute", Self::WASM_NAME_PATH),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
gas: gas.unwrap(),
|
||||
wasm_name: wasm_name.unwrap(),
|
||||
docs,
|
||||
signature: value.sig,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_number(value: &Expr) -> Result<usize, syn::Error> {
|
||||
match value {
|
||||
Expr::Lit(ExprLit {
|
||||
lit: Lit::Int(i), ..
|
||||
}) => i.base10_parse::<usize>(),
|
||||
other => Err(syn::Error::new_spanned(
|
||||
other,
|
||||
"expected an integer literal",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_string(value: &Expr) -> Result<String, syn::Error> {
|
||||
match value {
|
||||
Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(s), ..
|
||||
}) => Ok(s.value()),
|
||||
other => Err(syn::Error::new_spanned(other, "expected string literal")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -7,8 +172,25 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
fn reads_gas_and_wasm_name() {
|
||||
let f: TraitItemFn = syn::parse_quote! {
|
||||
/// some comment
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
};
|
||||
let p = ParsedHostFunction::parse(f).unwrap();
|
||||
assert_eq!(p.gas, 60);
|
||||
assert_eq!(p.wasm_name, "ldgr_index");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_unknown_attribute() {
|
||||
let f: TraitItemFn = syn::parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wsam_name = "typo"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
};
|
||||
assert!(ParsedHostFunction::parse(f).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@ version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
xrpl-host-functions-macros.path = "../xrpl-host-functions-macros"
|
||||
|
||||
@@ -1,14 +1,110 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
#![no_std]
|
||||
use xrpl_host_functions_macros::host_abi;
|
||||
|
||||
/// Error codes a host function may return.
|
||||
///
|
||||
/// The discriminants mirror `HostFunctionError` in
|
||||
/// `include/xrpl/tx/wasm/WasmCommon.h`, so a negative `i32` crossing the wasm
|
||||
/// boundary means the same thing to the guest, the Rust host, and the existing
|
||||
/// C++ code. The full set is kept (not just the ones the PoC uses today) to
|
||||
/// preserve that shared meaning.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
pub enum HostError {
|
||||
Internal = -1,
|
||||
FieldNotFound = -2,
|
||||
BufferTooSmall = -3,
|
||||
NoArray = -4,
|
||||
NotLeafField = -5,
|
||||
LocatorMalformed = -6,
|
||||
SlotOutRange = -7,
|
||||
SlotsFull = -8,
|
||||
EmptySlot = -9,
|
||||
LedgerObjNotFound = -10,
|
||||
Decoding = -11,
|
||||
DataFieldTooLarge = -12,
|
||||
PointerOutOfBounds = -13,
|
||||
NoMemExported = -14,
|
||||
InvalidParams = -15,
|
||||
InvalidAccount = -16,
|
||||
InvalidField = -17,
|
||||
IndexOutOfBounds = -18,
|
||||
FloatInputMalformed = -19,
|
||||
FloatComputationError = -20,
|
||||
NoRuntime = -21,
|
||||
OutOfGas = -22,
|
||||
OutOfTransferLimit = -23,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
impl HostError {
|
||||
/// The negative wire value the guest sees as the function's return code.
|
||||
#[inline]
|
||||
pub const fn code(self) -> i32 {
|
||||
self as i32
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
/// Reconstruct a `HostError` from its wire code; unknown/positive values map to `Internal`.
|
||||
pub const fn from_code(code: i32) -> HostError {
|
||||
match code {
|
||||
-1 => HostError::Internal,
|
||||
-2 => HostError::FieldNotFound,
|
||||
-3 => HostError::BufferTooSmall,
|
||||
-4 => HostError::NoArray,
|
||||
-5 => HostError::NotLeafField,
|
||||
-6 => HostError::LocatorMalformed,
|
||||
-7 => HostError::SlotOutRange,
|
||||
-8 => HostError::SlotsFull,
|
||||
-9 => HostError::EmptySlot,
|
||||
-10 => HostError::LedgerObjNotFound,
|
||||
-11 => HostError::Decoding,
|
||||
-12 => HostError::DataFieldTooLarge,
|
||||
-13 => HostError::PointerOutOfBounds,
|
||||
-14 => HostError::NoMemExported,
|
||||
-15 => HostError::InvalidParams,
|
||||
-16 => HostError::InvalidAccount,
|
||||
-17 => HostError::InvalidField,
|
||||
-18 => HostError::IndexOutOfBounds,
|
||||
-19 => HostError::FloatInputMalformed,
|
||||
-20 => HostError::FloatComputationError,
|
||||
-21 => HostError::NoRuntime,
|
||||
-22 => HostError::OutOfGas,
|
||||
-23 => HostError::OutOfTransferLimit,
|
||||
_ => HostError::Internal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience alias for the trait's fallible returns.
|
||||
pub type HostResult<T> = Result<T, HostError>;
|
||||
|
||||
/// A `sha512Half` digest: the first 32 bytes of a SHA-512, as XRPL uses it.
|
||||
pub const HASH_LEN: usize = 32;
|
||||
|
||||
/// Per-function ABI metadata: the wasm import name and the consensus-fixed base gas cost.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct HostFnSpec {
|
||||
pub name: &'static str,
|
||||
pub base_gas: u64,
|
||||
}
|
||||
|
||||
host_functions! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> [u8; 4];
|
||||
|
||||
#[gas = 70]
|
||||
#[wasm_name = "home_le_field"]
|
||||
fn get_current_ledger_obj_field(field: i32) -> Vec<u8>;
|
||||
|
||||
#[gas = 2000]
|
||||
#[wasm_name = "sha512_half"]
|
||||
fn sha512_half(data: &[u8]) -> [u8; 32];
|
||||
|
||||
#[gas = 500]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace(msg: &str, data: &[u8], as_hex: bool);
|
||||
|
||||
#[gas = 500]
|
||||
#[wasm_name = "trace_num"]
|
||||
fn trace_num(msg: &str, number: i64);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user