feat: Hook up nft_uri, nft_issuer, nft_taxon, nft_flags, nft_xfer_fee, nft_serial host functions

This commit is contained in:
TimothyBanks
2026-08-11 09:44:00 -04:00
parent 98cf3a0532
commit 7d52867e3c
11 changed files with 671 additions and 1 deletions

View File

@@ -427,4 +427,40 @@ host_functions! {
#[gas = 1000]
#[wasm_name = "set_data"]
fn update_data(&self, data: &[u8]) -> HostResult<i32>;
/// The URI of the `NFToken` with id `nft_id` (32 bytes) held by the 20-byte
/// `account`. Reads both regions and writes the URI bytes.
#[gas = 5000]
#[wasm_name = "nft_uri"]
fn get_nft(&self, account: &[u8], nft_id: &[u8], out: &mut [u8]) -> HostResult<usize>;
/// The 20-byte issuer account encoded in the `NFToken` id `nft_id` (32 bytes).
/// Reads the id region and writes the issuer bytes.
#[gas = 70]
#[wasm_name = "nft_issuer"]
fn get_nft_issuer(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize>;
/// The taxon encoded in the `NFToken` id `nft_id` (32 bytes). Reads the id region
/// and writes the taxon as its four little-endian bytes.
#[gas = 60]
#[wasm_name = "nft_taxon"]
fn get_nft_taxon(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize>;
/// The flags encoded in the `NFToken` id `nft_id` (32 bytes). Reads the id region
/// and returns the flags as the call's scalar result.
#[gas = 60]
#[wasm_name = "nft_flags"]
fn get_nft_flags(&self, nft_id: &[u8]) -> HostResult<i32>;
/// The transfer fee encoded in the `NFToken` id `nft_id` (32 bytes). Reads the id
/// region and returns the fee as the call's scalar result.
#[gas = 60]
#[wasm_name = "nft_xfer_fee"]
fn get_nft_transfer_fee(&self, nft_id: &[u8]) -> HostResult<i32>;
/// The sequence number encoded in the `NFToken` id `nft_id` (32 bytes). Reads the
/// id region and writes the sequence as its four little-endian bytes.
#[gas = 60]
#[wasm_name = "nft_serial"]
fn get_nft_sequence(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize>;
}

View File

@@ -406,6 +406,55 @@ impl HostFunctions for FakeHost {
fn update_data(&self, data: &[u8]) -> HostResult<i32> {
Ok(data.len() as i32)
}
/// Reads an account and an nft id, writes a byte value; `InvalidParams` if either
/// is empty.
fn get_nft(&self, account: &[u8], nft_id: &[u8], out: &mut [u8]) -> HostResult<usize> {
if account.is_empty() || nft_id.is_empty() {
return Err(HostError::InvalidParams);
}
put(out, &[account[0]; HASH_LEN])
}
/// Reads an nft id, writes a byte value; `InvalidParams` on an empty id.
fn get_nft_issuer(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize> {
if nft_id.is_empty() {
return Err(HostError::InvalidParams);
}
put(out, &[nft_id[0]; HASH_LEN])
}
/// The same, for the taxon.
fn get_nft_taxon(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize> {
if nft_id.is_empty() {
return Err(HostError::InvalidParams);
}
put(out, &nft_id[0].to_le_bytes())
}
/// Reads an nft id and returns a scalar; `InvalidParams` on an empty id.
fn get_nft_flags(&self, nft_id: &[u8]) -> HostResult<i32> {
if nft_id.is_empty() {
return Err(HostError::InvalidParams);
}
Ok(i32::from(nft_id[0]))
}
/// The same, for the transfer fee.
fn get_nft_transfer_fee(&self, nft_id: &[u8]) -> HostResult<i32> {
if nft_id.is_empty() {
return Err(HostError::InvalidParams);
}
Ok(i32::from(nft_id[0]))
}
/// The same byte-output shape, for the sequence number.
fn get_nft_sequence(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize> {
if nft_id.is_empty() {
return Err(HostError::InvalidParams);
}
put(out, &nft_id[0].to_le_bytes())
}
}
#[test]
@@ -621,6 +670,23 @@ fn the_trait_is_implementable() {
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.get_nft(&[7; 20], &[9; 32], &mut out), Ok(HASH_LEN));
assert_eq!(out[0], 7);
assert_eq!(
host.get_nft(&[], &[9; 32], &mut out),
Err(HostError::InvalidParams)
);
assert_eq!(host.get_nft_issuer(&[9; 32], &mut out), Ok(HASH_LEN));
assert_eq!(out[0], 9);
assert_eq!(
host.get_nft_issuer(&[], &mut out),
Err(HostError::InvalidParams)
);
assert_eq!(host.get_nft_taxon(&[9; 32], &mut out), Ok(1));
assert_eq!(host.get_nft_flags(&[9; 32]), Ok(9));
assert_eq!(host.get_nft_flags(&[]), Err(HostError::InvalidParams));
assert_eq!(host.get_nft_transfer_fee(&[9; 32]), Ok(9));
assert_eq!(host.get_nft_sequence(&[9; 32], &mut out), Ok(1));
assert_eq!(*host.traced.borrow(), ["hello/2/true", "count=-1"]);
}
@@ -726,6 +792,12 @@ fn the_spec_table_matches_the_declarations() {
("trace", 500),
("trace_num", 500),
("set_data", 1000),
("nft_uri", 5000),
("nft_issuer", 70),
("nft_taxon", 60),
("nft_flags", 60),
("nft_xfer_fee", 60),
("nft_serial", 60),
]
);
}

View File

@@ -395,6 +395,30 @@ mod ffi {
#[namespace = "xrpl"]
#[cxx_name = "updateData"]
fn update_data(self: &HostContext, data: &[u8]) -> i32;
#[namespace = "xrpl"]
#[cxx_name = "getNFT"]
fn get_nft(self: &HostContext, account: &[u8], nft_id: &[u8], out: &mut [u8]) -> i32;
#[namespace = "xrpl"]
#[cxx_name = "getNFTIssuer"]
fn get_nft_issuer(self: &HostContext, nft_id: &[u8], out: &mut [u8]) -> i32;
#[namespace = "xrpl"]
#[cxx_name = "getNFTTaxon"]
fn get_nft_taxon(self: &HostContext, nft_id: &[u8], out: &mut [u8]) -> i32;
#[namespace = "xrpl"]
#[cxx_name = "getNFTFlags"]
fn get_nft_flags(self: &HostContext, nft_id: &[u8]) -> i32;
#[namespace = "xrpl"]
#[cxx_name = "getNFTTransferFee"]
fn get_nft_transfer_fee(self: &HostContext, nft_id: &[u8]) -> i32;
#[namespace = "xrpl"]
#[cxx_name = "getNFTSequence"]
fn get_nft_sequence(self: &HostContext, nft_id: &[u8], out: &mut [u8]) -> i32;
}
}
@@ -665,6 +689,30 @@ impl HostFunctions for CxxHost<'_> {
fn update_data(&self, data: &[u8]) -> HostResult<i32> {
scalar(self.ctx.update_data(data))
}
fn get_nft(&self, account: &[u8], nft_id: &[u8], out: &mut [u8]) -> HostResult<usize> {
bytes_written(self.ctx.get_nft(account, nft_id, out))
}
fn get_nft_issuer(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize> {
bytes_written(self.ctx.get_nft_issuer(nft_id, out))
}
fn get_nft_taxon(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize> {
bytes_written(self.ctx.get_nft_taxon(nft_id, out))
}
fn get_nft_flags(&self, nft_id: &[u8]) -> HostResult<i32> {
scalar(self.ctx.get_nft_flags(nft_id))
}
fn get_nft_transfer_fee(&self, nft_id: &[u8]) -> HostResult<i32> {
scalar(self.ctx.get_nft_transfer_fee(nft_id))
}
fn get_nft_sequence(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize> {
bytes_written(self.ctx.get_nft_sequence(nft_id, out))
}
}
fn run_escrow(

View File

@@ -387,6 +387,24 @@ mod tests {
fn update_data(&self, _data: &[u8]) -> HostResult<i32> {
unreachable!("no unit test in this module calls the host")
}
fn get_nft(&self, _account: &[u8], _nft_id: &[u8], _out: &mut [u8]) -> HostResult<usize> {
unreachable!("no unit test in this module calls the host")
}
fn get_nft_issuer(&self, _nft_id: &[u8], _out: &mut [u8]) -> HostResult<usize> {
unreachable!("no unit test in this module calls the host")
}
fn get_nft_taxon(&self, _nft_id: &[u8], _out: &mut [u8]) -> HostResult<usize> {
unreachable!("no unit test in this module calls the host")
}
fn get_nft_flags(&self, _nft_id: &[u8]) -> HostResult<i32> {
unreachable!("no unit test in this module calls the host")
}
fn get_nft_transfer_fee(&self, _nft_id: &[u8]) -> HostResult<i32> {
unreachable!("no unit test in this module calls the host")
}
fn get_nft_sequence(&self, _nft_id: &[u8], _out: &mut [u8]) -> HostResult<usize> {
unreachable!("no unit test in this module calls the host")
}
}
fn state(budget: u64) -> VmState<'static> {

View File

@@ -795,6 +795,109 @@ pub(crate) fn register_host_functions(
})
},
),
HostFunctionSpec::GetNft => linker.func_wrap(
HOST_MODULE,
op.wasm_name(),
|mut caller: Caller<'_, VmState<'_>>,
acc_ptr: i32,
acc_len: i32,
nft_ptr: i32,
nft_len: i32,
out_ptr: i32,
out_len: i32|
-> Result<i32, wasmi::Error> {
charged(&mut caller, HostFunctionSpec::GetNft, |c| {
let out = Region::new(out_ptr, out_len);
let account = Region::new(acc_ptr, acc_len);
let nft_id = Region::new(nft_ptr, nft_len);
write_buffered(c, out, |host, data, buf| {
host.get_nft(account.read(data)?, nft_id.read(data)?, buf)
})
})
},
),
HostFunctionSpec::GetNftIssuer => linker.func_wrap(
HOST_MODULE,
op.wasm_name(),
|mut caller: Caller<'_, VmState<'_>>,
nft_ptr: i32,
nft_len: i32,
out_ptr: i32,
out_len: i32|
-> Result<i32, wasmi::Error> {
charged(&mut caller, HostFunctionSpec::GetNftIssuer, |c| {
let out = Region::new(out_ptr, out_len);
let nft_id = Region::new(nft_ptr, nft_len);
write_buffered(c, out, |host, data, buf| {
host.get_nft_issuer(nft_id.read(data)?, buf)
})
})
},
),
HostFunctionSpec::GetNftTaxon => linker.func_wrap(
HOST_MODULE,
op.wasm_name(),
|mut caller: Caller<'_, VmState<'_>>,
nft_ptr: i32,
nft_len: i32,
out_ptr: i32,
out_len: i32|
-> Result<i32, wasmi::Error> {
charged(&mut caller, HostFunctionSpec::GetNftTaxon, |c| {
let out = Region::new(out_ptr, out_len);
let nft_id = Region::new(nft_ptr, nft_len);
write_buffered(c, out, |host, data, buf| {
host.get_nft_taxon(nft_id.read(data)?, buf)
})
})
},
),
HostFunctionSpec::GetNftFlags => linker.func_wrap(
HOST_MODULE,
op.wasm_name(),
|mut caller: Caller<'_, VmState<'_>>,
nft_ptr: i32,
nft_len: i32|
-> Result<i32, wasmi::Error> {
charged(&mut caller, HostFunctionSpec::GetNftFlags, |c| {
let host = c.data().host;
let nft_id = read_borrowed(c, Region::new(nft_ptr, nft_len))?;
host.get_nft_flags(nft_id)
})
},
),
HostFunctionSpec::GetNftTransferFee => linker.func_wrap(
HOST_MODULE,
op.wasm_name(),
|mut caller: Caller<'_, VmState<'_>>,
nft_ptr: i32,
nft_len: i32|
-> Result<i32, wasmi::Error> {
charged(&mut caller, HostFunctionSpec::GetNftTransferFee, |c| {
let host = c.data().host;
let nft_id = read_borrowed(c, Region::new(nft_ptr, nft_len))?;
host.get_nft_transfer_fee(nft_id)
})
},
),
HostFunctionSpec::GetNftSequence => linker.func_wrap(
HOST_MODULE,
op.wasm_name(),
|mut caller: Caller<'_, VmState<'_>>,
nft_ptr: i32,
nft_len: i32,
out_ptr: i32,
out_len: i32|
-> Result<i32, wasmi::Error> {
charged(&mut caller, HostFunctionSpec::GetNftSequence, |c| {
let out = Region::new(out_ptr, out_len);
let nft_id = Region::new(nft_ptr, nft_len);
write_buffered(c, out, |host, data, buf| {
host.get_nft_sequence(nft_id.read(data)?, buf)
})
})
},
),
}?;
}
Ok(())

