mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-19 13:20:54 +00:00
feat: Hook up nft_uri, nft_issuer, nft_taxon, nft_flags, nft_xfer_fee, nft_serial host functions
This commit is contained in:
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user