View File

@@ -259,6 +259,36 @@ fn call_for(op: HostFunctionSpec) -> Call {
"(call $set_data (i32.const 0) (i32.const 8))",
2,
),
HostFunctionSpec::GetNft => (
import::NFT_URI,
"(call $nft_uri (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 32) (i32.const 52) (i32.const 12))",
6,
),
HostFunctionSpec::GetNftIssuer => (
import::NFT_ISSUER,
"(call $nft_issuer (i32.const 0) (i32.const 32) (i32.const 32) (i32.const 20))",
4,
),
HostFunctionSpec::GetNftTaxon => (
import::NFT_TAXON,
"(call $nft_taxon (i32.const 0) (i32.const 32) (i32.const 32) (i32.const 4))",
4,
),
HostFunctionSpec::GetNftFlags => (
import::NFT_FLAGS,
"(call $nft_flags (i32.const 0) (i32.const 32))",
2,
),
HostFunctionSpec::GetNftTransferFee => (
import::NFT_XFER_FEE,
"(call $nft_xfer_fee (i32.const 0) (i32.const 32))",
2,
),
HostFunctionSpec::GetNftSequence => (
import::NFT_SERIAL,
"(call $nft_serial (i32.const 0) (i32.const 32) (i32.const 32) (i32.const 4))",
4,
),
};
Call {
import,

View File

@@ -767,6 +767,105 @@ fn set_data_passes_the_data_through_and_returns_the_count() {
);
}
/// A getter that reads two input regions — an account and an nft id — and writes the
/// answer to a third: both inputs reach the host, keyed together, and the bytes it
/// answers land where the guest asked.
#[test]
fn nft_uri_reads_the_account_and_id_and_writes_the_uri() {
let account = vec![0u8; 20];
let nft_id = vec![0u8; 32];
let host = FakeHost::new().answering_get_nft(
account.clone(),
nft_id.clone(),
support::Answer::bytes([0xab, 0xcd, 0xef]),
);
let wat = module(
&[import::NFT_URI, ONE_PAGE],
"(call $nft_uri (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 32) (i32.const 64) (i32.const 64))",
);
assert_eq!(status(&wat, &host), 3, "the uri length");
assert_eq!(*host.nfts_asked.borrow(), vec![(account, nft_id)]);
}
/// A single-input byte getter: the nft id reaches the host and the issuer bytes it
/// answers land where the guest asked.
#[test]
fn nft_issuer_reads_the_id_and_writes_the_issuer() {
let nft_id = vec![0u8; 32];
let host = FakeHost::new().answering_nft_issuer(nft_id.clone(), support::Answer::filler(20));
let wat = module(
&[import::NFT_ISSUER, ONE_PAGE],
"(call $nft_issuer (i32.const 0) (i32.const 32) (i32.const 64) (i32.const 64))",
);
assert_eq!(status(&wat, &host), 20, "the issuer length");
assert_eq!(*host.nft_issuers_asked.borrow(), vec![nft_id]);
}
/// A u32-valued getter whose four bytes the host writes to the output region: the id
/// reaches the host, and the little-endian bytes land where the guest asked.
#[test]
fn nft_taxon_reads_the_id_and_writes_four_bytes() {
let nft_id = vec![0u8; 32];
let host =
FakeHost::new().answering_nft_taxon(nft_id.clone(), support::Answer::bytes([7, 0, 0, 0]));
let wat = module(
&[import::NFT_TAXON, ONE_PAGE],
"(drop (call $nft_taxon (i32.const 0) (i32.const 32) (i32.const 64) (i32.const 4)))
(i32.load (i32.const 64))",
);
assert_eq!(status(&wat, &host), 7, "the taxon the host wrote");
assert_eq!(*host.nft_taxons_asked.borrow(), vec![nft_id]);
}
/// A single-input scalar getter: the nft id reaches the host and the flags it reports
/// come back as the call's status, no output region involved.
#[test]
fn nft_flags_reads_the_id_and_returns_the_flags() {
let nft_id = vec![0u8; 32];
let host = FakeHost::new().answering_nft_flags(Ok(11));
let wat = module(
&[import::NFT_FLAGS, ONE_PAGE],
"(call $nft_flags (i32.const 0) (i32.const 32))",
);
assert_eq!(status(&wat, &host), 11, "the flags the host reported");
assert_eq!(*host.nft_flags_asked.borrow(), vec![nft_id]);
}
/// A second scalar getter, to pin the pattern: the transfer fee comes back as the
/// status.
#[test]
fn nft_xfer_fee_reads_the_id_and_returns_the_fee() {
let nft_id = vec![0u8; 32];
let host = FakeHost::new().answering_nft_transfer_fee(Ok(314));
let wat = module(
&[import::NFT_XFER_FEE, ONE_PAGE],
"(call $nft_xfer_fee (i32.const 0) (i32.const 32))",
);
assert_eq!(status(&wat, &host), 314, "the fee the host reported");
assert_eq!(*host.nft_fee_asked.borrow(), vec![nft_id]);
}
/// The last NFT getter, a u32 sequence written to the output region.
#[test]
fn nft_serial_reads_the_id_and_writes_four_bytes() {
let nft_id = vec![0u8; 32];
let host = FakeHost::new()
.answering_nft_sequence(nft_id.clone(), support::Answer::bytes([42, 0, 0, 0]));
let wat = module(
&[import::NFT_SERIAL, ONE_PAGE],
"(drop (call $nft_serial (i32.const 0) (i32.const 32) (i32.const 64) (i32.const 4)))
(i32.load (i32.const 64))",
);
assert_eq!(status(&wat, &host), 42, "the sequence the host wrote");
assert_eq!(*host.nft_sequences_asked.borrow(), vec![nft_id]);
}
/// 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; 42] = [
const ALL_IMPORTS: [&str; 48] = [
import::LDGR_INDEX,
import::PARENT_LDGR_TIME,
import::PARENT_LDGR_HASH,
@@ -141,6 +141,12 @@ const ALL_IMPORTS: [&str; 42] = [
import::TRACE,
import::TRACE_NUM,
import::SET_DATA,
import::NFT_URI,
import::NFT_ISSUER,
import::NFT_TAXON,
import::NFT_FLAGS,
import::NFT_XFER_FEE,
import::NFT_SERIAL,
];
#[test]

View File

@@ -286,6 +286,32 @@ pub struct FakeHost {
pub update_data_answer: HostResult<i32>,
/// Every data blob `update_data` was given.
pub update_data_asked: RefCell<Vec<Vec<u8>>>,
/// What `get_nft` answers, by (account, nft id) bytes. An unlisted key answers
/// `InvalidParams`.
pub nfts: HashMap<(Vec<u8>, Vec<u8>), Answer>,
/// Every (account, nft id) `get_nft` was asked for.
pub nfts_asked: RefCell<Vec<(Vec<u8>, Vec<u8>)>>,
/// What `get_nft_issuer` answers, by nft id. An unlisted id answers `InvalidParams`.
pub nft_issuers: HashMap<Vec<u8>, Answer>,
/// Every nft id `get_nft_issuer` was asked for.
pub nft_issuers_asked: RefCell<Vec<Vec<u8>>>,
/// What `get_nft_taxon` answers, by nft id. An unlisted id answers `InvalidParams`.
pub nft_taxons: HashMap<Vec<u8>, Answer>,
/// Every nft id `get_nft_taxon` was asked for.
pub nft_taxons_asked: RefCell<Vec<Vec<u8>>>,
/// What `get_nft_flags` answers, whatever nft id it is given.
pub nft_flags_answer: HostResult<i32>,
/// Every nft id `get_nft_flags` was asked for.
pub nft_flags_asked: RefCell<Vec<Vec<u8>>>,
/// What `get_nft_transfer_fee` answers, whatever nft id it is given.
pub nft_fee_answer: HostResult<i32>,
/// Every nft id `get_nft_transfer_fee` was asked for.
pub nft_fee_asked: RefCell<Vec<Vec<u8>>>,
/// What `get_nft_sequence` answers, by nft id. An unlisted id answers
/// `InvalidParams`.
pub nft_sequences: HashMap<Vec<u8>, Answer>,
/// Every nft id `get_nft_sequence` was asked for.
pub nft_sequences_asked: RefCell<Vec<Vec<u8>>>,
}
impl Default for FakeHost {
@@ -376,6 +402,18 @@ impl Default for FakeHost {
traces: RefCell::new(Vec::new()),
update_data_answer: Ok(0),
update_data_asked: RefCell::new(Vec::new()),
nfts: HashMap::new(),
nfts_asked: RefCell::new(Vec::new()),
nft_issuers: HashMap::new(),
nft_issuers_asked: RefCell::new(Vec::new()),
nft_taxons: HashMap::new(),
nft_taxons_asked: RefCell::new(Vec::new()),
nft_flags_answer: Ok(0),
nft_flags_asked: RefCell::new(Vec::new()),
nft_fee_answer: Ok(0),
nft_fee_asked: RefCell::new(Vec::new()),
nft_sequences: HashMap::new(),
nft_sequences_asked: RefCell::new(Vec::new()),
}
}
}
@@ -682,6 +720,41 @@ impl FakeHost {
self
}
pub fn answering_get_nft(
mut self,
account: Vec<u8>,
nft_id: Vec<u8>,
answer: Answer,
) -> FakeHost {
self.nfts.insert((account, nft_id), answer);
self
}
pub fn answering_nft_issuer(mut self, nft_id: Vec<u8>, answer: Answer) -> FakeHost {
self.nft_issuers.insert(nft_id, answer);
self
}
pub fn answering_nft_taxon(mut self, nft_id: Vec<u8>, answer: Answer) -> FakeHost {
self.nft_taxons.insert(nft_id, answer);
self
}
pub fn answering_nft_flags(mut self, answer: HostResult<i32>) -> FakeHost {
self.nft_flags_answer = answer;
self
}
pub fn answering_nft_transfer_fee(mut self, answer: HostResult<i32>) -> FakeHost {
self.nft_fee_answer = answer;
self
}
pub fn answering_nft_sequence(mut self, nft_id: Vec<u8>, answer: Answer) -> FakeHost {
self.nft_sequences.insert(nft_id, answer);
self
}
pub fn traces(&self) -> Vec<Trace> {
self.traces.borrow().clone()
}
@@ -1085,6 +1158,49 @@ impl HostFunctions for FakeHost {
self.update_data_asked.borrow_mut().push(data.to_vec());
self.update_data_answer
}
fn get_nft(&self, account: &[u8], nft_id: &[u8], out: &mut [u8]) -> HostResult<usize> {
let key = (account.to_vec(), nft_id.to_vec());
self.nfts_asked.borrow_mut().push(key.clone());
match self.nfts.get(&key) {
Some(answer) => answer.fill(out),
None => Err(HostError::InvalidParams),
}
}
fn get_nft_issuer(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize> {
self.nft_issuers_asked.borrow_mut().push(nft_id.to_vec());
match self.nft_issuers.get(nft_id) {
Some(answer) => answer.fill(out),
None => Err(HostError::InvalidParams),
}
}
fn get_nft_taxon(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize> {
self.nft_taxons_asked.borrow_mut().push(nft_id.to_vec());
match self.nft_taxons.get(nft_id) {
Some(answer) => answer.fill(out),
None => Err(HostError::InvalidParams),
}
}
fn get_nft_flags(&self, nft_id: &[u8]) -> HostResult<i32> {
self.nft_flags_asked.borrow_mut().push(nft_id.to_vec());
self.nft_flags_answer
}
fn get_nft_transfer_fee(&self, nft_id: &[u8]) -> HostResult<i32> {
self.nft_fee_asked.borrow_mut().push(nft_id.to_vec());
self.nft_fee_answer
}
fn get_nft_sequence(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize> {
self.nft_sequences_asked.borrow_mut().push(nft_id.to_vec());
match self.nft_sequences.get(nft_id) {
Some(answer) => answer.fill(out),
None => Err(HostError::InvalidParams),
}
}
}
// ---------------------------------------------------------------------------
@@ -1150,6 +1266,15 @@ pub mod import {
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)))"#;
pub const NFT_URI: &str = r#"(import "host_lib" "nft_uri" (func $nft_uri (param i32 i32 i32 i32 i32 i32) (result i32)))"#;
pub const NFT_ISSUER: &str = r#"(import "host_lib" "nft_issuer" (func $nft_issuer (param i32 i32 i32 i32) (result i32)))"#;
pub const NFT_TAXON: &str =
r#"(import "host_lib" "nft_taxon" (func $nft_taxon (param i32 i32 i32 i32) (result i32)))"#;
pub const NFT_FLAGS: &str =
r#"(import "host_lib" "nft_flags" (func $nft_flags (param i32 i32) (result i32)))"#;
pub const NFT_XFER_FEE: &str =
r#"(import "host_lib" "nft_xfer_fee" (func $nft_xfer_fee (param i32 i32) (result i32)))"#;
pub const NFT_SERIAL: &str = r#"(import "host_lib" "nft_serial" (func $nft_serial (param i32 i32 i32 i32) (result i32)))"#;
}
/// One page of linear memory, exported under the name the engine looks for.

View File

@@ -278,6 +278,42 @@ public:
// stored, or a negative `HostFunctionError` code.
[[nodiscard]] std::int32_t
updateData(rust::Slice<std::uint8_t const> data) const noexcept;
// The account id must be 20 bytes and the nft id 32 bytes, else `InvalidParams`.
// Writes the token's URI bytes.
[[nodiscard]] std::int32_t
getNFT(
rust::Slice<std::uint8_t const> account,
rust::Slice<std::uint8_t const> nftId,
rust::Slice<std::uint8_t> out) const noexcept;
// The nft id must be 32 bytes, else `InvalidParams`. Writes the 20-byte issuer
// account encoded in the id.
[[nodiscard]] std::int32_t
getNFTIssuer(rust::Slice<std::uint8_t const> nftId, rust::Slice<std::uint8_t> out)
const noexcept;
// The nft id must be 32 bytes, else `InvalidParams`. Writes the taxon as its four
// little-endian bytes.
[[nodiscard]] std::int32_t
getNFTTaxon(rust::Slice<std::uint8_t const> nftId, rust::Slice<std::uint8_t> out)
const noexcept;
// The nft id must be 32 bytes, else `InvalidParams`. Returns the flags, or a
// negative `HostFunctionError` code.
[[nodiscard]] std::int32_t
getNFTFlags(rust::Slice<std::uint8_t const> nftId) const noexcept;
// The nft id must be 32 bytes, else `InvalidParams`. Returns the transfer fee, or a
// negative `HostFunctionError` code.
[[nodiscard]] std::int32_t
getNFTTransferFee(rust::Slice<std::uint8_t const> nftId) const noexcept;
// The nft id must be 32 bytes, else `InvalidParams`. Writes the sequence number as
// its four little-endian bytes.
[[nodiscard]] std::int32_t
getNFTSequence(rust::Slice<std::uint8_t const> nftId, rust::Slice<std::uint8_t> out)
const noexcept;
};
} // namespace xrpl

View File

@@ -874,4 +874,101 @@ HostContext::updateData(rust::Slice<std::uint8_t const> data) const noexcept
});
}
std::int32_t
HostContext::getNFT(
rust::Slice<std::uint8_t const> account,
rust::Slice<std::uint8_t const> nftId,
rust::Slice<std::uint8_t> out) const noexcept
{
return guarded(hostFunctions_.getJournal(), kHostInternal, [&] {
if (account.size() != AccountID::size() || nftId.size() != uint256::size())
return hfErrorToInt(HostFunctionError::InvalidParams);
auto const value = hostFunctions_.getNFT(
AccountID::fromVoid(account.data()), uint256::fromVoid(nftId.data()));
if (!value)
return hfErrorToInt(value.error());
return answer(out, value->data(), value->size());
});
}
std::int32_t
HostContext::getNFTIssuer(rust::Slice<std::uint8_t const> nftId, rust::Slice<std::uint8_t> out)
const noexcept
{
return guarded(hostFunctions_.getJournal(), kHostInternal, [&] {
if (nftId.size() != uint256::size())
return hfErrorToInt(HostFunctionError::InvalidParams);
auto const value = hostFunctions_.getNFTIssuer(uint256::fromVoid(nftId.data()));
if (!value)
return hfErrorToInt(value.error());
return answer(out, value->data(), value->size());
});
}
std::int32_t
HostContext::getNFTTaxon(rust::Slice<std::uint8_t const> nftId, rust::Slice<std::uint8_t> out)
const noexcept
{
return guarded(hostFunctions_.getJournal(), kHostInternal, [&] {
if (nftId.size() != uint256::size())
return hfErrorToInt(HostFunctionError::InvalidParams);
auto const value = hostFunctions_.getNFTTaxon(uint256::fromVoid(nftId.data()));
if (!value)
return hfErrorToInt(value.error());
return answerScalar(out, *value);
});
}
std::int32_t
HostContext::getNFTFlags(rust::Slice<std::uint8_t const> nftId) const noexcept
{
return guarded(hostFunctions_.getJournal(), kHostInternal, [&] {
if (nftId.size() != uint256::size())
return hfErrorToInt(HostFunctionError::InvalidParams);
auto const value = hostFunctions_.getNFTFlags(uint256::fromVoid(nftId.data()));
if (!value)
return hfErrorToInt(value.error());
return *value;
});
}
std::int32_t
HostContext::getNFTTransferFee(rust::Slice<std::uint8_t const> nftId) const noexcept
{
return guarded(hostFunctions_.getJournal(), kHostInternal, [&] {
if (nftId.size() != uint256::size())
return hfErrorToInt(HostFunctionError::InvalidParams);
auto const value = hostFunctions_.getNFTTransferFee(uint256::fromVoid(nftId.data()));
if (!value)
return hfErrorToInt(value.error());
return *value;
});
}
std::int32_t
HostContext::getNFTSequence(rust::Slice<std::uint8_t const> nftId, rust::Slice<std::uint8_t> out)
const noexcept
{
return guarded(hostFunctions_.getJournal(), kHostInternal, [&] {
if (nftId.size() != uint256::size())
return hfErrorToInt(HostFunctionError::InvalidParams);
auto const value = hostFunctions_.getNFTSequence(uint256::fromVoid(nftId.data()));
if (!value)
return hfErrorToInt(value.error());
return answerScalar(out, *value);
});
}
} // namespace xrpl