mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-28 01:30:58 +00:00
Compare commits
78 Commits
audit_wasm
...
ripple/se/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ab800bf47 | ||
|
|
fe1436fb3e | ||
|
|
db6fe77959 | ||
|
|
be58ec0cd5 | ||
|
|
4073838f66 | ||
|
|
e227e2ce8b | ||
|
|
383a2f8819 | ||
|
|
e64703b151 | ||
|
|
c91640e3ac | ||
|
|
0ceee3a013 | ||
|
|
2e48bb4b7f | ||
|
|
4a01fe5aa6 | ||
|
|
f60a9f1c64 | ||
|
|
68f0dcae53 | ||
|
|
1f9675d22f | ||
|
|
029fa435ee | ||
|
|
c92ca426bf | ||
|
|
ff896b226a | ||
|
|
3c53c94e7a | ||
|
|
36fb84926e | ||
|
|
56decaf852 | ||
|
|
1d9fe9dbaf | ||
|
|
a4dfdaf77a | ||
|
|
34522bc668 | ||
|
|
22054a573d | ||
|
|
63bd5fc4ee | ||
|
|
82e7f7eeec | ||
|
|
63d09f9c51 | ||
|
|
d606f88e84 | ||
|
|
51458a92e2 | ||
|
|
864d88a3c2 | ||
|
|
7004d216ed | ||
|
|
00eeab6d44 | ||
|
|
5148098079 | ||
|
|
4fe508cb92 | ||
|
|
eb2d44d442 | ||
|
|
cd46b5d999 | ||
|
|
13707dda05 | ||
|
|
f625fb993a | ||
|
|
69c61b2235 | ||
|
|
6ae0e860ff | ||
|
|
c077e7f073 | ||
|
|
4621e4eda3 | ||
|
|
981ac7abf4 | ||
|
|
43c80edaf4 | ||
|
|
384b3608d7 | ||
|
|
e1513570df | ||
|
|
f0d0739528 | ||
|
|
103379836a | ||
|
|
397bc8781e | ||
|
|
8bb8c2e38b | ||
|
|
36ecd3b52b | ||
|
|
9d1f51b01a | ||
|
|
827ecc6e3a | ||
|
|
27ac30208d | ||
|
|
e40a4df777 | ||
|
|
95d78a8600 | ||
|
|
578413859c | ||
|
|
3195eb16b2 | ||
|
|
eed280d169 | ||
|
|
aebec5378c | ||
|
|
67e2c1b563 | ||
|
|
209ee25c32 | ||
|
|
af6beb1d7c | ||
|
|
ce19c13059 | ||
|
|
43caa1ef29 | ||
|
|
fe601308e7 | ||
|
|
51f1be7f5b | ||
|
|
f57b855d74 | ||
|
|
86525d8583 | ||
|
|
51ee06429b | ||
|
|
ca85d09f02 | ||
|
|
e59f5f3b01 | ||
|
|
c8b06e7de1 | ||
|
|
c3fd52c177 | ||
|
|
737fab5471 | ||
|
|
5a6c4e8ae0 | ||
|
|
7420f47658 |
24
.codecov.yml
24
.codecov.yml
@@ -1,32 +1,10 @@
|
||||
codecov:
|
||||
require_ci_to_pass: true
|
||||
# The C++ and Rust uploads land minutes apart; without this gate Codecov
|
||||
# publishes a near-zero total from whichever one arrives first.
|
||||
notify:
|
||||
after_n_builds: 2
|
||||
wait_for_ci: true
|
||||
|
||||
comment:
|
||||
behavior: default
|
||||
layout: reach,diff,flags,tree,reach
|
||||
show_carryforward_flags: true
|
||||
after_n_builds: 2
|
||||
|
||||
# C++ and Rust coverage upload from independent workflows under the `cpp` and
|
||||
# `rust` flags; carryforward keeps one language's total when only the other reran.
|
||||
flag_management:
|
||||
default_rules:
|
||||
carryforward: true
|
||||
individual_flags:
|
||||
- name: cpp
|
||||
carryforward: true
|
||||
paths:
|
||||
- include/
|
||||
- src/
|
||||
- name: rust
|
||||
carryforward: true
|
||||
paths:
|
||||
- crates/
|
||||
show_carryforward_flags: false
|
||||
|
||||
coverage:
|
||||
range: "70..85"
|
||||
|
||||
@@ -7,6 +7,8 @@ ignorePaths:
|
||||
- cmake/**
|
||||
- LICENSE.md
|
||||
- .clang-tidy
|
||||
- src/test/app/wasm_fixtures/**/*.wat
|
||||
- src/test/app/wasm_fixtures/*.c
|
||||
language: en
|
||||
allowCompoundWords: true # TODO (#6334)
|
||||
ignoreRandomStrings: true
|
||||
@@ -104,7 +106,6 @@ words:
|
||||
- deleteme
|
||||
- demultiplexer
|
||||
- deserializaton
|
||||
- desugars
|
||||
- desync
|
||||
- desynced
|
||||
- determ
|
||||
@@ -130,7 +131,6 @@ words:
|
||||
- gcov
|
||||
- gcovr
|
||||
- ghead
|
||||
- gmock
|
||||
- Gnutella
|
||||
- godexsoft
|
||||
- gpgcheck
|
||||
@@ -140,9 +140,7 @@ words:
|
||||
- hwaddress
|
||||
- hwrap
|
||||
- ifndef
|
||||
- impls
|
||||
- inequation
|
||||
- initialiser
|
||||
- insuf
|
||||
- insuff
|
||||
- invasively
|
||||
@@ -250,7 +248,6 @@ words:
|
||||
- pyparsing
|
||||
- qalloc
|
||||
- qbsprofile
|
||||
- qself
|
||||
- queuable
|
||||
- Raphson
|
||||
- rcflags
|
||||
@@ -315,7 +312,6 @@ words:
|
||||
- summands
|
||||
- superpeer
|
||||
- superpeers
|
||||
- Swatinem
|
||||
- takergets
|
||||
- takerpays
|
||||
- ters
|
||||
@@ -344,7 +340,6 @@ words:
|
||||
- unflatten
|
||||
- unfund
|
||||
- unimpair
|
||||
- unmetered
|
||||
- unroutable
|
||||
- unscalable
|
||||
- unserviced
|
||||
@@ -365,7 +360,6 @@ words:
|
||||
- vfalco
|
||||
- vinnie
|
||||
- wasmi
|
||||
- Werror
|
||||
- wextra
|
||||
- wptr
|
||||
- writeme
|
||||
|
||||
17
.github/dependabot.yml
vendored
17
.github/dependabot.yml
vendored
@@ -19,20 +19,3 @@ updates:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: cargo
|
||||
directory: /crates
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
open-pull-requests-limit: 10
|
||||
# Bundle all Rust dependency bumps into a single PR per run to reduce noise.
|
||||
groups:
|
||||
rust-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
84
.github/workflows/cargo-audit.yml
vendored
84
.github/workflows/cargo-audit.yml
vendored
@@ -1,84 +0,0 @@
|
||||
# This workflow audits the Rust dependencies in crates/ for known security
|
||||
# advisories using cargo-audit. It runs on a weekly schedule, whenever the
|
||||
# dependency graph changes (Cargo.lock / Cargo.toml), and on demand. On a
|
||||
# scheduled run, a failure opens a tracking issue (matching the clang-tidy
|
||||
# workflow's behavior); on push/PR it simply fails the check.
|
||||
name: Cargo audit
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 06:32 UTC every Monday.
|
||||
- cron: "32 6 * * 1"
|
||||
push:
|
||||
branches:
|
||||
- "develop"
|
||||
- "release*"
|
||||
paths:
|
||||
- "crates/**/Cargo.toml"
|
||||
- "crates/Cargo.lock"
|
||||
- ".github/workflows/cargo-audit.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/**/Cargo.toml"
|
||||
- "crates/Cargo.lock"
|
||||
- ".github/workflows/cargo-audit.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: crates
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-debian:sha-2e25435
|
||||
permissions:
|
||||
contents: read
|
||||
# Needed to open an issue on scheduled failures.
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Run cargo audit
|
||||
id: audit
|
||||
continue-on-error: true
|
||||
run: |
|
||||
set -o pipefail
|
||||
cargo audit | tee /tmp/cargo-audit.txt
|
||||
|
||||
- name: Prepare issue body
|
||||
if: ${{ steps.audit.outcome != 'success' && github.event_name == 'schedule' }}
|
||||
run: |
|
||||
{
|
||||
echo "## \`cargo audit\` found advisories"
|
||||
echo
|
||||
echo '```'
|
||||
cat /tmp/cargo-audit.txt
|
||||
echo '```'
|
||||
echo
|
||||
echo "---"
|
||||
echo "*This issue was automatically created by the cargo-audit workflow.*"
|
||||
} >/tmp/cargo-audit-issue.md
|
||||
|
||||
- name: Create issue
|
||||
if: ${{ steps.audit.outcome != 'success' && github.event_name == 'schedule' }}
|
||||
uses: XRPLF/actions/create-issue@2b8bc36af85b88bca0dd7bfac2e2dc05f94ad712
|
||||
with:
|
||||
title: "cargo audit found vulnerabilities"
|
||||
body_file: /tmp/cargo-audit-issue.md
|
||||
labels: "Bug,Security"
|
||||
|
||||
- name: Fail if advisories were found
|
||||
if: ${{ steps.audit.outcome != 'success' }}
|
||||
run: |
|
||||
echo "cargo audit found advisories!"
|
||||
exit 1
|
||||
10
.github/workflows/on-pr.yml
vendored
10
.github/workflows/on-pr.yml
vendored
@@ -85,7 +85,6 @@ jobs:
|
||||
.github/workflows/reusable-check-autogen.yml
|
||||
.github/workflows/reusable-clang-tidy.yml
|
||||
.github/workflows/reusable-package.yml
|
||||
.github/workflows/reusable-rust.yml
|
||||
.github/workflows/reusable-strategy-matrix.yml
|
||||
.github/workflows/reusable-test.yml
|
||||
.github/workflows/reusable-upload-recipe.yml
|
||||
@@ -96,7 +95,6 @@ jobs:
|
||||
cfg/**
|
||||
cmake/**
|
||||
conan/**
|
||||
crates/**
|
||||
external/**
|
||||
include/**
|
||||
src/**
|
||||
@@ -170,13 +168,6 @@ jobs:
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
rust:
|
||||
needs: should-run
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-rust.yml
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
package:
|
||||
needs: [should-run, build-test]
|
||||
# Packaging consumes the debian/rhel release binaries, which are only built
|
||||
@@ -220,7 +211,6 @@ jobs:
|
||||
- check-rename
|
||||
- clang-tidy
|
||||
- build-test
|
||||
- rust
|
||||
- package
|
||||
- upload-recipe
|
||||
- notify-clio
|
||||
|
||||
7
.github/workflows/on-trigger.yml
vendored
7
.github/workflows/on-trigger.yml
vendored
@@ -23,7 +23,6 @@ on:
|
||||
- ".github/workflows/reusable-check-autogen.yml"
|
||||
- ".github/workflows/reusable-clang-tidy.yml"
|
||||
- ".github/workflows/reusable-package.yml"
|
||||
- ".github/workflows/reusable-rust.yml"
|
||||
- ".github/workflows/reusable-strategy-matrix.yml"
|
||||
- ".github/workflows/reusable-test.yml"
|
||||
- ".github/workflows/reusable-upload-recipe.yml"
|
||||
@@ -34,7 +33,6 @@ on:
|
||||
- "cfg/**"
|
||||
- "cmake/**"
|
||||
- "conan/**"
|
||||
- "crates/**"
|
||||
- "external/**"
|
||||
- "include/**"
|
||||
- "src/**"
|
||||
@@ -98,11 +96,6 @@ jobs:
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
rust:
|
||||
uses: ./.github/workflows/reusable-rust.yml
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
upload-recipe:
|
||||
needs: build-test
|
||||
# Only run when pushing to the develop branch.
|
||||
|
||||
11
.github/workflows/reusable-build-test-config.yml
vendored
11
.github/workflows/reusable-build-test-config.yml
vendored
@@ -276,7 +276,7 @@ jobs:
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
ldd ./xrpld
|
||||
if [ "$(ldd ./xrpld | grep -E '(libstdc\+\+)' | wc -l)" -eq 0 ]; then
|
||||
if [ "$(ldd ./xrpld | grep -E '(libstdc\+\+|libgcc)' | wc -l)" -eq 0 ]; then
|
||||
echo 'The binary is statically linked.'
|
||||
else
|
||||
echo 'The binary is dynamically linked.'
|
||||
@@ -289,14 +289,6 @@ jobs:
|
||||
run: |
|
||||
./xrpld --version | grep libvoidstar
|
||||
|
||||
- name: Run Rust tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
working-directory: crates
|
||||
# `xrpl-wasm-vm-ffi` is left out on Windows: its tests link as an executable, and
|
||||
# MSVC - unlike the Unix linkers - will not dead-strip the never-called cxx wrappers
|
||||
# whose C++ shims only the CMake build defines. The other runners cover these tests.
|
||||
run: cargo nextest run --workspace --all-features --locked --no-tests=warn ${{ runner.os == 'Windows' && '--exclude xrpl-wasm-vm-ffi' || '' }}
|
||||
|
||||
- name: Run the separate tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
@@ -402,7 +394,6 @@ jobs:
|
||||
disable_telem: true
|
||||
fail_ci_if_error: true
|
||||
files: ${{ env.BUILD_DIR }}/coverage.xml
|
||||
flags: cpp
|
||||
plugins: noop
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
|
||||
6
.github/workflows/reusable-clang-tidy.yml
vendored
6
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -88,12 +88,6 @@ jobs:
|
||||
run: |
|
||||
ninja -j ${{ steps.nproc.outputs.nproc }} xrpl.libpb
|
||||
|
||||
# clang-tidy needs cxxbridge headers generated from Rust crates
|
||||
- name: Build xrpl_crates
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
ninja -j ${{ steps.nproc.outputs.nproc }} xrpl_crates
|
||||
|
||||
- name: Run clang tidy
|
||||
id: run_clang_tidy
|
||||
continue-on-error: true
|
||||
|
||||
86
.github/workflows/reusable-rust.yml
vendored
86
.github/workflows/reusable-rust.yml
vendored
@@ -1,86 +0,0 @@
|
||||
# Clippy, coverage and documentation for the Rust crates in crates/. Each runs
|
||||
# as an independent job on a GitHub-hosted runner, but inside the same container
|
||||
# image used to build the crates in the C++/Corrosion path, so the toolchain
|
||||
# (and therefore the lints, coverage instrumentation and the cargo cache) matches
|
||||
# what production builds use.
|
||||
#
|
||||
# Rust unit tests are deliberately NOT run here. They run as part of the C++
|
||||
# build (reusable-build-test-config.yml), which already compiles the crates on a
|
||||
# self-hosted runner, so there is no need to provision a toolchain again.
|
||||
name: Rust
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
description: "The Codecov token to use for uploading coverage reports."
|
||||
required: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: crates
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fecfc0c
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Cache cargo artifacts
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
workspaces: crates
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fecfc0c
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Cache cargo artifacts
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
workspaces: crates
|
||||
|
||||
- name: Generate coverage report
|
||||
run: cargo llvm-cov nextest --workspace --all-features --locked --no-tests=warn --lcov --output-path lcov.info
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
disable_search: true
|
||||
disable_telem: true
|
||||
fail_ci_if_error: true
|
||||
files: crates/lcov.info
|
||||
flags: rust
|
||||
plugins: noop
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
|
||||
doc:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fecfc0c
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Cache cargo artifacts
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
workspaces: crates
|
||||
|
||||
- name: Build documentation
|
||||
env:
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
run: cargo doc --workspace --no-deps --all-features --locked
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -89,6 +89,3 @@ target/
|
||||
|
||||
# clangd cache
|
||||
/.cache
|
||||
|
||||
# Rust build directory
|
||||
crates/target
|
||||
|
||||
@@ -62,15 +62,6 @@ repos:
|
||||
types_or: [c++, c, proto]
|
||||
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: cargo-fmt
|
||||
name: cargo fmt
|
||||
entry: cargo fmt --manifest-path crates/Cargo.toml --all
|
||||
language: system
|
||||
types: [rust]
|
||||
pass_filenames: false # rustfmt formats the whole workspace
|
||||
|
||||
- repo: https://github.com/BlankSpruce/gersemi-pre-commit
|
||||
rev: e98930bdc210d3387007f9252d8c1694ea7e410f # frozen: 0.27.7
|
||||
hooks:
|
||||
|
||||
@@ -114,6 +114,7 @@ find_package(OpenSSL REQUIRED)
|
||||
find_package(secp256k1 REQUIRED)
|
||||
find_package(SOCI REQUIRED)
|
||||
find_package(SQLite3 REQUIRED)
|
||||
find_package(wasmi REQUIRED)
|
||||
find_package(xxHash REQUIRED)
|
||||
|
||||
target_link_libraries(
|
||||
@@ -158,7 +159,6 @@ if(coverage)
|
||||
include(XrplCov)
|
||||
endif()
|
||||
|
||||
add_subdirectory(crates)
|
||||
include(XrplCore)
|
||||
include(XrplProtocolAutogen)
|
||||
include(XrplInstall)
|
||||
|
||||
@@ -1348,6 +1348,39 @@
|
||||
# Example:
|
||||
# owner_reserve = 200000 # 0.2 XRP
|
||||
#
|
||||
# gas_limit = <gas>
|
||||
#
|
||||
# The gas limit is the maximum amount of gas that can be
|
||||
# consumed by a single transaction. The gas limit is used to prevent
|
||||
# transactions from consuming too many resources.
|
||||
#
|
||||
# If this parameter is unspecified, xrpld will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# gas_limit = 1000000 # 1 million gas
|
||||
#
|
||||
# bytecode_size_limit = <bytes>
|
||||
#
|
||||
# The bytecode size limit is the maximum size of a WASM extension in
|
||||
# bytes. The size limit is used to prevent extensions from consuming
|
||||
# too many resources.
|
||||
#
|
||||
# If this parameter is unspecified, xrpld will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# bytecode_size_limit = 100000 # 100 kb
|
||||
#
|
||||
# gas_price = <micro-drops>
|
||||
#
|
||||
# The gas price is the conversion between WASM gas and its price in drops.
|
||||
#
|
||||
# If this parameter is unspecified, xrpld will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# gas_price = 1000000 # 1 drop per gas
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
# 9. Misc Settings
|
||||
|
||||
@@ -67,6 +67,7 @@ target_link_libraries(
|
||||
Xrpl::opts
|
||||
Xrpl::syslibs
|
||||
secp256k1::secp256k1
|
||||
wasmi::wasmi
|
||||
xrpl.libpb
|
||||
xxHash::xxhash
|
||||
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
|
||||
@@ -205,17 +206,7 @@ target_link_libraries(
|
||||
)
|
||||
|
||||
add_module(xrpl tx)
|
||||
# The wasm engine is a Rust crate reached over cxx: the bridge target supplies the
|
||||
# generated `lib.h` and `rust/cxx.h` that `tx/wasm` compiles against, and the Rust
|
||||
# static library everything downstream links. PUBLIC because the include path travels
|
||||
# with the module's own public headers.
|
||||
target_link_libraries(
|
||||
xrpl.libxrpl.tx
|
||||
PUBLIC xrpl.libxrpl.ledger xrpl_wasm_vm_ffi_cxxbridge
|
||||
)
|
||||
# Those headers do not exist at configure time, and the header-verification target
|
||||
# compiles this module's headers on their own, so both need the crates built first.
|
||||
add_dependencies(xrpl.libxrpl.tx xrpl_crates)
|
||||
target_link_libraries(xrpl.libxrpl.tx PUBLIC xrpl.libxrpl.ledger)
|
||||
|
||||
add_module(xrpl consensus)
|
||||
target_link_libraries(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"requires": [
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688",
|
||||
"wasmi/1.0.9#1fecdab9b90c96698eb35ea99ca4f5cb%1782307153.343419",
|
||||
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447",
|
||||
"soci/4.0.3#e726491a03468795453f7c83fc924a96%1782392402.679521",
|
||||
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1782307151.633168",
|
||||
|
||||
@@ -35,6 +35,7 @@ class Xrpl(ConanFile):
|
||||
"nudb/2.0.9",
|
||||
"openssl/3.6.3",
|
||||
"soci/4.0.3",
|
||||
"wasmi/1.0.9",
|
||||
"zlib/1.3.2",
|
||||
]
|
||||
|
||||
@@ -223,6 +224,7 @@ class Xrpl(ConanFile):
|
||||
"soci::soci",
|
||||
"secp256k1::secp256k1",
|
||||
"sqlite3::sqlite",
|
||||
"wasmi::wasmi",
|
||||
"xxhash::xxhash",
|
||||
"zlib::zlib",
|
||||
]
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
rustflags = ["-C", "link-args=-static-libgcc"]
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
@@ -1,59 +0,0 @@
|
||||
set(CORROSION_VERSION 0.6.1)
|
||||
|
||||
find_package(Corrosion ${CORROSION_VERSION} QUIET)
|
||||
if(NOT Corrosion_FOUND)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
Corrosion
|
||||
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
|
||||
GIT_TAG v${CORROSION_VERSION}
|
||||
)
|
||||
FetchContent_MakeAvailable(Corrosion)
|
||||
endif()
|
||||
|
||||
corrosion_import_crate(MANIFEST_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml)
|
||||
|
||||
file(
|
||||
WRITE "${CMAKE_CURRENT_BINARY_DIR}/.clang-tidy"
|
||||
"# Auto-generated by crates/CMakeLists.txt. Do not edit.\n"
|
||||
"# Neutralizes clang-tidy for corrosion/cxxbridge-generated C++.\n"
|
||||
"# One check kept enabled to avoid clang-tidy's \"no checks enabled\" error.\n"
|
||||
"Checks: '-*,google-readability-todo'\n"
|
||||
"WarningsAsErrors: ''\n"
|
||||
"HeaderFilterRegex: ''\n"
|
||||
"InheritParentConfig: false\n"
|
||||
)
|
||||
|
||||
# Umbrella target that aggregates all crate-generated code (cxxbridge headers,
|
||||
# etc.). Build this before running clang-tidy so generated headers are present.
|
||||
add_custom_target(xrpl_crates)
|
||||
|
||||
# add_xrpl_crate(<name> CRATE <crate> FILES <file>...) Creates a cxxbridge
|
||||
# target <name>_cxxbridge and registers it with xrpl_crates.
|
||||
function(add_xrpl_crate name)
|
||||
cmake_parse_arguments(ARG "" "CRATE" "FILES" ${ARGN})
|
||||
corrosion_add_cxxbridge(${name}_cxxbridge CRATE ${ARG_CRATE} FILES
|
||||
${ARG_FILES}
|
||||
)
|
||||
# Generated cxxbridge headers don't exist at configure time; CMake 3.28+
|
||||
# validates INTERFACE_SOURCES on consuming targets. Clear it to skip the
|
||||
# existence check — build-time ordering is enforced by the custom commands.
|
||||
set_target_properties(${name}_cxxbridge PROPERTIES INTERFACE_SOURCES "")
|
||||
add_dependencies(xrpl_crates ${name}_cxxbridge)
|
||||
endfunction()
|
||||
|
||||
add_xrpl_crate(xrpl_wasm_vm_ffi CRATE xrpl_wasm_vm_ffi FILES lib.rs)
|
||||
|
||||
# Test-only, and deliberately not part of xrpl_wasm_vm_ffi: it carries the `wat` assembler,
|
||||
# which the engine's `wasmi default-features = false` exists to keep out of the consensus
|
||||
# path. Linked from src/tests/libxrpl only, so the shipped node cannot contain it.
|
||||
add_xrpl_crate(xrpl_wasm_testkit CRATE xrpl_wasm_testkit FILES lib.rs)
|
||||
|
||||
# The wasm bridge `include!`s a project header, so its generated translation unit needs
|
||||
# the project's include root. Deliberately only that: a header reached from here must
|
||||
# stay light enough to compile without the Boost paths this target does not get, which
|
||||
# is why `HostContext.h` forward-declares `xrpl::HostFunctions` instead of including it.
|
||||
target_include_directories(
|
||||
xrpl_wasm_vm_ffi_cxxbridge
|
||||
PRIVATE ${CMAKE_SOURCE_DIR}/include
|
||||
)
|
||||
497
crates/Cargo.lock
generated
497
crates/Cargo.lock
generated
@@ -1,497 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fe442a792c7c736eea18b32a7f8a3b63cf8aafabda6760042dc2fdeda456291"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxx-build",
|
||||
"cxxbridge-cmd",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"foldhash 0.2.0",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3184a94384c663718698311a78a51ac00c484c10b4eeac06fb0a068c5f64fa2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-cmd"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0148d8fd1199329ddf1d157a5e134e51ceff37c6a7ddd38615c399d81cb05d8d"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codespan-reporting",
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52850339faed2eaadd24e286dc1d8268cc6f8a7bd9524d713adc9099566b4c89"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c77c856545d886c9bd5215409ebb63b925e262135248b50c79e5a5f194ee47c"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash 0.1.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
|
||||
|
||||
[[package]]
|
||||
name = "string-interner"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23de088478b31c349c9ba67816fa55d9355232d63c3afea8bf513e31f0f1d2c0"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.254.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09480d646178e5fdd12bb06e812d0af9a3a191dbc9cd697fdc86687beade7393"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser 0.254.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmi"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2300d0f78cba12f14e29e8dd157ea64050c0a688179aefdb2050105805594a0c"
|
||||
dependencies = [
|
||||
"spin",
|
||||
"wasmi_collections",
|
||||
"wasmi_core",
|
||||
"wasmi_ir",
|
||||
"wasmparser 0.239.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmi_collections"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a8c42a2a76148d43097b1d7cc2a5bf33d5c23bd4dd69015fc887e311767884"
|
||||
dependencies = [
|
||||
"string-interner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmi_core"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9013136083d988725953390bf668b64b7a218fabf26f8b913bbc59546b97ee27"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmi_ir"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba1fa003f79156f406d62ef0e1464dc03e11ace37170e9fa7524299a75ad8f68"
|
||||
dependencies = [
|
||||
"wasmi_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.239.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.254.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5769a29f799fbab136aaf65b4fe5384cd7d93fe6fc9ba0dcb6c8382a1f16e27"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wast"
|
||||
version = "254.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7ed4dfc8f6b9fc38b231065e2cdfbf7359af5ab945990abf09658dcc63c3e32"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"leb128fmt",
|
||||
"memchr",
|
||||
"unicode-width",
|
||||
"wasm-encoder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wat"
|
||||
version = "1.254.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7127f7f9b8f127c879991cecd35f494e4628bae1b0874c681414d8d8831e952c"
|
||||
dependencies = [
|
||||
"wast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[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",
|
||||
"xrpl-host-functions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-testkit"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"wat",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-vm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"wasmi",
|
||||
"wat",
|
||||
"xrpl-host-functions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-vm-ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"xrpl-host-functions",
|
||||
"xrpl-wasm-vm",
|
||||
]
|
||||
@@ -1,15 +0,0 @@
|
||||
[workspace]
|
||||
members = ["xrpl-wasm-vm-ffi", "xrpl-wasm-vm", "xrpl-wasm-testkit", "xrpl-host-functions", "xrpl-host-functions-macros"]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.dependencies]
|
||||
cxx = { version = "1.0.198", features = ["c++20"] }
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
overflow-checks = true
|
||||
lto = true
|
||||
debug = true
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
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"
|
||||
|
||||
# The expansion names `::xrpl_host_functions::HostFnSpec`, so the doctest needs the
|
||||
# facade crate. Cargo allows this cycle because dev-dependencies are outside the
|
||||
# library build graph.
|
||||
[dev-dependencies]
|
||||
xrpl-host-functions.path = "../xrpl-host-functions"
|
||||
@@ -1,12 +0,0 @@
|
||||
/// Folds accumulated diagnostics into the single error a macro can return.
|
||||
///
|
||||
/// `syn::Error` is itself a collection: `combine` appends, and
|
||||
/// `into_compile_error` emits one `compile_error!` per recorded span. Folding
|
||||
/// instead of returning the first error means every mistake in a
|
||||
/// `host_functions!` block surfaces in one build rather than one per rebuild.
|
||||
pub(crate) fn combine(errors: Vec<syn::Error>) -> Option<syn::Error> {
|
||||
errors.into_iter().reduce(|mut first, next| {
|
||||
first.combine(next);
|
||||
first
|
||||
})
|
||||
}
|
||||
@@ -1,384 +0,0 @@
|
||||
mod errors;
|
||||
mod parsed_host_function;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
TraitItemFn,
|
||||
parse::{Parse, ParseStream},
|
||||
parse2,
|
||||
};
|
||||
|
||||
use parsed_host_function::ParsedHostFunction;
|
||||
|
||||
/// Declares the wasm host ABI once, and generates everything that follows from it.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// This crate is an implementation detail of `xrpl-host-functions`, which
|
||||
/// hand-writes the types the expansion refers to and holds the one declaration
|
||||
/// block. The expansion names those types by absolute path, so a call site needs
|
||||
/// `xrpl-host-functions` as a dependency but no imports from it.
|
||||
///
|
||||
/// ```
|
||||
/// use xrpl_host_functions::HostResult;
|
||||
/// use xrpl_host_functions_macros::host_functions;
|
||||
///
|
||||
/// host_functions! {
|
||||
/// /// The sequence number of the ledger being built, as 4 little-endian bytes.
|
||||
/// #[gas = 60]
|
||||
/// #[wasm_name = "ldgr_index"]
|
||||
/// fn get_ledger_sqn(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
///
|
||||
/// /// Writes `msg` to the trace log.
|
||||
/// #[gas = 500]
|
||||
/// #[wasm_name = "trace_num"]
|
||||
/// fn trace_num(&self, msg: &str, number: i64) -> HostResult<()>;
|
||||
/// }
|
||||
///
|
||||
/// // A `HostFunctions` trait, holding the declarations verbatim:
|
||||
/// struct Host;
|
||||
/// impl HostFunctions for Host {
|
||||
/// fn get_ledger_sqn(&self, out: &mut [u8]) -> HostResult<usize> {
|
||||
/// out[..4].copy_from_slice(&7u32.to_le_bytes());
|
||||
/// Ok(4)
|
||||
/// }
|
||||
/// fn trace_num(&self, _msg: &str, _number: i64) -> HostResult<()> { Ok(()) }
|
||||
/// }
|
||||
///
|
||||
/// // A `HostFunctionSpec` enum carrying the ABI metadata as a `const` table:
|
||||
/// assert_eq!(HostFunctionSpec::GetLedgerSqn.gas(), 60);
|
||||
/// assert_eq!(HostFunctionSpec::TraceNum.wasm_name(), "trace_num");
|
||||
/// assert_eq!(HostFunctionSpec::ALL.len(), 2);
|
||||
/// ```
|
||||
///
|
||||
/// A declaration must be a plain `fn` taking `&self` and returning
|
||||
/// `HostResult<T>`, 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())
|
||||
.unwrap_or_else(syn::Error::into_compile_error)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||
let HostFunctionsInput { functions } = parse2(input)?;
|
||||
|
||||
let mut parsed = Vec::with_capacity(functions.len());
|
||||
let mut errors = Vec::new();
|
||||
for function in functions {
|
||||
match ParsedHostFunction::parse(function) {
|
||||
Ok(function) => parsed.push(function),
|
||||
Err(error) => errors.push(error),
|
||||
}
|
||||
}
|
||||
if let Some(error) = errors::combine(errors) {
|
||||
return Err(error);
|
||||
}
|
||||
if let Some(error) = errors::combine(collisions(&parsed)) {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
Ok(generate(&parsed))
|
||||
}
|
||||
|
||||
/// Names two declarations may not share, because the generated code would then
|
||||
/// fail to compile at a span the caller cannot see.
|
||||
fn collisions(functions: &[ParsedHostFunction]) -> Vec<syn::Error> {
|
||||
let mut errors = Vec::new();
|
||||
let mut variants = HashSet::new();
|
||||
let mut wasm_names = HashSet::new();
|
||||
|
||||
for function in functions {
|
||||
if !variants.insert(function.variant.to_string()) {
|
||||
errors.push(syn::Error::new_spanned(
|
||||
&function.variant,
|
||||
format!(
|
||||
"another host function already becomes the `{}` variant",
|
||||
function.variant
|
||||
),
|
||||
));
|
||||
}
|
||||
if !wasm_names.insert(function.wasm_name.value()) {
|
||||
errors.push(syn::Error::new_spanned(
|
||||
&function.wasm_name,
|
||||
format!(
|
||||
"another host function is already imported as `{}`",
|
||||
function.wasm_name.value()
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
errors
|
||||
}
|
||||
|
||||
fn generate(functions: &[ParsedHostFunction]) -> TokenStream {
|
||||
let trait_methods = functions.iter().map(ParsedHostFunction::trait_method);
|
||||
let variants = functions
|
||||
.iter()
|
||||
.map(ParsedHostFunction::variant_declaration);
|
||||
let spec_arms = functions.iter().map(ParsedHostFunction::spec_arm);
|
||||
let all = functions.iter().map(|function| &function.variant);
|
||||
|
||||
quote! {
|
||||
/// The host side of the wasm ABI: one method per function a guest may
|
||||
/// import.
|
||||
///
|
||||
/// 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 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.
|
||||
///
|
||||
/// # The output contract
|
||||
///
|
||||
/// A method handed an `out` buffer **writes into it only when the whole
|
||||
/// value fits, and returns the value's true length whether it fitted or
|
||||
/// not.**
|
||||
///
|
||||
/// The length is the value's, not the number of bytes written, because it
|
||||
/// is how a guest that asked with too small a buffer learns the size to
|
||||
/// ask for next time. The engine turns a length past the buffer into
|
||||
/// `BufferTooSmall`, and one past the field cap into `DataFieldTooLarge`,
|
||||
/// so a host needs to know neither.
|
||||
///
|
||||
/// Writing nothing unless the value fits is the half only a host can hold
|
||||
/// up. An engine can bound how many bytes are *writable* — and does, by
|
||||
/// handing over a region clamped to the field cap — but it cannot take
|
||||
/// back what a method already put there. A host that wrote a truncated
|
||||
/// prefix and then reported the larger length would leave those bytes in
|
||||
/// guest memory behind a refusal the guest is told to ignore. C++'s
|
||||
/// `setData` is the reference point: it wrote only on a value that fit.
|
||||
pub trait HostFunctions {
|
||||
#(#trait_methods)*
|
||||
}
|
||||
|
||||
/// One row of the ABI table: what [`HostFunctionSpec::wasm_name`] and
|
||||
/// [`HostFunctionSpec::gas`] read from.
|
||||
///
|
||||
/// Private, and the only reason it exists is to keep both of them fed
|
||||
/// from a single `match` over the declarations.
|
||||
struct HostFnSpec {
|
||||
name: &'static str,
|
||||
gas: u64,
|
||||
}
|
||||
|
||||
/// Identifies one host function, and is the compile-time source of its
|
||||
/// ABI metadata.
|
||||
///
|
||||
/// One variant per `host_functions!` declaration, named by converting the
|
||||
/// function name to PascalCase. [`Self::ALL`] is the whole ABI, which is
|
||||
/// what a wasm engine iterates to build its import table.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum HostFunctionSpec {
|
||||
#(#variants,)*
|
||||
}
|
||||
|
||||
impl HostFunctionSpec {
|
||||
/// Every host function, in the order declared.
|
||||
///
|
||||
/// This is the complete import surface a guest may link against: a
|
||||
/// function absent here cannot be called, and one present here must
|
||||
/// be registered for a module that imports it to instantiate.
|
||||
pub const ALL: &'static [Self] = &[#(Self::#all,)*];
|
||||
|
||||
/// This function's row of the ABI table.
|
||||
const fn spec(self) -> HostFnSpec {
|
||||
match self {
|
||||
#(#spec_arms,)*
|
||||
}
|
||||
}
|
||||
|
||||
/// The name a guest imports this function under.
|
||||
///
|
||||
/// A guest's import name must match this exactly, or the module
|
||||
/// fails to instantiate. Usable in `const` context, so import lists
|
||||
/// can be built at compile time.
|
||||
pub const fn wasm_name(self) -> &'static str {
|
||||
self.spec().name
|
||||
}
|
||||
|
||||
/// Gas charged before the call runs, independent of its arguments.
|
||||
///
|
||||
/// Consensus-relevant: two nodes that disagree on this value
|
||||
/// disagree on transaction outcomes. Usable in `const` context, so
|
||||
/// gas tables can be built at compile time.
|
||||
pub const fn gas(self) -> u64 {
|
||||
self.spec().gas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn accepts_an_empty_block() {
|
||||
expand(quote! {}).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_mistakes_from_every_function() {
|
||||
let error = expand(quote! {
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
|
||||
#[gas = 2000]
|
||||
fn sha512_half(&self, data: &[u8]) -> HostResult<[u8; 32]>;
|
||||
})
|
||||
.expect_err("expected parsing to fail");
|
||||
|
||||
let messages: Vec<_> = error.into_iter().map(|error| error.to_string()).collect();
|
||||
assert_eq!(messages.len(), 2, "{messages:?}");
|
||||
assert!(messages[0].contains("missing `#[gas"), "{messages:?}");
|
||||
assert!(messages[1].contains("missing `#[wasm_name"), "{messages:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn propagates_syntax_errors() {
|
||||
let error = expand(quote! { fn missing_semicolon() }).expect_err("expected a syntax error");
|
||||
assert!(!error.to_string().is_empty());
|
||||
}
|
||||
|
||||
/// The messages of every diagnostic recorded by one failed `expand`.
|
||||
fn messages(input: TokenStream) -> Vec<String> {
|
||||
let Err(error) = expand(input) else {
|
||||
panic!("expected expansion to fail");
|
||||
};
|
||||
error.into_iter().map(|error| error.to_string()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generates_the_trait_the_enum_and_the_table() {
|
||||
let generated = expand(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
|
||||
#[gas = 500]
|
||||
#[wasm_name = "trace_num"]
|
||||
fn trace_num(&self, msg: &str, number: i64) -> HostResult<()>;
|
||||
})
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
for expected in [
|
||||
"pub trait HostFunctions",
|
||||
"fn get_ledger_sqn (& self) -> HostResult < [u8 ; 4] > ;",
|
||||
"fn trace_num (& self , msg : & str , number : i64) -> HostResult < () > ;",
|
||||
"pub enum HostFunctionSpec { GetLedgerSqn , TraceNum , }",
|
||||
"pub const ALL : & 'static [Self] = & [Self :: GetLedgerSqn , Self :: TraceNum ,]",
|
||||
// The table's row type is generated too, and stays private.
|
||||
"struct HostFnSpec { name : & 'static str , gas : u64 , }",
|
||||
"const fn spec (self) -> HostFnSpec",
|
||||
"Self :: GetLedgerSqn => HostFnSpec { name : \"ldgr_index\" , gas : 60u64 }",
|
||||
"pub const fn wasm_name (self) -> & 'static str",
|
||||
"pub const fn gas (self) -> u64",
|
||||
] {
|
||||
assert!(generated.contains(expected), "missing {expected:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The expansion stands alone: every name in it is either generated here or
|
||||
/// written in the declarations, so it cannot depend on the crate it lands in.
|
||||
#[test]
|
||||
fn names_no_crate_of_its_own() {
|
||||
let generated = expand(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
})
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
assert!(!generated.contains("xrpl_host_functions"), "{generated}");
|
||||
|
||||
// `Self::Variant` is the only path the expansion may build: anything else
|
||||
// would reach out of the generated code. Doc comments spell paths without
|
||||
// spaces (`Self::ALL`), so they do not match.
|
||||
for (index, _) in generated.match_indices(" :: ") {
|
||||
assert!(
|
||||
generated[..index].ends_with("Self"),
|
||||
"path out of the expansion at {index}: {generated}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// `spec` is an implementation detail of the two accessors, so it must not
|
||||
/// become part of the ABI crate's public surface.
|
||||
#[test]
|
||||
fn keeps_the_table_row_private() {
|
||||
let generated = expand(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
})
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
assert!(!generated.contains("pub struct HostFnSpec"), "{generated}");
|
||||
assert!(!generated.contains("pub const fn spec"), "{generated}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_two_functions_that_share_a_wasm_name() {
|
||||
let messages = messages(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace(&self, msg: &str) -> HostResult<()>;
|
||||
|
||||
#[gas = 70]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace_num(&self, msg: &str, number: i64) -> HostResult<()>;
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
assert!(
|
||||
messages[0].contains("already imported as `trace`"),
|
||||
"{messages:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Names that differ only in underscores collapse to one enum variant.
|
||||
#[test]
|
||||
fn rejects_two_functions_that_share_a_variant() {
|
||||
let messages = messages(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "a"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
|
||||
#[gas = 70]
|
||||
#[wasm_name = "b"]
|
||||
fn get_ledger__sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
assert!(
|
||||
messages[0].contains("`GetLedgerSqn` variant"),
|
||||
"{messages:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,871 +0,0 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
Attribute, Expr, ExprLit, Ident, Lit, LitStr, PathArguments, ReceiverKind, ReturnType, Safety,
|
||||
Signature, TraitItemFn, Type, TypePath,
|
||||
};
|
||||
|
||||
use crate::errors;
|
||||
|
||||
/// `#[gas = N]`: the base gas charged before the call runs.
|
||||
const GAS: &str = "gas";
|
||||
/// `#[wasm_name = "..."]`: the name the guest imports the function under.
|
||||
const WASM_NAME: &str = "wasm_name";
|
||||
/// `///` desugars to `#[doc = "..."]` before macro expansion.
|
||||
const DOC: &str = "doc";
|
||||
/// The alias every declaration returns its success type through.
|
||||
const HOST_RESULT: &str = "HostResult";
|
||||
|
||||
/// One entry of a `host_functions!` block: its ABI metadata and its signature.
|
||||
pub(crate) struct ParsedHostFunction {
|
||||
pub(crate) gas: u64,
|
||||
/// Kept as the literal the user wrote, so diagnostics and the generated
|
||||
/// string both carry that span.
|
||||
pub(crate) wasm_name: LitStr,
|
||||
/// Doc comments, in source order, to re-emit on the generated items.
|
||||
pub(crate) docs: Vec<Attribute>,
|
||||
/// The enum variant this declaration becomes, spanned at the function name.
|
||||
pub(crate) variant: Ident,
|
||||
pub(crate) signature: Signature,
|
||||
}
|
||||
|
||||
impl ParsedHostFunction {
|
||||
/// `#[doc …] fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;`
|
||||
pub(crate) fn trait_method(&self) -> TokenStream {
|
||||
let docs = &self.docs;
|
||||
// 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)*
|
||||
#signature;
|
||||
}
|
||||
}
|
||||
|
||||
/// `#[doc …] GetLedgerSqn`
|
||||
pub(crate) fn variant_declaration(&self) -> TokenStream {
|
||||
let docs = &self.docs;
|
||||
let variant = &self.variant;
|
||||
quote! {
|
||||
#(#docs)*
|
||||
#variant
|
||||
}
|
||||
}
|
||||
|
||||
/// `Self::GetLedgerSqn => HostFnSpec { name: "ldgr_index", gas: 60u64 }`
|
||||
pub(crate) fn spec_arm(&self) -> TokenStream {
|
||||
let Self {
|
||||
gas,
|
||||
wasm_name,
|
||||
variant,
|
||||
..
|
||||
} = self;
|
||||
quote! {
|
||||
Self::#variant => HostFnSpec { name: #wasm_name, gas: #gas }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse(function: TraitItemFn) -> syn::Result<Self> {
|
||||
let mut gas = None;
|
||||
let mut wasm_name = None;
|
||||
let mut docs = Vec::new();
|
||||
let mut errors = Vec::new();
|
||||
|
||||
// Tracked separately from `gas`/`wasm_name` so a malformed attribute is
|
||||
// not also reported as a missing one.
|
||||
let mut saw_gas = false;
|
||||
let mut saw_wasm_name = false;
|
||||
|
||||
for attr in function.attrs {
|
||||
if attr.path().is_ident(GAS) {
|
||||
saw_gas = true;
|
||||
if let Err(error) = int_value(&attr).and_then(|v| set_once(&mut gas, v, &attr)) {
|
||||
errors.push(error);
|
||||
}
|
||||
} else if attr.path().is_ident(WASM_NAME) {
|
||||
saw_wasm_name = true;
|
||||
if let Err(error) =
|
||||
string_value(&attr).and_then(|v| set_once(&mut wasm_name, v, &attr))
|
||||
{
|
||||
errors.push(error);
|
||||
}
|
||||
} else if attr.path().is_ident(DOC) {
|
||||
docs.push(attr);
|
||||
} else {
|
||||
errors.push(syn::Error::new_spanned(
|
||||
&attr,
|
||||
format!("unexpected attribute `{}`", path_name(&attr)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if !saw_gas {
|
||||
errors.push(syn::Error::new_spanned(
|
||||
&function.sig.ident,
|
||||
format!("missing `#[{GAS} = ...]` attribute"),
|
||||
));
|
||||
}
|
||||
if !saw_wasm_name {
|
||||
errors.push(syn::Error::new_spanned(
|
||||
&function.sig.ident,
|
||||
format!("missing `#[{WASM_NAME} = \"...\"]` attribute"),
|
||||
));
|
||||
}
|
||||
if let Some(body) = &function.default {
|
||||
errors.push(syn::Error::new_spanned(
|
||||
body,
|
||||
"a host function is implemented by the host, so it must not have a body",
|
||||
));
|
||||
}
|
||||
if !function.sig.generics.params.is_empty() || function.sig.generics.where_clause.is_some()
|
||||
{
|
||||
errors.push(syn::Error::new_spanned(
|
||||
&function.sig.ident,
|
||||
"a host function must not be generic: it maps to one wasm import signature",
|
||||
));
|
||||
}
|
||||
errors.extend(check_receiver(&function.sig).err());
|
||||
errors.extend(check_return_type(&function.sig).err());
|
||||
if let Some(name) = &wasm_name {
|
||||
errors.extend(check_wasm_name(name).err());
|
||||
}
|
||||
reject_modifiers(&function.sig, &mut errors);
|
||||
|
||||
// A name whose PascalCase form is not a legal variant is reported here
|
||||
// rather than emitted, which would either panic or fail downstream.
|
||||
let variant = match variant_ident(&function.sig.ident) {
|
||||
Ok(variant) => Some(variant),
|
||||
Err(error) => {
|
||||
errors.push(error);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(error) = errors::combine(errors) {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let (Some(gas), Some(wasm_name), Some(variant)) = (gas, wasm_name, variant) else {
|
||||
unreachable!("every absent field is reported above");
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
gas,
|
||||
wasm_name,
|
||||
docs,
|
||||
variant,
|
||||
signature: function.sig,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(())
|
||||
}
|
||||
|
||||
/// Every declaration returns `HostResult<T>`, including the ones that yield
|
||||
/// nothing (`HostResult<()>`).
|
||||
///
|
||||
/// One shape for every function is what lets a single dispatch adapter lower them
|
||||
/// all: lift the arguments out of guest memory, call the host, then turn `Ok(T)`
|
||||
/// into the wire's non-negative `i32` and `Err(e)` into a negative code or a trap.
|
||||
/// A function returning a bare `T` would need its own arm.
|
||||
fn check_return_type(signature: &Signature) -> syn::Result<()> {
|
||||
const SHAPE: &str = "a host function must return `HostResult<T>` — \
|
||||
`HostResult<()>` if it yields nothing";
|
||||
|
||||
let ReturnType::Type(_, returned) = &signature.output else {
|
||||
return Err(syn::Error::new_spanned(&signature.ident, SHAPE));
|
||||
};
|
||||
|
||||
let Type::Path(TypePath {
|
||||
qself: None, path, ..
|
||||
}) = &**returned
|
||||
else {
|
||||
return Err(syn::Error::new_spanned(returned, SHAPE));
|
||||
};
|
||||
// The last segment only, so `HostResult<T>` may be written qualified.
|
||||
let Some(last) = path.segments.last() else {
|
||||
return Err(syn::Error::new_spanned(returned, SHAPE));
|
||||
};
|
||||
if last.ident != HOST_RESULT {
|
||||
return Err(syn::Error::new_spanned(returned, SHAPE));
|
||||
}
|
||||
|
||||
// `HostResult` without its success type is `HostResult` the alias, which names
|
||||
// no type; rustc's own message for that is unhelpfully far from the cause.
|
||||
let PathArguments::AngleBracketed(arguments) = &last.arguments else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
returned,
|
||||
format!("`{HOST_RESULT}` needs its success type: `{HOST_RESULT}<T>`"),
|
||||
));
|
||||
};
|
||||
if arguments.args.len() != 1 {
|
||||
return Err(syn::Error::new_spanned(
|
||||
arguments,
|
||||
format!("`{HOST_RESULT}` takes exactly one type: `{HOST_RESULT}<T>`"),
|
||||
));
|
||||
}
|
||||
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>) {
|
||||
const PLAIN: &str =
|
||||
"a host function must be a plain `fn`: this modifier is not part of the wasm ABI";
|
||||
|
||||
if let Some(constness) = &signature.constness {
|
||||
errors.push(syn::Error::new_spanned(constness, PLAIN));
|
||||
}
|
||||
if let Some(asyncness) = &signature.asyncness {
|
||||
errors.push(syn::Error::new_spanned(asyncness, PLAIN));
|
||||
}
|
||||
match &signature.safety {
|
||||
Safety::Default => {}
|
||||
Safety::Safe(token) => errors.push(syn::Error::new_spanned(token, PLAIN)),
|
||||
Safety::Unsafe(token) => errors.push(syn::Error::new_spanned(token, PLAIN)),
|
||||
}
|
||||
if let Some(abi) = &signature.abi {
|
||||
errors.push(syn::Error::new_spanned(abi, PLAIN));
|
||||
}
|
||||
}
|
||||
|
||||
/// The wasm import name reaches the engine's import table verbatim, so it is
|
||||
/// held to what an import name can sanely be rather than to any string.
|
||||
fn check_wasm_name(name: &LitStr) -> syn::Result<()> {
|
||||
let value = name.value();
|
||||
if value.is_empty() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
name,
|
||||
"the wasm name must not be empty",
|
||||
));
|
||||
}
|
||||
if let Some(character) = value
|
||||
.chars()
|
||||
.find(|c| !c.is_ascii_alphanumeric() && *c != '_')
|
||||
{
|
||||
return Err(syn::Error::new_spanned(
|
||||
name,
|
||||
format!(
|
||||
"a wasm name may only contain `A-Za-z0-9_`, but this one contains {character:?}"
|
||||
),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The enum variant a declaration becomes: `get_ledger_sqn` -> `GetLedgerSqn`.
|
||||
///
|
||||
/// The result carries `ident`'s span, so anything the compiler says about the
|
||||
/// variant points at the declaration that produced it.
|
||||
fn variant_ident(ident: &Ident) -> syn::Result<Ident> {
|
||||
// `to_string` spells raw identifiers `r#type`; the `r#` is not part of the name.
|
||||
let name = ident.to_string();
|
||||
let name = name.strip_prefix("r#").unwrap_or(&name);
|
||||
|
||||
let mut pascal = String::with_capacity(name.len());
|
||||
let mut capitalize = true;
|
||||
for character in name.chars() {
|
||||
if character == '_' {
|
||||
capitalize = true;
|
||||
} else if capitalize {
|
||||
pascal.extend(character.to_uppercase());
|
||||
capitalize = false;
|
||||
} else {
|
||||
pascal.push(character);
|
||||
}
|
||||
}
|
||||
|
||||
// A name of nothing but underscores leaves `pascal` empty; the original is
|
||||
// already a legal identifier, so keep it.
|
||||
if pascal.is_empty() {
|
||||
return Ok(ident.clone());
|
||||
}
|
||||
|
||||
// `Ident::new` panics on a leading digit (`_2fa` -> `2fa`) and silently
|
||||
// accepts keyword spellings (`self_` -> `Self`), which then fails to parse
|
||||
// where the variant is emitted. Parsing rejects both, without panicking.
|
||||
if let Err(error) = syn::parse_str::<Ident>(&pascal) {
|
||||
return Err(syn::Error::new_spanned(
|
||||
ident,
|
||||
format!(
|
||||
"this name becomes the enum variant `{pascal}`, which is not a valid \
|
||||
variant name ({error}); rename the host function"
|
||||
),
|
||||
));
|
||||
}
|
||||
Ok(format_ident!("{pascal}", span = ident.span()))
|
||||
}
|
||||
|
||||
/// Records `value`, or reports that the attribute appeared more than once.
|
||||
fn set_once<T>(slot: &mut Option<T>, value: T, attr: &Attribute) -> syn::Result<()> {
|
||||
if slot.replace(value).is_some() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attr,
|
||||
format!("duplicate `{}` attribute", path_name(attr)),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn int_value(attr: &Attribute) -> syn::Result<u64> {
|
||||
match &attr.meta.require_name_value()?.value {
|
||||
Expr::Lit(ExprLit {
|
||||
lit: Lit::Int(int), ..
|
||||
}) => {
|
||||
// `LitInt` keeps the sign in its digits, so `base10_parse::<u64>`
|
||||
// would report a negative value as "invalid digit found in string".
|
||||
if int.base10_digits().starts_with('-') {
|
||||
return Err(syn::Error::new_spanned(
|
||||
int,
|
||||
format!("`{}` must not be negative", path_name(attr)),
|
||||
));
|
||||
}
|
||||
int.base10_parse()
|
||||
}
|
||||
other => Err(syn::Error::new_spanned(
|
||||
other,
|
||||
format!("`{}` expects an integer literal", path_name(attr)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn string_value(attr: &Attribute) -> syn::Result<LitStr> {
|
||||
match &attr.meta.require_name_value()?.value {
|
||||
Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(string),
|
||||
..
|
||||
}) => Ok(string.clone()),
|
||||
other => Err(syn::Error::new_spanned(
|
||||
other,
|
||||
format!("`{}` expects a string literal", path_name(attr)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// The attribute's path as written, for diagnostics: `gas`, or `foo::bar`.
|
||||
fn path_name(attr: &Attribute) -> String {
|
||||
attr.path()
|
||||
.segments
|
||||
.iter()
|
||||
.map(|segment| segment.ident.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("::")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use quote::ToTokens;
|
||||
use syn::parse_quote;
|
||||
|
||||
/// The message of every diagnostic recorded by one failed `parse`.
|
||||
///
|
||||
/// `expect_err` is unavailable here: it needs `T: Debug`, and syn only
|
||||
/// implements `Debug` for its AST types under the `extra-traits` feature.
|
||||
fn messages(function: TraitItemFn) -> Vec<String> {
|
||||
let Err(error) = ParsedHostFunction::parse(function) else {
|
||||
panic!("expected parsing to fail");
|
||||
};
|
||||
error.into_iter().map(|error| error.to_string()).collect()
|
||||
}
|
||||
|
||||
fn doc_text(attr: &Attribute) -> String {
|
||||
match &attr.meta.require_name_value().unwrap().value {
|
||||
Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(text),
|
||||
..
|
||||
}) => text.value(),
|
||||
_ => panic!("doc attribute is not a string literal"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reads_gas_and_wasm_name() {
|
||||
let parsed = ParsedHostFunction::parse(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(parsed.gas, 60);
|
||||
assert_eq!(parsed.wasm_name.value(), "ldgr_index");
|
||||
assert_eq!(parsed.signature.ident.to_string(), "get_ledger_sqn");
|
||||
assert_eq!(parsed.variant.to_string(), "GetLedgerSqn");
|
||||
assert!(parsed.docs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derives_variant_names_from_function_names() {
|
||||
for (function, variant) in [
|
||||
("get_ledger_sqn", "GetLedgerSqn"),
|
||||
("sha512_half", "Sha512Half"),
|
||||
("trace", "Trace"),
|
||||
("get_current_ledger_obj_field", "GetCurrentLedgerObjField"),
|
||||
("r#type", "Type"),
|
||||
("trace2", "Trace2"),
|
||||
// Pathological, but must not panic: no letters to capitalize.
|
||||
("__", "__"),
|
||||
] {
|
||||
let ident = format_ident!("{function}");
|
||||
assert_eq!(
|
||||
variant_ident(&ident).map(|v| v.to_string()).ok(),
|
||||
Some(variant.to_owned()),
|
||||
"{function}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// `_2fa` would PascalCase to `2fa`; building that `Ident` panics, and a
|
||||
/// panic in a proc macro is reported with no useful span at all.
|
||||
#[test]
|
||||
fn rejects_a_name_that_becomes_a_leading_digit() {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "two_factor"]
|
||||
fn _2fa(&self) -> HostResult<()>;
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
assert!(
|
||||
messages[0].contains("becomes the enum variant `2fa`"),
|
||||
"{messages:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// `self_` PascalCases to `Self`, which `Ident::new` accepts and rustc then
|
||||
/// rejects where the variant is emitted. `r#Self` is not a legal escape.
|
||||
#[test]
|
||||
fn rejects_a_name_that_becomes_a_keyword() {
|
||||
for function in ["self_", "_self"] {
|
||||
let ident = format_ident!("{function}");
|
||||
let Err(error) = variant_ident(&ident) else {
|
||||
panic!("expected `{function}` to be rejected");
|
||||
};
|
||||
assert!(
|
||||
error.to_string().contains("variant `Self`"),
|
||||
"{}",
|
||||
error.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_negative_gas() {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = -5]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
assert_eq!(messages[0], "`gas` must not be negative");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_unusable_wasm_names() {
|
||||
let empty = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = ""]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
assert_eq!(empty.len(), 1, "{empty:?}");
|
||||
assert_eq!(empty[0], "the wasm name must not be empty");
|
||||
|
||||
let spaced = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
assert_eq!(spaced.len(), 1, "{spaced:?}");
|
||||
assert!(spaced[0].contains("may only contain"), "{spaced:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_signature_modifiers() {
|
||||
for declaration in [
|
||||
quote! { unsafe fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>; },
|
||||
quote! { async fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>; },
|
||||
quote! { const fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>; },
|
||||
quote! { extern "C" fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>; },
|
||||
] {
|
||||
let function: TraitItemFn = syn::parse2(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
#declaration
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let messages = messages(function);
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
assert!(messages[0].contains("must be a plain `fn`"), "{messages:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
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(&self, data: &[u8]) -> HostResult<[u8; 32]>;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// `///` reaches the macro as `#[doc = r"..."]`: rustc's lexer spells doc
|
||||
// comments as raw string literals.
|
||||
let method = parsed.trait_method().to_string();
|
||||
assert!(
|
||||
method.starts_with("# [doc = r\" Hashes `data`.\"]"),
|
||||
"{method}"
|
||||
);
|
||||
assert!(
|
||||
method
|
||||
.contains("fn sha512_half (& self , data : & [u8]) -> HostResult < [u8 ; 32] > ;"),
|
||||
"{method}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spec_arm_carries_the_name_and_the_gas() {
|
||||
let parsed = ParsedHostFunction::parse(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
parsed.spec_arm().to_string(),
|
||||
"Self :: GetLedgerSqn => HostFnSpec { name : \"ldgr_index\" , gas : 60u64 }"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_doc_comments_in_source_order() {
|
||||
let parsed = ParsedHostFunction::parse(parse_quote! {
|
||||
/// First line.
|
||||
///
|
||||
/// Third line.
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let docs: Vec<_> = parsed.docs.iter().map(doc_text).collect();
|
||||
assert_eq!(docs, vec![" First line.", "", " Third line."]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_parameters_and_return_type() {
|
||||
let traced = ParsedHostFunction::parse(parse_quote! {
|
||||
#[gas = 500]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace(&self, msg: &str, data: &[u8], as_hex: bool) -> HostResult<()>;
|
||||
})
|
||||
.unwrap();
|
||||
// The receiver is `inputs[0]`; the three wasm parameters follow it.
|
||||
assert_eq!(traced.signature.inputs.len(), 4);
|
||||
assert_eq!(
|
||||
traced.signature.output.to_token_stream().to_string(),
|
||||
"-> HostResult < () >"
|
||||
);
|
||||
|
||||
let hashed = ParsedHostFunction::parse(parse_quote! {
|
||||
#[gas = 2000]
|
||||
#[wasm_name = "sha512_half"]
|
||||
fn sha512_half(&self, data: &[u8]) -> HostResult<[u8; HASH_LEN]>;
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
hashed.signature.output.to_token_stream().to_string(),
|
||||
"-> HostResult < [u8 ; HASH_LEN] >"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_both_missing_attributes_at_once() {
|
||||
let messages = messages(parse_quote! {
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 2);
|
||||
assert!(messages[0].contains("missing `#[gas"), "{messages:?}");
|
||||
assert!(messages[1].contains("missing `#[wasm_name"), "{messages:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn names_the_unexpected_attribute() {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wsam_name = "typo"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
|
||||
// The typo'd attribute, plus the `wasm_name` it failed to be.
|
||||
assert_eq!(messages.len(), 2);
|
||||
assert!(
|
||||
messages.iter().any(|m| m.contains("`wsam_name`")),
|
||||
"{messages:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_wrong_literal_types() {
|
||||
let gas = messages(parse_quote! {
|
||||
#[gas = "60"]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
assert_eq!(gas.len(), 1, "{gas:?}");
|
||||
assert!(
|
||||
gas[0].contains("`gas` expects an integer literal"),
|
||||
"{gas:?}"
|
||||
);
|
||||
|
||||
let name = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = 7]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
assert_eq!(name.len(), 1, "{name:?}");
|
||||
assert!(
|
||||
name[0].contains("`wasm_name` expects a string literal"),
|
||||
"{name:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_gas_that_does_not_fit_in_u64() {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = 99999999999999999999999]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
assert!(messages[0].contains("number too large"), "{messages:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_attribute_shapes_other_than_name_value() {
|
||||
let bare = messages(parse_quote! {
|
||||
#[gas]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
assert_eq!(bare.len(), 1, "{bare:?}");
|
||||
assert!(bare[0].contains("gas = ..."), "{bare:?}");
|
||||
|
||||
let list = messages(parse_quote! {
|
||||
#[gas(60)]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
assert_eq!(list.len(), 1, "{list:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_duplicate_attributes() {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[gas = 70]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 2, "{messages:?}");
|
||||
assert!(messages[0].contains("duplicate `gas`"), "{messages:?}");
|
||||
assert!(
|
||||
messages[1].contains("duplicate `wasm_name`"),
|
||||
"{messages:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// A malformed attribute must not also be reported as an absent one.
|
||||
#[test]
|
||||
fn does_not_report_a_malformed_attribute_as_missing() {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = "60"]
|
||||
#[wasm_name = 7]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]>;
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 2, "{messages:?}");
|
||||
assert!(
|
||||
!messages.iter().any(|m| m.contains("missing")),
|
||||
"{messages:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_a_body() {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]> { Ok([0; 4]) }
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
assert!(messages[0].contains("must not have a body"), "{messages:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_generics() {
|
||||
let parameter = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn<T>(&self) -> HostResult<T>;
|
||||
});
|
||||
assert_eq!(parameter.len(), 1, "{parameter:?}");
|
||||
assert!(
|
||||
parameter[0].contains("must not be generic"),
|
||||
"{parameter:?}"
|
||||
);
|
||||
|
||||
let clause = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<[u8; 4]> where Self: Sized;
|
||||
});
|
||||
assert_eq!(clause.len(), 1, "{clause:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requires_a_receiver() {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn() -> HostResult<[u8; 4]>;
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{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) -> HostResult<[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:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A bare `T` return would need its own lowering arm, so the uniform shape is
|
||||
/// required rather than inferred.
|
||||
#[test]
|
||||
fn rejects_returns_that_are_not_host_result() {
|
||||
for output in [
|
||||
quote! {},
|
||||
quote! { -> () },
|
||||
quote! { -> [u8; 4] },
|
||||
quote! { -> i32 },
|
||||
quote! { -> Result<[u8; 4], HostError> },
|
||||
quote! { -> impl Iterator<Item = u8> },
|
||||
] {
|
||||
let function: TraitItemFn = syn::parse2(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) #output;
|
||||
})
|
||||
.unwrap_or_else(|_| panic!("`{output}` should parse"));
|
||||
|
||||
let messages = messages(function);
|
||||
assert_eq!(messages.len(), 1, "`{output}`: {messages:?}");
|
||||
assert!(
|
||||
messages[0].contains("must return `HostResult<T>`"),
|
||||
"`{output}`: {messages:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// `HostResult` may be written qualified, since the trait method keeps whatever
|
||||
/// path resolves where the block is written.
|
||||
#[test]
|
||||
fn accepts_a_qualified_host_result() {
|
||||
let parsed = ParsedHostFunction::parse(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> xrpl_host_functions::HostResult<[u8; 4]>;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
parsed
|
||||
.trait_method()
|
||||
.to_string()
|
||||
.contains("xrpl_host_functions :: HostResult < [u8 ; 4] >"),
|
||||
"{}",
|
||||
parsed.trait_method()
|
||||
);
|
||||
}
|
||||
|
||||
/// `HostResult` with no success type names no type at all; rustc's own error
|
||||
/// for that lands on the generated trait, far from the declaration.
|
||||
#[test]
|
||||
fn rejects_host_result_without_a_success_type() {
|
||||
let messages = messages(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self) -> HostResult;
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
assert!(
|
||||
messages[0].contains("needs its success type"),
|
||||
"{messages:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
[package]
|
||||
name = "xrpl-host-functions"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
xrpl-host-functions-macros.path = "../xrpl-host-functions-macros"
|
||||
@@ -1,539 +0,0 @@
|
||||
//! The wasm host ABI: the one place it is declared.
|
||||
//!
|
||||
//! `host_functions!` turns the declaration block at the bottom of this file into the
|
||||
//! [`HostFunctions`] trait a host implements and the [`HostFunctionSpec`] table a
|
||||
//! wasm engine registers from.
|
||||
//!
|
||||
//! The split: hand-written here is the vocabulary the declarations are written in —
|
||||
//! [`HostError`], [`TraceDataType`], [`HostResult`], [`HASH_LEN`] — and everything
|
||||
//! derived from the declarations is generated. The expansion names nothing this file
|
||||
//! does not, so the two sides meet only in the block below.
|
||||
//!
|
||||
//! So this file is lists — error codes, trace data types, functions. The `macro_rules!`
|
||||
//! that expand the first two into enums live in `macros.rs`.
|
||||
|
||||
#![no_std]
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
// Not re-exported: the ABI is declared once, here, and this is the only call site.
|
||||
use xrpl_host_functions_macros::host_functions;
|
||||
|
||||
host_errors! {
|
||||
Unimplemented = -1,
|
||||
FieldNotFound = -2,
|
||||
BufferTooSmall = -3,
|
||||
NoArray = -4,
|
||||
NotLeafField = -5,
|
||||
LocatorMalformed = -6,
|
||||
SlotOutRange = -7,
|
||||
SlotsFull = -8,
|
||||
EmptySlot = -9,
|
||||
LedgerObjNotFound = -10,
|
||||
OutOfTransferLimit = -11,
|
||||
DataFieldTooLarge = -12,
|
||||
PointerOutOfBounds = -13,
|
||||
NoMemExported = -14,
|
||||
InvalidParams = -15,
|
||||
InvalidAccount = -16,
|
||||
InvalidField = -17,
|
||||
IndexOutOfBounds = -18,
|
||||
FloatInputMalformed = -19,
|
||||
FloatComputationError = -20,
|
||||
/// Internal fatal error.
|
||||
/// User code will never see this error but keep it reserved to not rely on the value.
|
||||
InternalFatal = -2147483648,
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
trace_data_types! {
|
||||
/// 8 little-endian bytes, rendered as a signed decimal.
|
||||
Int64 = 1,
|
||||
/// 8 little-endian bytes, rendered as an unsigned decimal.
|
||||
Uint64 = 2,
|
||||
/// A serialized XRPL float: 12 bytes, mantissa then exponent.
|
||||
Xfloat = 3,
|
||||
/// A 20-byte account ID, rendered as base58.
|
||||
Account = 4,
|
||||
/// A serialized `STAmount`.
|
||||
Amount = 5,
|
||||
/// Raw bytes, hex-encoded.
|
||||
AsHex = 6,
|
||||
/// Bytes rendered verbatim as text.
|
||||
AsText = 7,
|
||||
}
|
||||
|
||||
host_functions! {
|
||||
/// The sequence number of the ledger being built, as 4 little-endian bytes.
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The close time of the parent (last-closed) ledger, as 4 little-endian bytes.
|
||||
#[gas = 60]
|
||||
#[wasm_name = "parent_ldgr_time"]
|
||||
fn get_parent_ledger_time(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The hash of the parent (last-closed) ledger, as 32 bytes.
|
||||
#[gas = 60]
|
||||
#[wasm_name = "parent_ldgr_hash"]
|
||||
fn get_parent_ledger_hash(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The base fee of the ledger being built, in drops, as 4 little-endian bytes.
|
||||
#[gas = 60]
|
||||
#[wasm_name = "base_fee"]
|
||||
fn get_base_fee(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// Whether an amendment is enabled. The input is either its 32-byte id or its
|
||||
/// name; the answer is `1` if enabled and `0` if not. Unlike the getters, this
|
||||
/// reads an input region and returns the flag directly rather than writing bytes.
|
||||
#[gas = 100]
|
||||
#[wasm_name = "amendment_enabled"]
|
||||
fn is_amendment_enabled(&self, amendment: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// Load the ledger object with the given 32-byte id into a cache slot, so later
|
||||
/// calls can read its fields. `cache_idx` selects the slot (1-based); `0` asks the
|
||||
/// host to assign a free one. Returns the slot used, or a negative error.
|
||||
#[gas = 5000]
|
||||
#[wasm_name = "cache_le"]
|
||||
fn cache_ledger_obj(&self, obj_id: &[u8], cache_idx: i32) -> HostResult<i32>;
|
||||
|
||||
/// The serialized bytes of one field of the transaction being executed, selected
|
||||
/// by its `SField` code.
|
||||
#[gas = 70]
|
||||
#[wasm_name = "tx_field"]
|
||||
fn get_tx_field(&self, field: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The serialized bytes of one field of the current (escrow) ledger object.
|
||||
#[gas = 70]
|
||||
#[wasm_name = "home_le_field"]
|
||||
fn get_current_ledger_obj_field(&self, field: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The serialized bytes of one field of a previously cached ledger object,
|
||||
/// selected by its cache slot and the field's `SField` code.
|
||||
#[gas = 70]
|
||||
#[wasm_name = "le_field"]
|
||||
fn get_ledger_obj_field(&self, cache_idx: i32, field: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The serialized bytes of a nested field of the transaction, reached by a
|
||||
/// `locator`: a path of little-endian `i32` steps (so its byte length is a
|
||||
/// non-zero multiple of 4). Reads the locator region and writes the field bytes.
|
||||
#[gas = 110]
|
||||
#[wasm_name = "tx_inner"]
|
||||
fn get_tx_nested_field(&self, locator: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The serialized bytes of a nested field of the current (escrow) ledger object,
|
||||
/// reached by a `locator`, as with [`HostFunctions::get_tx_nested_field`].
|
||||
#[gas = 110]
|
||||
#[wasm_name = "home_le_inner"]
|
||||
fn get_current_ledger_obj_nested_field(
|
||||
&self,
|
||||
locator: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The serialized bytes of a nested field of a previously cached ledger object,
|
||||
/// selected by its cache slot and reached by a `locator`.
|
||||
#[gas = 110]
|
||||
#[wasm_name = "le_inner"]
|
||||
fn get_ledger_obj_nested_field(
|
||||
&self,
|
||||
cache_idx: i32,
|
||||
locator: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The number of elements in an array field of the transaction, selected by its
|
||||
/// `SField` code. Answers the count directly, or a negative error (`NoArray` if
|
||||
/// the field is not an array). Reads and writes no memory.
|
||||
#[gas = 40]
|
||||
#[wasm_name = "tx_arr_len"]
|
||||
fn get_tx_array_len(&self, field: i32) -> HostResult<i32>;
|
||||
|
||||
/// The number of elements in an array field of the current (escrow) ledger
|
||||
/// object, as with [`HostFunctions::get_tx_array_len`].
|
||||
#[gas = 40]
|
||||
#[wasm_name = "home_le_arr_len"]
|
||||
fn get_current_ledger_obj_array_len(&self, field: i32) -> HostResult<i32>;
|
||||
|
||||
/// The number of elements in an array field of a previously cached ledger object,
|
||||
/// selected by its cache slot and `SField` code.
|
||||
#[gas = 40]
|
||||
#[wasm_name = "le_arr_len"]
|
||||
fn get_ledger_obj_array_len(&self, cache_idx: i32, field: i32) -> HostResult<i32>;
|
||||
|
||||
/// The number of elements in a nested array field of the transaction, reached by a
|
||||
/// `locator`. Reads the locator region and answers the count directly.
|
||||
#[gas = 70]
|
||||
#[wasm_name = "tx_inner_arr_len"]
|
||||
fn get_tx_nested_array_len(&self, locator: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// The number of elements in a nested array field of the current (escrow) ledger
|
||||
/// object, reached by a `locator`, as with [`HostFunctions::get_tx_nested_array_len`].
|
||||
#[gas = 70]
|
||||
#[wasm_name = "home_le_inner_arr_len"]
|
||||
fn get_current_ledger_obj_nested_array_len(&self, locator: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// The number of elements in a nested array field of a previously cached ledger
|
||||
/// object, selected by its cache slot and reached by a `locator`.
|
||||
#[gas = 70]
|
||||
#[wasm_name = "le_inner_arr_len"]
|
||||
fn get_ledger_obj_nested_array_len(&self, cache_idx: i32, locator: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// Verify `signature` over `message` under `pubkey`. Reads the three regions and
|
||||
/// answers `1` if the signature is valid, `0` if not, or a negative error.
|
||||
///
|
||||
/// GAS DISCREPANCY: this 300 is the value the C-ABI fork registered
|
||||
/// (`rippled-wasm-host-functions`, WasmVM.cpp), which this port follows. The
|
||||
/// prior C++ integration in this tree charged 35000 for the same call — 100x
|
||||
/// more, and closer to the real cost of signature verification. The value is
|
||||
/// consensus-critical, so confirm which is intended before this ships.
|
||||
#[gas = 300]
|
||||
#[wasm_name = "check_sig"]
|
||||
fn check_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
signature: &[u8],
|
||||
pubkey: &[u8],
|
||||
) -> HostResult<i32>;
|
||||
|
||||
/// The 32-byte ledger key (keylet) of an account's `AccountRoot`, computed from a
|
||||
/// 20-byte account id. Reads the account region and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "accountroot_id"]
|
||||
fn account_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an AMM, computed from its two assets. Each asset is a
|
||||
/// byte slice whose length selects its kind (24 = MPT, 20 = XRP, 40 = issued
|
||||
/// currency + issuer). Reads both asset regions and writes the keylet.
|
||||
#[gas = 450]
|
||||
#[wasm_name = "amm_id"]
|
||||
fn amm_keylet(&self, asset1: &[u8], asset2: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `Check`, computed from a 20-byte account id and its
|
||||
/// sequence number. `seq` is the guest's `u32` carried as its `i32` bit pattern.
|
||||
/// Reads the account region and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "check_id"]
|
||||
fn check_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `Credential`, computed from the 20-byte subject and
|
||||
/// issuer account ids and a credential-type byte string. Reads all three regions
|
||||
/// and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "credential_id"]
|
||||
fn credential_keylet(
|
||||
&self,
|
||||
subject: &[u8],
|
||||
issuer: &[u8],
|
||||
credential_type: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `Delegate` object, computed from the 20-byte account
|
||||
/// and the account it authorizes. Reads both account regions and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "delegate_id"]
|
||||
fn delegate_keylet(
|
||||
&self,
|
||||
account: &[u8],
|
||||
authorize: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `DepositPreauth`, computed from the 20-byte account and
|
||||
/// the account it authorizes to deposit. Reads both account regions and writes the
|
||||
/// keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "deposit_preauth_id"]
|
||||
fn deposit_preauth_keylet(
|
||||
&self,
|
||||
account: &[u8],
|
||||
authorize: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an account's `DID`, computed from its 20-byte account id.
|
||||
/// Reads the account region and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "did_id"]
|
||||
fn did_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an `Escrow`, computed from the 20-byte owner account and
|
||||
/// its sequence number. `seq` is the guest's `u32` carried as its `i32` bit
|
||||
/// pattern. Reads the account region and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "escrow_id"]
|
||||
fn escrow_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `RippleState` (trust line), computed from two 20-byte
|
||||
/// account ids and a 20-byte currency. Reads all three regions and writes the
|
||||
/// keylet.
|
||||
#[gas = 400]
|
||||
#[wasm_name = "trustline_id"]
|
||||
fn trust_line_keylet(
|
||||
&self,
|
||||
account1: &[u8],
|
||||
account2: &[u8],
|
||||
currency: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an `MPTokenIssuance`, computed from the 20-byte issuer
|
||||
/// account and its sequence number. `seq` is the guest's `u32` carried as its
|
||||
/// `i32` bit pattern. Reads the account region and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "mpt_issuance_id"]
|
||||
fn mptoken_issuance_keylet(
|
||||
&self,
|
||||
issuer: &[u8],
|
||||
seq: i32,
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an `MPToken`, computed from a 24-byte MPT issuance id and
|
||||
/// the 20-byte holder account. Reads both regions and writes the keylet.
|
||||
#[gas = 500]
|
||||
#[wasm_name = "mptoken_id"]
|
||||
fn mptoken_keylet(&self, mptid: &[u8], holder: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an `NFTokenOffer`, computed from the 20-byte owner account
|
||||
/// and its sequence number. `seq` is the guest's `u32` carried as its `i32` bit
|
||||
/// pattern. Reads the account region and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "nft_offer_id"]
|
||||
fn nftoken_offer_keylet(
|
||||
&self,
|
||||
account: &[u8],
|
||||
seq: i32,
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an `Offer`, computed from the 20-byte owner account and
|
||||
/// its sequence number. `seq` is the guest's `u32` carried as its `i32` bit
|
||||
/// pattern. Reads the account region and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "offer_id"]
|
||||
fn offer_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an `Oracle`, computed from the 20-byte owner account and
|
||||
/// its document id. `doc_id` is the guest's `u32` carried as its `i32` bit pattern.
|
||||
/// Reads the account region and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "oracle_id"]
|
||||
fn oracle_keylet(&self, account: &[u8], doc_id: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `PayChannel`, computed from the 20-byte source account,
|
||||
/// the 20-byte destination account, and the channel's sequence number. `seq` is the
|
||||
/// guest's `u32` carried as its `i32` bit pattern. Reads both account regions and
|
||||
/// writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "paychan_id"]
|
||||
fn paychannel_keylet(
|
||||
&self,
|
||||
account: &[u8],
|
||||
destination: &[u8],
|
||||
seq: i32,
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `PermissionedDomain`, computed from the 20-byte owner
|
||||
/// account and its sequence number. `seq` is the guest's `u32` carried as its `i32`
|
||||
/// bit pattern. Reads the account region and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "permissioned_domain_id"]
|
||||
fn permissioned_domain_keylet(
|
||||
&self,
|
||||
account: &[u8],
|
||||
seq: i32,
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `SignerList`, computed from its 20-byte owner account.
|
||||
/// Reads the account region and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "signers_id"]
|
||||
fn signer_list_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `Ticket`, computed from the 20-byte owner account and its
|
||||
/// ticket sequence number. `seq` is the guest's `u32` carried as its `i32` bit
|
||||
/// pattern. Reads the account region and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "ticket_id"]
|
||||
fn ticket_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `Vault`, computed from the 20-byte owner account and its
|
||||
/// sequence number. `seq` is the guest's `u32` carried as its `i32` bit pattern.
|
||||
/// Reads the account region and writes the keylet.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "vault_id"]
|
||||
fn vault_keylet(&self, account: &[u8], seq: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The XRPL `sha512Half` of `data`: the first [`HASH_LEN`] bytes of its SHA-512.
|
||||
#[gas = 2000]
|
||||
#[wasm_name = "sha512_half"]
|
||||
fn sha512_half(&self, data: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// Writes `msg` to the trace log, followed by `data` rendered as `data_type` says.
|
||||
///
|
||||
/// The one declaration whose wasm function has **no result**: this node's own log
|
||||
/// is its only effect, so a guest is told nothing. An `Err` from a host therefore
|
||||
/// reaches it in no form, and only the host-fatal ones do anything at all.
|
||||
///
|
||||
/// It is also the one declaration that is **not** the wasm parameter order.
|
||||
/// `data_type` is the third wasm parameter, between the two regions, because that
|
||||
/// is where xrpld's `trace_proto` and the guest stdlib put it; `register.rs` takes
|
||||
/// the arguments in wasm order and calls this in declaration order.
|
||||
#[gas = 30]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace(&self, msg: &str, data: &[u8], data_type: TraceDataType) -> 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>;
|
||||
|
||||
/// 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>;
|
||||
|
||||
// A "float" here is an XRPL `Number` in its serialized form: a byte blob the guest
|
||||
// holds opaquely and hands back to these functions. Inputs and outputs that are
|
||||
// floats are byte regions; `mode` is the rounding mode, a scalar the guest chooses.
|
||||
|
||||
/// A float built from the signed integer `x` under rounding `mode`. Writes the
|
||||
/// float bytes; no input region.
|
||||
#[gas = 100]
|
||||
#[wasm_name = "float_from_int"]
|
||||
fn float_from_int(&self, x: i64, mode: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// A float built from the unsigned integer in the 8-byte region `x` under rounding
|
||||
/// `mode`. Reads the integer region and writes the float bytes.
|
||||
#[gas = 130]
|
||||
#[wasm_name = "float_from_uint"]
|
||||
fn float_from_uint(&self, x: &[u8], mode: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// A float built from the serialized `STAmount` in `amount` under rounding `mode`.
|
||||
/// Reads the amount region and writes the float bytes.
|
||||
#[gas = 150]
|
||||
#[wasm_name = "float_from_stamount"]
|
||||
fn float_from_stamount(&self, amount: &[u8], mode: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// A float built from the serialized `STNumber` in `number` under rounding `mode`.
|
||||
/// Reads the number region and writes the float bytes.
|
||||
#[gas = 150]
|
||||
#[wasm_name = "float_from_stnumber"]
|
||||
fn float_from_stnumber(&self, number: &[u8], mode: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The float `x` rounded to a signed integer under rounding `mode`. Reads the float
|
||||
/// region and writes the integer as its eight little-endian bytes.
|
||||
#[gas = 130]
|
||||
#[wasm_name = "float_to_int"]
|
||||
fn float_to_int(&self, x: &[u8], mode: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The float `x` split into its mantissa and exponent. Reads the float region and
|
||||
/// writes the mantissa (eight little-endian bytes) and the exponent (four little-
|
||||
/// endian bytes) to two separate output regions.
|
||||
#[gas = 130]
|
||||
#[wasm_name = "float_to_mant_exp"]
|
||||
fn float_to_mant_exp(
|
||||
&self,
|
||||
x: &[u8],
|
||||
mantissa_out: &mut [u8],
|
||||
exponent_out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// A float built from `mantissa` and `exponent` under rounding `mode`. Writes the
|
||||
/// float bytes; no input region.
|
||||
#[gas = 100]
|
||||
#[wasm_name = "float_from_mant_exp"]
|
||||
fn float_from_mant_exp(
|
||||
&self,
|
||||
mantissa: i64,
|
||||
exponent: i32,
|
||||
mode: i32,
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// Compares floats `x` and `y`, returning a negative, zero, or positive scalar as
|
||||
/// `x` is less than, equal to, or greater than `y`. Reads both float regions.
|
||||
#[gas = 80]
|
||||
#[wasm_name = "float_cmp"]
|
||||
fn float_compare(&self, x: &[u8], y: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// The float sum `x + y` under rounding `mode`. Reads both float regions and writes
|
||||
/// the result bytes.
|
||||
#[gas = 160]
|
||||
#[wasm_name = "float_add"]
|
||||
fn float_add(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The float difference `x - y` under rounding `mode`. Reads both float regions and
|
||||
/// writes the result bytes.
|
||||
#[gas = 160]
|
||||
#[wasm_name = "float_sub"]
|
||||
fn float_subtract(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The float product `x * y` under rounding `mode`. Reads both float regions and
|
||||
/// writes the result bytes.
|
||||
#[gas = 300]
|
||||
#[wasm_name = "float_mult"]
|
||||
fn float_multiply(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The float quotient `x / y` under rounding `mode`. Reads both float regions and
|
||||
/// writes the result bytes.
|
||||
#[gas = 300]
|
||||
#[wasm_name = "float_div"]
|
||||
fn float_divide(&self, x: &[u8], y: &[u8], mode: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The `n`-th root of the float `x` under rounding `mode`. Reads the float region
|
||||
/// and writes the result bytes.
|
||||
#[gas = 5500]
|
||||
#[wasm_name = "float_root"]
|
||||
fn float_root(&self, x: &[u8], n: i32, mode: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The float `x` raised to the power `n` under rounding `mode`. Reads the float
|
||||
/// region and writes the result bytes.
|
||||
#[gas = 5500]
|
||||
#[wasm_name = "float_pow"]
|
||||
fn float_power(&self, x: &[u8], n: i32, mode: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
//! The `macro_rules!` behind the two hand-listed enums, [`crate::HostError`] and
|
||||
//! [`crate::TraceDataType`].
|
||||
//!
|
||||
//! Each takes one list of `Variant = code,` and expands the enum together with the
|
||||
//! `ALL`/`code`/`from_code` set that must not fall behind it. The lists themselves stay
|
||||
//! in `lib.rs`, beside the `host_functions!` block.
|
||||
|
||||
/// Declares [`crate::HostError`] from one list: the variants, `HostError::ALL` and
|
||||
/// `HostError::from_code`'s table all expand from the codes given.
|
||||
///
|
||||
/// One list is what makes `ALL` complete. Rust cannot enumerate an enum's
|
||||
/// variants — an exhaustive `match` forces an arm per variant but gives nothing to
|
||||
/// iterate — so a hand-written `ALL` beside a hand-written enum could only be kept
|
||||
/// in step by review, and `ALL`'s whole purpose is to be the set a test can trust.
|
||||
/// A code added to the list gains its `ALL` entry and its `from_code` arm by
|
||||
/// construction. `HostFunctionSpec::ALL` is complete the same way, from the
|
||||
/// `host_functions!` block.
|
||||
macro_rules! host_errors {
|
||||
($($(#[$doc:meta])* $variant:ident = $code:literal,)+) => {
|
||||
/// Error codes a host function may return.
|
||||
///
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
pub enum HostError {
|
||||
$($(#[$doc])* $variant = $code,)+
|
||||
}
|
||||
|
||||
impl HostError {
|
||||
/// Every error a host function may return, in code order.
|
||||
///
|
||||
/// The complete set, and complete by construction: a wasm engine's
|
||||
/// split between the codes it hands the guest and the conditions it
|
||||
/// traps on is a decision per variant, so the test that checks the
|
||||
/// split iterates this and a code added to the ABI cannot slip past it.
|
||||
pub const ALL: &'static [HostError] = &[$(HostError::$variant,)+];
|
||||
|
||||
/// The negative wire value a failed call returns. Every code but
|
||||
/// `InternalFatal` is one a guest reads off that value.
|
||||
#[inline]
|
||||
pub const fn code(self) -> i32 {
|
||||
self as i32
|
||||
}
|
||||
|
||||
/// Reconstruct a `HostError` from its wire code.
|
||||
///
|
||||
/// A code this ABI does not define is `InternalFatal`: an answer the
|
||||
/// caller cannot act on is the call not having been served, and that is
|
||||
/// the variant which says so. Positive values are not errors at all and go
|
||||
/// the same way, since this is reached only once a negative return has
|
||||
/// been read as a failure.
|
||||
pub const fn from_code(code: i32) -> HostError {
|
||||
match code {
|
||||
$($code => HostError::$variant,)+
|
||||
_ => HostError::InternalFatal,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Declares [`crate::TraceDataType`] from one list, so `TraceDataType::ALL`,
|
||||
/// `TraceDataType::code` and `TraceDataType::from_code` cannot fall behind the
|
||||
/// variants — the reason `host_errors!` above is written this way.
|
||||
macro_rules! trace_data_types {
|
||||
($($(#[$doc:meta])* $variant:ident = $code:literal,)+) => {
|
||||
/// How [`HostFunctions::trace`] is to read its data buffer.
|
||||
///
|
||||
/// The discriminants are wire values shared with the guest stdlib: append only,
|
||||
/// never renumber. They start at 1, so a zeroed argument names no type rather
|
||||
/// than the first one.
|
||||
///
|
||||
/// This is the declaration a guest and a host both compile against. The host
|
||||
/// side needs a second one — `cxx` cannot be a dependency here, since this
|
||||
/// crate also links into the guest — so `xrpl-wasm-vm-ffi` declares a shared
|
||||
/// enum for C++ and converts, exhaustively, from this.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
pub enum TraceDataType {
|
||||
$($(#[$doc])* $variant = $code,)+
|
||||
}
|
||||
|
||||
impl TraceDataType {
|
||||
/// Every data type a guest may name, in code order.
|
||||
pub const ALL: &'static [TraceDataType] = &[$(TraceDataType::$variant,)+];
|
||||
|
||||
/// The wire value a guest passes to name this type.
|
||||
#[inline]
|
||||
pub const fn code(self) -> i32 {
|
||||
self as i32
|
||||
}
|
||||
|
||||
/// The type `code` names, or `None`: the engine drops a call it cannot
|
||||
/// read rather than guessing at a rendering the guest did not ask for.
|
||||
pub const fn from_code(code: i32) -> Option<TraceDataType> {
|
||||
match code {
|
||||
$($code => Some(TraceDataType::$variant),)+
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
//! `host_functions!` must work outside the crate that declares the ABI: the only
|
||||
//! names its expansion needs are the ones the declarations themselves spell.
|
||||
|
||||
use xrpl_host_functions::HostResult;
|
||||
use xrpl_host_functions_macros::host_functions;
|
||||
|
||||
host_functions! {
|
||||
/// Answers with the number it was given.
|
||||
#[gas = 7]
|
||||
#[wasm_name = "ping"]
|
||||
fn ping(&self, number: i32) -> HostResult<i32>;
|
||||
}
|
||||
|
||||
struct Host;
|
||||
|
||||
impl HostFunctions for Host {
|
||||
fn ping(&self, number: i32) -> HostResult<i32> {
|
||||
Ok(number)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_generated_table_stands_on_its_own() {
|
||||
assert_eq!(HostFunctionSpec::ALL.len(), 1);
|
||||
assert_eq!(HostFunctionSpec::Ping.wasm_name(), "ping");
|
||||
assert_eq!(HostFunctionSpec::Ping.gas(), 7);
|
||||
}
|
||||
|
||||
/// The generated trait is implementable from another crate, which is the point of
|
||||
/// declaring the ABI in a library at all.
|
||||
#[test]
|
||||
fn the_generated_trait_is_implementable_here() {
|
||||
assert_eq!(Host.ping(3), Ok(3));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,102 +0,0 @@
|
||||
//! Exercises what `host_errors!` generates: the wire codes, the set
|
||||
//! [`HostError::ALL`] names, and the round trip between them.
|
||||
//!
|
||||
//! The codes are consensus input — they are what a guest reads off a failed host
|
||||
//! call — so they are pinned here as literals and derived everywhere else.
|
||||
|
||||
use xrpl_host_functions::HostError;
|
||||
|
||||
/// The whole set, written out in the order `ALL` gives it: the one place the wire
|
||||
/// codes appear as literals, and a deliberate change-detector, since a code that
|
||||
/// moves changes what every deployed guest is told.
|
||||
#[test]
|
||||
fn the_error_table_matches_the_declarations() {
|
||||
let table: Vec<(HostError, i32)> = HostError::ALL
|
||||
.iter()
|
||||
.map(|&error| (error, error.code()))
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
table,
|
||||
[
|
||||
(HostError::Unimplemented, -1),
|
||||
(HostError::FieldNotFound, -2),
|
||||
(HostError::BufferTooSmall, -3),
|
||||
(HostError::NoArray, -4),
|
||||
(HostError::NotLeafField, -5),
|
||||
(HostError::LocatorMalformed, -6),
|
||||
(HostError::SlotOutRange, -7),
|
||||
(HostError::SlotsFull, -8),
|
||||
(HostError::EmptySlot, -9),
|
||||
(HostError::LedgerObjNotFound, -10),
|
||||
(HostError::OutOfTransferLimit, -11),
|
||||
(HostError::DataFieldTooLarge, -12),
|
||||
(HostError::PointerOutOfBounds, -13),
|
||||
(HostError::NoMemExported, -14),
|
||||
(HostError::InvalidParams, -15),
|
||||
(HostError::InvalidAccount, -16),
|
||||
(HostError::InvalidField, -17),
|
||||
(HostError::IndexOutOfBounds, -18),
|
||||
(HostError::FloatInputMalformed, -19),
|
||||
(HostError::FloatComputationError, -20),
|
||||
(HostError::InternalFatal, i32::MIN),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// The guest-facing set is `-1 ..= -20` and nothing else: those entries are xrpld's
|
||||
/// `HostFunctionError`, and each is a code some contract may read.
|
||||
///
|
||||
/// `InternalFatal` is the one deliberate exception, exempted by name rather than by
|
||||
/// widening the range: a condition with no number a contract can act on needs no number
|
||||
/// in the range a contract reads, and holding it at `i32::MIN` is what keeps it from
|
||||
/// ever colliding with a code appended to xrpld's list.
|
||||
#[test]
|
||||
fn every_code_but_the_sentinel_is_in_the_shared_range() {
|
||||
let shared: Vec<HostError> = HostError::ALL
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&error| error != HostError::InternalFatal)
|
||||
.collect();
|
||||
|
||||
let outside: Vec<HostError> = shared
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|error| !(-20..=-1).contains(&error.code()))
|
||||
.collect();
|
||||
|
||||
assert!(outside.is_empty(), "outside -1..=-20: {outside:?}");
|
||||
assert_eq!(shared.len(), 20);
|
||||
assert_eq!(HostError::InternalFatal.code(), i32::MIN);
|
||||
assert_eq!(HostError::ALL.len(), 21);
|
||||
}
|
||||
|
||||
/// Every code a guest can be handed comes back as the error that produced it, so a
|
||||
/// caller reading a negative return value recovers the condition and not a
|
||||
/// neighbouring one. The table above pins the numbers; this adds only the round
|
||||
/// trip.
|
||||
#[test]
|
||||
fn every_wire_code_round_trips_back_to_its_error() {
|
||||
for &error in HostError::ALL {
|
||||
assert_eq!(HostError::from_code(error.code()), error, "{error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A code from outside the set is `InternalFatal`: a host answering something this ABI
|
||||
/// does not define has not served the call, whatever it meant by it, and success is not
|
||||
/// an error at all.
|
||||
///
|
||||
/// `-21` is the code xrpld would append next, so it is the one that decides whether a
|
||||
/// list this crate has not caught up with reaches a guest or stops the run. `i32::MIN +
|
||||
/// 1` is next to the sentinel and unassigned, which is what makes the sentinel a value
|
||||
/// rather than a range.
|
||||
#[test]
|
||||
fn a_code_outside_the_set_is_internal_fatal() {
|
||||
for code in [-21, i32::MIN + 1, 0, 1, i32::MAX] {
|
||||
assert_eq!(
|
||||
HostError::from_code(code),
|
||||
HostError::InternalFatal,
|
||||
"{code}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "xrpl-wasm-testkit"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
cxx.workspace = true
|
||||
wat = "1"
|
||||
@@ -1,49 +0,0 @@
|
||||
//! Assembles WebAssembly text for the C++ test suite. **Test-only.**
|
||||
//!
|
||||
//! A crate of its own rather than an entry on `xrpl-wasm-vm-ffi`, and the separation is the
|
||||
//! point. The engine pins `wasmi = { default-features = false }` precisely so a text
|
||||
//! assembler cannot reach the consensus path — wasmi's `wat` feature is on by default and
|
||||
//! makes `Module::new` accept text as readily as binary, which would make a transaction's
|
||||
//! validity a build flag (review finding A5). Putting `compile_wat` on the production bridge
|
||||
//! would link `wat` into xrpld even if nothing called it.
|
||||
//!
|
||||
//! Linked only into `xrpl_tests`, never into `libxrpl` or `xrpld`, so "no assembler in the
|
||||
//! shipped node" is a property of the link graph rather than a flag someone can flip.
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
|
||||
#[cxx::bridge(namespace = "rs::wasm_testkit")]
|
||||
mod ffi {
|
||||
extern "Rust" {
|
||||
/// Assemble `wat` to a wasm module.
|
||||
///
|
||||
/// Throws `rust::Error` on invalid input, which is what a test wants: a typo in a
|
||||
/// fixture should fail the test that holds it, at the line that holds it.
|
||||
fn compile_wat(wat: &str) -> Result<Vec<u8>>;
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_wat(wat: &str) -> Result<Vec<u8>, wat::Error> {
|
||||
wat::parse_str(wat)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::compile_wat;
|
||||
|
||||
#[test]
|
||||
fn a_module_assembles_to_something_beginning_with_the_wasm_magic() {
|
||||
let wasm = compile_wat("(module)").expect("assembles");
|
||||
|
||||
assert_eq!(&wasm[..4], b"\0asm");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_typo_is_an_error_rather_than_a_module() {
|
||||
let error = compile_wat("(module (func (export").expect_err("must not assemble");
|
||||
|
||||
assert!(
|
||||
!error.to_string().is_empty(),
|
||||
"the error has to say something"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "xrpl-wasm-vm-ffi"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
cxx.workspace = true
|
||||
xrpl-host-functions = { path = "../xrpl-host-functions" }
|
||||
xrpl-wasm-vm = { path = "../xrpl-wasm-vm" }
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "xrpl-wasm-vm"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
wasmi = { version = "1.1.0", default-features = false, features = ["std"] }
|
||||
xrpl-host-functions = { path = "../xrpl-host-functions" }
|
||||
|
||||
[dev-dependencies]
|
||||
wat = "1"
|
||||
@@ -1,824 +0,0 @@
|
||||
use crate::region::Region;
|
||||
use crate::vm::{MAX_FIELD_BYTES, VmState};
|
||||
use wasmi::{Caller, Memory};
|
||||
use xrpl_host_functions::{HostError, HostFunctionSpec, HostFunctions, HostResult};
|
||||
|
||||
/// A condition that stops the run. It is a property of the run rather than an answer
|
||||
/// to a call, so it reaches no guest and carries no wire code — which is why it is
|
||||
/// not a [`HostError`]: no host can report one and no contract can read one.
|
||||
///
|
||||
/// The three are the outcomes a host call can end a run with, and
|
||||
/// `From<Fault> for RunError` in `vm.rs` is where each gets its name.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum Fault {
|
||||
/// This call's charge would take the meter below zero. The guest exhausting the
|
||||
/// meter with its own instructions reaches [`crate::vm::RunError::OutOfGas`] by
|
||||
/// wasmi's `OutOfFuel` trap instead, never through here.
|
||||
OutOfGas,
|
||||
/// The call could not be served: either the host said so, or this engine's own
|
||||
/// fuel meter did not answer.
|
||||
Internal,
|
||||
/// There is no linear memory to work in — the module exports none, or the call
|
||||
/// came from a start section, which runs before there is an instance.
|
||||
NoMemory,
|
||||
}
|
||||
|
||||
/// How a host call fails: with a code the guest reads off the return value, or with a
|
||||
/// [`Fault`] that stops the run.
|
||||
///
|
||||
/// **The variant picks the channel.** [`to_wire`] reads it rather than asking a
|
||||
/// predicate, so the two cannot disagree, and a [`FatalHostError`] cannot be built
|
||||
/// around something a guest was supposed to see.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum CallError {
|
||||
Code(HostError),
|
||||
Fatal(Fault),
|
||||
}
|
||||
|
||||
/// A host call's result inside the engine: [`HostResult`] plus the faults only the
|
||||
/// engine can raise.
|
||||
pub(crate) type CallResult<T> = Result<T, CallError>;
|
||||
|
||||
/// Which channel a host's answer takes, decided once, here.
|
||||
///
|
||||
/// Three codes stop the run instead of reaching the contract that asked. Each says the
|
||||
/// call was not served at all — the host could not do it, it has not been wired, or
|
||||
/// there is nowhere to put the answer — and a contract has no business interpreting
|
||||
/// any of them, so it is told nothing and the run ends. Every other code is the
|
||||
/// contract's to read.
|
||||
impl From<HostError> for CallError {
|
||||
fn from(error: HostError) -> CallError {
|
||||
match error {
|
||||
HostError::InternalFatal => CallError::Fatal(Fault::Internal),
|
||||
HostError::Unimplemented => CallError::Fatal(Fault::Internal),
|
||||
HostError::NoMemExported => CallError::Fatal(Fault::NoMemory),
|
||||
code => CallError::Code(code),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The payload a trap carries so [`crate::vm::run`] can name the outcome without
|
||||
/// parsing a message. Holds a [`Fault`], so by construction no guest-visible code can
|
||||
/// leave through this channel.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct FatalHostError(pub(crate) Fault);
|
||||
|
||||
impl wasmi::errors::HostError for FatalHostError {}
|
||||
|
||||
impl core::fmt::Display for FatalHostError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "host call refused: {:?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Charge the call's gas, run its body, put the result on the wire. The one path
|
||||
/// every registered closure takes, so gas cannot be forgotten.
|
||||
pub(crate) fn charged(
|
||||
caller: &mut Caller<'_, VmState<'_>>,
|
||||
op: HostFunctionSpec,
|
||||
body: impl FnOnce(&mut Caller<'_, VmState<'_>>) -> CallResult<i32>,
|
||||
) -> Result<i32, wasmi::Error> {
|
||||
to_wire(charge(caller, op.gas()).and_then(|()| body(caller)))
|
||||
}
|
||||
|
||||
/// [`charged`] for a call the guest gets no answer from: its wasm function has no
|
||||
/// result, so a soft error has nowhere to go and is dropped. The gas is charged first
|
||||
/// and charged whatever happens after, so the cost is all such a call leaves behind.
|
||||
///
|
||||
/// Only `trace` takes this path.
|
||||
pub(crate) fn charged_unreported(
|
||||
caller: &mut Caller<'_, VmState<'_>>,
|
||||
op: HostFunctionSpec,
|
||||
body: impl FnOnce(&mut Caller<'_, VmState<'_>>) -> CallResult<()>,
|
||||
) -> Result<(), wasmi::Error> {
|
||||
dropped(charge(caller, op.gas()).and_then(|()| body(caller)))
|
||||
}
|
||||
|
||||
/// [`to_wire`] for a call with no result: there is no return value to encode a code
|
||||
/// in, so it is dropped. A [`Fault`] still stops the run — that is a property of the
|
||||
/// run, not an answer to the call.
|
||||
fn dropped(result: CallResult<()>) -> Result<(), wasmi::Error> {
|
||||
match result {
|
||||
Err(CallError::Fatal(fault)) => Err(wasmi::Error::host(FatalHostError(fault))),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_wire(result: CallResult<i32>) -> Result<i32, wasmi::Error> {
|
||||
match result {
|
||||
Ok(value) => Ok(value),
|
||||
Err(CallError::Code(error)) => Ok(error.code()),
|
||||
Err(CallError::Fatal(fault)) => Err(wasmi::Error::host(FatalHostError(fault))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deduct `cost` fuel; [`Fault::OutOfGas`] if it would go negative.
|
||||
///
|
||||
/// A meter that will not answer is this crate's own defect, not the contract's, so it
|
||||
/// is [`Fault::Internal`] rather than a number a guest could act on.
|
||||
fn charge<T>(caller: &mut Caller<'_, T>, cost: u64) -> CallResult<()> {
|
||||
let remaining = caller
|
||||
.get_fuel()
|
||||
.map_err(|_| CallError::Fatal(Fault::Internal))?;
|
||||
match remaining.checked_sub(cost) {
|
||||
Some(left) => caller
|
||||
.set_fuel(left)
|
||||
.map_err(|_| CallError::Fatal(Fault::Internal)),
|
||||
None => {
|
||||
let _ = caller.set_fuel(0);
|
||||
Err(CallError::Fatal(Fault::OutOfGas))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn charge_transfer(state: &VmState<'_>, n: usize) -> Result<(), HostError> {
|
||||
let n = n as u64;
|
||||
let remaining = state.transfer_budget.get();
|
||||
match remaining.checked_sub(n) {
|
||||
Some(left) => {
|
||||
state.transfer_budget.set(left);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(HostError::OutOfTransferLimit),
|
||||
}
|
||||
}
|
||||
|
||||
fn memory(caller: &Caller<'_, VmState<'_>>) -> CallResult<Memory> {
|
||||
caller
|
||||
.data()
|
||||
.memory
|
||||
.ok_or(CallError::Fatal(Fault::NoMemory))
|
||||
}
|
||||
|
||||
/// [`Region::read`] of the guest's memory, for a call that reads and writes nothing
|
||||
/// back (`trace`).
|
||||
pub(crate) fn read_borrowed<'a>(
|
||||
caller: &'a Caller<'_, VmState<'_>>,
|
||||
input: Region,
|
||||
) -> CallResult<&'a [u8]> {
|
||||
let mem = memory(caller)?;
|
||||
Ok(input.read(mem.data(caller))?)
|
||||
}
|
||||
|
||||
/// Decode a guest `u32` argument — a keylet's sequence number or document id — from
|
||||
/// its four little-endian bytes, carried on to the host as its `i32` bit pattern.
|
||||
///
|
||||
/// The ABI transports these as a 4-byte region rather than a wasm scalar (the guest
|
||||
/// SDK passes `seq.to_le_bytes()`), so the region must be exactly four bytes;
|
||||
/// `InvalidParams` otherwise, matching the C-ABI wrapper's `getDataUInt32`.
|
||||
pub(crate) fn read_u32_arg(bytes: &[u8]) -> HostResult<i32> {
|
||||
let arr: [u8; 4] = bytes.try_into().map_err(|_| HostError::InvalidParams)?;
|
||||
Ok(i32::from_le_bytes(arr))
|
||||
}
|
||||
|
||||
/// Service a call whose answer is bytes, written straight into the guest's output
|
||||
/// region.
|
||||
///
|
||||
/// **`fill` returns the value's true length, not what it wrote**: a host holding 64
|
||||
/// bytes and offered room for 4 writes nothing and answers `64`, which is how the
|
||||
/// guest learns the size to ask for. So `n` is bounded by neither the region nor the
|
||||
/// cap, and both checks below are reachable.
|
||||
pub(crate) fn write_into(
|
||||
caller: &mut Caller<'_, VmState<'_>>,
|
||||
out: Region,
|
||||
fill: impl FnOnce(&dyn HostFunctions, &mut [u8]) -> HostResult<usize>,
|
||||
) -> CallResult<i32> {
|
||||
let range = out.range()?;
|
||||
let cap = range.len();
|
||||
let mem = memory(caller)?;
|
||||
let host: &dyn HostFunctions = caller.data().host;
|
||||
// Bounds-checked over the guest's whole declared region, so a buffer running
|
||||
// past memory is a wrong pointer rather than a truncated prefix being served…
|
||||
let buf = mem
|
||||
.data_mut(&mut *caller)
|
||||
.get_mut(range)
|
||||
.ok_or(HostError::PointerOutOfBounds)?;
|
||||
// …of which only the field cap is writable, so no call can exceed it whatever
|
||||
// the guest declared.
|
||||
let buf = &mut buf[..cap.min(MAX_FIELD_BYTES)];
|
||||
|
||||
let n = fill(host, buf)?;
|
||||
|
||||
if n > MAX_FIELD_BYTES {
|
||||
return Err(HostError::DataFieldTooLarge.into());
|
||||
}
|
||||
if n > cap {
|
||||
return Err(HostError::BufferTooSmall.into());
|
||||
}
|
||||
charge_transfer(caller.data(), n)?;
|
||||
#[expect(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_possible_wrap,
|
||||
reason = "`n > MAX_FIELD_BYTES` returned above, and the cap is far inside i32"
|
||||
)]
|
||||
let n = n as i32;
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
/// Service a call that reads guest memory and writes bytes back to it: the host
|
||||
/// fills the run's output buffer, which is copied to the guest once every rule has
|
||||
/// passed.
|
||||
///
|
||||
/// `call` gets the guest's whole memory, so it can borrow any number of input
|
||||
/// regions with [`Region::read`] — which a `&mut` view of that memory would forbid.
|
||||
/// That is why the answer goes through a buffer instead of straight into the guest
|
||||
/// as [`write_into`]'s does.
|
||||
///
|
||||
/// **The host is never told the guest's capacity**: it is offered the whole buffer
|
||||
/// and reports the value's true length, so the fit is decided here, with nothing yet
|
||||
/// in guest memory. A refused value therefore reaches it in no part.
|
||||
///
|
||||
/// The output is judged after the inputs, so a call with both bad reports the
|
||||
/// input's verdict. `NoMemExported` precedes both: there is no memory to validate a
|
||||
/// region against.
|
||||
pub(crate) fn write_buffered(
|
||||
caller: &mut Caller<'_, VmState<'_>>,
|
||||
out: Region,
|
||||
call: impl FnOnce(&dyn HostFunctions, &[u8], &mut [u8]) -> HostResult<usize>,
|
||||
) -> CallResult<i32> {
|
||||
let mem = memory(caller)?;
|
||||
// One borrow split in two: the guest's bytes for the inputs, the store data for
|
||||
// the output buffer. Taking them together is what keeps the inputs borrowed
|
||||
// rather than copied out.
|
||||
let (data, state) = mem.data_and_store_mut(&mut *caller);
|
||||
let host: &dyn HostFunctions = state.host;
|
||||
|
||||
let n = call(host, data, &mut state.out_buffer[..])?;
|
||||
|
||||
// `out` is checked here rather than before the call: the inputs are judged
|
||||
// first, so a call with both malformed reports the input's verdict.
|
||||
let range = out.range()?;
|
||||
let cap = range.len();
|
||||
if n > MAX_FIELD_BYTES {
|
||||
return Err(HostError::DataFieldTooLarge.into());
|
||||
}
|
||||
let buf = data.get_mut(range).ok_or(HostError::PointerOutOfBounds)?;
|
||||
if n > cap {
|
||||
return Err(HostError::BufferTooSmall.into());
|
||||
}
|
||||
charge_transfer(state, n)?;
|
||||
buf[..n].copy_from_slice(&state.out_buffer[..n]);
|
||||
#[expect(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_possible_wrap,
|
||||
reason = "`n > MAX_FIELD_BYTES` returned above, and the cap is far inside i32"
|
||||
)]
|
||||
let n = n as i32;
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
/// The mantissa and exponent widths `float_to_mant_exp` writes: an `i64` and an `i32`.
|
||||
/// Fixed by the ABI, not the guest, so the split is a constant rather than a reported
|
||||
/// length.
|
||||
const MANTISSA_BYTES: usize = 8;
|
||||
const EXPONENT_BYTES: usize = 4;
|
||||
|
||||
/// Service `float_to_mant_exp`, the one call that writes two output regions: the host
|
||||
/// fills the run's output buffer with the mantissa followed by the exponent, and each
|
||||
/// is copied to its own guest region once every rule has passed.
|
||||
///
|
||||
/// Like [`write_buffered`], the host reads its input from the guest's memory and writes
|
||||
/// to a scratch buffer, so the input stays borrowed rather than copied. The two output
|
||||
/// regions are judged after the input, and the mantissa's region before the exponent's,
|
||||
/// so the first fault reported is the leftmost.
|
||||
///
|
||||
/// The two widths are the ABI's rather than the guest's, so the length the host reports
|
||||
/// is checked against their sum for equality rather than as a bound, and ahead of the
|
||||
/// output regions: a wrong total means there is no answer to place, whatever the guest
|
||||
/// declared. That is a fatal error and not a status, since the guest asked for nothing
|
||||
/// wrong.
|
||||
pub(crate) fn write_mant_exp(
|
||||
caller: &mut Caller<'_, VmState<'_>>,
|
||||
mantissa_out: Region,
|
||||
exponent_out: Region,
|
||||
call: impl FnOnce(&dyn HostFunctions, &[u8], &mut [u8], &mut [u8]) -> HostResult<usize>,
|
||||
) -> CallResult<i32> {
|
||||
let mem = memory(caller)?;
|
||||
let (data, state) = mem.data_and_store_mut(&mut *caller);
|
||||
let host: &dyn HostFunctions = state.host;
|
||||
|
||||
// The scratch buffer is split at the fixed mantissa width: the host fills the first
|
||||
// eight bytes with the mantissa and the next four with the exponent.
|
||||
let (mant_buf, exp_buf) = state.out_buffer.split_at_mut(MANTISSA_BYTES);
|
||||
let mant_buf = &mut mant_buf[..MANTISSA_BYTES];
|
||||
let exp_buf = &mut exp_buf[..EXPONENT_BYTES];
|
||||
|
||||
let total = call(host, data, mant_buf, exp_buf)?;
|
||||
|
||||
// Both buffers are fixed-width and were offered whole, so the only length the host
|
||||
// can correctly report is their sum. Anything else is the host contradicting the
|
||||
// ABI: with the widths in doubt, part of what would be copied out is whatever the
|
||||
// previous call left in the buffer, so none of it is copied.
|
||||
if total != MANTISSA_BYTES + EXPONENT_BYTES {
|
||||
return Err(HostError::InternalFatal.into());
|
||||
}
|
||||
|
||||
// Copy the mantissa, then the exponent, each only if its whole value fits its
|
||||
// region — a region too small is `BufferTooSmall`, with nothing written.
|
||||
let mant_range = mantissa_out.range()?;
|
||||
let mant_dst = data
|
||||
.get_mut(mant_range)
|
||||
.ok_or(HostError::PointerOutOfBounds)?;
|
||||
if mant_dst.len() < MANTISSA_BYTES {
|
||||
return Err(HostError::BufferTooSmall.into());
|
||||
}
|
||||
mant_dst[..MANTISSA_BYTES].copy_from_slice(&state.out_buffer[..MANTISSA_BYTES]);
|
||||
|
||||
let exp_range = exponent_out.range()?;
|
||||
let exp_dst = data
|
||||
.get_mut(exp_range)
|
||||
.ok_or(HostError::PointerOutOfBounds)?;
|
||||
if exp_dst.len() < EXPONENT_BYTES {
|
||||
return Err(HostError::BufferTooSmall.into());
|
||||
}
|
||||
exp_dst[..EXPONENT_BYTES]
|
||||
.copy_from_slice(&state.out_buffer[MANTISSA_BYTES..MANTISSA_BYTES + EXPONENT_BYTES]);
|
||||
|
||||
charge_transfer(state, MANTISSA_BYTES + EXPONENT_BYTES)?;
|
||||
#[expect(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_possible_wrap,
|
||||
reason = "a total other than 12 returned above, and 12 is far inside i32"
|
||||
)]
|
||||
let total = total as i32;
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::vm::TRANSFER_LIMIT_BYTES;
|
||||
use std::cell::Cell;
|
||||
use wasmi::StoreLimitsBuilder;
|
||||
use xrpl_host_functions::TraceDataType;
|
||||
|
||||
/// `charge_transfer` takes the store data, which has to hold a host.
|
||||
struct UncalledHost;
|
||||
|
||||
impl HostFunctions for UncalledHost {
|
||||
fn get_ledger_sqn(&self, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_parent_ledger_time(&self, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_parent_ledger_hash(&self, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_base_fee(&self, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn is_amendment_enabled(&self, _amendment: &[u8]) -> HostResult<i32> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn cache_ledger_obj(&self, _obj_id: &[u8], _cache_idx: i32) -> HostResult<i32> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_tx_field(&self, _field: i32, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_current_ledger_obj_field(&self, _field: i32, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_ledger_obj_field(
|
||||
&self,
|
||||
_cache_idx: i32,
|
||||
_field: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_tx_nested_field(&self, _locator: &[u8], _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_current_ledger_obj_nested_field(
|
||||
&self,
|
||||
_locator: &[u8],
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_ledger_obj_nested_field(
|
||||
&self,
|
||||
_cache_idx: i32,
|
||||
_locator: &[u8],
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_tx_array_len(&self, _field: i32) -> HostResult<i32> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_current_ledger_obj_array_len(&self, _field: i32) -> HostResult<i32> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_ledger_obj_array_len(&self, _cache_idx: i32, _field: i32) -> HostResult<i32> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_tx_nested_array_len(&self, _locator: &[u8]) -> HostResult<i32> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_current_ledger_obj_nested_array_len(&self, _locator: &[u8]) -> HostResult<i32> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn get_ledger_obj_nested_array_len(
|
||||
&self,
|
||||
_cache_idx: i32,
|
||||
_locator: &[u8],
|
||||
) -> HostResult<i32> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn check_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_signature: &[u8],
|
||||
_pubkey: &[u8],
|
||||
) -> HostResult<i32> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn account_keylet(&self, _account: &[u8], _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn amm_keylet(&self, _asset1: &[u8], _asset2: &[u8], _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn check_keylet(&self, _account: &[u8], _seq: i32, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn credential_keylet(
|
||||
&self,
|
||||
_subject: &[u8],
|
||||
_issuer: &[u8],
|
||||
_credential_type: &[u8],
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn delegate_keylet(
|
||||
&self,
|
||||
_account: &[u8],
|
||||
_authorize: &[u8],
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn deposit_preauth_keylet(
|
||||
&self,
|
||||
_account: &[u8],
|
||||
_authorize: &[u8],
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn did_keylet(&self, _account: &[u8], _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn escrow_keylet(&self, _account: &[u8], _seq: i32, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn trust_line_keylet(
|
||||
&self,
|
||||
_account1: &[u8],
|
||||
_account2: &[u8],
|
||||
_currency: &[u8],
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn mptoken_issuance_keylet(
|
||||
&self,
|
||||
_issuer: &[u8],
|
||||
_seq: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn mptoken_keylet(
|
||||
&self,
|
||||
_mptid: &[u8],
|
||||
_holder: &[u8],
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn nftoken_offer_keylet(
|
||||
&self,
|
||||
_account: &[u8],
|
||||
_seq: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn offer_keylet(&self, _account: &[u8], _seq: i32, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn oracle_keylet(
|
||||
&self,
|
||||
_account: &[u8],
|
||||
_doc_id: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn paychannel_keylet(
|
||||
&self,
|
||||
_account: &[u8],
|
||||
_destination: &[u8],
|
||||
_seq: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn permissioned_domain_keylet(
|
||||
&self,
|
||||
_account: &[u8],
|
||||
_seq: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn signer_list_keylet(&self, _account: &[u8], _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn ticket_keylet(&self, _account: &[u8], _seq: i32, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn vault_keylet(&self, _account: &[u8], _seq: i32, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn sha512_half(&self, _data: &[u8], _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn trace(&self, _msg: &str, _data: &[u8], _data_type: TraceDataType) -> 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 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 float_from_int(&self, _x: i64, _mode: i32, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_from_uint(&self, _x: &[u8], _mode: i32, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_from_stamount(
|
||||
&self,
|
||||
_amount: &[u8],
|
||||
_mode: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_from_stnumber(
|
||||
&self,
|
||||
_number: &[u8],
|
||||
_mode: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_to_int(&self, _x: &[u8], _mode: i32, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_to_mant_exp(
|
||||
&self,
|
||||
_x: &[u8],
|
||||
_mantissa_out: &mut [u8],
|
||||
_exponent_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_from_mant_exp(
|
||||
&self,
|
||||
_mantissa: i64,
|
||||
_exponent: i32,
|
||||
_mode: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_compare(&self, _x: &[u8], _y: &[u8]) -> HostResult<i32> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_add(
|
||||
&self,
|
||||
_x: &[u8],
|
||||
_y: &[u8],
|
||||
_mode: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_subtract(
|
||||
&self,
|
||||
_x: &[u8],
|
||||
_y: &[u8],
|
||||
_mode: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_multiply(
|
||||
&self,
|
||||
_x: &[u8],
|
||||
_y: &[u8],
|
||||
_mode: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_divide(
|
||||
&self,
|
||||
_x: &[u8],
|
||||
_y: &[u8],
|
||||
_mode: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_root(&self, _x: &[u8], _n: i32, _mode: i32, _out: &mut [u8]) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
fn float_power(
|
||||
&self,
|
||||
_x: &[u8],
|
||||
_n: i32,
|
||||
_mode: i32,
|
||||
_out: &mut [u8],
|
||||
) -> HostResult<usize> {
|
||||
unreachable!("no unit test in this module calls the host")
|
||||
}
|
||||
}
|
||||
|
||||
fn state(budget: u64) -> VmState<'static> {
|
||||
VmState {
|
||||
host: &UncalledHost,
|
||||
mem_limits: StoreLimitsBuilder::new().build(),
|
||||
transfer_budget: Cell::new(budget),
|
||||
memory: None,
|
||||
out_buffer: [0u8; MAX_FIELD_BYTES],
|
||||
}
|
||||
}
|
||||
|
||||
/// `wasmi::Error` is not `PartialEq`, so a test expecting the guest-visible
|
||||
/// channel says so by going through here.
|
||||
fn wire(result: CallResult<i32>) -> i32 {
|
||||
to_wire(result)
|
||||
.unwrap_or_else(|trap| panic!("expected a guest-visible status, got a trap: {trap}"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_success_becomes_the_value_and_an_error_becomes_its_code() {
|
||||
assert_eq!(wire(Ok(0)), 0);
|
||||
assert_eq!(wire(Ok(32)), 32);
|
||||
assert_eq!(wire(Err(HostError::BufferTooSmall.into())), -3);
|
||||
}
|
||||
|
||||
/// The codes a host may answer that a contract must not see, and the fault each
|
||||
/// becomes. Written out rather than derived from `From<HostError>`, which is what
|
||||
/// they are asserting.
|
||||
const STOPS_THE_RUN: [(HostError, Fault); 3] = [
|
||||
(HostError::InternalFatal, Fault::Internal),
|
||||
(HostError::Unimplemented, Fault::Internal),
|
||||
(HostError::NoMemExported, Fault::NoMemory),
|
||||
];
|
||||
|
||||
/// Every fault, so the two tests below are the whole set and not a sample.
|
||||
/// `From<Fault> for RunError` is what forces a fault added later to be
|
||||
/// considered; this is what forces it to be tested.
|
||||
const ALL_FAULTS: [Fault; 3] = [Fault::OutOfGas, Fault::Internal, Fault::NoMemory];
|
||||
|
||||
#[test]
|
||||
fn a_code_that_stops_the_run_converts_to_its_fault() {
|
||||
for (error, fault) in STOPS_THE_RUN {
|
||||
assert_eq!(CallError::from(error), CallError::Fatal(fault), "{error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Over `HostError::ALL`, so it is the whole ABI and not a sample: a code added
|
||||
/// to the ABI arrives already asserted to reach the guest as itself, and stopping
|
||||
/// the run on it is then a change someone has to come and make.
|
||||
///
|
||||
/// `OutOfTransferLimit` is the row worth reading twice: the one budget a
|
||||
/// contract can be expected to handle, so it is told no rather than killed.
|
||||
#[test]
|
||||
fn every_other_code_reaches_the_guest_as_itself() {
|
||||
for &error in HostError::ALL {
|
||||
if STOPS_THE_RUN.iter().any(|&(stops, _)| stops == error) {
|
||||
continue;
|
||||
}
|
||||
assert_eq!(CallError::from(error), CallError::Code(error), "{error:?}");
|
||||
assert_eq!(wire(Err(error.into())), error.code(), "{error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The trap carries the fault, so `run` can name the outcome without parsing a
|
||||
/// message.
|
||||
#[test]
|
||||
fn a_fault_becomes_a_trap_carrying_it() {
|
||||
for fault in ALL_FAULTS {
|
||||
let trap = to_wire(Err(CallError::Fatal(fault)))
|
||||
.expect_err("a fault must not reach the guest as a code");
|
||||
let payload = trap.downcast_ref::<FatalHostError>().unwrap_or_else(|| {
|
||||
panic!("{fault:?}: expected a FatalHostError payload, got: {trap}")
|
||||
});
|
||||
assert_eq!(*payload, FatalHostError(fault));
|
||||
}
|
||||
}
|
||||
|
||||
/// The result-less path splits the same two channels differently: a fault still
|
||||
/// stops the run, and every code is dropped, since `trace` has no return value to
|
||||
/// carry it. Over `HostError::ALL` for the reason above — a code added to the ABI
|
||||
/// arrives asserted against both paths.
|
||||
#[test]
|
||||
fn a_call_with_no_result_drops_a_code_and_traps_on_a_fault() {
|
||||
assert!(dropped(Ok(())).is_ok());
|
||||
|
||||
for &error in HostError::ALL {
|
||||
if let CallError::Code(code) = CallError::from(error) {
|
||||
assert!(
|
||||
dropped(Err(CallError::Code(code))).is_ok(),
|
||||
"{error:?} has no channel to the guest and must be dropped"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for fault in ALL_FAULTS {
|
||||
let trap =
|
||||
dropped(Err(CallError::Fatal(fault))).expect_err("a fault must stop the run");
|
||||
let payload = trap.downcast_ref::<FatalHostError>().unwrap_or_else(|| {
|
||||
panic!("{fault:?}: expected a FatalHostError payload, got: {trap}")
|
||||
});
|
||||
assert_eq!(*payload, FatalHostError(fault));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_transfer_spends_the_budget() {
|
||||
let state = state(100);
|
||||
|
||||
assert_eq!(charge_transfer(&state, 30), Ok(()));
|
||||
assert_eq!(state.transfer_budget.get(), 70);
|
||||
assert_eq!(charge_transfer(&state, 70), Ok(()));
|
||||
assert_eq!(state.transfer_budget.get(), 0);
|
||||
}
|
||||
|
||||
/// The budget bounds the total, so the transfer that would overrun it is
|
||||
/// refused whole rather than partially charged.
|
||||
#[test]
|
||||
fn a_transfer_past_the_budget_is_refused_and_charges_nothing() {
|
||||
let state = state(100);
|
||||
|
||||
assert_eq!(
|
||||
charge_transfer(&state, 101),
|
||||
Err(HostError::OutOfTransferLimit)
|
||||
);
|
||||
assert_eq!(
|
||||
state.transfer_budget.get(),
|
||||
100,
|
||||
"a refusal must not charge"
|
||||
);
|
||||
assert_eq!(charge_transfer(&state, 100), Ok(()));
|
||||
assert_eq!(
|
||||
charge_transfer(&state, 1),
|
||||
Err(HostError::OutOfTransferLimit)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transferring_nothing_costs_nothing() {
|
||||
let state = state(0);
|
||||
|
||||
assert_eq!(charge_transfer(&state, 0), Ok(()));
|
||||
assert_eq!(state.transfer_budget.get(), 0);
|
||||
}
|
||||
|
||||
/// The field cap holds one call to a small share of the run's budget, so the
|
||||
/// budget bounds a run rather than a call. An inequality, not the two values:
|
||||
/// those are pinned in `vm.rs`.
|
||||
#[test]
|
||||
fn no_single_value_can_exhaust_the_run_budget() {
|
||||
assert!(
|
||||
(MAX_FIELD_BYTES as u64) * 64 <= TRANSFER_LIMIT_BYTES,
|
||||
"one {MAX_FIELD_BYTES}-byte value against a {TRANSFER_LIMIT_BYTES}-byte budget"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
//! The escrow wasm VM: compile a contract, meter it, and serve its host calls.
|
||||
//!
|
||||
//! Every guest access goes through `abi.rs` and reaches linear memory only by
|
||||
//! wasmi's bounds-checked slice operations; `forbid(unsafe_code)` makes that a
|
||||
//! property rather than a claim. The cast lints are on for the same reason — on a
|
||||
//! consensus path a truncating or sign-losing cast changes what a contract is
|
||||
//! charged or told, so each one is argued for at its site.
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![deny(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_possible_wrap,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_lossless
|
||||
)]
|
||||
|
||||
mod abi;
|
||||
mod preflight;
|
||||
mod region;
|
||||
mod register;
|
||||
mod vm;
|
||||
|
||||
pub use preflight::{CheckError, check};
|
||||
pub use vm::{
|
||||
MAX_FIELD_BYTES, MAX_MEMORY_BYTES, MAX_MEMORY_PAGES, RunError, RunFailure, RunOutcome,
|
||||
TRANSFER_LIMIT_BYTES, run,
|
||||
};
|
||||
@@ -1,351 +0,0 @@
|
||||
//! Screening a contract before it reaches the ledger.
|
||||
//!
|
||||
//! [`check`] answers whether [`crate::run`] would refuse a module before the
|
||||
//! guest's first instruction — the three stages a caller maps to a malformed
|
||||
//! transaction rather than to a failed one. It needs **no host, no store and no
|
||||
//! gas**: everything it reads is a property of the compiled module. That is what
|
||||
//! makes it callable from a transaction's preflight, which has no ledger to serve
|
||||
//! host calls from.
|
||||
//!
|
||||
//! Two things it deliberately does not screen. A module exporting **no** linear
|
||||
//! memory passes: a contract that makes no host call needs none, and one that
|
||||
//! does is refused at the call and charged for what it burned. A start section
|
||||
//! passes: it is guest code, and executing it is the one thing a check must not do
|
||||
//! — a trap in one is charged to the contract like any other trap.
|
||||
//!
|
||||
//! One thing it screens that a run can only discover: an exported memory larger
|
||||
//! than the engine grants. See [`check_memory`] for what stays invisible.
|
||||
|
||||
use std::fmt;
|
||||
use wasmi::{ExternType, FuncType, Module, ValType};
|
||||
use xrpl_host_functions::HostFunctionSpec;
|
||||
|
||||
use crate::register::HOST_MODULE;
|
||||
use crate::vm::{MAX_MEMORY_PAGES, compile};
|
||||
|
||||
/// Why a module cannot be run. One variant per stage, since the caller maps the
|
||||
/// stages separately.
|
||||
#[derive(Debug)]
|
||||
pub enum CheckError {
|
||||
/// `wasm` is not a valid module under this engine's configuration.
|
||||
Compile(String),
|
||||
/// An import no engine of this ABI defines: another module namespace, a name
|
||||
/// that is not a host function, or one imported as something other than a
|
||||
/// function.
|
||||
Import(String),
|
||||
/// No export named `function_name` with signature `() -> i32`.
|
||||
EntryPoint(String),
|
||||
/// The module asks for more linear memory than the engine grants.
|
||||
Memory(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for CheckError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
CheckError::Compile(detail) => write!(f, "compile: {detail}"),
|
||||
CheckError::Import(detail) => write!(f, "import: {detail}"),
|
||||
// The detail says which of the entry point's failures this is, since
|
||||
// "no entry point" would be wrong for an export of the wrong type.
|
||||
CheckError::EntryPoint(detail) => write!(f, "{detail}"),
|
||||
CheckError::Memory(detail) => write!(f, "memory: {detail}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Screen `wasm`: it must compile, import only what the engine serves, export
|
||||
/// `function_name` as `() -> i32`, and ask for no more memory than it may have.
|
||||
///
|
||||
/// The stages are ordered by how much of the module each explains. An import fault
|
||||
/// is reported before a missing entry point because the imports are what the rest of
|
||||
/// the module is built on; memory comes last, being a resource request rather than a
|
||||
/// mistake about the ABI.
|
||||
pub fn check(wasm: &[u8], function_name: &str) -> Result<(), CheckError> {
|
||||
let module = compile(wasm).map_err(CheckError::Compile)?;
|
||||
check_imports(&module)?;
|
||||
check_entry_point(&module, function_name)?;
|
||||
check_memory(&module)
|
||||
}
|
||||
|
||||
/// Every import must be one the linker defines. The first that is not ends the
|
||||
/// check, so a module with several faults reports the earliest.
|
||||
fn check_imports(module: &Module) -> Result<(), CheckError> {
|
||||
for import in module.imports() {
|
||||
check_import(import.module(), import.name(), import.ty()).map_err(CheckError::Import)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Whether the engine defines this one import.
|
||||
///
|
||||
/// The set of names is [`HostFunctionSpec::ALL`], which is also what
|
||||
/// [`crate::register::register_host_functions`] iterates — so a check and a run
|
||||
/// cannot disagree about which names exist, and adding a host function extends
|
||||
/// both at once. The one thing this does not compare is `ty`'s *signature*, which
|
||||
/// still parts a module from the engine at instantiation; the kind is compared
|
||||
/// because the engine defines these names as functions and as nothing else.
|
||||
///
|
||||
/// The rules are ordered, not merely alternatives: a guest importing `env::malloc`
|
||||
/// is told about the namespace rather than that `malloc` is not a host function,
|
||||
/// because the namespace is the one that explains every other import it has too.
|
||||
fn check_import(module: &str, name: &str, ty: &ExternType) -> Result<(), String> {
|
||||
if module != HOST_MODULE {
|
||||
return Err(format!("'{module}::{name}' is not from '{HOST_MODULE}'"));
|
||||
}
|
||||
if !HostFunctionSpec::ALL
|
||||
.iter()
|
||||
.any(|op| op.wasm_name() == name)
|
||||
{
|
||||
return Err(format!("no host function '{name}'"));
|
||||
}
|
||||
if !matches!(ty, ExternType::Func(_)) {
|
||||
return Err(format!("'{HOST_MODULE}::{name}' is not a function"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_entry_point(module: &Module, name: &str) -> Result<(), CheckError> {
|
||||
match module.get_export(name) {
|
||||
Some(ExternType::Func(ty)) if is_entry_point(&ty) => Ok(()),
|
||||
found => Err(CheckError::EntryPoint(entry_point_fault(found, name))),
|
||||
}
|
||||
}
|
||||
|
||||
/// The entry point's type: nothing in, one `i32` out — what [`crate::run`]'s
|
||||
/// `get_typed_func::<(), i32>` accepts.
|
||||
fn is_entry_point(ty: &FuncType) -> bool {
|
||||
ty.params().is_empty() && matches!(ty.results(), [ValType::I32])
|
||||
}
|
||||
|
||||
/// A module may not declare more linear memory than the engine grants.
|
||||
///
|
||||
/// Only what it *exports* is visible here. A memory a module keeps to itself is not
|
||||
/// in its exports, and the store's limiter is what refuses that one — at
|
||||
/// instantiation, where the run is charged nothing and the caller cannot tell it
|
||||
/// from any other resource failure. Screening the exported case covers every
|
||||
/// contract built against the guest SDK, since a contract needs an exported memory
|
||||
/// to make a host call at all.
|
||||
fn check_memory(module: &Module) -> Result<(), CheckError> {
|
||||
for export in module.exports() {
|
||||
if let ExternType::Memory(ty) = export.ty() {
|
||||
check_initial_pages(ty.minimum()).map_err(CheckError::Memory)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Whether the engine will grant a memory of this declared initial size.
|
||||
///
|
||||
/// The *minimum* only: a declared maximum past the cap is legal and simply
|
||||
/// unreachable, which `vm_limits::a_declared_maximum_past_the_cap_is_allowed_but_
|
||||
/// unreachable` pins on the run side. Refusing it here would turn a runnable
|
||||
/// contract away.
|
||||
fn check_initial_pages(pages: u64) -> Result<(), String> {
|
||||
if pages > u64::from(MAX_MEMORY_PAGES) {
|
||||
return Err(format!(
|
||||
"initial memory of {pages} pages is past the {MAX_MEMORY_PAGES}-page cap"
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// How an entry-point lookup failed, in the words both stages use: a check and a
|
||||
/// run describe the same module the same way, and "no entry point" would send a
|
||||
/// contract author looking for a function they already have.
|
||||
pub(crate) fn entry_point_fault(found: Option<ExternType>, name: &str) -> String {
|
||||
match found {
|
||||
Some(ExternType::Func(_)) => {
|
||||
format!("entry point '{name}' has the wrong signature, expected '() -> i32'")
|
||||
}
|
||||
Some(_) => format!("export '{name}' is not a function"),
|
||||
None => format!("no entry point '{name}'"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The rules, one by one, on inputs built directly rather than parsed out of a
|
||||
/// module. `tests/preflight.rs` runs real modules through [`check`]; what is here is
|
||||
/// what a module cannot state precisely — which rule fires, in which order, and in
|
||||
/// what words the caller logs it.
|
||||
///
|
||||
/// `wat` is a dev-dependency, so the one test here that does need a module writes it
|
||||
/// as text like every other test in the crate. What the library must not gain is a
|
||||
/// text *entry point* — `check` and `run` take binaries — and a `cfg(test)` caller
|
||||
/// cannot give it one.
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use wasmi::{GlobalType, MemoryType, Mutability};
|
||||
|
||||
/// A host function as a guest declares it. Any function type will do: the
|
||||
/// signature is not what [`check_import`] compares.
|
||||
fn a_function() -> ExternType {
|
||||
ExternType::Func(FuncType::new([ValType::I32], [ValType::I32]))
|
||||
}
|
||||
|
||||
/// A name every one of these tests can use, taken from the ABI rather than
|
||||
/// spelled, so it stays a real host function as the ABI changes.
|
||||
fn a_host_function_name() -> &'static str {
|
||||
HostFunctionSpec::ALL[0].wasm_name()
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Imports
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/// Every name the ABI declares is served. Derived from `ALL` rather than
|
||||
/// listed, so a host function added to the ABI is covered the day it lands.
|
||||
#[test]
|
||||
fn every_declared_host_function_is_served() {
|
||||
for op in HostFunctionSpec::ALL {
|
||||
assert_eq!(
|
||||
check_import(HOST_MODULE, op.wasm_name(), &a_function()),
|
||||
Ok(()),
|
||||
"{}",
|
||||
op.wasm_name()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_import_from_another_namespace_is_refused() {
|
||||
for namespace in ["env", "host", "host_lib2", ""] {
|
||||
let refusal = check_import(namespace, a_host_function_name(), &a_function())
|
||||
.expect_err(namespace);
|
||||
assert!(
|
||||
refusal.contains("is not from 'host_lib'"),
|
||||
"{namespace}: {refusal}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_unknown_name_is_refused() {
|
||||
let refusal =
|
||||
check_import(HOST_MODULE, "no_such_function", &a_function()).expect_err("unknown name");
|
||||
assert_eq!(refusal, "no host function 'no_such_function'");
|
||||
}
|
||||
|
||||
/// The engine defines these names as functions and as nothing else, so a module
|
||||
/// importing one as a global or a memory does not link either.
|
||||
#[test]
|
||||
fn a_host_function_imported_as_anything_else_is_refused() {
|
||||
for ty in [
|
||||
ExternType::Global(GlobalType::new(ValType::I32, Mutability::Const)),
|
||||
ExternType::Memory(MemoryType::new(1, None)),
|
||||
] {
|
||||
let name = a_host_function_name();
|
||||
let refusal = check_import(HOST_MODULE, name, &ty).expect_err("not a function");
|
||||
assert_eq!(refusal, format!("'host_lib::{name}' is not a function"));
|
||||
}
|
||||
}
|
||||
|
||||
/// The rules are ordered. An import that breaks two of them is reported by the
|
||||
/// first, so the message a contract author reads is the one that explains the
|
||||
/// rest of their imports too.
|
||||
#[test]
|
||||
fn the_namespace_is_reported_before_the_name() {
|
||||
let refusal = check_import("env", "no_such_function", &a_function())
|
||||
.expect_err("neither the namespace nor the name is served");
|
||||
|
||||
assert!(refusal.contains("is not from 'host_lib'"), "{refusal}");
|
||||
assert!(
|
||||
!refusal.contains("no host function"),
|
||||
"the namespace explains it: {refusal}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Both halves of the type are load-bearing, and neither is checked anywhere
|
||||
/// a module cannot reach.
|
||||
#[test]
|
||||
fn the_entry_point_type_is_nothing_in_and_one_i32_out() {
|
||||
assert!(is_entry_point(&FuncType::new([], [ValType::I32])));
|
||||
|
||||
for wrong in [
|
||||
FuncType::new([], []),
|
||||
FuncType::new([], [ValType::I64]),
|
||||
FuncType::new([ValType::I32], [ValType::I32]),
|
||||
FuncType::new([], [ValType::I32, ValType::I32]),
|
||||
] {
|
||||
assert!(!is_entry_point(&wrong), "{wrong:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Three faults, three descriptions. A run reports these too, with wasmi's own
|
||||
/// error appended, so a swapped arm would mislead at both stages at once.
|
||||
#[test]
|
||||
fn each_entry_point_fault_is_described_as_itself() {
|
||||
assert_eq!(
|
||||
entry_point_fault(Some(a_function()), "finish"),
|
||||
"entry point 'finish' has the wrong signature, expected '() -> i32'"
|
||||
);
|
||||
assert_eq!(
|
||||
entry_point_fault(
|
||||
Some(ExternType::Global(GlobalType::new(
|
||||
ValType::I32,
|
||||
Mutability::Const
|
||||
))),
|
||||
"finish"
|
||||
),
|
||||
"export 'finish' is not a function"
|
||||
);
|
||||
assert_eq!(
|
||||
entry_point_fault(None, "finish"),
|
||||
"no entry point 'finish'",
|
||||
"an absent export must not be reported as a wrong signature"
|
||||
);
|
||||
}
|
||||
|
||||
/// The cap itself is granted; one page past it is not. The boundary is the whole
|
||||
/// rule, and it is the same boundary the store's limiter applies at
|
||||
/// instantiation.
|
||||
#[test]
|
||||
fn the_initial_memory_may_reach_the_cap_but_not_pass_it() {
|
||||
assert_eq!(check_initial_pages(0), Ok(()));
|
||||
assert_eq!(check_initial_pages(u64::from(MAX_MEMORY_PAGES)), Ok(()));
|
||||
|
||||
let past = u64::from(MAX_MEMORY_PAGES) + 1;
|
||||
let refusal = check_initial_pages(past).expect_err("one page past the cap");
|
||||
assert_eq!(
|
||||
refusal,
|
||||
format!("initial memory of {past} pages is past the {MAX_MEMORY_PAGES}-page cap")
|
||||
);
|
||||
}
|
||||
|
||||
/// The bridge logs this string and the C++ tests match on it, so the stage's
|
||||
/// prefix is part of the interface rather than a debugging aid.
|
||||
#[test]
|
||||
fn a_refusal_names_its_stage() {
|
||||
assert_eq!(
|
||||
CheckError::Compile("bad magic".to_string()).to_string(),
|
||||
"compile: bad magic"
|
||||
);
|
||||
assert_eq!(
|
||||
CheckError::Memory("initial memory of 129 pages".to_string()).to_string(),
|
||||
"memory: initial memory of 129 pages"
|
||||
);
|
||||
assert_eq!(
|
||||
CheckError::Import("no host function 'x'".to_string()).to_string(),
|
||||
"import: no host function 'x'"
|
||||
);
|
||||
// The entry point's detail already says which of its three faults it is,
|
||||
// so a prefix would only repeat it.
|
||||
assert_eq!(
|
||||
CheckError::EntryPoint("no entry point 'finish'".to_string()).to_string(),
|
||||
"no entry point 'finish'"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_stages_run_in_order() {
|
||||
assert!(
|
||||
matches!(check(b"not wasm", "finish"), Err(CheckError::Compile(_))),
|
||||
"nothing is screened until the module compiles"
|
||||
);
|
||||
|
||||
// A module that compiles and imports nothing, so it reaches the entry point.
|
||||
let empty = wat::parse_str("(module)").expect("assembles");
|
||||
assert!(
|
||||
matches!(check(&empty, "finish"), Err(CheckError::EntryPoint(_))),
|
||||
"a module that compiles and imports nothing reaches the entry point"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
use crate::vm::MAX_FIELD_BYTES;
|
||||
use core::ops::Range;
|
||||
use xrpl_host_functions::{HostError, HostResult};
|
||||
|
||||
/// A byte region as the guest declared it: the `(ptr, len)` pair off the wire, not
|
||||
/// yet checked.
|
||||
///
|
||||
/// Every byte parameter in this ABI is such a pair, so pairing them once at the wire
|
||||
/// boundary is what keeps the helpers in `abi.rs` from each taking two loose integers
|
||||
/// they could be handed in either order.
|
||||
///
|
||||
/// It lives in a module of its own so that the fields are out of reach and
|
||||
/// [`range`](Region::range) is the *only* way to indices — the check cannot be
|
||||
/// skipped, only deferred. Construction is infallible for that reason: a call whose
|
||||
/// output region is malformed is then refused in the order its own helper chooses,
|
||||
/// rather than at the moment the pair happened to be formed.
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct Region {
|
||||
ptr: i32,
|
||||
len: i32,
|
||||
}
|
||||
|
||||
impl Region {
|
||||
pub(crate) fn new(ptr: i32, len: i32) -> Region {
|
||||
Region { ptr, len }
|
||||
}
|
||||
|
||||
/// `start..end` as indices. The conversion is the negativity check — it fails on
|
||||
/// exactly the negative values — and the addition guards a 32-bit `usize`, where
|
||||
/// two `i32`s can sum past the end.
|
||||
pub(crate) fn range(self) -> HostResult<Range<usize>> {
|
||||
let (Ok(start), Ok(len)) = (usize::try_from(self.ptr), usize::try_from(self.len)) else {
|
||||
return Err(HostError::InvalidParams);
|
||||
};
|
||||
let end = start
|
||||
.checked_add(len)
|
||||
.ok_or(HostError::PointerOutOfBounds)?;
|
||||
Ok(start..end)
|
||||
}
|
||||
|
||||
/// The region's bytes, refused past the field cap. No copy: the slice aliases
|
||||
/// `data`.
|
||||
pub(crate) fn read(self, data: &[u8]) -> HostResult<&[u8]> {
|
||||
let range = self.range()?;
|
||||
if range.len() > MAX_FIELD_BYTES {
|
||||
return Err(HostError::DataFieldTooLarge);
|
||||
}
|
||||
data.get(range).ok_or(HostError::PointerOutOfBounds)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,353 +0,0 @@
|
||||
use std::cell::Cell;
|
||||
use std::fmt;
|
||||
use std::sync::LazyLock;
|
||||
use wasmi::{
|
||||
Config, Engine, Export, Linker, Memory, Module, Store, StoreLimits, StoreLimitsBuilder,
|
||||
TrapCode,
|
||||
};
|
||||
use xrpl_host_functions::HostFunctions;
|
||||
|
||||
use crate::abi::{FatalHostError, Fault};
|
||||
use crate::preflight::entry_point_fault;
|
||||
use crate::register::register_host_functions;
|
||||
|
||||
/// wasm linear-memory page size, fixed by the wasm spec (64 KiB).
|
||||
const WASM_PAGE_BYTES: u32 = 64 * 1024;
|
||||
|
||||
/// Linear-memory page cap.
|
||||
pub const MAX_MEMORY_PAGES: u32 = 128;
|
||||
|
||||
/// [`MAX_MEMORY_PAGES`] in bytes: 8 MiB.
|
||||
pub const MAX_MEMORY_BYTES: usize = (MAX_MEMORY_PAGES * WASM_PAGE_BYTES) as usize;
|
||||
|
||||
/// Total bytes that may cross the host/guest boundary in one [`run`], separate
|
||||
/// from gas.
|
||||
pub const TRANSFER_LIMIT_BYTES: u64 = 1 << 20;
|
||||
|
||||
/// Size cap on any single value crossing the boundary, in either direction; over
|
||||
/// it is `DataFieldTooLarge`.
|
||||
///
|
||||
/// A protocol limit: `kMaxWasmDataLength` in `include/xrpl/protocol/Protocol.h`.
|
||||
pub const MAX_FIELD_BYTES: usize = 1024;
|
||||
|
||||
/// State threaded through every host call, stored in the wasmi [`Store`].
|
||||
pub(crate) struct VmState<'h> {
|
||||
pub(crate) host: &'h dyn HostFunctions,
|
||||
/// Enforces [`MAX_MEMORY_BYTES`] via `Store::limiter`, which needs a `&mut`
|
||||
/// into it from `&mut VmState` — hence a field rather than a local.
|
||||
pub(crate) mem_limits: StoreLimits,
|
||||
/// Remaining transfer budget for this run ([`TRANSFER_LIMIT_BYTES`]).
|
||||
///
|
||||
/// A `Cell` because it is decremented from a shared `&Caller`. One thread per
|
||||
/// invocation touches the store, so the lack of `Sync` costs nothing.
|
||||
///
|
||||
/// TODO: the extra charge for an unaligned field copy has nothing to attach to
|
||||
/// until this ABI gains a `FieldLocator` host function.
|
||||
pub(crate) transfer_budget: Cell<u64>,
|
||||
/// The guest's linear memory, resolved once by [`run`] after instantiation so
|
||||
/// no host call pays for an export lookup.
|
||||
///
|
||||
/// Caching the handle is sound because a [`Memory`] is an arena index, not a
|
||||
/// pointer to the bytes: it survives `memory.grow`, and `data`/`data_mut`
|
||||
/// re-derive the slice per call.
|
||||
///
|
||||
/// The handle is scoped to one store, so this assumes **one module, one
|
||||
/// instance, one store per `run`**. Module linking or nested execution would
|
||||
/// have to resolve per instance: a cached handle would serve a call against the
|
||||
/// wrong instance's memory, which is a wrong answer rather than an error.
|
||||
pub(crate) memory: Option<Memory>,
|
||||
/// Where a host writes a value before [`crate::abi::write_buffered`] copies it
|
||||
/// to the guest. One buffer per run, so no call zero-fills one of its own.
|
||||
///
|
||||
/// Inline rather than boxed: the store's data is built once and then only
|
||||
/// borrowed, so a kilobyte in it costs a move where a `Box` costs an
|
||||
/// allocation. A local would cost neither, but `forbid(unsafe_code)` means a
|
||||
/// stack buffer is zero-filled — per call, which is the cost this removes.
|
||||
pub(crate) out_buffer: [u8; MAX_FIELD_BYTES],
|
||||
}
|
||||
|
||||
/// Outcome of running an escrow contract to completion.
|
||||
#[derive(Debug)]
|
||||
pub struct RunOutcome {
|
||||
/// The value returned by the exported entry point (`finish`): `> 0` means
|
||||
/// allow the escrow to finish.
|
||||
pub result: i32,
|
||||
/// Fuel (gas) consumed by the whole invocation — guest instructions plus
|
||||
/// the per-call host charges.
|
||||
pub fuel_used: u64,
|
||||
}
|
||||
|
||||
/// Why a run produced no result. Each variant is one outcome for the caller to
|
||||
/// map to a TER.
|
||||
#[derive(Debug)]
|
||||
pub enum RunError {
|
||||
/// `wasm` is not a valid module under this engine's configuration.
|
||||
Compile(String),
|
||||
/// The module compiled but the engine would not accept it: an import the
|
||||
/// linker does not define, or an initial memory past the page cap. Not guest
|
||||
/// code failing — a start section that traps is [`RunError::Trap`].
|
||||
Instantiate(String),
|
||||
/// No export named `function_name` with signature `() -> i32`: absent, not a
|
||||
/// function, or a function of another type — which the detail tells apart.
|
||||
EntryPoint(String),
|
||||
/// Gas exhausted — by the guest's own instructions or by a host call's
|
||||
/// charge. [`RunFailure::fuel_used`] is the whole limit.
|
||||
OutOfGas,
|
||||
/// The host could not serve a call.
|
||||
Internal,
|
||||
/// A host call had no linear memory to work in: the module exports none, or
|
||||
/// the call came from a start section, which runs before there is an instance
|
||||
/// to resolve the memory from.
|
||||
NoMemory,
|
||||
/// The guest trapped: `unreachable`, division by zero, an out-of-bounds
|
||||
/// access, or `memory.grow` past the page cap. Wherever the guest was
|
||||
/// executing, including a start section during instantiation.
|
||||
Trap(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for RunError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RunError::Compile(detail) => write!(f, "compile: {detail}"),
|
||||
RunError::Instantiate(detail) => write!(f, "instantiate: {detail}"),
|
||||
// The detail says which of the entry point's failures this is, since
|
||||
// "no entry point" would be wrong for an export of the wrong type.
|
||||
RunError::EntryPoint(detail) => write!(f, "{detail}"),
|
||||
RunError::OutOfGas => write!(f, "out of gas"),
|
||||
RunError::Internal => write!(f, "internal error"),
|
||||
RunError::NoMemory => write!(f, "no exported memory"),
|
||||
RunError::Trap(detail) => write!(f, "trap: {detail}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A failed run, with the gas it still owes: a contract that traps or exhausts
|
||||
/// its gas is charged for what it burned.
|
||||
#[derive(Debug)]
|
||||
pub struct RunFailure {
|
||||
pub error: RunError,
|
||||
/// Fuel consumed before the failure. The whole limit when gas ran out; `0`
|
||||
/// when the module never ran.
|
||||
pub fuel_used: u64,
|
||||
}
|
||||
|
||||
impl fmt::Display for RunFailure {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} (fuel used: {})", self.error, self.fuel_used)
|
||||
}
|
||||
}
|
||||
|
||||
impl RunFailure {
|
||||
/// A failure with no fuel accounted: it stopped the run at or before the guest's
|
||||
/// first instruction, or under a store with no meter to read.
|
||||
fn owing_nothing(error: RunError) -> RunFailure {
|
||||
RunFailure {
|
||||
error,
|
||||
fuel_used: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fuel spent out of `gas`: the one place a run's cost is measured, so success,
|
||||
/// trap and refusal all report it the same way.
|
||||
///
|
||||
/// `Store::get_fuel` fails only on a store without fuel metering, which
|
||||
/// [`build_wasm_engine`] rules out and `run`'s `set_fuel` would already have
|
||||
/// caught — so a failure here is a defect in this crate. It must not become a
|
||||
/// number: `0` forgives a run its whole cost, `gas` charges an untouched one for
|
||||
/// everything. [`RunError::Internal`] instead.
|
||||
fn fuel_used(store: &Store<VmState<'_>>, gas: u64) -> Result<u64, RunError> {
|
||||
store
|
||||
.get_fuel()
|
||||
.map(|remaining| gas.saturating_sub(remaining))
|
||||
.map_err(|_| RunError::Internal)
|
||||
}
|
||||
|
||||
/// Report `error` with the run's cost attached. A cost that cannot be read replaces
|
||||
/// the outcome rather than being invented — see [`fuel_used`].
|
||||
fn failed(store: &Store<VmState<'_>>, gas: u64, error: RunError) -> RunFailure {
|
||||
match fuel_used(store, gas) {
|
||||
Ok(fuel_used) => RunFailure { error, fuel_used },
|
||||
Err(unmetered) => RunFailure::owing_nothing(unmetered),
|
||||
}
|
||||
}
|
||||
|
||||
/// The outcome a `wasmi::Error` names for itself, if any, rather than leaving it to
|
||||
/// the stage that raised it.
|
||||
///
|
||||
/// Two ways a run halts mid-flight: a host call that could not be served, which
|
||||
/// carries a [`FatalHostError`] saying which condition it was, and the guest's own
|
||||
/// instructions exhausting the meter, which wasmi raises as `OutOfFuel`.
|
||||
///
|
||||
/// Both can happen anywhere the guest executes — including a start section, which
|
||||
/// is guest code running during instantiation — so every stage from there on asks
|
||||
/// this before naming a failure after itself.
|
||||
fn guest_halted(error: &wasmi::Error) -> Option<RunError> {
|
||||
if let Some(fatal) = error.downcast_ref::<FatalHostError>() {
|
||||
return Some(fatal.0.into());
|
||||
}
|
||||
(error.as_trap_code() == Some(TrapCode::OutOfFuel)).then_some(RunError::OutOfGas)
|
||||
}
|
||||
|
||||
/// Why instantiation failed, once [`guest_halted`] has ruled out the two conditions
|
||||
/// that can arise anywhere.
|
||||
///
|
||||
/// A start section is guest code, so it can trap on its own — `unreachable`, a
|
||||
/// division by zero, an out-of-bounds access — and a trap is the guest's fault
|
||||
/// wherever it happens. Naming that after the *stage* would file it beside the
|
||||
/// module faults a caller treats as its own defect, and charge nothing for
|
||||
/// instructions the contract burned. What is left for [`RunError::Instantiate`] is a
|
||||
/// module the linker or the store would not accept at all.
|
||||
fn instantiation_failure(error: &wasmi::Error) -> RunError {
|
||||
match error.as_trap_code() {
|
||||
Some(_) => RunError::Trap(error.to_string()),
|
||||
None => RunError::Instantiate(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// The outcome a [`Fault`] is: the one place a stopped call becomes a stopped run.
|
||||
///
|
||||
/// Total and one arm each, because a `Fault` is only ever a condition that stops the
|
||||
/// run — the guest-visible codes cannot reach here, which is what
|
||||
/// [`crate::abi::CallError`] buys. A fault added later has no arm and does not
|
||||
/// compile.
|
||||
impl From<Fault> for RunError {
|
||||
fn from(fault: Fault) -> RunError {
|
||||
match fault {
|
||||
Fault::OutOfGas => RunError::OutOfGas,
|
||||
Fault::Internal => RunError::Internal,
|
||||
Fault::NoMemory => RunError::NoMemory,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The process-wide wasmi engine, built once on first use.
|
||||
///
|
||||
/// The configuration is consensus-fixed and identical for every invocation, and an
|
||||
/// [`Engine`] is an internally `Arc`ed `Send + Sync` handle, so one shared engine
|
||||
/// serves concurrent [`run`] calls.
|
||||
pub(crate) fn wasm_engine() -> &'static Engine {
|
||||
static ENGINE: LazyLock<Engine> = LazyLock::new(build_wasm_engine);
|
||||
&ENGINE
|
||||
}
|
||||
|
||||
/// Build the wasmi engine the escrow VM requires: deterministic, minimal
|
||||
/// features, fuel metering on.
|
||||
fn build_wasm_engine() -> Engine {
|
||||
let mut config = Config::default();
|
||||
config.consume_fuel(true);
|
||||
config.ignore_custom_sections(true);
|
||||
config.wasm_mutable_global(false);
|
||||
config.wasm_multi_value(false);
|
||||
config.wasm_sign_extension(false);
|
||||
config.wasm_saturating_float_to_int(false);
|
||||
config.wasm_bulk_memory(false);
|
||||
config.wasm_reference_types(false);
|
||||
config.wasm_tail_call(false);
|
||||
config.wasm_extended_const(false);
|
||||
config.floats(false);
|
||||
config.wasm_multi_memory(false);
|
||||
config.wasm_custom_page_sizes(false);
|
||||
config.wasm_memory64(false);
|
||||
config.wasm_wide_arithmetic(false);
|
||||
// TODO: enable option to reject wasm code containing start section after wasmi 2.0 release
|
||||
Engine::new(&config)
|
||||
}
|
||||
|
||||
/// Compile `wasm` for this engine.
|
||||
///
|
||||
/// The one path to a [`Module`]: the configuration is what decides whether a
|
||||
/// contract is valid at all, so [`run`] and [`crate::check`] must not be able to
|
||||
/// compile against different ones.
|
||||
pub(crate) fn compile(wasm: &[u8]) -> Result<Module, String> {
|
||||
Module::new(wasm_engine(), wasm).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Run a contract: compile `wasm`, give it `gas` fuel, service its host
|
||||
/// calls through `host`, and call the exported `function_name`.
|
||||
pub fn run<'h>(
|
||||
wasm: &[u8],
|
||||
gas: u64,
|
||||
host: &'h dyn HostFunctions,
|
||||
function_name: &str,
|
||||
) -> Result<RunOutcome, RunFailure> {
|
||||
let engine = wasm_engine();
|
||||
let module =
|
||||
compile(wasm).map_err(|detail| RunFailure::owing_nothing(RunError::Compile(detail)))?;
|
||||
|
||||
let mem_limits = StoreLimitsBuilder::new()
|
||||
.memory_size(MAX_MEMORY_BYTES)
|
||||
.trap_on_grow_failure(true)
|
||||
.build();
|
||||
let mut store = Store::new(
|
||||
engine,
|
||||
VmState {
|
||||
host,
|
||||
mem_limits,
|
||||
transfer_budget: Cell::new(TRANSFER_LIMIT_BYTES),
|
||||
memory: None,
|
||||
out_buffer: [0u8; MAX_FIELD_BYTES],
|
||||
},
|
||||
);
|
||||
|
||||
store
|
||||
.set_fuel(gas)
|
||||
.map_err(|_| RunFailure::owing_nothing(RunError::Internal))?;
|
||||
store.limiter(|state| &mut state.mem_limits);
|
||||
|
||||
let mut linker = Linker::<VmState<'h>>::new(engine);
|
||||
register_host_functions(&mut linker)
|
||||
.map_err(|_| RunFailure::owing_nothing(RunError::Internal))?;
|
||||
|
||||
let instance = match linker.instantiate_and_start(&mut store, &module) {
|
||||
Ok(instance) => instance,
|
||||
Err(e) => {
|
||||
let error = guest_halted(&e).unwrap_or_else(|| instantiation_failure(&e));
|
||||
return Err(failed(&store, gas, error));
|
||||
}
|
||||
};
|
||||
store.data_mut().memory = instance.exports(&store).find_map(Export::into_memory);
|
||||
|
||||
let function = match instance.get_typed_func::<(), i32>(&store, function_name) {
|
||||
Ok(function) => function,
|
||||
Err(e) => {
|
||||
let found = instance
|
||||
.get_export(&store, function_name)
|
||||
.map(|export| export.ty(&store));
|
||||
let error =
|
||||
RunError::EntryPoint(format!("{}: {e}", entry_point_fault(found, function_name)));
|
||||
return Err(failed(&store, gas, error));
|
||||
}
|
||||
};
|
||||
|
||||
let result = match function.call(&mut store, ()) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
let error = guest_halted(&e).unwrap_or_else(|| RunError::Trap(e.to_string()));
|
||||
return Err(failed(&store, gas, error));
|
||||
}
|
||||
};
|
||||
|
||||
let fuel_used = fuel_used(&store, gas).map_err(RunFailure::owing_nothing)?;
|
||||
Ok(RunOutcome { result, fuel_used })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn the_engine_is_one_engine() {
|
||||
assert!(Engine::same(wasm_engine(), wasm_engine()));
|
||||
}
|
||||
|
||||
/// The only place these numbers appear as literals; every other test derives
|
||||
/// them from the constants.
|
||||
#[test]
|
||||
fn the_limits_are_the_protocol_limits() {
|
||||
assert_eq!(MAX_MEMORY_PAGES, 128, "linear-memory page cap");
|
||||
assert_eq!(MAX_MEMORY_BYTES, 8 * 1024 * 1024, "page cap in bytes");
|
||||
assert_eq!(MAX_FIELD_BYTES, 1024, "kMaxWasmDataLength");
|
||||
assert_eq!(TRANSFER_LIMIT_BYTES, 1 << 20, "kWasmTransferLimit");
|
||||
}
|
||||
}
|
||||
@@ -1,750 +0,0 @@
|
||||
//! The two budgets a run spends: gas (fuel), and the transfer limit on bytes
|
||||
//! crossing the boundary. Both are consensus input, so several of these tests
|
||||
//! assert exact numbers.
|
||||
|
||||
mod support;
|
||||
|
||||
use support::{
|
||||
Answer, EMPTY_REGION, FakeHost, ONE_PAGE, PLENTY_OF_GAS, code, import, module, run,
|
||||
run_with_gas, trace_call,
|
||||
};
|
||||
use xrpl_host_functions::{HASH_LEN, HostError, HostFunctionSpec, TraceDataType};
|
||||
use xrpl_wasm_vm::{MAX_FIELD_BYTES, RunError, TRANSFER_LIMIT_BYTES};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Gas
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// The fuel a module of `body` burns, given gas to spare.
|
||||
fn fuel_for(body: &str, parts: &[&str], host: &FakeHost) -> u64 {
|
||||
let wat = module(parts, body);
|
||||
run(&wat, host).expect("the module should run").fuel_used
|
||||
}
|
||||
|
||||
/// The fuel a module burns doing nothing but returning a constant; every figure
|
||||
/// below builds on it. wasmi's number, pinned deliberately because wasmi's fuel
|
||||
/// table is consensus input.
|
||||
const EMPTY_MODULE_FUEL: u64 = 30;
|
||||
|
||||
/// wasmi's own fuel for a host call whose operands are all constants under 64: 14
|
||||
/// per `*.const`, plus 1 for the call. Our gas sits on top.
|
||||
///
|
||||
/// The formula holds only under 64, because wasmi widens a constant's encoding
|
||||
/// above that, each tier costing 7 more. Every call in [`call_for`] keeps its
|
||||
/// operands small for that reason; one with a larger constant fails here by a
|
||||
/// multiple of 7.
|
||||
fn wasmi_call_fuel(small_const_operands: u64) -> u64 {
|
||||
14 * small_const_operands + 1
|
||||
}
|
||||
|
||||
/// What wasmi charges on top of that for a call to a function with no result —
|
||||
/// `trace`'s shape, and nothing else in the ABI. Per call, not per module. Measured
|
||||
/// and pinned like the figures above.
|
||||
const WASMI_NO_RESULT_FUEL: u64 = 14;
|
||||
|
||||
/// wasmi's fuel for one `(drop …)`, which is how a module makes more than one call
|
||||
/// and keeps only the last result. Pinned like the two above.
|
||||
const WASMI_DROP_FUEL: u64 = 21;
|
||||
|
||||
/// The wasm a test needs in order to call one host function: the `(import …)`
|
||||
/// declaration, a call with small-constant operands, and how many it pushes.
|
||||
struct Call {
|
||||
import: &'static str,
|
||||
call: &'static str,
|
||||
operands: u64,
|
||||
/// Whether the call leaves an `i32` behind. `trace` does not, which is why
|
||||
/// [`Call::body`] ends every module with a constant instead of the call.
|
||||
yields: bool,
|
||||
}
|
||||
|
||||
impl Call {
|
||||
/// `n` calls in a row, leaving one `i32` for the module to return: the last
|
||||
/// answer where there is one, and a constant where the call has none.
|
||||
fn body(&self, n: usize) -> String {
|
||||
if self.yields {
|
||||
format!(
|
||||
"{}{}",
|
||||
format!("(drop {}) ", self.call).repeat(n - 1),
|
||||
self.call
|
||||
)
|
||||
} else {
|
||||
format!("{}(i32.const 0)", format!("{} ", self.call).repeat(n))
|
||||
}
|
||||
}
|
||||
|
||||
/// What [`Call::body`] burns beside the calls' own gas and the module's floor:
|
||||
/// one `drop` between consecutive answers, or wasmi's own surcharge on a call
|
||||
/// that has none.
|
||||
fn overhead(&self, n: u64) -> u64 {
|
||||
if self.yields {
|
||||
(n - 1) * WASMI_DROP_FUEL
|
||||
} else {
|
||||
n * WASMI_NO_RESULT_FUEL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The test wasm for each host function. The `match` is exhaustive, so a function
|
||||
/// added to the ABI fails to compile until it has wasm here, and iterating
|
||||
/// [`HostFunctionSpec::ALL`] then covers the whole ABI.
|
||||
fn call_for(op: HostFunctionSpec) -> Call {
|
||||
let (import, call, operands) = match op {
|
||||
HostFunctionSpec::GetLedgerSqn => (
|
||||
import::LDGR_INDEX,
|
||||
"(call $ldgr_index (i32.const 0) (i32.const 4))",
|
||||
2,
|
||||
),
|
||||
HostFunctionSpec::GetParentLedgerTime => (
|
||||
import::PARENT_LDGR_TIME,
|
||||
"(call $parent_ldgr_time (i32.const 0) (i32.const 4))",
|
||||
2,
|
||||
),
|
||||
HostFunctionSpec::GetParentLedgerHash => (
|
||||
import::PARENT_LDGR_HASH,
|
||||
"(call $parent_ldgr_hash (i32.const 0) (i32.const 32))",
|
||||
2,
|
||||
),
|
||||
HostFunctionSpec::GetBaseFee => (
|
||||
import::BASE_FEE,
|
||||
"(call $base_fee (i32.const 0) (i32.const 4))",
|
||||
2,
|
||||
),
|
||||
HostFunctionSpec::IsAmendmentEnabled => (
|
||||
import::AMENDMENT_ENABLED,
|
||||
"(call $amendment_enabled (i32.const 0) (i32.const 32))",
|
||||
2,
|
||||
),
|
||||
HostFunctionSpec::CacheLedgerObj => (
|
||||
import::CACHE_LE,
|
||||
"(call $cache_le (i32.const 0) (i32.const 32) (i32.const 0))",
|
||||
3,
|
||||
),
|
||||
HostFunctionSpec::GetTxField => (
|
||||
import::TX_FIELD,
|
||||
"(call $tx_field (i32.const 1) (i32.const 0) (i32.const 4))",
|
||||
3,
|
||||
),
|
||||
HostFunctionSpec::GetCurrentLedgerObjField => (
|
||||
import::HOME_LE_FIELD,
|
||||
"(call $home_le_field (i32.const 1) (i32.const 0) (i32.const 4))",
|
||||
3,
|
||||
),
|
||||
HostFunctionSpec::GetLedgerObjField => (
|
||||
import::LE_FIELD,
|
||||
"(call $le_field (i32.const 1) (i32.const 1) (i32.const 0) (i32.const 4))",
|
||||
4,
|
||||
),
|
||||
HostFunctionSpec::GetTxNestedField => (
|
||||
import::TX_INNER,
|
||||
"(call $tx_inner (i32.const 0) (i32.const 4) (i32.const 8) (i32.const 4))",
|
||||
4,
|
||||
),
|
||||
HostFunctionSpec::GetCurrentLedgerObjNestedField => (
|
||||
import::HOME_LE_INNER,
|
||||
"(call $home_le_inner (i32.const 0) (i32.const 4) (i32.const 8) (i32.const 4))",
|
||||
4,
|
||||
),
|
||||
HostFunctionSpec::GetLedgerObjNestedField => (
|
||||
import::LE_INNER,
|
||||
"(call $le_inner (i32.const 1) (i32.const 0) (i32.const 4) (i32.const 8) (i32.const 4))",
|
||||
5,
|
||||
),
|
||||
HostFunctionSpec::GetTxArrayLen => {
|
||||
(import::TX_ARR_LEN, "(call $tx_arr_len (i32.const 1))", 1)
|
||||
}
|
||||
HostFunctionSpec::GetCurrentLedgerObjArrayLen => (
|
||||
import::HOME_LE_ARR_LEN,
|
||||
"(call $home_le_arr_len (i32.const 1))",
|
||||
1,
|
||||
),
|
||||
HostFunctionSpec::GetLedgerObjArrayLen => (
|
||||
import::LE_ARR_LEN,
|
||||
"(call $le_arr_len (i32.const 1) (i32.const 1))",
|
||||
2,
|
||||
),
|
||||
HostFunctionSpec::GetTxNestedArrayLen => (
|
||||
import::TX_INNER_ARR_LEN,
|
||||
"(call $tx_inner_arr_len (i32.const 0) (i32.const 4))",
|
||||
2,
|
||||
),
|
||||
HostFunctionSpec::GetCurrentLedgerObjNestedArrayLen => (
|
||||
import::HOME_LE_INNER_ARR_LEN,
|
||||
"(call $home_le_inner_arr_len (i32.const 0) (i32.const 4))",
|
||||
2,
|
||||
),
|
||||
HostFunctionSpec::GetLedgerObjNestedArrayLen => (
|
||||
import::LE_INNER_ARR_LEN,
|
||||
"(call $le_inner_arr_len (i32.const 1) (i32.const 0) (i32.const 4))",
|
||||
3,
|
||||
),
|
||||
HostFunctionSpec::CheckSignature => (
|
||||
import::CHECK_SIG,
|
||||
"(call $check_sig (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::AccountKeylet => (
|
||||
import::ACCOUNTROOT_ID,
|
||||
"(call $accountroot_id (i32.const 0) (i32.const 20) (i32.const 32) (i32.const 32))",
|
||||
4,
|
||||
),
|
||||
HostFunctionSpec::AmmKeylet => (
|
||||
import::AMM_ID,
|
||||
"(call $amm_id (i32.const 0) (i32.const 20) (i32.const 24) (i32.const 40) (i32.const 0) (i32.const 32))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::CheckKeylet => (
|
||||
import::CHECK_ID,
|
||||
"(call $check_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::CredentialKeylet => (
|
||||
import::CREDENTIAL_ID,
|
||||
"(call $credential_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 20) (i32.const 40) (i32.const 4) (i32.const 44) (i32.const 20))",
|
||||
8,
|
||||
),
|
||||
HostFunctionSpec::DelegateKeylet => (
|
||||
import::DELEGATE_ID,
|
||||
"(call $delegate_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 20) (i32.const 40) (i32.const 32))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::DepositPreauthKeylet => (
|
||||
import::DEPOSIT_PREAUTH_ID,
|
||||
"(call $deposit_preauth_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 20) (i32.const 40) (i32.const 32))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::DidKeylet => (
|
||||
import::DID_ID,
|
||||
"(call $did_id (i32.const 0) (i32.const 20) (i32.const 32) (i32.const 32))",
|
||||
4,
|
||||
),
|
||||
HostFunctionSpec::EscrowKeylet => (
|
||||
import::ESCROW_ID,
|
||||
"(call $escrow_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::TrustLineKeylet => (
|
||||
import::TRUSTLINE_ID,
|
||||
"(call $trustline_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 20) (i32.const 40) (i32.const 20) (i32.const 60) (i32.const 32))",
|
||||
8,
|
||||
),
|
||||
HostFunctionSpec::MptokenIssuanceKeylet => (
|
||||
import::MPT_ISSUANCE_ID,
|
||||
"(call $mpt_issuance_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::MptokenKeylet => (
|
||||
import::MPTOKEN_ID,
|
||||
"(call $mptoken_id (i32.const 0) (i32.const 24) (i32.const 24) (i32.const 20) (i32.const 44) (i32.const 20))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::NftokenOfferKeylet => (
|
||||
import::NFT_OFFER_ID,
|
||||
"(call $nft_offer_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::OfferKeylet => (
|
||||
import::OFFER_ID,
|
||||
"(call $offer_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::OracleKeylet => (
|
||||
import::ORACLE_ID,
|
||||
"(call $oracle_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::PaychannelKeylet => (
|
||||
import::PAYCHAN_ID,
|
||||
"(call $paychan_id (i32.const 0) (i32.const 20) (i32.const 20) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 40) (i32.const 20))",
|
||||
8,
|
||||
),
|
||||
HostFunctionSpec::PermissionedDomainKeylet => (
|
||||
import::PERMISSIONED_DOMAIN_ID,
|
||||
"(call $permissioned_domain_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::SignerListKeylet => (
|
||||
import::SIGNERS_ID,
|
||||
"(call $signers_id (i32.const 0) (i32.const 20) (i32.const 32) (i32.const 32))",
|
||||
4,
|
||||
),
|
||||
HostFunctionSpec::TicketKeylet => (
|
||||
import::TICKET_ID,
|
||||
"(call $ticket_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::VaultKeylet => (
|
||||
import::VAULT_ID,
|
||||
"(call $vault_id (i32.const 0) (i32.const 20) (i32.const 0) (i32.const 4) (i32.const 32) (i32.const 32))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::Sha512Half => (
|
||||
import::SHA512_HALF,
|
||||
"(call $sha512_half (i32.const 0) (i32.const 4) (i32.const 0) (i32.const 32))",
|
||||
4,
|
||||
),
|
||||
HostFunctionSpec::Trace => (
|
||||
import::TRACE,
|
||||
"(call $trace (i32.const 0) (i32.const 0) (i32.const 1) (i32.const 0) (i32.const 0))",
|
||||
5,
|
||||
),
|
||||
HostFunctionSpec::UpdateData => (
|
||||
import::SET_DATA,
|
||||
"(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,
|
||||
),
|
||||
HostFunctionSpec::FloatFromInt => (
|
||||
import::FLOAT_FROM_INT,
|
||||
"(call $float_from_int (i64.const 0) (i32.const 0) (i32.const 8) (i32.const 0))",
|
||||
4,
|
||||
),
|
||||
HostFunctionSpec::FloatFromUint => (
|
||||
import::FLOAT_FROM_UINT,
|
||||
"(call $float_from_uint (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 0))",
|
||||
5,
|
||||
),
|
||||
HostFunctionSpec::FloatFromStamount => (
|
||||
import::FLOAT_FROM_STAMOUNT,
|
||||
"(call $float_from_stamount (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 0))",
|
||||
5,
|
||||
),
|
||||
HostFunctionSpec::FloatFromStnumber => (
|
||||
import::FLOAT_FROM_STNUMBER,
|
||||
"(call $float_from_stnumber (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 0))",
|
||||
5,
|
||||
),
|
||||
HostFunctionSpec::FloatToInt => (
|
||||
import::FLOAT_TO_INT,
|
||||
"(call $float_to_int (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 0))",
|
||||
5,
|
||||
),
|
||||
HostFunctionSpec::FloatToMantExp => (
|
||||
import::FLOAT_TO_MANT_EXP,
|
||||
"(call $float_to_mant_exp (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 16) (i32.const 4))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::FloatFromMantExp => (
|
||||
import::FLOAT_FROM_MANT_EXP,
|
||||
"(call $float_from_mant_exp (i64.const 0) (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 0))",
|
||||
5,
|
||||
),
|
||||
HostFunctionSpec::FloatCompare => (
|
||||
import::FLOAT_CMP,
|
||||
"(call $float_cmp (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8))",
|
||||
4,
|
||||
),
|
||||
HostFunctionSpec::FloatAdd => (
|
||||
import::FLOAT_ADD,
|
||||
"(call $float_add (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 16) (i32.const 8) (i32.const 0))",
|
||||
7,
|
||||
),
|
||||
HostFunctionSpec::FloatSubtract => (
|
||||
import::FLOAT_SUB,
|
||||
"(call $float_sub (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 16) (i32.const 8) (i32.const 0))",
|
||||
7,
|
||||
),
|
||||
HostFunctionSpec::FloatMultiply => (
|
||||
import::FLOAT_MULT,
|
||||
"(call $float_mult (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 16) (i32.const 8) (i32.const 0))",
|
||||
7,
|
||||
),
|
||||
HostFunctionSpec::FloatDivide => (
|
||||
import::FLOAT_DIV,
|
||||
"(call $float_div (i32.const 0) (i32.const 8) (i32.const 8) (i32.const 8) (i32.const 16) (i32.const 8) (i32.const 0))",
|
||||
7,
|
||||
),
|
||||
HostFunctionSpec::FloatRoot => (
|
||||
import::FLOAT_ROOT,
|
||||
"(call $float_root (i32.const 0) (i32.const 8) (i32.const 2) (i32.const 8) (i32.const 8) (i32.const 0))",
|
||||
6,
|
||||
),
|
||||
HostFunctionSpec::FloatPower => (
|
||||
import::FLOAT_POW,
|
||||
"(call $float_pow (i32.const 0) (i32.const 8) (i32.const 2) (i32.const 8) (i32.const 8) (i32.const 0))",
|
||||
6,
|
||||
),
|
||||
};
|
||||
Call {
|
||||
import,
|
||||
call,
|
||||
operands,
|
||||
yields: !matches!(op, HostFunctionSpec::Trace),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_module_burns_a_fixed_amount_of_fuel() {
|
||||
let fuel = fuel_for("(i32.const 0)", &[ONE_PAGE], &FakeHost::new());
|
||||
assert_eq!(fuel, EMPTY_MODULE_FUEL);
|
||||
}
|
||||
|
||||
/// Calling a host function `n` times costs `n` times its gas, to the unit. Every
|
||||
/// other term is known — the module's floor, wasmi's fuel per call, one `drop` per
|
||||
/// answered call — so the total is a closed form, with the gas read from the spec
|
||||
/// table rather than restated. `n = 1` pins the charge, `n > 1` pins that it lands
|
||||
/// on every call rather than once per run.
|
||||
#[test]
|
||||
fn a_host_call_costs_its_gas_every_time_it_is_called() {
|
||||
let host = FakeHost::new().answering_field(1, Answer::bytes([0xaa]));
|
||||
|
||||
for &op in HostFunctionSpec::ALL {
|
||||
let call = call_for(op);
|
||||
let per_call = wasmi_call_fuel(call.operands) + op.gas();
|
||||
|
||||
for n in 1..=3 {
|
||||
let body = call.body(n);
|
||||
let n = n as u64;
|
||||
|
||||
assert_eq!(
|
||||
fuel_for(&body, &[call.import, ONE_PAGE], &host),
|
||||
EMPTY_MODULE_FUEL + n * per_call + call.overhead(n),
|
||||
"{n} x {}",
|
||||
call.call
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The gas charge precedes the call's body, so a failing call costs exactly what a
|
||||
/// successful one costs. Field 1 is answered and field 7 is not; the two modules
|
||||
/// are otherwise identical, so their totals are comparable.
|
||||
#[test]
|
||||
fn a_failing_host_call_costs_exactly_what_a_successful_one_costs() {
|
||||
let host = FakeHost::new().answering_field(1, Answer::bytes([0xaa]));
|
||||
let call = |field: i32| {
|
||||
module(
|
||||
&[import::HOME_LE_FIELD, ONE_PAGE],
|
||||
&format!("(call $home_le_field (i32.const {field}) (i32.const 0) (i32.const 4))"),
|
||||
)
|
||||
};
|
||||
|
||||
let answered = run(&call(1), &host).expect("the module should run");
|
||||
let refused = run(&call(7), &host).expect("the module should run");
|
||||
|
||||
assert_eq!(answered.result, 1);
|
||||
assert_eq!(refused.result, code(HostError::FieldNotFound));
|
||||
assert_eq!(refused.fuel_used, answered.fuel_used);
|
||||
}
|
||||
|
||||
/// `fuel_used` is `gas - remaining`: what the run spent, not what was left or what
|
||||
/// it was handed. The gas figures are derived from the run's cost, so the boundary
|
||||
/// — exactly enough, and one short — is among the cases.
|
||||
#[test]
|
||||
fn fuel_used_is_what_was_spent_not_what_was_supplied() {
|
||||
let host = FakeHost::new();
|
||||
let op = HostFunctionSpec::GetLedgerSqn;
|
||||
let call = call_for(op);
|
||||
let wat = module(&[call.import, ONE_PAGE], call.call);
|
||||
let cost = EMPTY_MODULE_FUEL + wasmi_call_fuel(call.operands) + op.gas();
|
||||
|
||||
// Exactly its cost is enough, and no amount above it changes the figure. The
|
||||
// result is checked too, so the figure belongs to a run that did the work
|
||||
// rather than to one that was cut short.
|
||||
for gas in [cost, cost + 1, cost * 100, PLENTY_OF_GAS] {
|
||||
let outcome = run_with_gas(&wat, gas, &host).expect("should run");
|
||||
assert_eq!(
|
||||
outcome.result, 4,
|
||||
"gas {gas}: the call should have succeeded"
|
||||
);
|
||||
assert_eq!(outcome.fuel_used, cost, "gas {gas}");
|
||||
}
|
||||
|
||||
// One fuel short: the run ends at the call it cannot pay for and still owes the
|
||||
// whole limit, because `charge` spends what is left.
|
||||
let short = run_with_gas(&wat, cost - 1, &host).expect_err("one fuel short must not complete");
|
||||
assert!(
|
||||
matches!(short.error, RunError::OutOfGas),
|
||||
"expected the run to end out of gas, got: {short}"
|
||||
);
|
||||
assert_eq!(short.fuel_used, cost - 1);
|
||||
}
|
||||
|
||||
/// Fuel is metered, so the same module burns the same fuel every time — a
|
||||
/// property consensus depends on.
|
||||
#[test]
|
||||
fn the_same_run_burns_the_same_fuel() {
|
||||
let call = call_for(HostFunctionSpec::Trace);
|
||||
let wat = module(&[call.import, ONE_PAGE], &call.body(1));
|
||||
|
||||
let first = run(&wat, &FakeHost::new()).expect("should run").fuel_used;
|
||||
for _ in 0..4 {
|
||||
assert_eq!(
|
||||
run(&wat, &FakeHost::new()).expect("should run").fuel_used,
|
||||
first
|
||||
);
|
||||
}
|
||||
assert!(first > HostFunctionSpec::Trace.gas());
|
||||
}
|
||||
|
||||
/// Too little gas to finish stops the run: the meter refuses the guest's own
|
||||
/// instructions before it ever reaches the host call.
|
||||
#[test]
|
||||
fn a_run_that_cannot_afford_itself_fails() {
|
||||
let host = FakeHost::new();
|
||||
let wat = module(
|
||||
&[import::LDGR_INDEX, ONE_PAGE],
|
||||
"(call $ldgr_index (i32.const 0) (i32.const 4))",
|
||||
);
|
||||
|
||||
for gas in [0, 1, 10] {
|
||||
let Err(failure) = run_with_gas(&wat, gas, &host) else {
|
||||
panic!("gas {gas} should not have completed");
|
||||
};
|
||||
assert!(
|
||||
matches!(failure.error, RunError::OutOfGas),
|
||||
"gas {gas}: expected the run to end out of gas, got: {failure}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A guest looping forever is stopped by gas rather than running away, and owes
|
||||
/// the gas it burned doing it.
|
||||
#[test]
|
||||
fn an_endless_loop_is_stopped_by_gas() {
|
||||
const GAS: u64 = 100_000;
|
||||
|
||||
let host = FakeHost::new();
|
||||
let wat = module(&[ONE_PAGE], "(loop $l (br $l)) (i32.const 0)");
|
||||
|
||||
let failure = run_with_gas(&wat, GAS, &host).expect_err("an endless loop must not complete");
|
||||
assert!(
|
||||
matches!(failure.error, RunError::OutOfGas),
|
||||
"expected the meter to stop it, got: {failure}"
|
||||
);
|
||||
assert_eq!(
|
||||
failure.fuel_used, GAS,
|
||||
"a runaway guest burns the whole limit"
|
||||
);
|
||||
}
|
||||
|
||||
/// A host call refused its gas stops the run: the guest never gets a chance to
|
||||
/// ignore the refusal and carry on, and it is charged the whole limit.
|
||||
///
|
||||
/// The gas range is every amount that reaches the call and cannot pay for it, so
|
||||
/// the case is the whole boundary rather than one number. `trace` is the call under
|
||||
/// it because it is the one that could not report a refusal even if it wanted to:
|
||||
/// stopping the run is the whole of what the guest sees.
|
||||
#[test]
|
||||
fn a_host_call_refused_its_gas_stops_the_run() {
|
||||
let host = FakeHost::new();
|
||||
let op = HostFunctionSpec::Trace;
|
||||
let call = call_for(op);
|
||||
let wat = module(&[call.import, ONE_PAGE], &call.body(1));
|
||||
// Measured rather than derived: the whole run's cost, less the call's own gas,
|
||||
// is the least a guest can be given and still reach the call. Below that the
|
||||
// meter stops the guest's own instructions instead, which is
|
||||
// `a_run_that_cannot_afford_itself_fails`'s case, not this one.
|
||||
let cost = run(&wat, &FakeHost::new())
|
||||
.expect("the module should run")
|
||||
.fuel_used;
|
||||
|
||||
for gas in cost - op.gas()..cost {
|
||||
let Err(failure) = run_with_gas(&wat, gas, &host) else {
|
||||
panic!("gas {gas}: the run completed, so the guest was handed the refusal");
|
||||
};
|
||||
assert!(
|
||||
matches!(failure.error, RunError::OutOfGas),
|
||||
"gas {gas}: expected the run to end out of gas, got: {failure}"
|
||||
);
|
||||
assert_eq!(
|
||||
failure.fuel_used, gas,
|
||||
"gas {gas}: a call it cannot afford burns the whole limit"
|
||||
);
|
||||
}
|
||||
assert!(host.traces().is_empty(), "the host body must not have run");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The transfer limit
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A module that repeats `call` while `keep_going` holds, then returns the last
|
||||
/// status, so a budget can be run to exhaustion inside one invocation.
|
||||
fn until_refused(imports: &str, call: &str, keep_going: &str) -> String {
|
||||
module(
|
||||
&[imports, ONE_PAGE],
|
||||
&format!(
|
||||
"(local $r i32)
|
||||
(loop $l
|
||||
(local.set $r {call})
|
||||
(br_if $l {keep_going}))
|
||||
(local.get $r)"
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// For a call whose success is a positive byte count.
|
||||
const WHILE_POSITIVE: &str = "(i32.gt_s (local.get $r) (i32.const 0))";
|
||||
|
||||
/// Bytes written into guest memory are charged against the run's budget, and the
|
||||
/// budget is a per-run total: 1 MiB of 1 KiB values exhausts it.
|
||||
#[test]
|
||||
fn writes_spend_the_transfer_budget() {
|
||||
let host = FakeHost::new().answering_field(1, Answer::filler(MAX_FIELD_BYTES));
|
||||
let wat = until_refused(
|
||||
import::HOME_LE_FIELD,
|
||||
&format!("(call $home_le_field (i32.const 1) (i32.const 0) (i32.const {MAX_FIELD_BYTES}))"),
|
||||
WHILE_POSITIVE,
|
||||
);
|
||||
|
||||
let outcome = run(&wat, &host).expect("the module should run");
|
||||
assert_eq!(outcome.result, code(HostError::OutOfTransferLimit));
|
||||
assert_eq!(
|
||||
host.fields_asked.borrow().len() as u64,
|
||||
TRANSFER_LIMIT_BYTES / MAX_FIELD_BYTES as u64 + 1,
|
||||
"one call per 1 KiB of budget, plus the one that was refused"
|
||||
);
|
||||
}
|
||||
|
||||
/// The budget is per run, not per call: a fresh run starts with a full budget.
|
||||
#[test]
|
||||
fn each_run_gets_its_own_budget() {
|
||||
let wat = until_refused(
|
||||
import::HOME_LE_FIELD,
|
||||
&format!("(call $home_le_field (i32.const 1) (i32.const 0) (i32.const {MAX_FIELD_BYTES}))"),
|
||||
WHILE_POSITIVE,
|
||||
);
|
||||
|
||||
for _ in 0..2 {
|
||||
let host = FakeHost::new().answering_field(1, Answer::filler(MAX_FIELD_BYTES));
|
||||
let outcome = run(&wat, &host).expect("the module should run");
|
||||
assert_eq!(outcome.result, code(HostError::OutOfTransferLimit));
|
||||
assert_eq!(
|
||||
host.fields_asked.borrow().len() as u64,
|
||||
TRANSFER_LIMIT_BYTES / MAX_FIELD_BYTES as u64 + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A run well inside the budget never sees it.
|
||||
#[test]
|
||||
fn a_modest_run_never_meets_the_budget() {
|
||||
let host = FakeHost::new().answering_field(1, Answer::filler(MAX_FIELD_BYTES));
|
||||
let wat = module(
|
||||
&[import::HOME_LE_FIELD, ONE_PAGE],
|
||||
&format!("(call $home_le_field (i32.const 1) (i32.const 0) (i32.const {MAX_FIELD_BYTES}))"),
|
||||
);
|
||||
|
||||
let outcome = run(&wat, &host).expect("the module should run");
|
||||
assert_eq!(outcome.result, MAX_FIELD_BYTES as i32);
|
||||
}
|
||||
|
||||
/// Reads leave the budget alone: `read_borrowed` hands the host a slice *aliasing*
|
||||
/// guest memory, so there are no copied bytes to charge. What bounds how many reads
|
||||
/// a run can make is gas, which every host call pays before its body runs.
|
||||
///
|
||||
/// The observation is the write at the end, not the reads: the module reads four
|
||||
/// times the whole budget first, so a rule that charged reads would have nothing
|
||||
/// left, and the write would answer `OutOfTransferLimit` instead of a byte count.
|
||||
#[test]
|
||||
fn reads_do_not_spend_the_transfer_budget() {
|
||||
/// 1 KiB reads, four times over the budget.
|
||||
const READS: u64 = 4 * TRANSFER_LIMIT_BYTES / MAX_FIELD_BYTES as u64;
|
||||
|
||||
let host = FakeHost::new().answering_field(1, Answer::filler(MAX_FIELD_BYTES));
|
||||
let read = trace_call(
|
||||
TraceDataType::AsHex,
|
||||
EMPTY_REGION,
|
||||
&format!("(i32.const 0) (i32.const {MAX_FIELD_BYTES})"),
|
||||
);
|
||||
let wat = module(
|
||||
&[import::TRACE, import::HOME_LE_FIELD, ONE_PAGE],
|
||||
&format!(
|
||||
"(local $i i32)
|
||||
(loop $l
|
||||
{read}
|
||||
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
||||
(br_if $l (i32.lt_u (local.get $i) (i32.const {READS}))))
|
||||
(call $home_le_field (i32.const 1) (i32.const 0) (i32.const {MAX_FIELD_BYTES}))"
|
||||
),
|
||||
);
|
||||
|
||||
let outcome = run(&wat, &host).expect("the module should run");
|
||||
assert_eq!(
|
||||
host.traces().len() as u64,
|
||||
READS,
|
||||
"every read should have been served"
|
||||
);
|
||||
assert_eq!(
|
||||
outcome.result, MAX_FIELD_BYTES as i32,
|
||||
"the write after {READS} reads of {MAX_FIELD_BYTES} bytes should still have its budget"
|
||||
);
|
||||
}
|
||||
|
||||
/// Only the output half of a read-write call spends the budget. `sha512_half`'s
|
||||
/// input is a borrowed read like any other, aliasing guest memory rather than
|
||||
/// crossing the boundary, so a run may hash far more bytes than the budget holds as
|
||||
/// long as the digests it writes fit inside it.
|
||||
///
|
||||
/// The two totals are asserted, so the arithmetic that makes the case is in the
|
||||
/// test rather than in a comment: the inputs alone would overrun the budget, the
|
||||
/// digests alone are a small fraction of it.
|
||||
#[test]
|
||||
fn only_the_output_half_of_a_read_write_spends_the_budget() {
|
||||
/// Enough 1 KiB inputs to overrun the budget twice over.
|
||||
const CALLS: u64 = 2 * TRANSFER_LIMIT_BYTES / MAX_FIELD_BYTES as u64;
|
||||
|
||||
assert!(
|
||||
CALLS * MAX_FIELD_BYTES as u64 > TRANSFER_LIMIT_BYTES,
|
||||
"the inputs alone must overrun the budget"
|
||||
);
|
||||
assert!(
|
||||
CALLS * HASH_LEN as u64 <= TRANSFER_LIMIT_BYTES / 2,
|
||||
"the digests alone must stay well inside it"
|
||||
);
|
||||
|
||||
let host = FakeHost::new().answering_digest(Answer::filler(HASH_LEN));
|
||||
let wat = module(
|
||||
&[import::SHA512_HALF, ONE_PAGE],
|
||||
&format!(
|
||||
"(local $i i32)
|
||||
(local $r i32)
|
||||
(loop $l
|
||||
(local.set $r (call $sha512_half (i32.const 0) (i32.const {MAX_FIELD_BYTES})
|
||||
(i32.const 0) (i32.const {HASH_LEN})))
|
||||
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
||||
(br_if $l (i32.lt_u (local.get $i) (i32.const {CALLS}))))
|
||||
(local.get $r)"
|
||||
),
|
||||
);
|
||||
|
||||
let outcome = run(&wat, &host).expect("the module should run");
|
||||
assert_eq!(
|
||||
host.digested.borrow().len() as u64,
|
||||
CALLS,
|
||||
"every call should have been served"
|
||||
);
|
||||
assert_eq!(
|
||||
outcome.result, HASH_LEN as i32,
|
||||
"only the digests are charged, and they fit"
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,621 +0,0 @@
|
||||
//! The bounds, field-cap and buffer-fit rules `abi.rs` enforces on every region
|
||||
//! crossing the boundary. This is the policy the guest observes, so each rule is
|
||||
//! pinned to the code it answers with.
|
||||
|
||||
mod support;
|
||||
|
||||
use support::{
|
||||
Answer, COMPLETED, EMPTY_REGION, FakeHost, ONE_PAGE, code, failure, import, module, status,
|
||||
traced,
|
||||
};
|
||||
use xrpl_host_functions::{HASH_LEN, HostError, TraceDataType};
|
||||
use xrpl_wasm_vm::{MAX_FIELD_BYTES, RunError};
|
||||
|
||||
/// One page, so anything at or past 65536 is out of bounds.
|
||||
const PAGE: i64 = 64 * 1024;
|
||||
|
||||
/// The per-field size cap, as a wasm operand.
|
||||
const CAP: i64 = MAX_FIELD_BYTES as i64;
|
||||
/// One byte over the cap: the smallest value the engine must refuse.
|
||||
const OVER_CAP: i64 = CAP + 1;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Output regions (`write_into`)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// The whole output region must be in bounds, not merely its start — the engine
|
||||
/// checks `[dst, dst + cap)` before the host is allowed to write.
|
||||
#[test]
|
||||
fn an_output_region_running_past_memory_is_refused() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
for (dst, cap) in [(PAGE, 4), (PAGE - 3, 4), (PAGE + 1024, 4), (0, PAGE + 1)] {
|
||||
let wat = module(
|
||||
&[import::LDGR_INDEX, ONE_PAGE],
|
||||
&format!("(call $ldgr_index (i32.const {dst}) (i32.const {cap}))"),
|
||||
);
|
||||
assert_eq!(
|
||||
status(&wat, &host),
|
||||
code(HostError::PointerOutOfBounds),
|
||||
"dst {dst} cap {cap}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A region ending exactly at the last byte of memory is in bounds.
|
||||
#[test]
|
||||
fn an_output_region_ending_at_the_last_byte_is_allowed() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(
|
||||
&[import::LDGR_INDEX, ONE_PAGE],
|
||||
&format!("(call $ldgr_index (i32.const {}) (i32.const 4))", PAGE - 4),
|
||||
);
|
||||
assert_eq!(status(&wat, &host), 4);
|
||||
}
|
||||
|
||||
/// The wire carries `i32`, so a guest can present a negative pointer or length.
|
||||
#[test]
|
||||
fn a_negative_output_pointer_or_length_is_refused() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
for (dst, cap) in [(-1, 4), (0, -1), (-1, -1), (i32::MIN, 4)] {
|
||||
let wat = module(
|
||||
&[import::LDGR_INDEX, ONE_PAGE],
|
||||
&format!("(call $ldgr_index (i32.const {dst}) (i32.const {cap}))"),
|
||||
);
|
||||
assert_eq!(
|
||||
status(&wat, &host),
|
||||
code(HostError::InvalidParams),
|
||||
"dst {dst} cap {cap}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The host reports a value's true length whether or not it fitted; a value that
|
||||
/// did not fit is the guest's error, not the host's.
|
||||
#[test]
|
||||
fn a_value_larger_than_the_buffer_is_refused() {
|
||||
let host = FakeHost::new().answering_field(1, Answer::filler(64));
|
||||
|
||||
let wat = module(
|
||||
&[import::HOME_LE_FIELD, ONE_PAGE],
|
||||
"(call $home_le_field (i32.const 1) (i32.const 0) (i32.const 63))",
|
||||
);
|
||||
assert_eq!(status(&wat, &host), code(HostError::BufferTooSmall));
|
||||
|
||||
let wat = module(
|
||||
&[import::HOME_LE_FIELD, ONE_PAGE],
|
||||
"(call $home_le_field (i32.const 1) (i32.const 0) (i32.const 64))",
|
||||
);
|
||||
assert_eq!(status(&wat, &host), 64, "exactly enough room is enough");
|
||||
}
|
||||
|
||||
/// A zero-length output region is in bounds and simply cannot hold anything.
|
||||
#[test]
|
||||
fn a_zero_length_output_region_is_in_bounds_but_too_small() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(
|
||||
&[import::LDGR_INDEX, ONE_PAGE],
|
||||
"(call $ldgr_index (i32.const 0) (i32.const 0))",
|
||||
);
|
||||
assert_eq!(status(&wat, &host), code(HostError::BufferTooSmall));
|
||||
}
|
||||
|
||||
/// A host that reports more than the per-field cap is refused even when the
|
||||
/// guest offered room for it: the cap is the engine's rule, not the buffer's.
|
||||
#[test]
|
||||
fn a_value_past_the_field_cap_is_refused() {
|
||||
let host = FakeHost::new()
|
||||
.answering_field(1, Answer::claiming(OVER_CAP as usize))
|
||||
.answering_field(2, Answer::claiming(MAX_FIELD_BYTES));
|
||||
|
||||
let wat = module(
|
||||
&[import::HOME_LE_FIELD, ONE_PAGE],
|
||||
"(call $home_le_field (i32.const 1) (i32.const 0) (i32.const 4096))",
|
||||
);
|
||||
assert_eq!(status(&wat, &host), code(HostError::DataFieldTooLarge));
|
||||
|
||||
let wat = module(
|
||||
&[import::HOME_LE_FIELD, ONE_PAGE],
|
||||
"(call $home_le_field (i32.const 2) (i32.const 0) (i32.const 4096))",
|
||||
);
|
||||
assert_eq!(status(&wat, &host), CAP as i32, "the cap itself is allowed");
|
||||
}
|
||||
|
||||
/// A refused over-cap value leaves nothing behind. `write_into` hands the host at
|
||||
/// most [`MAX_FIELD_BYTES`] of the guest's buffer however much room the guest
|
||||
/// declared, so a value past the cap does not fit the region it is offered and no
|
||||
/// prefix of it can reach guest memory either.
|
||||
///
|
||||
/// The host answers with a real over-cap value: [`Answer::claiming`] writes
|
||||
/// nothing whatever the engine does, so it could not tell the two apart. The
|
||||
/// second module folds the *whole* declared buffer rather than one byte, so the
|
||||
/// claim is about the region and not about its first byte.
|
||||
#[test]
|
||||
fn an_over_cap_value_is_refused_without_reaching_guest_memory() {
|
||||
/// The buffer the guest declares: well over the cap, so the clamp bites.
|
||||
const BUFFER: usize = 4096;
|
||||
|
||||
let over_cap = vec![0xff; MAX_FIELD_BYTES + 1];
|
||||
let host = FakeHost::new().answering_field(1, Answer::bytes(over_cap));
|
||||
let call = format!("(call $home_le_field (i32.const 1) (i32.const 0) (i32.const {BUFFER}))");
|
||||
|
||||
// The status the guest sees, from a module that returns it directly.
|
||||
let refusing = module(&[import::HOME_LE_FIELD, ONE_PAGE], &call);
|
||||
assert_eq!(
|
||||
status(&refusing, &host),
|
||||
code(HostError::DataFieldTooLarge),
|
||||
"the value is refused"
|
||||
);
|
||||
|
||||
// Every byte of the buffer, or-ed together: guest memory starts zero-filled,
|
||||
// so any byte the host wrote shows up here.
|
||||
let reading = module(
|
||||
&[import::HOME_LE_FIELD, ONE_PAGE],
|
||||
&format!(
|
||||
"(local $i i32)
|
||||
(local $seen i32)
|
||||
(drop {call})
|
||||
(loop $l
|
||||
(local.set $seen (i32.or (local.get $seen) (i32.load8_u (local.get $i))))
|
||||
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
||||
(br_if $l (i32.lt_u (local.get $i) (i32.const {BUFFER}))))
|
||||
(local.get $seen)"
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
status(&reading, &host),
|
||||
0,
|
||||
"and not one of its bytes is in the guest's buffer"
|
||||
);
|
||||
}
|
||||
|
||||
/// The field cap is checked before the buffer-fit rule, so a value that breaks both
|
||||
/// is reported as over-cap. The guest branches on the code, and the two rules
|
||||
/// answer different questions, so the order is worth pinning.
|
||||
#[test]
|
||||
fn the_field_cap_precedes_the_buffer_fit_check() {
|
||||
let host = FakeHost::new().answering_field(1, Answer::claiming(MAX_FIELD_BYTES + 1));
|
||||
|
||||
// A 63-byte buffer: the value is both over the cap and far too big to fit.
|
||||
let wat = module(
|
||||
&[import::HOME_LE_FIELD, ONE_PAGE],
|
||||
"(call $home_le_field (i32.const 1) (i32.const 0) (i32.const 63))",
|
||||
);
|
||||
assert_eq!(status(&wat, &host), code(HostError::DataFieldTooLarge));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Input regions (`Region::read`, via `sha512_half`)
|
||||
//
|
||||
// `sha512_half`'s first pair is an input region like any other, and it is the
|
||||
// input the guest gets a status back from: `trace`, the other reader, answers
|
||||
// nothing at all. So the codes are pinned here and the silence below.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// An input region is bounds-checked the same way an output region is. Every case
|
||||
/// here stays within the field cap, which on an input is checked first.
|
||||
#[test]
|
||||
fn an_input_region_running_past_memory_is_refused() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
for (ptr, len) in [(PAGE, 1), (PAGE - 3, 4), (PAGE - 1, CAP)] {
|
||||
let wat = module(
|
||||
&[import::SHA512_HALF, ONE_PAGE],
|
||||
&format!(
|
||||
"(call $sha512_half (i32.const {ptr}) (i32.const {len})
|
||||
(i32.const 0) (i32.const {HASH_LEN}))"
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
status(&wat, &host),
|
||||
code(HostError::PointerOutOfBounds),
|
||||
"ptr {ptr} len {len}"
|
||||
);
|
||||
assert!(host.digested.borrow().is_empty(), "the host is not called");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_negative_input_pointer_or_length_is_refused() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
for (ptr, len) in [(-1, 1), (0, -1), (i32::MIN, 1)] {
|
||||
let wat = module(
|
||||
&[import::SHA512_HALF, ONE_PAGE],
|
||||
&format!(
|
||||
"(call $sha512_half (i32.const {ptr}) (i32.const {len})
|
||||
(i32.const 0) (i32.const {HASH_LEN}))"
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
status(&wat, &host),
|
||||
code(HostError::InvalidParams),
|
||||
"ptr {ptr} len {len}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The field cap bounds what the guest may hand *in*, too.
|
||||
#[test]
|
||||
fn an_input_past_the_field_cap_is_refused() {
|
||||
let host = FakeHost::new();
|
||||
let digest = |len: i64| {
|
||||
module(
|
||||
&[import::SHA512_HALF, ONE_PAGE],
|
||||
&format!(
|
||||
"(call $sha512_half (i32.const 0) (i32.const {len})
|
||||
(i32.const 2048) (i32.const {HASH_LEN}))"
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
status(&digest(OVER_CAP), &host),
|
||||
code(HostError::DataFieldTooLarge)
|
||||
);
|
||||
assert!(host.digested.borrow().is_empty());
|
||||
|
||||
assert_eq!(
|
||||
status(&digest(CAP), &host),
|
||||
HASH_LEN as i32,
|
||||
"the cap itself is allowed"
|
||||
);
|
||||
}
|
||||
|
||||
/// The two directions check in opposite orders: an input's length is known before
|
||||
/// the read, so the cap comes first, while an output's region has to be resolved
|
||||
/// before the host can produce a value, so bounds come first there.
|
||||
#[test]
|
||||
fn the_field_cap_precedes_the_bounds_check_on_an_input() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let reading = module(
|
||||
&[import::SHA512_HALF, ONE_PAGE],
|
||||
&format!(
|
||||
"(call $sha512_half (i32.const 0) (i32.const {})
|
||||
(i32.const 0) (i32.const {HASH_LEN}))",
|
||||
PAGE + 1
|
||||
),
|
||||
);
|
||||
assert_eq!(status(&reading, &host), code(HostError::DataFieldTooLarge));
|
||||
|
||||
let writing = module(
|
||||
&[import::LDGR_INDEX, ONE_PAGE],
|
||||
&format!("(call $ldgr_index (i32.const 0) (i32.const {}))", PAGE + 1),
|
||||
);
|
||||
assert_eq!(status(&writing, &host), code(HostError::PointerOutOfBounds));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The reader with no result (`read_borrowed`, via `trace`)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// `trace` reads two regions and either one being bad refuses the call. The same
|
||||
/// rule as above, and the guest is told nothing: the refusal is the host not being
|
||||
/// called, and the run carries on to the constant that follows.
|
||||
#[test]
|
||||
fn both_of_traces_regions_are_checked_silently() {
|
||||
let host = FakeHost::new();
|
||||
let regions = [
|
||||
(
|
||||
format!("(i32.const {PAGE}) (i32.const 1)"),
|
||||
EMPTY_REGION.to_owned(),
|
||||
),
|
||||
(
|
||||
EMPTY_REGION.to_owned(),
|
||||
format!("(i32.const {PAGE}) (i32.const 1)"),
|
||||
),
|
||||
(
|
||||
EMPTY_REGION.to_owned(),
|
||||
format!("(i32.const 0) (i32.const {OVER_CAP})"),
|
||||
),
|
||||
(
|
||||
"(i32.const -1) (i32.const 1)".to_owned(),
|
||||
EMPTY_REGION.to_owned(),
|
||||
),
|
||||
];
|
||||
|
||||
for (msg, data) in regions {
|
||||
let wat = module(
|
||||
&[import::TRACE, ONE_PAGE],
|
||||
&traced(TraceDataType::AsHex, &msg, &data),
|
||||
);
|
||||
assert_eq!(status(&wat, &host), COMPLETED, "msg {msg} data {data}");
|
||||
assert!(
|
||||
host.traces().is_empty(),
|
||||
"msg {msg} data {data}: the host must not be called"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Both at once (`write_buffered`, via `sha512_half`)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A call with an input and an output region decides everything about the input
|
||||
/// before anything about the output, so a bad input is reported however the output
|
||||
/// region is wrong — out of bounds, or a pointer that is not one at all.
|
||||
///
|
||||
/// The whole output region, params included, is judged after the host has answered.
|
||||
/// Hoisting any part of that above the call would put the output's verdict first for
|
||||
/// these cases, and there is no half of it that can be hoisted on a principle the
|
||||
/// other half shares.
|
||||
#[test]
|
||||
fn a_read_write_checks_its_input_before_its_output() {
|
||||
let host = FakeHost::new();
|
||||
let digest = |src: i64, src_len: i64, dst: i64| {
|
||||
module(
|
||||
&[import::SHA512_HALF, ONE_PAGE],
|
||||
&format!(
|
||||
"(call $sha512_half (i32.const {src}) (i32.const {src_len})
|
||||
(i32.const {dst}) (i32.const {HASH_LEN}))"
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let over_cap = digest(0, OVER_CAP, 0);
|
||||
assert_eq!(status(&over_cap, &host), code(HostError::DataFieldTooLarge));
|
||||
|
||||
let out_of_bounds = digest(PAGE, 4, 0);
|
||||
assert_eq!(
|
||||
status(&out_of_bounds, &host),
|
||||
code(HostError::PointerOutOfBounds)
|
||||
);
|
||||
|
||||
// A bad input against each way the output can be wrong: the input's verdict is
|
||||
// the one reported, and the host is never asked for a value nobody can take.
|
||||
for dst in [PAGE, -1] {
|
||||
let both_bad = digest(0, OVER_CAP, dst);
|
||||
assert_eq!(
|
||||
status(&both_bad, &host),
|
||||
code(HostError::DataFieldTooLarge),
|
||||
"dst {dst}"
|
||||
);
|
||||
}
|
||||
assert!(host.digested.borrow().is_empty(), "the host is not reached");
|
||||
}
|
||||
|
||||
/// The output half of a read-write call obeys the same rules as a plain write.
|
||||
#[test]
|
||||
fn a_read_write_output_obeys_the_write_rules() {
|
||||
let host = FakeHost::new().answering_digest(Answer::filler(32));
|
||||
|
||||
let wat = module(
|
||||
&[import::SHA512_HALF, ONE_PAGE],
|
||||
"(call $sha512_half (i32.const 0) (i32.const 4) (i32.const 0) (i32.const 31))",
|
||||
);
|
||||
assert_eq!(status(&wat, &host), code(HostError::BufferTooSmall));
|
||||
|
||||
let wat = module(
|
||||
&[import::SHA512_HALF, ONE_PAGE],
|
||||
&format!(
|
||||
"(call $sha512_half (i32.const 0) (i32.const 4) (i32.const {PAGE}) (i32.const 32))"
|
||||
),
|
||||
);
|
||||
assert_eq!(status(&wat, &host), code(HostError::PointerOutOfBounds));
|
||||
}
|
||||
|
||||
/// A refused value reaches guest memory in no part, however much of it the host
|
||||
/// wrote. The host answers with 32 bytes it did write and a length it did not, so
|
||||
/// the refusal happens with the value sitting in the run's output buffer — and the
|
||||
/// guest's buffer has to come back untouched.
|
||||
///
|
||||
/// Stronger than the contract asks for: a guest must not read its buffer on a
|
||||
/// negative status. It holds because the buffer is copied to the guest only after
|
||||
/// the length, the bounds, the fit and the budget have all passed, so there is no
|
||||
/// window in which a refused value is in guest memory.
|
||||
#[test]
|
||||
fn a_refused_value_leaves_nothing_in_guest_memory() {
|
||||
const MARKER: u8 = 77;
|
||||
|
||||
// The two refusals a value can meet after the host has produced it: longer
|
||||
// than the field cap, and longer than the buffer the guest offered.
|
||||
let refusals = [
|
||||
(MAX_FIELD_BYTES + 1, HASH_LEN, HostError::DataFieldTooLarge),
|
||||
(HASH_LEN, HASH_LEN - 1, HostError::BufferTooSmall),
|
||||
];
|
||||
|
||||
for (claimed, cap, expected) in refusals {
|
||||
let host =
|
||||
FakeHost::new().answering_digest(Answer::writing_but_claiming([MARKER; 32], claimed));
|
||||
let call = format!(
|
||||
"(call $sha512_half (i32.const 0) (i32.const 4) (i32.const 64) (i32.const {cap}))"
|
||||
);
|
||||
|
||||
let refused = module(&[import::SHA512_HALF, ONE_PAGE], &call);
|
||||
assert_eq!(
|
||||
status(&refused, &host),
|
||||
code(expected),
|
||||
"claiming {claimed}"
|
||||
);
|
||||
|
||||
// The same call, reporting what is at the output region afterwards.
|
||||
let inspect = module(
|
||||
&[import::SHA512_HALF, ONE_PAGE],
|
||||
&format!("(drop {call}) (i32.load8_u (i32.const 64))"),
|
||||
);
|
||||
assert_eq!(
|
||||
status(&inspect, &host),
|
||||
0,
|
||||
"claiming {claimed}: the refused value must not have been written"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An input region may overlap the output region: the host is served the input as
|
||||
/// it stands and its answer lands afterwards, so the two cannot interfere. The
|
||||
/// marker is any byte distinct from the input's first (`a`), so `finish` returning
|
||||
/// it proves the write landed.
|
||||
#[test]
|
||||
fn an_input_may_overlap_the_output() {
|
||||
const MARKER: u8 = 99;
|
||||
|
||||
let host = FakeHost::new().answering_digest(Answer::bytes([MARKER; HASH_LEN]));
|
||||
|
||||
let wat = module(
|
||||
&[
|
||||
import::SHA512_HALF,
|
||||
ONE_PAGE,
|
||||
r#"(data (i32.const 0) "abcd")"#,
|
||||
],
|
||||
&format!(
|
||||
"(drop (call $sha512_half (i32.const 0) (i32.const 4)
|
||||
(i32.const 0) (i32.const {HASH_LEN})))
|
||||
(i32.load8_u (i32.const 0))"
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
status(&wat, &host),
|
||||
i32::from(MARKER),
|
||||
"the output overwrote the input"
|
||||
);
|
||||
assert_eq!(
|
||||
*host.digested.borrow(),
|
||||
vec![b"abcd".to_vec()],
|
||||
"the host saw the input as it was"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The memory export itself
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A host call with no memory to work in ends the run instead of answering the
|
||||
/// guest: there is no buffer for a status to describe, and nothing the guest could
|
||||
/// do about the answer — which is what puts this beside out-of-gas on the fatal
|
||||
/// channel. What the guest burned getting there is still charged.
|
||||
fn assert_no_memory(wat: &str, host: &FakeHost) {
|
||||
let failure = failure(wat, host);
|
||||
assert!(
|
||||
matches!(failure.error, RunError::NoMemory),
|
||||
"expected the run to end for want of a memory export, got: {failure}"
|
||||
);
|
||||
assert!(failure.fuel_used > 0, "{failure}");
|
||||
}
|
||||
|
||||
/// Every region is relative to the guest's exported memory, so a module without
|
||||
/// one cannot make a host call at all.
|
||||
#[test]
|
||||
fn a_module_that_exports_no_memory_cannot_call_the_host() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(
|
||||
&[import::LDGR_INDEX, "(memory 1)"],
|
||||
"(call $ldgr_index (i32.const 0) (i32.const 4))",
|
||||
);
|
||||
assert_no_memory(&wat, &host);
|
||||
}
|
||||
|
||||
/// Having no memory is answered before anything about a call's arguments, so a
|
||||
/// module without one ends the run even when its arguments would have earned a
|
||||
/// guest-visible code of their own (here an input over the field cap).
|
||||
///
|
||||
/// The order is deliberate: no memory is a fact about the instance, not about this
|
||||
/// call, and a region cannot be validated against a memory that is not there. It
|
||||
/// costs the guest nothing — every call such a module makes ends the run anyway.
|
||||
#[test]
|
||||
fn no_memory_is_answered_before_a_calls_arguments_are() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(
|
||||
&[import::SHA512_HALF, "(memory 1)"],
|
||||
&format!(
|
||||
"(call $sha512_half (i32.const 0) (i32.const {OVER_CAP})
|
||||
(i32.const 0) (i32.const {HASH_LEN}))"
|
||||
),
|
||||
);
|
||||
assert_no_memory(&wat, &host);
|
||||
}
|
||||
|
||||
/// The memory's export *name* is not part of the contract: the engine takes the
|
||||
/// module's memory whatever it is called. Nothing in the wasm spec attaches meaning
|
||||
/// to `"memory"` — it is a toolchain convention, so the kind decides.
|
||||
#[test]
|
||||
fn a_memory_exported_under_any_name_is_the_guests_memory() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
for name in ["mem", "linear", "the memory"] {
|
||||
let wat = module(
|
||||
&[
|
||||
import::LDGR_INDEX,
|
||||
&format!(r#"(memory (export "{name}") 1)"#),
|
||||
],
|
||||
"(drop (call $ldgr_index (i32.const 64) (i32.const 4)))
|
||||
(i32.load (i32.const 64))",
|
||||
);
|
||||
assert_eq!(
|
||||
status(&wat, &host),
|
||||
7,
|
||||
"the host wrote into the memory exported as '{name}'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// One memory exported under several names is one memory. The engine resolves the
|
||||
/// first export of kind memory, and with at most one memory per module every such
|
||||
/// export is that memory, so the order the exports are walked in cannot change the
|
||||
/// answer.
|
||||
#[test]
|
||||
fn one_memory_exported_under_several_names_is_still_that_memory() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(
|
||||
&[
|
||||
import::LDGR_INDEX,
|
||||
r#"(memory (export "memory") (export "mem") (export "linear") 1)"#,
|
||||
],
|
||||
"(drop (call $ldgr_index (i32.const 64) (i32.const 4)))
|
||||
(i32.load (i32.const 64))",
|
||||
);
|
||||
assert_eq!(status(&wat, &host), 7);
|
||||
}
|
||||
|
||||
/// The export has to *be* a memory: a global named `memory` is not one, and it
|
||||
/// neither serves as the guest's memory nor hides the memory the module really
|
||||
/// exports. The kind decides, so the conventional name carries no weight on
|
||||
/// either side.
|
||||
#[test]
|
||||
fn an_export_named_memory_that_is_not_a_memory_is_not_the_guests_memory() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let call = "(call $ldgr_index (i32.const 0) (i32.const 4))";
|
||||
|
||||
let wrong_kind = module(
|
||||
&[
|
||||
import::LDGR_INDEX,
|
||||
"(memory 1)",
|
||||
r#"(global (export "memory") i32 (i32.const 0))"#,
|
||||
],
|
||||
call,
|
||||
);
|
||||
assert_no_memory(&wrong_kind, &host);
|
||||
|
||||
let shadowed = module(
|
||||
&[
|
||||
import::LDGR_INDEX,
|
||||
r#"(memory (export "mem") 1)"#,
|
||||
r#"(global (export "memory") i32 (i32.const 0))"#,
|
||||
],
|
||||
call,
|
||||
);
|
||||
assert_eq!(
|
||||
status(&shadowed, &host),
|
||||
4,
|
||||
"the real memory is found past the global that took its name"
|
||||
);
|
||||
}
|
||||
|
||||
/// Bounds follow the memory the module actually declared, not a fixed page.
|
||||
#[test]
|
||||
fn bounds_follow_the_declared_memory_size() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(
|
||||
&[import::LDGR_INDEX, r#"(memory (export "memory") 2)"#],
|
||||
&format!("(call $ldgr_index (i32.const {PAGE}) (i32.const 4))"),
|
||||
);
|
||||
assert_eq!(status(&wat, &host), 4, "the second page is in bounds");
|
||||
}
|
||||
@@ -1,516 +0,0 @@
|
||||
//! What screening refuses, and that it refuses nothing a run would have served.
|
||||
//!
|
||||
//! `check` reaches its verdict from the compiled module alone, so these tests take
|
||||
//! no host — except the ones that put the same module through `run` to compare the
|
||||
//! two.
|
||||
|
||||
mod support;
|
||||
|
||||
use support::{ENTRY, FakeHost, ONE_PAGE, PLENTY_OF_GAS, assemble, import, module};
|
||||
use xrpl_host_functions::HostFunctionSpec;
|
||||
use xrpl_wasm_vm::{CheckError, MAX_MEMORY_PAGES, RunError};
|
||||
|
||||
/// Assert which stage screening refused a module at, because the caller maps the
|
||||
/// stages separately. The error comes back out for the tests that also read its
|
||||
/// message.
|
||||
macro_rules! assert_stage {
|
||||
($refusal:expr, $stage:pat) => {{
|
||||
let refusal = $refusal;
|
||||
assert!(
|
||||
matches!(refusal, $stage),
|
||||
concat!("expected a ", stringify!($stage), " refusal, got: {}"),
|
||||
refusal
|
||||
);
|
||||
refusal
|
||||
}};
|
||||
}
|
||||
|
||||
/// Screens `wat`, which must assemble.
|
||||
fn check(wat: &str) -> Result<(), CheckError> {
|
||||
xrpl_wasm_vm::check(&assemble(wat), ENTRY)
|
||||
}
|
||||
|
||||
fn refusal(wat: &str) -> CheckError {
|
||||
check(wat).expect_err(&format!("expected this module to be refused:\n{wat}"))
|
||||
}
|
||||
|
||||
fn passes(wat: &str) {
|
||||
if let Err(refusal) = check(wat) {
|
||||
panic!("expected this module to pass, but: {refusal}\n{wat}");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compiling
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A contract that imports a host function, exports its memory and exports the
|
||||
/// entry point is what screening is looking for.
|
||||
#[test]
|
||||
fn a_runnable_contract_passes() {
|
||||
passes(&module(
|
||||
&[import::LDGR_INDEX, ONE_PAGE],
|
||||
"(call $ldgr_index (i32.const 0) (i32.const 4))",
|
||||
));
|
||||
}
|
||||
|
||||
/// Bytes that are not a wasm module at all.
|
||||
#[test]
|
||||
fn garbage_does_not_pass() {
|
||||
for bytes in [b"".as_slice(), b"not wasm", &[0x00, 0x61, 0x73, 0x6d]] {
|
||||
let refusal = xrpl_wasm_vm::check(bytes, ENTRY).expect_err("garbage must not pass");
|
||||
assert_stage!(refusal, CheckError::Compile(_));
|
||||
}
|
||||
}
|
||||
|
||||
/// Screening takes wasm binaries, and text is not one — the same rule the VM
|
||||
/// applies, from the same `wasmi` built without its `wat` feature. Turning that
|
||||
/// feature on would make this transaction blob valid at both ends.
|
||||
#[test]
|
||||
fn a_text_format_module_does_not_pass() {
|
||||
let text = module(&[ONE_PAGE], "(i32.const 0)");
|
||||
|
||||
let refusal =
|
||||
xrpl_wasm_vm::check(text.as_bytes(), ENTRY).expect_err("text must not pass as a module");
|
||||
assert_stage!(refusal, CheckError::Compile(_));
|
||||
|
||||
// The same module, assembled first, passes: the text is sound and only the
|
||||
// format was refused.
|
||||
passes(&text);
|
||||
}
|
||||
|
||||
/// A feature the engine disables is refused here too, because both stages compile
|
||||
/// against the one engine. `vm_limits.rs` walks every disabled feature; this pins
|
||||
/// that screening sees the same configuration.
|
||||
#[test]
|
||||
fn a_disabled_feature_does_not_pass() {
|
||||
let refusal = refusal(&module(
|
||||
&[ONE_PAGE],
|
||||
"(drop (f64.add (f64.const 1) (f64.const 2))) (i32.const 0)",
|
||||
));
|
||||
let refusal = assert_stage!(refusal, CheckError::Compile(_)).to_string();
|
||||
assert!(refusal.contains("floating-point"), "{refusal}");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Imports
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 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; 61] = [
|
||||
import::LDGR_INDEX,
|
||||
import::PARENT_LDGR_TIME,
|
||||
import::PARENT_LDGR_HASH,
|
||||
import::BASE_FEE,
|
||||
import::AMENDMENT_ENABLED,
|
||||
import::CACHE_LE,
|
||||
import::TX_FIELD,
|
||||
import::HOME_LE_FIELD,
|
||||
import::LE_FIELD,
|
||||
import::TX_INNER,
|
||||
import::HOME_LE_INNER,
|
||||
import::LE_INNER,
|
||||
import::TX_ARR_LEN,
|
||||
import::HOME_LE_ARR_LEN,
|
||||
import::LE_ARR_LEN,
|
||||
import::TX_INNER_ARR_LEN,
|
||||
import::HOME_LE_INNER_ARR_LEN,
|
||||
import::LE_INNER_ARR_LEN,
|
||||
import::CHECK_SIG,
|
||||
import::ACCOUNTROOT_ID,
|
||||
import::AMM_ID,
|
||||
import::CHECK_ID,
|
||||
import::CREDENTIAL_ID,
|
||||
import::DELEGATE_ID,
|
||||
import::DEPOSIT_PREAUTH_ID,
|
||||
import::DID_ID,
|
||||
import::ESCROW_ID,
|
||||
import::TRUSTLINE_ID,
|
||||
import::MPT_ISSUANCE_ID,
|
||||
import::MPTOKEN_ID,
|
||||
import::NFT_OFFER_ID,
|
||||
import::OFFER_ID,
|
||||
import::ORACLE_ID,
|
||||
import::PAYCHAN_ID,
|
||||
import::PERMISSIONED_DOMAIN_ID,
|
||||
import::SIGNERS_ID,
|
||||
import::TICKET_ID,
|
||||
import::VAULT_ID,
|
||||
import::SHA512_HALF,
|
||||
import::TRACE,
|
||||
import::SET_DATA,
|
||||
import::NFT_URI,
|
||||
import::NFT_ISSUER,
|
||||
import::NFT_TAXON,
|
||||
import::NFT_FLAGS,
|
||||
import::NFT_XFER_FEE,
|
||||
import::NFT_SERIAL,
|
||||
import::FLOAT_FROM_INT,
|
||||
import::FLOAT_FROM_UINT,
|
||||
import::FLOAT_FROM_STAMOUNT,
|
||||
import::FLOAT_FROM_STNUMBER,
|
||||
import::FLOAT_TO_INT,
|
||||
import::FLOAT_TO_MANT_EXP,
|
||||
import::FLOAT_FROM_MANT_EXP,
|
||||
import::FLOAT_CMP,
|
||||
import::FLOAT_ADD,
|
||||
import::FLOAT_SUB,
|
||||
import::FLOAT_MULT,
|
||||
import::FLOAT_DIV,
|
||||
import::FLOAT_ROOT,
|
||||
import::FLOAT_POW,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn every_declared_host_function_may_be_imported() {
|
||||
assert_eq!(
|
||||
ALL_IMPORTS.len(),
|
||||
HostFunctionSpec::ALL.len(),
|
||||
"the ABI gained a host function with no import declaration in this test"
|
||||
);
|
||||
|
||||
let mut parts = ALL_IMPORTS.to_vec();
|
||||
parts.push(ONE_PAGE);
|
||||
passes(&module(&parts, "(i32.const 0)"));
|
||||
}
|
||||
|
||||
/// A module may import fewer host functions than are registered, but not more.
|
||||
#[test]
|
||||
fn an_unknown_host_function_does_not_pass() {
|
||||
let refusal = refusal(&module(
|
||||
&[
|
||||
r#"(import "host_lib" "no_such_function" (func $f (param i32) (result i32)))"#,
|
||||
ONE_PAGE,
|
||||
],
|
||||
"(call $f (i32.const 0))",
|
||||
));
|
||||
let refusal = assert_stage!(refusal, CheckError::Import(_)).to_string();
|
||||
assert!(
|
||||
refusal.contains("no host function 'no_such_function'"),
|
||||
"{refusal}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Host functions live under one module name — `host_lib` — and an import naming
|
||||
/// another is refused even when the function name is real. `env` is in the list
|
||||
/// because that is what plain clang emits.
|
||||
#[test]
|
||||
fn an_import_from_another_module_does_not_pass() {
|
||||
for module_name in ["host", "env", ""] {
|
||||
let refusal = refusal(&module(
|
||||
&[
|
||||
&format!(
|
||||
r#"(import "{module_name}" "ldgr_index" (func $f (param i32 i32) (result i32)))"#
|
||||
),
|
||||
ONE_PAGE,
|
||||
],
|
||||
"(call $f (i32.const 0) (i32.const 4))",
|
||||
));
|
||||
let refusal = assert_stage!(refusal, CheckError::Import(_)).to_string();
|
||||
assert!(refusal.contains("is not from 'host_lib'"), "{refusal}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A host function's name imported as something other than a function. The engine
|
||||
/// defines it as a function and nothing else, so this does not link either.
|
||||
#[test]
|
||||
fn a_host_function_imported_as_a_global_does_not_pass() {
|
||||
let refusal = refusal(&module(
|
||||
&[
|
||||
r#"(import "host_lib" "ldgr_index" (global $g i32))"#,
|
||||
ONE_PAGE,
|
||||
],
|
||||
"(global.get $g)",
|
||||
));
|
||||
let refusal = assert_stage!(refusal, CheckError::Import(_)).to_string();
|
||||
assert!(
|
||||
refusal.contains("'host_lib::ldgr_index' is not a function"),
|
||||
"{refusal}"
|
||||
);
|
||||
}
|
||||
|
||||
/// A module faulty at two stages is refused by the earlier one — it imports what no
|
||||
/// engine serves *and* exports no entry point. The imports are what the rest of the
|
||||
/// module depends on, so that is the message worth having.
|
||||
#[test]
|
||||
fn the_earlier_stage_is_the_one_reported() {
|
||||
let refusal = refusal(
|
||||
r#"(module
|
||||
(import "host_lib" "no_such_function" (func $f (result i32)))
|
||||
(memory (export "memory") 1)
|
||||
(func (export "not_the_entry_point") (result i32) (call $f)))"#,
|
||||
);
|
||||
|
||||
assert_stage!(refusal, CheckError::Import(_));
|
||||
}
|
||||
|
||||
/// The signature is the one part of an import screening does not compare, so a
|
||||
/// module that will not link can still pass. Recorded here because it is the gap
|
||||
/// this stage leaves, not because it is wanted.
|
||||
#[test]
|
||||
fn an_import_with_the_wrong_signature_still_passes() {
|
||||
let wat = module(
|
||||
&[
|
||||
r#"(import "host_lib" "ldgr_index" (func $f (param i64 i64) (result i32)))"#,
|
||||
ONE_PAGE,
|
||||
],
|
||||
"(i32.const 0)",
|
||||
);
|
||||
passes(&wat);
|
||||
|
||||
let host = FakeHost::new();
|
||||
let failure = xrpl_wasm_vm::run(&assemble(&wat), PLENTY_OF_GAS, &host, ENTRY)
|
||||
.expect_err("a mistyped import must not link");
|
||||
assert!(
|
||||
matches!(failure.error, RunError::Instantiate(_)),
|
||||
"{failure}"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The entry point
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn a_missing_entry_point_does_not_pass() {
|
||||
let refusal = refusal(
|
||||
r#"(module (memory (export "memory") 1)
|
||||
(func (export "other") (result i32) (i32.const 0)))"#,
|
||||
);
|
||||
let refusal = assert_stage!(refusal, CheckError::EntryPoint(_)).to_string();
|
||||
assert_eq!(refusal, "no entry point 'finish'");
|
||||
}
|
||||
|
||||
/// The entry point is looked up by the name the caller asks for, as a run looks it
|
||||
/// up: screening a contract for one entry point says nothing about another.
|
||||
#[test]
|
||||
fn the_entry_point_is_the_name_the_caller_gives() {
|
||||
let wasm = assemble(
|
||||
r#"(module (memory (export "memory") 1)
|
||||
(func (export "other") (result i32) (i32.const 0)))"#,
|
||||
);
|
||||
|
||||
assert!(xrpl_wasm_vm::check(&wasm, "other").is_ok());
|
||||
assert!(xrpl_wasm_vm::check(&wasm, ENTRY).is_err());
|
||||
}
|
||||
|
||||
/// Both halves of the entry point's type are screened: a module returning the
|
||||
/// wrong thing, or taking anything at all, would fail the run's typed lookup.
|
||||
#[test]
|
||||
fn an_entry_point_of_the_wrong_type_does_not_pass() {
|
||||
for (signature, body) in [
|
||||
("(result i64)", "(i64.const 0)"),
|
||||
("(param i32) (result i32)", "(i32.const 0)"),
|
||||
("", "(nop)"),
|
||||
] {
|
||||
let refusal = refusal(&format!(
|
||||
r#"(module (memory (export "memory") 1)
|
||||
(func (export "finish") {signature} {body}))"#
|
||||
));
|
||||
let refusal = assert_stage!(refusal, CheckError::EntryPoint(_)).to_string();
|
||||
assert_eq!(
|
||||
refusal, "entry point 'finish' has the wrong signature, expected '() -> i32'",
|
||||
"{signature}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An export of the entry point's name that is not a function at all is a third
|
||||
/// case, and named as such: nothing is missing and no signature is wrong.
|
||||
#[test]
|
||||
fn an_entry_point_that_is_not_a_function_does_not_pass() {
|
||||
let refusal = refusal(
|
||||
r#"(module (memory (export "memory") 1) (global (export "finish") i32 (i32.const 0)))"#,
|
||||
);
|
||||
let refusal = assert_stage!(refusal, CheckError::EntryPoint(_)).to_string();
|
||||
assert_eq!(refusal, "export 'finish' is not a function");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Agreement with a run
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A module with no linear memory to export passes. A contract that makes no host
|
||||
/// call needs none, and one that does is refused at the call and charged — a
|
||||
/// runtime fault, not a malformed module.
|
||||
#[test]
|
||||
fn a_module_exporting_no_memory_passes() {
|
||||
let wat = r#"(module (func (export "finish") (result i32) (i32.const 0)))"#;
|
||||
passes(wat);
|
||||
|
||||
let host = FakeHost::new();
|
||||
assert_eq!(
|
||||
xrpl_wasm_vm::run(&assemble(wat), PLENTY_OF_GAS, &host, ENTRY)
|
||||
.expect("a module that calls no host function needs no memory")
|
||||
.result,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
/// Modules spanning what screening decides, each also put through a run.
|
||||
fn modules() -> Vec<(&'static str, String)> {
|
||||
vec![
|
||||
(
|
||||
"a runnable contract",
|
||||
module(&[import::LDGR_INDEX, ONE_PAGE], "(i32.const 0)"),
|
||||
),
|
||||
(
|
||||
"a contract that traps",
|
||||
module(&[ONE_PAGE], "(unreachable)"),
|
||||
),
|
||||
(
|
||||
"a disabled feature",
|
||||
module(&[ONE_PAGE], "(i32.extend8_s (i32.const 1))"),
|
||||
),
|
||||
(
|
||||
"an unknown host function",
|
||||
module(
|
||||
&[
|
||||
r#"(import "host_lib" "nope" (func $f (result i32)))"#,
|
||||
ONE_PAGE,
|
||||
],
|
||||
"(call $f)",
|
||||
),
|
||||
),
|
||||
(
|
||||
"an import from another module",
|
||||
module(
|
||||
&[
|
||||
r#"(import "env" "ldgr_index" (func $f (param i32 i32) (result i32)))"#,
|
||||
ONE_PAGE,
|
||||
],
|
||||
"(i32.const 0)",
|
||||
),
|
||||
),
|
||||
(
|
||||
"a host function imported as a global",
|
||||
module(
|
||||
&[r#"(import "host_lib" "trace" (global $g i32))"#, ONE_PAGE],
|
||||
"(global.get $g)",
|
||||
),
|
||||
),
|
||||
(
|
||||
"no entry point",
|
||||
r#"(module (memory (export "memory") 1)
|
||||
(func (export "other") (result i32) (i32.const 0)))"#
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
"an entry point of the wrong type",
|
||||
r#"(module (memory (export "memory") 1)
|
||||
(func (export "finish") (result i64) (i64.const 0)))"#
|
||||
.to_string(),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/// Screening refuses a module exactly when a run would refuse it at one of the
|
||||
/// three stages screening covers — nothing it rejects would have run, and nothing
|
||||
/// it passes stops before the entry point is called. The exceptions are the ones
|
||||
/// [`what_static_screening_cannot_see`] lists.
|
||||
#[test]
|
||||
fn screening_and_a_run_agree() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
for (label, wat) in modules() {
|
||||
let wasm = assemble(&wat);
|
||||
let refused_early = match xrpl_wasm_vm::run(&wasm, PLENTY_OF_GAS, &host, ENTRY) {
|
||||
Err(failure) => matches!(
|
||||
failure.error,
|
||||
RunError::Compile(_) | RunError::Instantiate(_) | RunError::EntryPoint(_)
|
||||
),
|
||||
Ok(_) => false,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
xrpl_wasm_vm::check(&wasm, ENTRY).is_err(),
|
||||
refused_early,
|
||||
"{label}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A module asking for more memory than the engine grants is refused, so the
|
||||
/// contract that could never run does not reach the ledger. The cap itself passes.
|
||||
#[test]
|
||||
fn an_exported_memory_past_the_cap_does_not_pass() {
|
||||
let wat = module(
|
||||
&[&format!(
|
||||
r#"(memory (export "memory") {})"#,
|
||||
MAX_MEMORY_PAGES + 1
|
||||
)],
|
||||
"(i32.const 0)",
|
||||
);
|
||||
let refusal = assert_stage!(refusal(&wat), CheckError::Memory(_)).to_string();
|
||||
assert!(refusal.contains("past the 128-page cap"), "{refusal}");
|
||||
|
||||
passes(&module(
|
||||
&[&format!(r#"(memory (export "memory") {MAX_MEMORY_PAGES})"#)],
|
||||
"(i32.const 0)",
|
||||
));
|
||||
}
|
||||
|
||||
/// A declared *maximum* past the cap is legal and simply unreachable, so screening
|
||||
/// must not turn it away: `vm_limits` runs this very module to completion.
|
||||
#[test]
|
||||
fn a_declared_maximum_past_the_cap_still_passes() {
|
||||
passes(&module(
|
||||
&[&format!(
|
||||
r#"(memory (export "memory") 1 {})"#,
|
||||
MAX_MEMORY_PAGES + 1
|
||||
)],
|
||||
"(i32.const 0)",
|
||||
));
|
||||
}
|
||||
|
||||
/// The gap, listed rather than described, and now one entry long. A memory a module
|
||||
/// keeps to itself is not in its exports, so this is the one module that passes
|
||||
/// screening and then fails to *instantiate* — which is why a run's refusal at that
|
||||
/// stage cannot be read as the node's fault.
|
||||
///
|
||||
/// A contract needs an exported memory to make any host call, so a module of this
|
||||
/// shape can do nothing but compute; the SDK does not produce one.
|
||||
#[test]
|
||||
fn what_static_screening_cannot_see() {
|
||||
let host = FakeHost::new();
|
||||
let wat = format!(
|
||||
r#"(module (memory {})
|
||||
(func (export "finish") (result i32) (i32.const 0)))"#,
|
||||
MAX_MEMORY_PAGES + 1
|
||||
);
|
||||
|
||||
passes(&wat);
|
||||
|
||||
let failure = xrpl_wasm_vm::run(&assemble(&wat), PLENTY_OF_GAS, &host, ENTRY)
|
||||
.expect_err("the store's limiter must refuse the memory");
|
||||
assert!(
|
||||
matches!(failure.error, RunError::Instantiate(_)),
|
||||
"{failure}"
|
||||
);
|
||||
}
|
||||
|
||||
/// A start section is guest code, so screening cannot see whether it traps — but it
|
||||
/// no longer has to. A trap is the guest's fault wherever it happens, so the run
|
||||
/// charges the contract for what it burned instead of reporting a module the node
|
||||
/// should have screened.
|
||||
#[test]
|
||||
fn a_start_section_screening_cannot_see_is_charged_as_a_trap() {
|
||||
let host = FakeHost::new();
|
||||
let wat = format!(
|
||||
r#"(module {ONE_PAGE}
|
||||
(func $init (unreachable))
|
||||
(start $init)
|
||||
(func (export "finish") (result i32) (i32.const 0)))"#
|
||||
);
|
||||
|
||||
passes(&wat);
|
||||
|
||||
let failure = xrpl_wasm_vm::run(&assemble(&wat), PLENTY_OF_GAS, &host, ENTRY)
|
||||
.expect_err("a start section that traps must not complete the run");
|
||||
assert!(matches!(failure.error, RunError::Trap(_)), "{failure}");
|
||||
assert!(
|
||||
failure.fuel_used > 0,
|
||||
"charged for what it burned: {failure}"
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,580 +0,0 @@
|
||||
//! What the engine refuses outright: modules it will not compile, will not
|
||||
//! instantiate, or cannot find an entry point in — plus the linear-memory cap.
|
||||
//!
|
||||
//! These are the sandbox's outer wall. Everything here fails the run rather than
|
||||
//! returning a code to the guest, so each test reads the failure's message.
|
||||
|
||||
mod support;
|
||||
|
||||
use support::{
|
||||
FakeHost, ONE_PAGE, PLENTY_OF_GAS, failure, import, module, run, run_entry, run_with_gas,
|
||||
};
|
||||
use xrpl_wasm_vm::{MAX_MEMORY_PAGES, RunError};
|
||||
|
||||
/// Assert which stage a run failed at, because the caller maps the stages to
|
||||
/// different outcomes. A stage is one `RunError` variant, so the expectation is a
|
||||
/// pattern; the failure comes back out for the tests that also read its message.
|
||||
macro_rules! assert_stage {
|
||||
($failure:expr, $stage:pat) => {{
|
||||
let failure = $failure;
|
||||
assert!(
|
||||
matches!(failure.error, $stage),
|
||||
concat!("expected a ", stringify!($stage), " failure, got: {}"),
|
||||
failure
|
||||
);
|
||||
failure
|
||||
}};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Linear memory
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A module declaring more than the cap fails to instantiate — the limit applies
|
||||
/// to the initial memory, not only to growth.
|
||||
#[test]
|
||||
fn an_initial_memory_past_the_cap_is_refused() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(
|
||||
&[&format!(
|
||||
r#"(memory (export "memory") {})"#,
|
||||
MAX_MEMORY_PAGES + 1
|
||||
)],
|
||||
"(i32.const 0)",
|
||||
);
|
||||
assert_stage!(failure(&wat, &host), RunError::Instantiate(_));
|
||||
}
|
||||
|
||||
/// The cap itself is allowed.
|
||||
#[test]
|
||||
fn an_initial_memory_at_the_cap_is_allowed() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(
|
||||
&[&format!(r#"(memory (export "memory") {MAX_MEMORY_PAGES})"#)],
|
||||
"(i32.const 0)",
|
||||
);
|
||||
assert_eq!(run(&wat, &host).expect("should run").result, 0);
|
||||
}
|
||||
|
||||
/// Growth up to the cap succeeds; growth past it traps rather than answering -1 as
|
||||
/// `memory.grow` otherwise would, because the engine's limiter sets
|
||||
/// `trap_on_grow_failure(true)`.
|
||||
#[test]
|
||||
fn growth_stops_at_the_cap() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(
|
||||
&[ONE_PAGE],
|
||||
&format!("(memory.grow (i32.const {}))", MAX_MEMORY_PAGES - 1),
|
||||
);
|
||||
assert_eq!(
|
||||
run(&wat, &host).expect("should run").result,
|
||||
1,
|
||||
"growing to exactly the cap answers the previous size"
|
||||
);
|
||||
|
||||
let wat = module(
|
||||
&[ONE_PAGE],
|
||||
&format!("(memory.grow (i32.const {MAX_MEMORY_PAGES}))"),
|
||||
);
|
||||
assert_stage!(failure(&wat, &host), RunError::Trap(_));
|
||||
}
|
||||
|
||||
/// A module may declare a maximum above the cap: the cap is enforced on the initial
|
||||
/// memory and on growth, not on the memory type's declared bound.
|
||||
#[test]
|
||||
fn a_declared_maximum_past_the_cap_is_allowed_but_unreachable() {
|
||||
let host = FakeHost::new();
|
||||
let memory = format!(r#"(memory (export "memory") 1 {})"#, MAX_MEMORY_PAGES + 1);
|
||||
|
||||
let wat = module(&[&memory], "(i32.const 0)");
|
||||
assert_eq!(run(&wat, &host).expect("should run").result, 0);
|
||||
|
||||
let wat = module(
|
||||
&[&memory],
|
||||
&format!("(memory.grow (i32.const {MAX_MEMORY_PAGES}))"),
|
||||
);
|
||||
assert_stage!(failure(&wat, &host), RunError::Trap(_));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Engine configuration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// One row per feature `build_wasm_engine` turns off: the smallest module that uses
|
||||
/// it, and the fragment of wasmi's refusal that names the feature. A row declaring
|
||||
/// its own memory omits [`ONE_PAGE`], or it is refused for having two memories
|
||||
/// instead.
|
||||
fn disabled_features() -> Vec<(&'static str, Vec<&'static str>, &'static str, &'static str)> {
|
||||
vec![
|
||||
(
|
||||
"wasm_multi_value",
|
||||
vec![
|
||||
ONE_PAGE,
|
||||
"(func $two (result i32 i32) (i32.const 1) (i32.const 2))",
|
||||
],
|
||||
"(call $two) (drop) (drop) (i32.const 0)",
|
||||
"multi-value",
|
||||
),
|
||||
(
|
||||
"wasm_sign_extension",
|
||||
vec![ONE_PAGE],
|
||||
"(i32.extend8_s (i32.const 1))",
|
||||
"sign extension",
|
||||
),
|
||||
(
|
||||
"wasm_bulk_memory",
|
||||
vec![ONE_PAGE],
|
||||
"(memory.fill (i32.const 0) (i32.const 0) (i32.const 1)) (i32.const 0)",
|
||||
"bulk memory",
|
||||
),
|
||||
(
|
||||
"wasm_reference_types",
|
||||
vec![ONE_PAGE, "(table 1 externref)"],
|
||||
"(i32.const 0)",
|
||||
"reference types",
|
||||
),
|
||||
// The proposal covers mutable globals crossing the module boundary; an
|
||||
// internal one is core wasm and stays allowed — see the test below.
|
||||
(
|
||||
"wasm_mutable_global",
|
||||
vec![ONE_PAGE, r#"(global (export "g") (mut i32) (i32.const 0))"#],
|
||||
"(i32.const 0)",
|
||||
"mutable global",
|
||||
),
|
||||
(
|
||||
"wasm_tail_call",
|
||||
vec![ONE_PAGE, "(func $f (result i32) (i32.const 0))"],
|
||||
"(return_call $f)",
|
||||
"tail call",
|
||||
),
|
||||
// Arithmetic in a constant initialiser. wasmi names the operator rather
|
||||
// than the proposal here.
|
||||
(
|
||||
"wasm_extended_const",
|
||||
vec![
|
||||
ONE_PAGE,
|
||||
"(global $g i32 (i32.add (i32.const 1) (i32.const 2)))",
|
||||
],
|
||||
"(global.get $g)",
|
||||
"non-constant operator",
|
||||
),
|
||||
(
|
||||
"wasm_multi_memory",
|
||||
vec![ONE_PAGE, "(memory 1)"],
|
||||
"(i32.const 0)",
|
||||
"multiple memories",
|
||||
),
|
||||
(
|
||||
"wasm_memory64",
|
||||
vec![r#"(memory (export "memory") i64 1)"#],
|
||||
"(i32.const 0)",
|
||||
"memory64",
|
||||
),
|
||||
(
|
||||
"wasm_custom_page_sizes",
|
||||
vec![r#"(memory (export "memory") 1 (pagesize 1))"#],
|
||||
"(i32.const 0)",
|
||||
"custom page sizes",
|
||||
),
|
||||
(
|
||||
"wasm_wide_arithmetic",
|
||||
vec![ONE_PAGE],
|
||||
"(drop (i64.add128 (i64.const 1) (i64.const 2) (i64.const 3) (i64.const 4)))
|
||||
(i32.const 0)",
|
||||
"wide arithmetic",
|
||||
),
|
||||
// Determinism across nodes is the reason floats are off.
|
||||
(
|
||||
"floats",
|
||||
vec![ONE_PAGE],
|
||||
"(drop (f64.add (f64.const 1) (f64.const 2))) (i32.const 0)",
|
||||
"floating-point",
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/// Every feature the engine disables is refused, and refused for that reason.
|
||||
///
|
||||
/// `wasm_custom_page_sizes` and `wasm_wide_arithmetic` are off by default in wasmi
|
||||
/// 1.1 (`engine/config.rs:72,74`), so their rows guard against wasmi changing that
|
||||
/// default rather than against our own config.
|
||||
#[test]
|
||||
fn every_disabled_feature_is_refused_by_name() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
for (knob, parts, body, expected) in disabled_features() {
|
||||
let wat = module(&parts, body);
|
||||
let failure = assert_stage!(failure(&wat, &host), RunError::Compile(_)).to_string();
|
||||
|
||||
assert!(
|
||||
failure.contains(expected),
|
||||
"{knob}: expected a refusal mentioning {expected:?}, got: {failure}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The three knobs [`every_disabled_feature_is_refused_by_name`] cannot cover. The
|
||||
/// engine is a process-wide `LazyLock`, so a test observes the one configuration we
|
||||
/// build: a knob masked by another, or with no caller-visible effect, has no
|
||||
/// distinguishing module.
|
||||
#[test]
|
||||
fn the_knobs_without_a_module_of_their_own() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
// `wasm_saturating_float_to_int(false)`: every saturating conversion takes a
|
||||
// float operand, so `floats(false)` refuses it first, as the message shows.
|
||||
let wat = module(&[ONE_PAGE], "(i32.trunc_sat_f32_s (f32.const 1))");
|
||||
let refusal = failure(&wat, &host).to_string();
|
||||
assert!(refusal.contains("floating-point"), "{refusal}");
|
||||
assert!(!refusal.contains("saturating"), "{refusal}");
|
||||
|
||||
// `ignore_custom_sections(true)`: governs whether wasmi retains custom
|
||||
// sections, not accept/reject, so this pins only that one is harmless.
|
||||
let wat = module(
|
||||
&[ONE_PAGE, r#"(@custom "note" "ignored")"#],
|
||||
"(i32.const 0)",
|
||||
);
|
||||
assert_eq!(run(&wat, &host).expect("should run").result, 0);
|
||||
|
||||
// `consume_fuel(true)`: with it off, `Store::set_fuel` fails and `run` returns
|
||||
// before instantiating, so every test in the suite fails.
|
||||
let wat = module(&[ONE_PAGE], "(i32.const 0)");
|
||||
assert!(run(&wat, &host).expect("should run").fuel_used > 0);
|
||||
}
|
||||
|
||||
/// A mutable global the module keeps to itself is core wasm, so the disabled
|
||||
/// proposal does not reach it: a guest can still have mutable state.
|
||||
#[test]
|
||||
fn an_internal_mutable_global_is_still_allowed() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(
|
||||
&[ONE_PAGE, "(global $g (mut i32) (i32.const 0))"],
|
||||
"(global.set $g (i32.const 7)) (global.get $g)",
|
||||
);
|
||||
assert_eq!(run(&wat, &host).expect("should run").result, 7);
|
||||
}
|
||||
|
||||
/// Bytes that are not a wasm module at all.
|
||||
#[test]
|
||||
fn garbage_does_not_compile() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
for bytes in [b"".as_slice(), b"not wasm", &[0x00, 0x61, 0x73, 0x6d]] {
|
||||
let failure = xrpl_wasm_vm::run(bytes, PLENTY_OF_GAS, &host, support::ENTRY)
|
||||
.expect_err("garbage must not compile");
|
||||
assert_stage!(failure, RunError::Compile(_));
|
||||
}
|
||||
}
|
||||
|
||||
/// The VM takes wasm binaries, and text is not one. wasmi's `wat` feature is on by
|
||||
/// default and would have `Module::new` assemble text too, so the crate builds
|
||||
/// wasmi without it; turning it back on would make this transaction blob valid.
|
||||
#[test]
|
||||
fn the_vm_refuses_a_text_format_module() {
|
||||
let host = FakeHost::new();
|
||||
let text = module(&[ONE_PAGE], "(i32.const 0)");
|
||||
|
||||
let failure = xrpl_wasm_vm::run(text.as_bytes(), PLENTY_OF_GAS, &host, support::ENTRY)
|
||||
.expect_err("text must not compile as a module");
|
||||
assert_stage!(failure, RunError::Compile(_));
|
||||
|
||||
// The same module, assembled first, runs: the text is sound and only the
|
||||
// format was refused.
|
||||
assert_eq!(run(&text, &host).expect("should run").result, 0);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Imports
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A module may import fewer host functions than are registered, but not more:
|
||||
/// an import the linker does not define fails instantiation.
|
||||
#[test]
|
||||
fn an_unknown_import_fails_instantiation() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(
|
||||
&[
|
||||
r#"(import "host_lib" "no_such_function" (func $f (param i32) (result i32)))"#,
|
||||
ONE_PAGE,
|
||||
],
|
||||
"(call $f (i32.const 0))",
|
||||
);
|
||||
assert_stage!(failure(&wat, &host), RunError::Instantiate(_));
|
||||
}
|
||||
|
||||
/// Host functions are registered under one module name — `host_lib`, the name the
|
||||
/// guest SDK and this repo's fixtures import from — and a guest naming a different
|
||||
/// one does not link. `env` is in the list because that is what plain clang emits.
|
||||
#[test]
|
||||
fn the_import_module_name_must_match() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
for module_name in ["host", "env", ""] {
|
||||
let wat = module(
|
||||
&[
|
||||
&format!(
|
||||
r#"(import "{module_name}" "ldgr_index" (func $f (param i32 i32) (result i32)))"#
|
||||
),
|
||||
ONE_PAGE,
|
||||
],
|
||||
"(call $f (i32.const 0) (i32.const 4))",
|
||||
);
|
||||
assert_stage!(failure(&wat, &host), RunError::Instantiate(_));
|
||||
}
|
||||
}
|
||||
|
||||
/// An import spelled with the wrong signature does not link even under the right
|
||||
/// name, which is what makes the registered signatures load-bearing.
|
||||
#[test]
|
||||
fn an_import_with_the_wrong_signature_fails_instantiation() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
for signature in [
|
||||
"(param i32) (result i32)", // too few parameters
|
||||
"(param i32 i32 i32) (result i32)", // too many
|
||||
"(param i64 i64) (result i32)", // wrong parameter types
|
||||
"(param i32 i32) (result i64)", // wrong result type
|
||||
"(param i32 i32)", // no result
|
||||
] {
|
||||
let wat = module(
|
||||
&[
|
||||
&format!(r#"(import "host_lib" "ldgr_index" (func $f {signature}))"#),
|
||||
ONE_PAGE,
|
||||
],
|
||||
"(i32.const 0)",
|
||||
);
|
||||
assert_stage!(failure(&wat, &host), RunError::Instantiate(_));
|
||||
}
|
||||
}
|
||||
|
||||
/// A module that imports a host function it never calls still has to link.
|
||||
#[test]
|
||||
fn an_unused_import_is_still_linked() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(
|
||||
&[import::LDGR_INDEX, import::TRACE, ONE_PAGE],
|
||||
"(i32.const 0)",
|
||||
);
|
||||
assert_eq!(run(&wat, &host).expect("should run").result, 0);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The start section
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A start section runs guest code during instantiation, before the entry point
|
||||
/// is even looked up, and `set_fuel` and the memory limiter are both installed by
|
||||
/// then — so it is metered like any other guest code, and a run it stops is
|
||||
/// charged for what it burned.
|
||||
///
|
||||
/// Reported as a **trap**, not as a module that would not instantiate: a trap is the
|
||||
/// guest's fault wherever it happens, and the stage a run stopped at is not what the
|
||||
/// caller maps. Filing it under the stage would put a contract's own defect among the
|
||||
/// faults a caller treats as the node's, and charge nothing for the instructions the
|
||||
/// contract burned reaching it.
|
||||
#[test]
|
||||
fn a_trapping_start_section_is_a_guest_trap_and_is_charged() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = format!(
|
||||
r#"(module {ONE_PAGE}
|
||||
(func $init (unreachable))
|
||||
(start $init)
|
||||
(func (export "finish") (result i32) (i32.const 0)))"#
|
||||
);
|
||||
let failure = assert_stage!(
|
||||
run_with_gas(&wat, PLENTY_OF_GAS, &host)
|
||||
.expect_err("a start section that traps must not complete the run"),
|
||||
RunError::Trap(_)
|
||||
);
|
||||
assert!(
|
||||
failure.fuel_used > 0,
|
||||
"the start section's instructions are metered: {failure}"
|
||||
);
|
||||
}
|
||||
|
||||
/// What `RunError::Instantiate` is left to mean: a module the linker or the store
|
||||
/// would not accept, rather than one whose guest code failed. Its two shapes, so the
|
||||
/// variant is not left standing for nothing.
|
||||
#[test]
|
||||
fn instantiation_failure_is_a_module_the_engine_will_not_accept() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
// The linker defines no such import.
|
||||
let wat = module(
|
||||
&[
|
||||
r#"(import "host_lib" "no_such_function" (func $f (result i32)))"#,
|
||||
ONE_PAGE,
|
||||
],
|
||||
"(call $f)",
|
||||
);
|
||||
assert_stage!(failure(&wat, &host), RunError::Instantiate(_));
|
||||
|
||||
// The store's limiter will not grant the memory, and does not trap to say so.
|
||||
let wat = module(
|
||||
&[&format!("(memory {})", MAX_MEMORY_PAGES + 1)],
|
||||
"(i32.const 0)",
|
||||
);
|
||||
assert_stage!(failure(&wat, &host), RunError::Instantiate(_));
|
||||
}
|
||||
|
||||
/// A start section that runs out of gas is reported as out of gas, not as a module
|
||||
/// that would not instantiate. The stage a run stopped at is not what the caller
|
||||
/// maps — the reason is — and gas exhaustion is one outcome wherever the guest
|
||||
/// reaches it.
|
||||
#[test]
|
||||
fn a_start_section_that_exhausts_gas_is_out_of_gas_not_an_instantiation_failure() {
|
||||
const GAS: u64 = 10_000;
|
||||
|
||||
let host = FakeHost::new();
|
||||
let wat = format!(
|
||||
r#"(module {ONE_PAGE}
|
||||
(func $init (loop $l (br $l)))
|
||||
(start $init)
|
||||
(func (export "finish") (result i32) (i32.const 0)))"#
|
||||
);
|
||||
|
||||
let failure = assert_stage!(
|
||||
run_with_gas(&wat, GAS, &host).expect_err("an endless start section must not instantiate"),
|
||||
RunError::OutOfGas
|
||||
);
|
||||
assert_eq!(
|
||||
failure.fuel_used, GAS,
|
||||
"a runaway start section burns the whole limit"
|
||||
);
|
||||
}
|
||||
|
||||
/// A start section cannot make a host call that needs guest memory, even in a
|
||||
/// module that exports one: the memory is resolved from the *instance's* exports,
|
||||
/// and instantiation is what produces the instance, so a call made while it is
|
||||
/// still running has no memory to work in and ends the run.
|
||||
///
|
||||
/// Not a choice: `Module::instantiate` is `pub(crate)` in wasmi, so instantiation
|
||||
/// cannot be split from the start section to resolve the memory in between.
|
||||
#[test]
|
||||
fn a_start_section_cannot_make_a_host_call() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = format!(
|
||||
r#"(module {ldgr_index} {ONE_PAGE}
|
||||
(func $init (drop (call $ldgr_index (i32.const 0) (i32.const 4))))
|
||||
(start $init)
|
||||
(func (export "finish") (result i32) (i32.const 0)))"#,
|
||||
ldgr_index = import::LDGR_INDEX
|
||||
);
|
||||
|
||||
let failure = assert_stage!(
|
||||
run_with_gas(&wat, PLENTY_OF_GAS, &host)
|
||||
.expect_err("a host call from a start section must not be served"),
|
||||
RunError::NoMemory
|
||||
);
|
||||
assert!(
|
||||
failure.fuel_used > 0,
|
||||
"the start section is metered up to the refused call: {failure}"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The entry point
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn a_missing_entry_point_fails() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = r#"(module (memory (export "memory") 1) (func (export "other") (result i32) (i32.const 0)))"#;
|
||||
let failure = assert_stage!(
|
||||
run_with_gas(wat, PLENTY_OF_GAS, &host)
|
||||
.expect_err("a module without the entry point must not run"),
|
||||
RunError::EntryPoint(_)
|
||||
);
|
||||
assert!(
|
||||
failure.to_string().contains("no entry point 'finish'"),
|
||||
"{failure}"
|
||||
);
|
||||
}
|
||||
|
||||
/// The entry point is looked up by the name the caller asks for.
|
||||
#[test]
|
||||
fn the_entry_point_is_the_name_the_caller_gives() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = r#"(module (memory (export "memory") 1) (func (export "other") (result i32) (i32.const 9)))"#;
|
||||
let outcome = run_entry(wat, &host, "other").expect("should run");
|
||||
assert_eq!(outcome.result, 9);
|
||||
}
|
||||
|
||||
/// The entry point must take nothing and return an `i32`. A module that exports the
|
||||
/// name with another signature is told so, rather than being told the export is
|
||||
/// missing: wasmi answers both cases with one error, and "no entry point" would send
|
||||
/// a contract author looking for a function they already have.
|
||||
#[test]
|
||||
fn an_entry_point_of_the_wrong_type_fails() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
for signature in ["(result i64)", "(param i32) (result i32)", ""] {
|
||||
let body = if signature.contains("result i64") {
|
||||
"(i64.const 0)"
|
||||
} else if signature.is_empty() {
|
||||
"(nop)"
|
||||
} else {
|
||||
"(i32.const 0)"
|
||||
};
|
||||
let wat = format!(
|
||||
r#"(module (memory (export "memory") 1) (func (export "finish") {signature} {body}))"#
|
||||
);
|
||||
let failure = assert_stage!(
|
||||
run_with_gas(&wat, PLENTY_OF_GAS, &host)
|
||||
.expect_err("a wrongly-typed entry point must not run"),
|
||||
RunError::EntryPoint(_)
|
||||
)
|
||||
.to_string();
|
||||
assert!(
|
||||
failure.contains("entry point 'finish' has the wrong signature"),
|
||||
"{signature}: {failure}"
|
||||
);
|
||||
assert!(
|
||||
!failure.contains("no entry point"),
|
||||
"a present export must not be reported as absent — {signature}: {failure}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An export of the entry point's name that is not a function at all is a third
|
||||
/// case, and named as such: nothing is missing and no signature is wrong.
|
||||
#[test]
|
||||
fn an_entry_point_that_is_not_a_function_fails() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat =
|
||||
r#"(module (memory (export "memory") 1) (global (export "finish") i32 (i32.const 0)))"#;
|
||||
let failure = assert_stage!(
|
||||
run_with_gas(wat, PLENTY_OF_GAS, &host).expect_err("a non-function export must not run"),
|
||||
RunError::EntryPoint(_)
|
||||
)
|
||||
.to_string();
|
||||
assert!(
|
||||
failure.contains("export 'finish' is not a function"),
|
||||
"{failure}"
|
||||
);
|
||||
}
|
||||
|
||||
/// A guest that traps fails the run rather than returning a value.
|
||||
#[test]
|
||||
fn a_trapping_guest_fails_the_run() {
|
||||
let host = FakeHost::new();
|
||||
|
||||
let wat = module(&[ONE_PAGE], "(unreachable)");
|
||||
assert_stage!(failure(&wat, &host), RunError::Trap(_));
|
||||
|
||||
// An out-of-bounds guest access is a trap too, caught by the engine rather
|
||||
// than anything the host is asked about.
|
||||
let wat = module(&[ONE_PAGE], "(i32.load (i32.const 100000))");
|
||||
assert_stage!(failure(&wat, &host), RunError::Trap(_));
|
||||
}
|
||||
@@ -94,6 +94,7 @@ struct Keys
|
||||
static constexpr auto kBbtOptions = "bbt_options";
|
||||
static constexpr auto kBgThreads = "bg_threads";
|
||||
static constexpr auto kBlockSize = "block_size";
|
||||
static constexpr auto kBytecodeSizeLimit = "bytecode_size_limit";
|
||||
static constexpr auto kCacheAge = "cache_age";
|
||||
static constexpr auto kCacheMb = "cache_mb";
|
||||
static constexpr auto kCacheSize = "cache_size";
|
||||
@@ -108,6 +109,8 @@ struct Keys
|
||||
static constexpr auto kFileSizeMult = "file_size_mult";
|
||||
static constexpr auto kFilterBits = "filter_bits";
|
||||
static constexpr auto kFilterFull = "filter_full";
|
||||
static constexpr auto kGasLimit = "gas_limit";
|
||||
static constexpr auto kGasPrice = "gas_price";
|
||||
static constexpr auto kHardSet = "hard_set";
|
||||
static constexpr auto kHighThreads = "high_threads";
|
||||
static constexpr auto kHoldTime = "hold_time";
|
||||
|
||||
@@ -10,6 +10,15 @@ namespace xrpl {
|
||||
// This was the reference fee units used in the old fee calculation.
|
||||
inline constexpr std::uint32_t kFeeUnitsDeprecated = 10;
|
||||
|
||||
// Number of micro-drops in one drop.
|
||||
constexpr std::uint32_t microDropsPerDrop{1'000'000};
|
||||
|
||||
/**
|
||||
* Maximum Feature Extension fee settings.
|
||||
*/
|
||||
inline constexpr std::uint32_t kMaxGasLimit{2'000'000};
|
||||
inline constexpr std::uint32_t kMaxBytecodeSizeLimit{200'000};
|
||||
|
||||
/**
|
||||
* Reflects the fee settings for a particular ledger.
|
||||
*
|
||||
@@ -33,6 +42,21 @@ struct Fees
|
||||
*/
|
||||
XRPAmount increment{0};
|
||||
|
||||
/**
|
||||
* @brief Gas limit for Feature Extensions (instructions).
|
||||
*/
|
||||
std::uint32_t gasLimit{0};
|
||||
|
||||
/**
|
||||
* @brief Bytecode size limit for Feature Extensions (bytes).
|
||||
*/
|
||||
std::uint32_t bytecodeSizeLimit{0};
|
||||
|
||||
/**
|
||||
* @brief Price of WASM gas (micro-drops).
|
||||
*/
|
||||
std::uint32_t gasPrice{0};
|
||||
|
||||
explicit Fees() = default;
|
||||
Fees(Fees const&) = default;
|
||||
Fees&
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FIX (Cleanup3_4_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SmartEscrow, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocolV1_1, Supported::No, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -309,6 +309,11 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
||||
{sfBaseFeeDrops, SoeOptional},
|
||||
{sfReserveBaseDrops, SoeOptional},
|
||||
{sfReserveIncrementDrops, SoeOptional},
|
||||
// Smart Escrow fields
|
||||
{sfGasLimit, SoeOptional},
|
||||
{sfBytecodeSizeLimit, SoeOptional},
|
||||
{sfGasPrice, SoeOptional},
|
||||
|
||||
{sfPreviousTxnID, SoeOptional},
|
||||
{sfPreviousTxnLgrSeq, SoeOptional},
|
||||
}))
|
||||
|
||||
@@ -120,6 +120,9 @@ TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 71)
|
||||
TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 72)
|
||||
TYPED_SFIELD(sfRemainingOwnerCount, UINT32, 73)
|
||||
TYPED_SFIELD(sfSponsorFlags, UINT32, 74)
|
||||
TYPED_SFIELD(sfGasLimit, UINT32, 75)
|
||||
TYPED_SFIELD(sfBytecodeSizeLimit, UINT32, 76)
|
||||
TYPED_SFIELD(sfGasPrice, UINT32, 77)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
|
||||
@@ -1228,6 +1228,10 @@ TRANSACTION(ttFEE, 101, SetFee,
|
||||
{sfBaseFeeDrops, SoeOptional},
|
||||
{sfReserveBaseDrops, SoeOptional},
|
||||
{sfReserveIncrementDrops, SoeOptional},
|
||||
// Smart Escrow fields
|
||||
{sfGasLimit, SoeOptional},
|
||||
{sfBytecodeSizeLimit, SoeOptional},
|
||||
{sfGasPrice, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the network's negative UNL
|
||||
|
||||
@@ -252,6 +252,9 @@ JSS(expected_date); // out: any (warnings)
|
||||
JSS(expected_date_UTC); // out: any (warnings)
|
||||
JSS(expected_ledger_size); // out: TxQ
|
||||
JSS(expiration); // out: AccountOffers, AccountChannels, ValidatorList, amm_info
|
||||
JSS(gas_limit); // out: NetworkOPs
|
||||
JSS(bytecode_size_limit); // out: NetworkOPs
|
||||
JSS(gas_price); // out: NetworkOPs
|
||||
JSS(fail_hard); // in: Sign, Submit
|
||||
JSS(failed); // out: InboundLedger
|
||||
JSS(feature); // in: Feature
|
||||
|
||||
@@ -213,6 +213,78 @@ public:
|
||||
return this->sle_->isFieldPresent(sfReserveIncrementDrops);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasLimit() const
|
||||
{
|
||||
if (hasGasLimit())
|
||||
return this->sle_->at(sfGasLimit);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasLimit() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfGasLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getBytecodeSizeLimit() const
|
||||
{
|
||||
if (hasBytecodeSizeLimit())
|
||||
return this->sle_->at(sfBytecodeSizeLimit);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecodeSizeLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecodeSizeLimit() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfBytecodeSizeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasPrice (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasPrice() const
|
||||
{
|
||||
if (hasGasPrice())
|
||||
return this->sle_->at(sfGasPrice);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasPrice is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasPrice() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfGasPrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnID (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -375,6 +447,39 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setGasLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setBytecodeSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecodeSizeLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasPrice (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasPrice] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnID (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -254,6 +254,84 @@ public:
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfReserveIncrementDrops);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasLimit() const
|
||||
{
|
||||
if (hasGasLimit())
|
||||
{
|
||||
return this->tx_->at(sfGasLimit);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasLimit() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGasLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getBytecodeSizeLimit() const
|
||||
{
|
||||
if (hasBytecodeSizeLimit())
|
||||
{
|
||||
return this->tx_->at(sfBytecodeSizeLimit);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecodeSizeLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecodeSizeLimit() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfBytecodeSizeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasPrice (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasPrice() const
|
||||
{
|
||||
if (hasGasPrice())
|
||||
{
|
||||
return this->tx_->at(sfGasPrice);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasPrice is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasPrice() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGasPrice);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -386,6 +464,39 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setGasLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setBytecodeSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecodeSizeLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasPrice (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasPrice] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the SetFee wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -1,427 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <rust/cxx.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
// `xrpl::HostFunctions` is forward-declared rather than included: this header is
|
||||
// `include!()`d by the cxxbridge-generated translation unit, whose target gets only the
|
||||
// project's `include/` directory - not the Boost paths that HostFunc.h -> Slice.h ->
|
||||
// strHex.h transitively need. A reference member and declarations alone do not require a
|
||||
// complete type; HostContext.cpp, compiled into libxrpl, includes the real header.
|
||||
class HostFunctions;
|
||||
|
||||
// Defined by the cxx bridge, which emits it into `xrpl_wasm_vm_ffi_cxxbridge/lib.h` from the
|
||||
// declaration in `crates/xrpl-wasm-vm-ffi` - so the data types and their wire values are
|
||||
// written once, in Rust, rather than kept in step with a copy here.
|
||||
//
|
||||
// Forward-declared for the reason `HostFunctions` above is: that generated header includes
|
||||
// this one, so naming its definition here would be circular. A scoped enum with a fixed
|
||||
// underlying type needs no definition to appear in a signature; `HostContext.cpp` includes
|
||||
// the generated header for the `switch`.
|
||||
enum class TraceDataType : std::int32_t;
|
||||
|
||||
// The host handed to the Rust wasm engine: one method per entry in the wasm host ABI,
|
||||
// each forwarding to `xrpl::HostFunctions` - the single source of truth for ledger
|
||||
// access - and lowering its typed `std::expected` result onto the ABI's wire form.
|
||||
//
|
||||
// Every method is `noexcept`, and every body catches everything: a C++ exception
|
||||
// unwinding into the Rust frames that called it would be undefined behaviour, so a
|
||||
// failure leaves here as -1, which the engine reads as a fatal error and reports as
|
||||
// `tecINTERNAL`.
|
||||
//
|
||||
// Not an owner: it borrows `hf` for the length of one run. Declared `struct` because the
|
||||
// Rust side only ever sees an opaque pointer.
|
||||
class HostContext
|
||||
{
|
||||
// Non-const so a host function that mutates (`cacheLedgerObj`, `updateData`) can be
|
||||
// reached from the `const` methods below: constness of the reference is not
|
||||
// constness of the referent.
|
||||
HostFunctions& hostFunctions_;
|
||||
|
||||
public:
|
||||
HostContext(HostFunctions& hostFunctions);
|
||||
|
||||
// A byte-producing call is handed `out` - a slice aliasing either guest linear
|
||||
// memory or the engine's output buffer - writes the value only if the whole of it
|
||||
// fits, and returns the value's *true* length, which may exceed `out`. That is how a
|
||||
// guest learns the size to ask for, and it is why these methods never need to know
|
||||
// the guest's capacity: the engine owns the buffer-fit, field-cap and transfer-budget
|
||||
// rules and derives all three from the length returned here.
|
||||
//
|
||||
// A negative return is a `HostFunctionError` code.
|
||||
[[nodiscard]] std::int32_t
|
||||
getLedgerSqn(rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getParentLedgerTime(rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getParentLedgerHash(rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getBaseFee(rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// The amendment is either a 32-byte id or a name; a 32-byte input is tried as an
|
||||
// id first and falls back to a name lookup. Answers 1 or 0, or a negative
|
||||
// `HostFunctionError` code.
|
||||
[[nodiscard]] std::int32_t
|
||||
isAmendmentEnabled(rust::Slice<std::uint8_t const> amendment) const noexcept;
|
||||
|
||||
// The object id must be a 32-byte uint256, else `InvalidParams`. `cacheIdx` selects
|
||||
// the slot (0 = pick a free one). Answers the slot used, or a negative
|
||||
// `HostFunctionError` code.
|
||||
[[nodiscard]] std::int32_t
|
||||
cacheLedgerObj(rust::Slice<std::uint8_t const> objId, std::int32_t cacheIdx) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getTxField(std::int32_t field, rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getCurrentLedgerObjField(std::int32_t field, rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getLedgerObjField(std::int32_t cacheIdx, std::int32_t field, rust::Slice<std::uint8_t> out)
|
||||
const noexcept;
|
||||
|
||||
// The locator is a path of little-endian i32 steps, so its byte length must be a
|
||||
// non-zero multiple of 4, else `LocatorMalformed`.
|
||||
[[nodiscard]] std::int32_t
|
||||
getTxNestedField(rust::Slice<std::uint8_t const> locator, rust::Slice<std::uint8_t> out)
|
||||
const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getCurrentLedgerObjNestedField(
|
||||
rust::Slice<std::uint8_t const> locator,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getLedgerObjNestedField(
|
||||
std::int32_t cacheIdx,
|
||||
rust::Slice<std::uint8_t const> locator,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// Answers the array's element count directly, or a negative `HostFunctionError`
|
||||
// code (`NoArray` if the field is not an array).
|
||||
[[nodiscard]] std::int32_t
|
||||
getTxArrayLen(std::int32_t field) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getCurrentLedgerObjArrayLen(std::int32_t field) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getLedgerObjArrayLen(std::int32_t cacheIdx, std::int32_t field) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getTxNestedArrayLen(rust::Slice<std::uint8_t const> locator) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getCurrentLedgerObjNestedArrayLen(rust::Slice<std::uint8_t const> locator) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
getLedgerObjNestedArrayLen(std::int32_t cacheIdx, rust::Slice<std::uint8_t const> locator)
|
||||
const noexcept;
|
||||
|
||||
// Answers 1/0 for a valid/invalid signature, or a negative `HostFunctionError`.
|
||||
[[nodiscard]] std::int32_t
|
||||
checkSignature(
|
||||
rust::Slice<std::uint8_t const> message,
|
||||
rust::Slice<std::uint8_t const> signature,
|
||||
rust::Slice<std::uint8_t const> pubkey) const noexcept;
|
||||
|
||||
// The account id must be 20 bytes, else `InvalidParams`. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
accountKeylet(rust::Slice<std::uint8_t const> account, rust::Slice<std::uint8_t> out)
|
||||
const noexcept;
|
||||
|
||||
// Each asset is decoded by length (24 = MPT, 20 = XRP, 40 = issue), else
|
||||
// `InvalidParams`. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
ammKeylet(
|
||||
rust::Slice<std::uint8_t const> asset1,
|
||||
rust::Slice<std::uint8_t const> asset2,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's
|
||||
// u32 as its i32 bit pattern. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
checkKeylet(
|
||||
rust::Slice<std::uint8_t const> account,
|
||||
std::int32_t seq,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// Subject and issuer must each be 20 bytes, else `InvalidParams`. Writes the
|
||||
// 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
credentialKeylet(
|
||||
rust::Slice<std::uint8_t const> subject,
|
||||
rust::Slice<std::uint8_t const> issuer,
|
||||
rust::Slice<std::uint8_t const> credentialType,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// Both accounts must be 20 bytes, else `InvalidParams`. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
delegateKeylet(
|
||||
rust::Slice<std::uint8_t const> account,
|
||||
rust::Slice<std::uint8_t const> authorize,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// Both accounts must be 20 bytes, else `InvalidParams`. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
depositPreauthKeylet(
|
||||
rust::Slice<std::uint8_t const> account,
|
||||
rust::Slice<std::uint8_t const> authorize,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// The account id must be 20 bytes, else `InvalidParams`. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
didKeylet(rust::Slice<std::uint8_t const> account, rust::Slice<std::uint8_t> out)
|
||||
const noexcept;
|
||||
|
||||
// The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's
|
||||
// u32 as its i32 bit pattern. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
escrowKeylet(
|
||||
rust::Slice<std::uint8_t const> account,
|
||||
std::int32_t seq,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// Both accounts and the currency must each be 20 bytes, else `InvalidParams`.
|
||||
// Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
trustLineKeylet(
|
||||
rust::Slice<std::uint8_t const> account1,
|
||||
rust::Slice<std::uint8_t const> account2,
|
||||
rust::Slice<std::uint8_t const> currency,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// The issuer id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's
|
||||
// u32 as its i32 bit pattern. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
mptokenIssuanceKeylet(
|
||||
rust::Slice<std::uint8_t const> issuer,
|
||||
std::int32_t seq,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// The MPT id must be 24 bytes and the holder 20, else `InvalidParams`. Writes the
|
||||
// 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
mptokenKeylet(
|
||||
rust::Slice<std::uint8_t const> mptid,
|
||||
rust::Slice<std::uint8_t const> holder,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's
|
||||
// u32 as its i32 bit pattern. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
nftokenOfferKeylet(
|
||||
rust::Slice<std::uint8_t const> account,
|
||||
std::int32_t seq,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's
|
||||
// u32 as its i32 bit pattern. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
offerKeylet(
|
||||
rust::Slice<std::uint8_t const> account,
|
||||
std::int32_t seq,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// The account id must be 20 bytes, else `InvalidParams`. `docId` carries the
|
||||
// guest's u32 as its i32 bit pattern. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
oracleKeylet(
|
||||
rust::Slice<std::uint8_t const> account,
|
||||
std::int32_t docId,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// Both account ids must be 20 bytes, else `InvalidParams`. `seq` carries the
|
||||
// guest's u32 as its i32 bit pattern. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
paychannelKeylet(
|
||||
rust::Slice<std::uint8_t const> account,
|
||||
rust::Slice<std::uint8_t const> destination,
|
||||
std::int32_t seq,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's
|
||||
// u32 as its i32 bit pattern. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
permissionedDomainKeylet(
|
||||
rust::Slice<std::uint8_t const> account,
|
||||
std::int32_t seq,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// The account id must be 20 bytes, else `InvalidParams`. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
signerListKeylet(rust::Slice<std::uint8_t const> account, rust::Slice<std::uint8_t> out)
|
||||
const noexcept;
|
||||
|
||||
// The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's
|
||||
// u32 as its i32 bit pattern. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
ticketKeylet(
|
||||
rust::Slice<std::uint8_t const> account,
|
||||
std::int32_t seq,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// The account id must be 20 bytes, else `InvalidParams`. `seq` carries the guest's
|
||||
// u32 as its i32 bit pattern. Writes the 32-byte keylet.
|
||||
[[nodiscard]] std::int32_t
|
||||
vaultKeylet(
|
||||
rust::Slice<std::uint8_t const> account,
|
||||
std::int32_t seq,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
sha512Half(rust::Slice<std::uint8_t const> data, rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// Renders `data` as `dataType` says, and hands the text to `HostFunctions::trace`, which
|
||||
// is what puts it in this node's log.
|
||||
//
|
||||
// The one call that answers nothing: the guest's wasm function has no result, and this
|
||||
// node's own log is the only thing a trace touches, so a buffer that does not hold what
|
||||
// it claims is logged here and dropped rather than reported to a contract.
|
||||
void
|
||||
trace(rust::Str msg, rust::Slice<std::uint8_t const> data, TraceDataType dataType)
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
// Float / number arithmetic. A float is an XRPL `Number` in serialized form;
|
||||
// `mode` is a rounding mode. Each writes the result float bytes unless noted.
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
floatFromInt(std::int64_t x, std::int32_t mode, rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// The integer region must be eight bytes, else `InvalidParams`.
|
||||
[[nodiscard]] std::int32_t
|
||||
floatFromUint(
|
||||
rust::Slice<std::uint8_t const> x,
|
||||
std::int32_t mode,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// `amount` must be a serialized `STAmount`, else `InvalidParams`.
|
||||
[[nodiscard]] std::int32_t
|
||||
floatFromSTAmount(
|
||||
rust::Slice<std::uint8_t const> amount,
|
||||
std::int32_t mode,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// `number` must be a serialized `STNumber`, else `InvalidParams`.
|
||||
[[nodiscard]] std::int32_t
|
||||
floatFromSTNumber(
|
||||
rust::Slice<std::uint8_t const> number,
|
||||
std::int32_t mode,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// Rounds the float to an integer, written as its eight little-endian bytes.
|
||||
[[nodiscard]] std::int32_t
|
||||
floatToInt(rust::Slice<std::uint8_t const> x, std::int32_t mode, rust::Slice<std::uint8_t> out)
|
||||
const noexcept;
|
||||
|
||||
// Writes the mantissa (eight little-endian bytes) and the exponent (four little-
|
||||
// endian bytes) to two output regions; returns their total size.
|
||||
[[nodiscard]] std::int32_t
|
||||
floatToMantExp(
|
||||
rust::Slice<std::uint8_t const> x,
|
||||
rust::Slice<std::uint8_t> mantissaOut,
|
||||
rust::Slice<std::uint8_t> exponentOut) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
floatFromMantExp(
|
||||
std::int64_t mantissa,
|
||||
std::int32_t exponent,
|
||||
std::int32_t mode,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
// Returns a negative, zero, or positive scalar as `x` is less than, equal to, or
|
||||
// greater than `y`, or a negative `HostFunctionError` code on failure.
|
||||
[[nodiscard]] std::int32_t
|
||||
floatCompare(rust::Slice<std::uint8_t const> x, rust::Slice<std::uint8_t const> y)
|
||||
const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
floatAdd(
|
||||
rust::Slice<std::uint8_t const> x,
|
||||
rust::Slice<std::uint8_t const> y,
|
||||
std::int32_t mode,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
floatSubtract(
|
||||
rust::Slice<std::uint8_t const> x,
|
||||
rust::Slice<std::uint8_t const> y,
|
||||
std::int32_t mode,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
floatMultiply(
|
||||
rust::Slice<std::uint8_t const> x,
|
||||
rust::Slice<std::uint8_t const> y,
|
||||
std::int32_t mode,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
floatDivide(
|
||||
rust::Slice<std::uint8_t const> x,
|
||||
rust::Slice<std::uint8_t const> y,
|
||||
std::int32_t mode,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
floatRoot(
|
||||
rust::Slice<std::uint8_t const> x,
|
||||
std::int32_t n,
|
||||
std::int32_t mode,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
|
||||
[[nodiscard]] std::int32_t
|
||||
floatPower(
|
||||
rust::Slice<std::uint8_t const> x,
|
||||
std::int32_t n,
|
||||
std::int32_t mode,
|
||||
rust::Slice<std::uint8_t> out) const noexcept;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
@@ -11,6 +12,9 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
@@ -69,6 +73,7 @@ floatPowerImpl(Slice const& x, int32_t n, int32_t mode);
|
||||
class HostFunctions
|
||||
{
|
||||
protected:
|
||||
RTOptRef rt_;
|
||||
beast::Journal j_;
|
||||
|
||||
public:
|
||||
@@ -76,6 +81,26 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
setRT(WasmRuntimeWrapper& rt)
|
||||
{
|
||||
rt_ = rt;
|
||||
}
|
||||
|
||||
void
|
||||
resetRT()
|
||||
{
|
||||
rt_ = std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] WasmRuntimeWrapper&
|
||||
getRT() const
|
||||
{
|
||||
if (!rt_)
|
||||
Throw<std::logic_error>("Wasm runtime not set");
|
||||
return rt_->get();
|
||||
}
|
||||
|
||||
[[nodiscard]] beast::Journal
|
||||
getJournal() const
|
||||
{
|
||||
@@ -470,4 +495,6 @@ public:
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
|
||||
using HFRef = std::reference_wrapper<HostFunctions>;
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
244
include/xrpl/tx/wasm/HostFuncWrapper.h
Normal file
244
include/xrpl/tx/wasm/HostFuncWrapper.h
Normal file
@@ -0,0 +1,244 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
|
||||
#include <wasm.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
#define WASM_CB_PARAMS_LIST void *env, wasm_val_vec_t const *params, wasm_val_vec_t *results
|
||||
#define WASM_SECONDARY_CB_PARAMS_LIST \
|
||||
HostFunctions &hf, wasm_val_vec_t const *params, wasm_val_vec_t *results
|
||||
|
||||
wasm_trap_t* HostFuncMain_wrap(WASM_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerSqn_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t* getLedgerSqn_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getParentLedgerTime_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t* getParentLedgerTime_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getParentLedgerHash_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t* getParentLedgerHash_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getBaseFee_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t* getBaseFee_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using isAmendmentEnabled_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* isAmendmentEnabled_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using cacheLedgerObj_proto = int32_t(uint8_t const*, int32_t, int32_t);
|
||||
wasm_trap_t* cacheLedgerObj_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getTxField_proto = int32_t(int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getTxField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getCurrentLedgerObjField_proto = int32_t(int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getCurrentLedgerObjField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerObjField_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getLedgerObjField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getTxNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getTxNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getCurrentLedgerObjNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getCurrentLedgerObjNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerObjNestedField_proto = int32_t(int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getLedgerObjNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getTxArrayLen_proto = int32_t(int32_t);
|
||||
wasm_trap_t* getTxArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getCurrentLedgerObjArrayLen_proto = int32_t(int32_t);
|
||||
wasm_trap_t* getCurrentLedgerObjArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerObjArrayLen_proto = int32_t(int32_t, int32_t);
|
||||
wasm_trap_t* getLedgerObjArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getTxNestedArrayLen_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* getTxNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getCurrentLedgerObjNestedArrayLen_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* getCurrentLedgerObjNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerObjNestedArrayLen_proto = int32_t(int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* getLedgerObjNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using updateData_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* updateData_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using checkSignature_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* checkSignature_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using computeSha512HalfHash_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* computeSha512HalfHash_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using accountKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* accountKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using ammKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* ammKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using checkKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* checkKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using credentialKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t* credentialKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using delegateKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* delegateKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using depositPreauthKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* depositPreauthKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using didKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* didKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using escrowKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* escrowKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using trustLineKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t* trustLineKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using mptokenIssuanceKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* mptokenIssuanceKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using mptokenKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* mptokenKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using nftokenOfferKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* nftokenOfferKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using offerKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* offerKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using oracleKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* oracleKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using paychannelKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t* paychannelKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using permissionedDomainKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* permissionedDomainKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using signerListKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* signerListKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using ticketKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* ticketKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using vaultKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* vaultKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFT_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getNFT_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTIssuer_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getNFTIssuer_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTTaxon_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getNFTTaxon_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTFlags_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* getNFTFlags_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTTransferFee_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* getNFTTransferFee_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTSequence_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getNFTSequence_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
// trace(msg_ptr, msg_len, data_type, data_ptr, data_len); data_type is a
|
||||
// TraceDataType.
|
||||
using trace_proto = void(uint8_t const*, int32_t, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* trace_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromInt_proto = int32_t(int64_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromInt_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromUint_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromUint_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromSTAmount_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromSTAmount_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromSTNumber_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromSTNumber_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatToInt_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatToInt_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatToMantExp_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* floatToMantExp_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromMantExp_proto = int32_t(int64_t, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromMantExp_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatCompare_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* floatCompare_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatAdd_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatAdd_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatSubtract_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatSubtract_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatMultiply_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatMultiply_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatDivide_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatDivide_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatRoot_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatRoot_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatPower_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatPower_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,40 +1,189 @@
|
||||
# WASM Module for Programmable Escrows
|
||||
|
||||
WebAssembly execution for programmable escrows. When an escrow is finished, its contract
|
||||
runs to decide whether the release conditions are met. Specification:
|
||||
This module provides WebAssembly (WASM) execution capabilities for programmable
|
||||
escrows on the XRP Ledger. When an escrow is finished, the WASM code runs to
|
||||
determine whether the escrow conditions are met, enabling custom programmable
|
||||
logic for escrow release conditions.
|
||||
|
||||
For the full specification, see
|
||||
[XLS-0102: WASM VM](https://xls.xrpl.org/xls/XLS-0102-wasm-vm.html).
|
||||
|
||||
The engine itself is Rust (`crates/xrpl-wasm-vm`, over wasmi), reached through a cxx
|
||||
bridge.
|
||||
## Architecture
|
||||
|
||||
## What is in this directory
|
||||
The module follows a layered architecture:
|
||||
|
||||
- **`WasmVM.h`** — the entry points xrpld calls: `runEscrowWasm` (execute a contract,
|
||||
returning a result and its gas cost, or a `WasmTER`) and `preflightEscrowWasm` (screen a
|
||||
module with no host and no execution). Both own their TER maps.
|
||||
- **`HostFunc.h`** — the `HostFunctions` interface: one virtual per host function, each
|
||||
defaulting to `Unimplemented`, returning `std::expected<T, HostFunctionError>`.
|
||||
- **`HostFuncImpl.h`** — `WasmHostFunctionsImpl`, the implementation over an
|
||||
`ApplyContext&`. Bodies are split across `HostFuncImpl*.cpp` by category.
|
||||
- **`HostContext.h`** — the bridge's C++ half: an ABI-shaped, `noexcept` view of
|
||||
`HostFunctions` that the engine calls back into. Nothing may unwind into Rust, so every
|
||||
method routes through `guarded()`.
|
||||
- **`WasmCommon.h`** — the shared vocabulary: `HostFunctionError` (the codes a contract
|
||||
sees), `Bytes`, `FieldLocator`, `WasmTER`, `adjustWasmEndianess`, which is where the
|
||||
boundary's byte order is decided, and `guarded()`, the one catch every crossing of the
|
||||
bridge's C++ half goes through.
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ WasmEngine (WasmVM.h) │
|
||||
│ runEscrowWasm(), preflightEscrowWasm() │
|
||||
│ Host function registration │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ WasmiEngine (WasmiVM.h) │
|
||||
│ Low-level wasmi interpreter integration │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ HostFuncWrapper │ HostFuncImpl │
|
||||
│ C-style WASM bridges │ C++ implementations │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ HostFunc (Interface) │
|
||||
│ Abstract base class for host functions │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Host functions
|
||||
### Key Components
|
||||
|
||||
Grouped by what they reach: ledger information; transaction and ledger-object field access;
|
||||
keylet construction; cryptography; float arithmetic; NFT queries; tracing.
|
||||
- **`WasmVM.h` / `detail/WasmVM.cpp`** - High-level facade providing:
|
||||
- `WasmEngine` singleton that wraps the underlying WASM interpreter
|
||||
- `runEscrowWasm()` - Execute WASM code for escrow finish
|
||||
- `preflightEscrowWasm()` - Validate WASM code during preflight
|
||||
- `createWasmImport()` - Register all host functions
|
||||
|
||||
The wire names and per-call gas costs are declared in `crates/xrpl-host-functions` —
|
||||
one `host_functions!` block that generates the ABI trait and the spec table. That
|
||||
declaration is the single source of truth; `HostFunc.h` is the C++ side of it.
|
||||
- **`WasmiVM.h` / `detail/WasmiVM.cpp`** - Low-level integration with the
|
||||
[wasmi](https://github.com/wasmi-labs/wasmi) WebAssembly interpreter:
|
||||
- `WasmiEngine` - Manages WASM modules, instances, and execution
|
||||
- Memory management and gas metering
|
||||
- Function invocation and result handling
|
||||
|
||||
## Entry point
|
||||
- **`HostFunc.h`** - Abstract `HostFunctions` base class defining the interface
|
||||
for all callable host functions. Each method returns
|
||||
`std::expected<T, HostFunctionError>`.
|
||||
|
||||
A module must export `escrow_finish` (`escrowFunctionName`) taking no parameters and
|
||||
returning `int32_t`: positive means the conditions are met, zero or negative rejects the
|
||||
finish. Everything the contract needs it asks for through a host call.
|
||||
- **`HostFuncImpl.h` / `detail/HostFuncImpl*.cpp`** - Concrete
|
||||
`WasmHostFunctionsImpl` class that implements host functions with access to
|
||||
`ApplyContext` for ledger state queries. Implementation split across files:
|
||||
- `HostFuncImpl.cpp` - Core utilities (updateData, checkSignature, etc.)
|
||||
- `HostFuncImplFloat.cpp` - Float/number arithmetic operations
|
||||
- `HostFuncImplGetter.cpp` - Field access (transaction, ledger objects)
|
||||
- `HostFuncImplKeylet.cpp` - Keylet construction functions
|
||||
- `HostFuncImplLedgerHeader.cpp` - Ledger header info access
|
||||
- `HostFuncImplNFT.cpp` - NFT-related queries
|
||||
- `HostFuncImplTrace.cpp` - Debugging/tracing functions
|
||||
|
||||
- **`HostFuncWrapper.h` / `detail/HostFuncWrapper.cpp`** - C-style wrapper
|
||||
functions that bridge WASM calls to C++ `HostFunctions` methods. Each host
|
||||
function has:
|
||||
- A `_proto` type alias defining the function signature
|
||||
- A `_wrap` function that extracts parameters and calls the implementation
|
||||
|
||||
- **`ParamsHelper.h`** - Utilities for WASM parameter handling:
|
||||
- `WASM_IMPORT_FUNC` / `WASM_IMPORT_FUNC2` macros for registration
|
||||
- `wasmParams()` helper for building parameter vectors
|
||||
- Type conversion between WASM and C++ types
|
||||
|
||||
## Host Functions
|
||||
|
||||
Host functions allow WASM code to interact with the XRP Ledger. They are
|
||||
organized into categories:
|
||||
|
||||
- **Ledger Information** - Access ledger sequence, timestamps, hashes, fees
|
||||
- **Transaction & Ledger Object Access** - Read fields from the transaction
|
||||
and ledger objects (including the current escrow object)
|
||||
- **Keylet Construction** - Build keylets to look up various ledger object types
|
||||
- **Cryptography** - Signature verification and hashing
|
||||
- **Float Arithmetic** - Mathematical operations for amount calculations
|
||||
- **NFT Operations** - Query NFT properties
|
||||
- **Tracing/Debugging** - Log messages for debugging
|
||||
|
||||
For the complete list of available host functions, their WASM names, and gas
|
||||
costs, see the [XLS-0102 specification](https://xls.xrpl.org/xls/XLS-0102-wasm-vm.html)
|
||||
or `detail/WasmVM.cpp` where they are registered via `WASM_IMPORT_FUNC2` macros.
|
||||
For method signatures, see `HostFunc.h`.
|
||||
|
||||
## Gas Model
|
||||
|
||||
Each host function has an associated gas cost. The gas cost is specified when
|
||||
registering the function in `detail/WasmVM.cpp`:
|
||||
|
||||
```cpp
|
||||
WASM_IMPORT_FUNC2(i, getLedgerSqn, "get_ledger_sqn", hfs, 60);
|
||||
// ^^ gas cost
|
||||
```
|
||||
|
||||
WASM execution is metered, and if the gas limit is exceeded, execution fails.
|
||||
|
||||
## Entry Point
|
||||
|
||||
The WASM module must export a function with the name defined by
|
||||
`escrowFunctionName` (currently `"escrow_finish"`). This function:
|
||||
|
||||
- Takes no parameters (or parameters passed via host function calls)
|
||||
- Returns an `int32_t`:
|
||||
- `1` (or positive): Escrow conditions are met, allow finish
|
||||
- `0` (or negative): Escrow conditions are not met, reject finish
|
||||
|
||||
## Adding a New Host Function
|
||||
|
||||
To add a new host function, follow these steps:
|
||||
|
||||
### 1. Add to HostFunc.h (Base Class)
|
||||
|
||||
Add a virtual method declaration with a default implementation that returns an
|
||||
error:
|
||||
|
||||
```cpp
|
||||
virtual std::expected<ReturnType, HostFunctionError>
|
||||
myNewFunction(ParamType1 param1, ParamType2 param2)
|
||||
{
|
||||
return std::unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Add to HostFuncImpl.h (Declaration)
|
||||
|
||||
Add the method override declaration in `WasmHostFunctionsImpl`:
|
||||
|
||||
```cpp
|
||||
std::expected<ReturnType, HostFunctionError>
|
||||
myNewFunction(ParamType1 param1, ParamType2 param2) override;
|
||||
```
|
||||
|
||||
### 3. Implement in detail/HostFuncImpl\*.cpp
|
||||
|
||||
Add the implementation in the appropriate file:
|
||||
|
||||
```cpp
|
||||
std::expected<ReturnType, HostFunctionError>
|
||||
WasmHostFunctionsImpl::myNewFunction(ParamType1 param1, ParamType2 param2)
|
||||
{
|
||||
// Implementation using ctx (ApplyContext) for ledger access
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Add Wrapper to HostFuncWrapper.h
|
||||
|
||||
Add the prototype and wrapper declaration:
|
||||
|
||||
```cpp
|
||||
using myNewFunction_proto = int32_t(uint8_t const*, int32_t, ...);
|
||||
wasm_trap_t*
|
||||
myNewFunction_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
```
|
||||
|
||||
### 5. Implement Wrapper in detail/HostFuncWrapper.cpp
|
||||
|
||||
Implement the C-style wrapper that bridges WASM to C++:
|
||||
|
||||
```cpp
|
||||
wasm_trap_t*
|
||||
myNewFunction_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
|
||||
{
|
||||
// Extract parameters from params
|
||||
// Call hfs->myNewFunction(...)
|
||||
// Set results and return
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Register in WasmVM.cpp
|
||||
|
||||
Add the function registration in `setCommonHostFunctions()` or
|
||||
`createWasmImport()`:
|
||||
|
||||
```cpp
|
||||
WASM_IMPORT_FUNC2(i, myNewFunction, "my_new_function", hfs, 100);
|
||||
// ^^ WASM name ^^ gas cost
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> New host functions MUST be amendment-gated in `WasmVM.cpp`.
|
||||
> Wrap the registration in an amendment check to ensure the function is only
|
||||
> available after the corresponding amendment is enabled on the network.
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <source_location>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -24,6 +21,30 @@ using Bytes = std::vector<std::uint8_t>;
|
||||
using Hash = xrpl::uint256;
|
||||
using FloatPair = std::pair<int64_t, int32_t>;
|
||||
|
||||
// Error signals that cross the wasm boundary as trap messages (the C API has no
|
||||
// trap code). WasmiEngine::call maps them to TER: hfErrInternal -> tecINTERNAL,
|
||||
// hfErrOutOfGas / wasmi's OutOfFuel -> tecOUT_OF_GAS, anything else ->
|
||||
// tecFAILED_PROCESSING.
|
||||
//
|
||||
// Matched as substrings, not by equality: the C API returns the Rust Debug form
|
||||
// of the error, e.g. `Error { kind: Message("HfInternal") }` or
|
||||
// `Error { kind: TrapCode(OutOfFuel) }`.
|
||||
std::string_view inline constexpr hfErrInternal = "HfInternal";
|
||||
std::string_view inline constexpr hfErrOutOfGas = "HfOutOfGas";
|
||||
std::string_view inline constexpr wasmiTrapOutOfFuel = "OutOfFuel";
|
||||
|
||||
// Guest ABI, mirrored in the wasm stdlib: append only, never renumber. Starts at
|
||||
// 1 so a zeroed data_type is rejected rather than treated as Int64.
|
||||
enum class TraceDataType : std::int32_t {
|
||||
Int64 = 1,
|
||||
Uint64,
|
||||
Xfloat,
|
||||
Account,
|
||||
Amount,
|
||||
AsHex, // raw bytes, hex-encoded by the host before printing
|
||||
AsText, // bytes printed verbatim as text
|
||||
};
|
||||
|
||||
enum class HostFunctionError : int32_t {
|
||||
Unimplemented = -1,
|
||||
FieldNotFound = -2,
|
||||
@@ -45,15 +66,19 @@ enum class HostFunctionError : int32_t {
|
||||
IndexOutOfBounds = -18,
|
||||
FloatInputMalformed = -19,
|
||||
FloatComputationError = -20,
|
||||
};
|
||||
|
||||
// The call was not served at all, so the engine stops the run and the transaction is
|
||||
// tecINTERNAL rather than the contract being handed a code to interpret. `guarded`
|
||||
// answers it for a host body that throws.
|
||||
//
|
||||
// Outside the -1 ..= -20 range that a contract reads, and the only entry that is: it
|
||||
// needs no number in that range, and INT32_MIN cannot collide with a code appended
|
||||
// above. Negative so that a reader treating it as an ordinary failure is still right.
|
||||
InternalFatal = std::numeric_limits<int32_t>::min(),
|
||||
enum class WasmTypes { WtI32, WtI64 };
|
||||
|
||||
struct Wmem
|
||||
{
|
||||
std::uint8_t* p = nullptr;
|
||||
std::size_t s = 0;
|
||||
|
||||
Wmem() = default;
|
||||
Wmem(void* ptr, std::size_t size) : p(reinterpret_cast<std::uint8_t*>(ptr)), s(size)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -123,6 +148,71 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class WasmRuntimeWrapper
|
||||
{
|
||||
public:
|
||||
virtual ~WasmRuntimeWrapper() = default;
|
||||
|
||||
virtual Wmem
|
||||
getMem() = 0;
|
||||
|
||||
virtual std::int64_t
|
||||
getGas() = 0;
|
||||
|
||||
virtual std::int64_t
|
||||
setGas(std::int64_t gas) = 0;
|
||||
|
||||
virtual std::int64_t
|
||||
getTransferLimit() = 0;
|
||||
|
||||
virtual std::int64_t
|
||||
setTransferLimit(std::int64_t transferLimit) = 0;
|
||||
};
|
||||
using RTOptRef = std::optional<std::reference_wrapper<WasmRuntimeWrapper>>;
|
||||
|
||||
struct WasmParam
|
||||
{
|
||||
// We are not supporting float/double
|
||||
|
||||
WasmTypes type = WasmTypes::WtI32;
|
||||
union
|
||||
{
|
||||
std::int32_t i32;
|
||||
std::int64_t i64 = 0;
|
||||
} of;
|
||||
};
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v, std::int32_t p, Types&&... args)
|
||||
{
|
||||
v.push_back({.type = WasmTypes::WtI32, .of = {.i32 = p}});
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v, std::int64_t p, Types&&... args)
|
||||
{
|
||||
v.push_back({.type = WasmTypes::WtI64, .of = {.i64 = p}});
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v)
|
||||
{
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline std::vector<WasmParam>
|
||||
wasmParams(Types&&... args)
|
||||
{
|
||||
std::vector<WasmParam> v;
|
||||
v.reserve(sizeof...(args));
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
return v;
|
||||
}
|
||||
|
||||
template <typename T, size_t Size = sizeof(T)>
|
||||
constexpr T
|
||||
adjustWasmEndianessHlp(T x)
|
||||
@@ -160,28 +250,4 @@ hfErrorToInt(HostFunctionError e)
|
||||
return static_cast<int32_t>(e);
|
||||
}
|
||||
|
||||
template <class Body>
|
||||
std::invoke_result_t<Body>
|
||||
guarded(
|
||||
beast::Journal journal,
|
||||
std::invoke_result_t<Body> onThrow,
|
||||
Body&& body,
|
||||
std::source_location const location = std::source_location::current()) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return body();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(journal.error()) << "wasm: " << location.function_name() << " threw: " << e.what();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(journal.error()) << "wasm: " << location.function_name() << " threw";
|
||||
}
|
||||
|
||||
return onThrow;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
126
include/xrpl/tx/wasm/WasmImportsHelper.h
Normal file
126
include/xrpl/tx/wasm/WasmImportsHelper.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <boost/function_types/function_arity.hpp>
|
||||
#include <boost/function_types/parameter_types.hpp>
|
||||
#include <boost/function_types/result_type.hpp>
|
||||
#include <boost/mpl/vector.hpp>
|
||||
|
||||
#include <wasm.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace bft = boost::function_types;
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
using wasmSecondaryCbFuncType =
|
||||
wasm_trap_t*(HostFunctions&, wasm_val_vec_t const*, wasm_val_vec_t*);
|
||||
|
||||
struct WasmImportFunc
|
||||
{
|
||||
std::string_view name;
|
||||
std::optional<WasmTypes> result;
|
||||
std::vector<WasmTypes> params;
|
||||
|
||||
wasmSecondaryCbFuncType* wrap = nullptr;
|
||||
uint32_t gas = 0;
|
||||
};
|
||||
|
||||
using WasmUserData = std::pair<HFRef, WasmImportFunc>;
|
||||
// string - import function name
|
||||
using ImportVec = std::unordered_map<std::string_view, WasmUserData>;
|
||||
|
||||
template <int N, int C, typename Mpl>
|
||||
void
|
||||
WasmImpArgs(WasmImportFunc& e)
|
||||
{
|
||||
if constexpr (N < C)
|
||||
{
|
||||
using at = boost::mpl::at_c<Mpl, N>::type;
|
||||
if constexpr (std::is_pointer_v<at> || std::is_same_v<at, std::int32_t>)
|
||||
{
|
||||
e.params.push_back(WasmTypes::WtI32);
|
||||
}
|
||||
else if constexpr (std::is_same_v<at, std::int64_t>)
|
||||
{
|
||||
e.params.push_back(WasmTypes::WtI64);
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(std::is_pointer_v<at>, "Unsupported argument type");
|
||||
}
|
||||
|
||||
return WasmImpArgs<N + 1, C, Mpl>(e);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename>
|
||||
inline constexpr bool wasmDependentFalse = false;
|
||||
|
||||
template <typename Rt>
|
||||
void
|
||||
WasmImpRet(WasmImportFunc& e)
|
||||
{
|
||||
if constexpr (std::is_pointer_v<Rt> || std::is_same_v<Rt, std::int32_t>)
|
||||
{
|
||||
e.result = WasmTypes::WtI32;
|
||||
}
|
||||
else if constexpr (std::is_same_v<Rt, std::int64_t>)
|
||||
{
|
||||
e.result = WasmTypes::WtI64;
|
||||
}
|
||||
else if constexpr (std::is_void_v<Rt>)
|
||||
{
|
||||
e.result.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(wasmDependentFalse<Rt>, "Unsupported return type");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void
|
||||
WasmImpFuncHelper(WasmImportFunc& e)
|
||||
{
|
||||
using rt = bft::result_type<F>::type;
|
||||
using pt = bft::parameter_types<F>::type;
|
||||
// typename boost::mpl::at_c<mpl, N>::type
|
||||
|
||||
WasmImpRet<rt>(e);
|
||||
WasmImpArgs<0, bft::function_arity<F>::value, pt>(e);
|
||||
// WasmImpWrap(e, std::forward<F>(f));
|
||||
}
|
||||
|
||||
// imp_name - string literal, must have static lifetime
|
||||
template <typename F>
|
||||
void
|
||||
WasmImpFunc(
|
||||
ImportVec& v,
|
||||
std::string_view impName,
|
||||
wasmSecondaryCbFuncType* fWrap,
|
||||
HostFunctions& hf,
|
||||
uint32_t gas = 0)
|
||||
{
|
||||
WasmImportFunc e;
|
||||
e.name = impName;
|
||||
e.wrap = fWrap;
|
||||
e.gas = gas;
|
||||
WasmImpFuncHelper<F>(e);
|
||||
v.emplace(impName, std::make_pair(HFRef(hf), std::move(e)));
|
||||
}
|
||||
|
||||
#define WASM_IMPORT_FUNC(v, f, ...) WasmImpFunc<f##_proto>(v, #f, &f##_wrap, ##__VA_ARGS__)
|
||||
|
||||
// n - string literal name, must have static lifetime
|
||||
#define WASM_IMPORT_FUNC2(v, f, n, ...) WasmImpFunc<f##_proto>(v, n, &f##_wrap, ##__VA_ARGS__)
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -4,47 +4,94 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
#include <xrpl/tx/wasm/WasmImportsHelper.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// The export a programmable escrow's contract is run through.
|
||||
std::string_view inline constexpr wEnv = "env";
|
||||
std::string_view inline constexpr wHostLib = "host_lib";
|
||||
std::string_view inline constexpr wMem = "memory";
|
||||
std::string_view inline constexpr wStore = "store";
|
||||
std::string_view inline constexpr wLoad = "load";
|
||||
std::string_view inline constexpr wSize = "size";
|
||||
std::string_view inline constexpr wAlloc = "allocate";
|
||||
std::string_view inline constexpr wDealloc = "deallocate";
|
||||
std::string_view inline constexpr wProcExit = "proc_exit";
|
||||
|
||||
std::string_view inline constexpr escrowFunctionName = "escrow_finish";
|
||||
|
||||
// Run `wasmCode`'s `funcName` export with `gasLimit` gas, servicing its host calls
|
||||
// through `hfs`.
|
||||
//
|
||||
// On success the result is what the contract returned - positive means the escrow may
|
||||
// finish - together with the gas it consumed. On failure it is the TER to apply and,
|
||||
// when the number means anything, the gas to write to transaction metadata: a contract
|
||||
// that traps or exhausts its budget is charged for what it burned, while a `tecINTERNAL`
|
||||
// reports no cost because the fault is the node's rather than the transaction's.
|
||||
uint32_t inline constexpr maxPages = 128; // 8MB = 64KB*128
|
||||
|
||||
class WasmiEngine;
|
||||
|
||||
class WasmEngine
|
||||
{
|
||||
std::unique_ptr<WasmiEngine> const impl_;
|
||||
|
||||
WasmEngine();
|
||||
|
||||
public:
|
||||
WasmEngine(WasmEngine const&) = delete;
|
||||
WasmEngine(WasmEngine&&) = delete;
|
||||
WasmEngine&
|
||||
operator=(WasmEngine const&) = delete;
|
||||
WasmEngine&
|
||||
operator=(WasmEngine&&) = delete;
|
||||
|
||||
static WasmEngine&
|
||||
instance();
|
||||
|
||||
std::expected<WasmResult<int32_t>, WasmTER>
|
||||
run(Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gasLimit,
|
||||
std::string_view funcName = {},
|
||||
std::vector<WasmParam> const& params = {},
|
||||
ImportVec const& imports = {},
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
NotTEC
|
||||
check(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params = {},
|
||||
ImportVec const& imports = {},
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
// Host functions helper functionality
|
||||
void*
|
||||
newTrap(std::string const& txt = std::string());
|
||||
|
||||
[[nodiscard]] beast::Journal
|
||||
getJournal() const;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ImportVec
|
||||
createWasmImport(HostFunctions& hfs);
|
||||
|
||||
std::expected<EscrowResult, WasmTER>
|
||||
runEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::int64_t gasLimit,
|
||||
std::string_view funcName = escrowFunctionName) noexcept;
|
||||
int64_t gasLimit,
|
||||
std::string_view funcName = escrowFunctionName,
|
||||
std::vector<WasmParam> const& params = {});
|
||||
|
||||
// Screen `wasmCode`: whether `runEscrowWasm` would refuse it before the contract's
|
||||
// first instruction. Compiles the module and reads its imports and exports; runs
|
||||
// nothing.
|
||||
//
|
||||
// Takes no `HostFunctions`, because the verdict comes from the compiled module alone.
|
||||
// That is what makes this callable from a transactor's `preflight`, which has no view
|
||||
// to build a host over.
|
||||
//
|
||||
// `temBAD_WASM` for every fault in the module - the transaction carries something this
|
||||
// engine cannot run, so it is refused before it can reach the ledger.
|
||||
// `telFAILED_PROCESSING` if the engine itself failed: nothing was learned about the
|
||||
// module, and a defect here is not evidence that the transaction is malformed.
|
||||
NotTEC
|
||||
preflightEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
beast::Journal j,
|
||||
std::string_view funcName = escrowFunctionName) noexcept;
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName = escrowFunctionName,
|
||||
std::vector<WasmParam> const& params = {});
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
462
include/xrpl/tx/wasm/WasmiVM.h
Normal file
462
include/xrpl/tx/wasm/WasmiVM.h
Normal file
@@ -0,0 +1,462 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
#include <xrpl/tx/wasm/WasmImportsHelper.h>
|
||||
#include <xrpl/tx/wasm/WasmVM.h>
|
||||
|
||||
#include <wasm.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <class T, void (*Create)(T*, size_t), void (*Destroy)(T*)>
|
||||
class WasmVec
|
||||
{
|
||||
using TD = std::remove_pointer_t<decltype(T::data)>;
|
||||
T vec_;
|
||||
|
||||
public:
|
||||
WasmVec(size_t s = 0) : vec_ WASM_EMPTY_VEC
|
||||
{
|
||||
if (s > 0)
|
||||
Create(&vec_, s); // zeroes memory
|
||||
}
|
||||
|
||||
~WasmVec()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
WasmVec(WasmVec const&) = delete;
|
||||
WasmVec&
|
||||
operator=(WasmVec const&) = delete;
|
||||
|
||||
WasmVec(WasmVec&& other) noexcept : vec_ WASM_EMPTY_VEC
|
||||
{
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
WasmVec&
|
||||
operator=(WasmVec&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
clear();
|
||||
vec_ = other.vec_;
|
||||
other.vec_ = WASM_EMPTY_VEC;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
clear()
|
||||
{
|
||||
Destroy(&vec_); // call destructor for every elements too
|
||||
vec_ = WASM_EMPTY_VEC;
|
||||
}
|
||||
|
||||
T
|
||||
release()
|
||||
{
|
||||
T result = vec_;
|
||||
vec_ = WASM_EMPTY_VEC;
|
||||
return result;
|
||||
}
|
||||
|
||||
T*
|
||||
get()
|
||||
{
|
||||
return &vec_;
|
||||
}
|
||||
|
||||
[[nodiscard]] T const*
|
||||
get() const
|
||||
{
|
||||
return &vec_;
|
||||
}
|
||||
|
||||
TD&
|
||||
operator[](size_t i)
|
||||
{
|
||||
if (i >= vec_.size)
|
||||
Throw<std::runtime_error>("Out of bound");
|
||||
return vec_.data[i];
|
||||
}
|
||||
|
||||
TD const&
|
||||
operator[](size_t i) const
|
||||
{
|
||||
if (i >= vec_.size)
|
||||
Throw<std::runtime_error>("Out of bound");
|
||||
return vec_.data[i];
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t
|
||||
size() const
|
||||
{
|
||||
return vec_.size;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
empty() const
|
||||
{
|
||||
return vec_.size == 0u;
|
||||
}
|
||||
};
|
||||
|
||||
using WasmValtypeVec =
|
||||
WasmVec<wasm_valtype_vec_t, &wasm_valtype_vec_new_uninitialized, &wasm_valtype_vec_delete>;
|
||||
using WasmValVec = WasmVec<wasm_val_vec_t, &wasm_val_vec_new_uninitialized, &wasm_val_vec_delete>;
|
||||
using WasmExternVec =
|
||||
WasmVec<wasm_extern_vec_t, &wasm_extern_vec_new_uninitialized, &wasm_extern_vec_delete>;
|
||||
using WasmExporttypeVec = WasmVec<
|
||||
wasm_exporttype_vec_t,
|
||||
&wasm_exporttype_vec_new_uninitialized,
|
||||
&wasm_exporttype_vec_delete>;
|
||||
using WasmImporttypeVec = WasmVec<
|
||||
wasm_importtype_vec_t,
|
||||
&wasm_importtype_vec_new_uninitialized,
|
||||
&wasm_importtype_vec_delete>;
|
||||
|
||||
struct WasmiResult
|
||||
{
|
||||
WasmValVec r;
|
||||
// Set iff the call trapped. Holds the TER the trap was classified into
|
||||
// (tecINTERNAL / tecOUT_OF_GAS / tecFAILED_PROCESSING); see
|
||||
// WasmiEngine::call. std::nullopt means the call returned normally.
|
||||
std::optional<TER> ter;
|
||||
|
||||
WasmiResult(unsigned n = 0) : r(n)
|
||||
{
|
||||
}
|
||||
|
||||
WasmiResult() = delete;
|
||||
~WasmiResult() = default;
|
||||
WasmiResult(WasmiResult&& o) = default;
|
||||
WasmiResult&
|
||||
operator=(WasmiResult&& o) = default;
|
||||
};
|
||||
|
||||
using ModulePtr = std::unique_ptr<wasm_module_t, decltype(&wasm_module_delete)>;
|
||||
using InstancePtr = std::unique_ptr<wasm_instance_t, decltype(&wasm_instance_delete)>;
|
||||
using EnginePtr = std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)>;
|
||||
using StorePtr = std::unique_ptr<wasm_store_t, decltype(&wasm_store_delete)>;
|
||||
|
||||
using FuncInfo = std::pair<wasm_func_t const*, wasm_functype_t const*>;
|
||||
|
||||
class InstanceWrapper
|
||||
{
|
||||
wasm_store_t* store_ = nullptr;
|
||||
WasmExternVec exports_;
|
||||
mutable int memIdx_ = -1;
|
||||
InstancePtr instance_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
std::int64_t transferLimit_ = kWasmTransferLimit;
|
||||
|
||||
private:
|
||||
static InstancePtr
|
||||
init(
|
||||
StorePtr& s,
|
||||
ModulePtr& m,
|
||||
WasmExternVec& expt,
|
||||
WasmExternVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
public:
|
||||
InstanceWrapper() : instance_(nullptr, &wasm_instance_delete) {};
|
||||
|
||||
InstanceWrapper(InstanceWrapper const&) = delete;
|
||||
|
||||
InstanceWrapper(InstanceWrapper&& o) : instance_(nullptr, &wasm_instance_delete)
|
||||
{
|
||||
*this = std::move(o); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
InstanceWrapper(StorePtr& s, ModulePtr& m, WasmExternVec const& imports, beast::Journal j)
|
||||
: store_(s.get()), instance_(init(s, m, exports_, imports, j)), j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
InstanceWrapper&
|
||||
operator=(InstanceWrapper&& o);
|
||||
|
||||
InstanceWrapper&
|
||||
operator=(InstanceWrapper const&) = delete;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return static_cast<bool>(instance_);
|
||||
}
|
||||
|
||||
FuncInfo
|
||||
getFunc(std::string_view funcName, WasmExporttypeVec const& exportTypes) const;
|
||||
|
||||
Wmem
|
||||
getMem() const;
|
||||
|
||||
std::int64_t
|
||||
getGas() const;
|
||||
|
||||
std::int64_t
|
||||
setGas(std::int64_t) const;
|
||||
|
||||
std::int64_t
|
||||
getTransferLimit() const;
|
||||
|
||||
std::int64_t
|
||||
setTransferLimit(std::int64_t);
|
||||
};
|
||||
|
||||
class ModuleWrapper
|
||||
{
|
||||
ModulePtr module_;
|
||||
InstanceWrapper instanceWrap_;
|
||||
WasmExporttypeVec exportTypes_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
public:
|
||||
// LCOV_EXCL_START
|
||||
ModuleWrapper() : module_(nullptr, &wasm_module_delete)
|
||||
{
|
||||
}
|
||||
|
||||
ModuleWrapper(ModuleWrapper&& o) : module_(nullptr, &wasm_module_delete)
|
||||
{
|
||||
*this = std::move(o);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
ModuleWrapper&
|
||||
operator=(ModuleWrapper&& o);
|
||||
ModuleWrapper(
|
||||
StorePtr& s,
|
||||
Bytes const& wasmBin,
|
||||
bool instantiate,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
~ModuleWrapper() = default;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return instanceWrap_;
|
||||
}
|
||||
|
||||
FuncInfo
|
||||
getFunc(std::string_view funcName) const
|
||||
{
|
||||
return instanceWrap_.getFunc(funcName, exportTypes_);
|
||||
}
|
||||
|
||||
wasm_functype_t const*
|
||||
getFuncType(std::string_view funcName) const;
|
||||
|
||||
Wmem
|
||||
getMem() const
|
||||
{
|
||||
return instanceWrap_.getMem();
|
||||
}
|
||||
|
||||
InstanceWrapper&
|
||||
getInstance(int i = 0)
|
||||
{
|
||||
return instanceWrap_;
|
||||
}
|
||||
|
||||
InstanceWrapper const&
|
||||
getInstance(int i = 0) const
|
||||
{
|
||||
return instanceWrap_;
|
||||
}
|
||||
|
||||
int
|
||||
addInstance(StorePtr& s, WasmExternVec const& imports)
|
||||
{
|
||||
instanceWrap_ = {s, module_, imports, j_};
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
getGas() const
|
||||
{
|
||||
return instanceWrap_ ? instanceWrap_.getGas() : -1;
|
||||
}
|
||||
|
||||
private:
|
||||
static ModulePtr
|
||||
init(StorePtr& s, Bytes const& wasmBin, beast::Journal j);
|
||||
|
||||
WasmExternVec
|
||||
buildImports(StorePtr& s, ImportVec const& imports) const;
|
||||
};
|
||||
|
||||
class WasmiEngine
|
||||
{
|
||||
EnginePtr engine_;
|
||||
StorePtr store_;
|
||||
std::unique_ptr<ModuleWrapper> moduleWrap_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
std::mutex m_; // 1 instance mutex
|
||||
|
||||
public:
|
||||
WasmiEngine() : engine_(init()), store_(nullptr, &wasm_store_delete)
|
||||
{
|
||||
}
|
||||
|
||||
~WasmiEngine() = default;
|
||||
|
||||
static EnginePtr
|
||||
init();
|
||||
|
||||
std::expected<WasmResult<int32_t>, WasmTER>
|
||||
run(Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gas,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
NotTEC
|
||||
check(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] std::int64_t
|
||||
getGas() const
|
||||
{
|
||||
return moduleWrap_ ? moduleWrap_->getGas() : -1; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Host functions helper functionality
|
||||
wasm_trap_t*
|
||||
newTrap(std::string const& msg);
|
||||
|
||||
// LCOV_EXCL_START
|
||||
[[nodiscard]] beast::Journal
|
||||
getJournal() const
|
||||
{
|
||||
return j_;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
private:
|
||||
[[nodiscard]] InstanceWrapper&
|
||||
getRT(int m = 0, int i = 0) const
|
||||
{
|
||||
if (!moduleWrap_)
|
||||
Throw<std::runtime_error>("no module");
|
||||
return moduleWrap_->getInstance(i);
|
||||
}
|
||||
|
||||
[[nodiscard]] Wmem
|
||||
getMem() const
|
||||
{
|
||||
return moduleWrap_ ? moduleWrap_->getMem() : Wmem();
|
||||
}
|
||||
|
||||
std::expected<WasmResult<int32_t>, WasmTER>
|
||||
runHlp(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gas,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
NotTEC
|
||||
checkHlp(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
int
|
||||
addModule(Bytes const& wasmCode, bool instantiate, ImportVec const& imports, int64_t gas);
|
||||
void
|
||||
clearModules();
|
||||
|
||||
// int addInstance();
|
||||
|
||||
int32_t
|
||||
runFunc(std::string_view const funcName, int32_t p);
|
||||
|
||||
int32_t
|
||||
makeModule(Bytes const& wasmCode, WasmExternVec const& imports = {});
|
||||
|
||||
[[nodiscard]] FuncInfo
|
||||
getFunc(std::string_view funcName) const
|
||||
{
|
||||
return moduleWrap_->getFunc(funcName);
|
||||
}
|
||||
|
||||
static std::vector<wasm_val_t>
|
||||
convertParams(std::vector<WasmParam> const& params);
|
||||
|
||||
static int
|
||||
compareParamTypes(wasm_valtype_vec_t const* ftp, std::vector<wasm_val_t> const& p);
|
||||
|
||||
static void
|
||||
addParam(std::vector<wasm_val_t>& in, int32_t p);
|
||||
static void
|
||||
addParam(std::vector<wasm_val_t>& in, int64_t p);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(std::string_view func, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in, std::int32_t p, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in, std::int64_t p, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(
|
||||
FuncInfo const& f,
|
||||
std::vector<wasm_val_t>& in,
|
||||
uint8_t const* d,
|
||||
int32_t sz,
|
||||
Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in, Bytes const& p, Types&&... args);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -198,6 +198,12 @@ Ledger::Ledger(
|
||||
sle->at(sfReserveIncrement) = *f;
|
||||
sle->at(sfReferenceFeeUnits) = kFeeUnitsDeprecated;
|
||||
}
|
||||
if (std::ranges::find(amendments, featureSmartEscrow) != amendments.end())
|
||||
{
|
||||
sle->at(sfGasLimit) = fees.gasLimit;
|
||||
sle->at(sfBytecodeSizeLimit) = fees.bytecodeSizeLimit;
|
||||
sle->at(sfGasPrice) = fees.gasPrice;
|
||||
}
|
||||
rawInsert(sle);
|
||||
}
|
||||
|
||||
@@ -564,6 +570,7 @@ Ledger::setup()
|
||||
{
|
||||
bool oldFees = false;
|
||||
bool newFees = false;
|
||||
bool extensionFees = false;
|
||||
{
|
||||
auto const baseFee = sle->at(~sfBaseFee);
|
||||
auto const reserveBase = sle->at(~sfReserveBase);
|
||||
@@ -580,6 +587,7 @@ Ledger::setup()
|
||||
auto const baseFeeXRP = sle->at(~sfBaseFeeDrops);
|
||||
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
|
||||
auto const reserveIncrementXRP = sle->at(~sfReserveIncrementDrops);
|
||||
|
||||
auto assign = [&ret](XRPAmount& dest, std::optional<STAmount> const& src) {
|
||||
if (src)
|
||||
{
|
||||
@@ -598,6 +606,22 @@ Ledger::setup()
|
||||
assign(fees_.increment, reserveIncrementXRP);
|
||||
newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP;
|
||||
}
|
||||
{
|
||||
auto const gasLimit = sle->at(~sfGasLimit);
|
||||
auto const bytecodeSizeLimit = sle->at(~sfBytecodeSizeLimit);
|
||||
auto const gasPrice = sle->at(~sfGasPrice);
|
||||
|
||||
auto assign = [](std::uint32_t& dest, std::optional<std::uint32_t> const& src) {
|
||||
if (src)
|
||||
{
|
||||
dest = src.value();
|
||||
}
|
||||
};
|
||||
assign(fees_.gasLimit, gasLimit);
|
||||
assign(fees_.bytecodeSizeLimit, bytecodeSizeLimit);
|
||||
assign(fees_.gasPrice, gasPrice);
|
||||
extensionFees = gasLimit || bytecodeSizeLimit || gasPrice;
|
||||
}
|
||||
if (oldFees && newFees)
|
||||
{
|
||||
// Should be all of one or the other, but not both
|
||||
@@ -608,6 +632,12 @@ Ledger::setup()
|
||||
// Can't populate the new fees before the amendment is enabled
|
||||
ret = false;
|
||||
}
|
||||
if (!rules_.enabled(featureSmartEscrow) && extensionFees)
|
||||
{
|
||||
// Can't populate the extension fees before the amendment is
|
||||
// enabled
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SHAMapMissingNode const&)
|
||||
|
||||
@@ -59,6 +59,10 @@ STValidation::validationFormat()
|
||||
{sfBaseFeeDrops, SoeOptional},
|
||||
{sfReserveBaseDrops, SoeOptional},
|
||||
{sfReserveIncrementDrops, SoeOptional},
|
||||
// featureSmartEscrow
|
||||
{sfGasLimit, SoeOptional},
|
||||
{sfBytecodeSizeLimit, SoeOptional},
|
||||
{sfGasPrice, SoeOptional},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/AmendmentTable.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
@@ -123,6 +124,22 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
|
||||
return temDISABLED;
|
||||
}
|
||||
if (ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfGasLimit) ||
|
||||
!ctx.tx.isFieldPresent(sfBytecodeSizeLimit) ||
|
||||
!ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temMALFORMED;
|
||||
if (ctx.tx[sfGasLimit] > kMaxGasLimit ||
|
||||
ctx.tx[sfBytecodeSizeLimit] > kMaxBytecodeSizeLimit)
|
||||
return temBAD_FEE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfGasLimit) ||
|
||||
ctx.tx.isFieldPresent(sfBytecodeSizeLimit) || ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temDISABLED;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
case ttAMENDMENT:
|
||||
case ttUNL_MODIFY:
|
||||
@@ -284,6 +301,12 @@ Change::applyFee()
|
||||
set(feeObject, ctx_.tx, sfReserveBase);
|
||||
set(feeObject, ctx_.tx, sfReserveIncrement);
|
||||
}
|
||||
if (view().rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
set(feeObject, ctx_.tx, sfGasLimit);
|
||||
set(feeObject, ctx_.tx, sfBytecodeSizeLimit);
|
||||
set(feeObject, ctx_.tx, sfGasPrice);
|
||||
}
|
||||
|
||||
view().update(feeObject);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
@@ -123,10 +124,9 @@ getAnyFieldData(FieldValue const& variantObj)
|
||||
if (uint256 const* const* u = std::get_if<uint256 const*>(&variantObj))
|
||||
return Bytes((*u)->begin(), (*u)->end());
|
||||
|
||||
// Unreachable: the variant only holds the two alternatives above. If not, it is an
|
||||
// xrpld bug, and `guarded` turns the throw into `InternalFatal`, which stops the run ->
|
||||
// tecINTERNAL.
|
||||
Throw<std::runtime_error>("field value variant holds neither alternative"); // LCOV_EXCL_LINE
|
||||
// Unreachable: the variant only holds the two alternatives above. If not,
|
||||
// it's an xrpld bug -> tecINTERNAL (thrown, caught by HostFuncMain_wrap).
|
||||
Throw<std::runtime_error>(std::string(hfErrInternal)); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
static inline bool
|
||||
|
||||
1903
src/libxrpl/tx/wasm/HostFuncWrapper.cpp
Normal file
1903
src/libxrpl/tx/wasm/HostFuncWrapper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,180 +1,215 @@
|
||||
#include <xrpl/tx/wasm/WasmVM.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/tx/wasm/HostContext.h>
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
#include <xrpl/tx/wasm/HostFuncWrapper.h> // IWYU pragma: keep
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <rust/cxx.h>
|
||||
#include <xrpl_wasm_vm_ffi_cxxbridge/lib.h>
|
||||
#include <xrpl/tx/wasm/WasmImportsHelper.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#ifdef _DEBUG
|
||||
// #define DEBUG_OUTPUT 1
|
||||
#endif
|
||||
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
#include <xrpl/tx/wasm/WasmiVM.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace {
|
||||
|
||||
using RunStatus = rs::wasm_vm::RunStatus;
|
||||
using CheckStatus = rs::wasm_vm::CheckStatus;
|
||||
|
||||
// The engine's outcome as the caller's: a value with its cost, or a TER with the cost to
|
||||
// record beside it.
|
||||
//
|
||||
// A `tecINTERNAL` reports no cost. It says the fault is the node's, and charging a
|
||||
// transaction for a node's defect would write that defect into the ledger.
|
||||
//
|
||||
// Exhaustive over the status enum, with no `default`: the enum is generated from the
|
||||
// engine's `RunError`, so an outcome added there fails this switch under -Wswitch -Werror
|
||||
// rather than quietly picking up a neighbour's TER. The return past the switch is for the
|
||||
// compilers that will not call an exhaustive switch exhaustive; it sits after the switch,
|
||||
// not in a `default`, so the coverage check above still holds.
|
||||
std::expected<EscrowResult, WasmTER>
|
||||
outcome(rs::wasm_vm::RunResult const& run)
|
||||
// WARNING: Per XLS-0102, the host functions registered here form a stable
|
||||
// ABI. Their name, semantics, parameters, and return types must NEVER be
|
||||
// changed, as there may always be a program that uses it. New host functions
|
||||
// may be added and existing gas costs may be adjusted, but every such change
|
||||
// must be gated by an amendment.
|
||||
// See XLS-0102 §6.5 (Future-Proofing):
|
||||
// https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0102-wasm-vm#65-future-proofing
|
||||
static void
|
||||
setCommonHostFunctions(HostFunctions& hfs, ImportVec& i)
|
||||
{
|
||||
auto const cost = static_cast<std::int64_t>(run.gas_used);
|
||||
// clang-format off
|
||||
WASM_IMPORT_FUNC2(i, getLedgerSqn, "ldgr_index", hfs, 60);
|
||||
WASM_IMPORT_FUNC2(i, getParentLedgerTime, "parent_ldgr_time", hfs, 60);
|
||||
WASM_IMPORT_FUNC2(i, getParentLedgerHash, "parent_ldgr_hash", hfs, 60);
|
||||
WASM_IMPORT_FUNC2(i, getBaseFee, "base_fee", hfs, 60);
|
||||
WASM_IMPORT_FUNC2(i, isAmendmentEnabled, "amendment_enabled", hfs, 100);
|
||||
|
||||
switch (run.status)
|
||||
{
|
||||
case RunStatus::Ok:
|
||||
return EscrowResult{.result = run.result, .cost = cost};
|
||||
WASM_IMPORT_FUNC2(i, cacheLedgerObj, "cache_le", hfs, 5'000);
|
||||
WASM_IMPORT_FUNC2(i, getTxField, "tx_field", hfs, 70);
|
||||
WASM_IMPORT_FUNC2(i, getCurrentLedgerObjField, "home_le_field", hfs, 70);
|
||||
WASM_IMPORT_FUNC2(i, getLedgerObjField, "le_field", hfs, 70);
|
||||
WASM_IMPORT_FUNC2(i, getTxNestedField, "tx_inner", hfs, 110);
|
||||
WASM_IMPORT_FUNC2(i, getCurrentLedgerObjNestedField, "home_le_inner", hfs, 110);
|
||||
WASM_IMPORT_FUNC2(i, getLedgerObjNestedField, "le_inner", hfs, 110);
|
||||
WASM_IMPORT_FUNC2(i, getTxArrayLen, "tx_arr_len", hfs, 40);
|
||||
WASM_IMPORT_FUNC2(i, getCurrentLedgerObjArrayLen, "home_le_arr_len", hfs, 40);
|
||||
WASM_IMPORT_FUNC2(i, getLedgerObjArrayLen, "le_arr_len", hfs, 40);
|
||||
WASM_IMPORT_FUNC2(i, getTxNestedArrayLen, "tx_inner_arr_len", hfs, 70);
|
||||
WASM_IMPORT_FUNC2(i, getCurrentLedgerObjNestedArrayLen, "home_le_inner_arr_len", hfs, 70);
|
||||
WASM_IMPORT_FUNC2(i, getLedgerObjNestedArrayLen, "le_inner_arr_len", hfs, 70);
|
||||
|
||||
// The cost is the whole limit: XLS-0102 halts the guest the instant the meter runs
|
||||
// out, and the run is charged for all of it.
|
||||
case RunStatus::OutOfGas:
|
||||
return std::unexpected{WasmTER{.ter = tecOUT_OF_GAS, .cost = cost}};
|
||||
WASM_IMPORT_FUNC2(i, checkSignature, "check_sig", hfs, 300);
|
||||
WASM_IMPORT_FUNC2(i, computeSha512HalfHash, "sha512_half", hfs, 2000);
|
||||
|
||||
// The contract's own fault - it trapped, or it never exported the linear memory
|
||||
// its host calls need - so it is charged for what it burned reaching that point.
|
||||
case RunStatus::Trap:
|
||||
case RunStatus::NoMemory:
|
||||
// A module that will not instantiate is the contract's fault too. Screening
|
||||
// cannot see every way this happens - a linear memory the module keeps to itself
|
||||
// is absent from its exports - so a module can pass preflight and still be
|
||||
// refused here. It is a deterministic property of the code either way, and one
|
||||
// this node's own conduct had no part in.
|
||||
case RunStatus::Instantiate:
|
||||
return std::unexpected{WasmTER{.ter = tecFAILED_PROCESSING, .cost = cost}};
|
||||
WASM_IMPORT_FUNC2(i, accountKeylet, "accountroot_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, ammKeylet, "amm_id", hfs, 450);
|
||||
WASM_IMPORT_FUNC2(i, checkKeylet, "check_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, credentialKeylet, "credential_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, delegateKeylet, "delegate_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, depositPreauthKeylet, "deposit_preauth_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, didKeylet, "did_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, escrowKeylet, "escrow_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, trustLineKeylet, "trustline_id", hfs, 400);
|
||||
WASM_IMPORT_FUNC2(i, mptokenIssuanceKeylet, "mpt_issuance_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, mptokenKeylet, "mptoken_id", hfs, 500);
|
||||
WASM_IMPORT_FUNC2(i, nftokenOfferKeylet, "nft_offer_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, offerKeylet, "offer_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, oracleKeylet, "oracle_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, paychannelKeylet, "paychan_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, permissionedDomainKeylet, "permissioned_domain_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, signerListKeylet, "signers_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, ticketKeylet, "ticket_id", hfs, 350);
|
||||
WASM_IMPORT_FUNC2(i, vaultKeylet, "vault_id", hfs, 350);
|
||||
|
||||
// A module that will not compile, or does not expose the entry point, should have
|
||||
// been refused at preflight with `temBAD_WASM`: screening decides both from the
|
||||
// same bytes and the same engine, so agreeing here is not a matter of degree.
|
||||
// Reaching apply means the screening did not happen, which is a node-side fault
|
||||
// rather than the transaction's.
|
||||
case RunStatus::Compile:
|
||||
case RunStatus::EntryPoint:
|
||||
// The host could not serve a call, or it threw and `HostContext` caught it.
|
||||
case RunStatus::Internal:
|
||||
// The engine panicked: a defect in the engine, reported rather than fatal to the
|
||||
// node.
|
||||
case RunStatus::Panic:
|
||||
return std::unexpected{WasmTER{.ter = tecINTERNAL, .cost = std::nullopt}};
|
||||
}
|
||||
UNREACHABLE("xrpl::outcome : unknown RunStatus");
|
||||
return std::unexpected{WasmTER{.ter = tecINTERNAL, .cost = std::nullopt}};
|
||||
WASM_IMPORT_FUNC2(i, getNFT, "nft_uri", hfs, 5'000);
|
||||
WASM_IMPORT_FUNC2(i, getNFTIssuer, "nft_issuer", hfs, 70);
|
||||
WASM_IMPORT_FUNC2(i, getNFTTaxon, "nft_taxon", hfs, 60);
|
||||
WASM_IMPORT_FUNC2(i, getNFTFlags, "nft_flags", hfs, 60);
|
||||
WASM_IMPORT_FUNC2(i, getNFTTransferFee, "nft_xfer_fee", hfs, 60);
|
||||
WASM_IMPORT_FUNC2(i, getNFTSequence, "nft_serial", hfs, 60);
|
||||
|
||||
WASM_IMPORT_FUNC (i, trace, hfs, 30);
|
||||
|
||||
WASM_IMPORT_FUNC2(i, floatFromInt, "float_from_int", hfs, 100);
|
||||
WASM_IMPORT_FUNC2(i, floatFromUint, "float_from_uint", hfs, 130);
|
||||
WASM_IMPORT_FUNC2(i, floatFromSTAmount, "float_from_stamount", hfs, 150);
|
||||
WASM_IMPORT_FUNC2(i, floatFromSTNumber, "float_from_stnumber", hfs, 150);
|
||||
WASM_IMPORT_FUNC2(i, floatToInt, "float_to_int", hfs, 130);
|
||||
WASM_IMPORT_FUNC2(i, floatToMantExp, "float_to_mant_exp", hfs, 130);
|
||||
WASM_IMPORT_FUNC2(i, floatFromMantExp, "float_from_mant_exp", hfs, 100);
|
||||
WASM_IMPORT_FUNC2(i, floatCompare, "float_cmp", hfs, 80);
|
||||
WASM_IMPORT_FUNC2(i, floatAdd, "float_add", hfs, 160);
|
||||
WASM_IMPORT_FUNC2(i, floatSubtract, "float_sub", hfs, 160);
|
||||
WASM_IMPORT_FUNC2(i, floatMultiply, "float_mult", hfs, 300);
|
||||
WASM_IMPORT_FUNC2(i, floatDivide, "float_div", hfs, 300);
|
||||
WASM_IMPORT_FUNC2(i, floatRoot, "float_root", hfs, 5'500);
|
||||
WASM_IMPORT_FUNC2(i, floatPower, "float_pow", hfs, 5'500);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
// A screening verdict as a TER.
|
||||
//
|
||||
// `temBAD_WASM` says the transaction carries something this engine cannot run: a
|
||||
// malformed transaction, refused before it can reach the ledger. A panic inside the
|
||||
// engine is different in kind - nothing was learned about the module - so the answer is
|
||||
// node-local rather than a claim about the transaction.
|
||||
//
|
||||
// Exhaustive over the status enum, with no `default`, for the same reason `outcome` is.
|
||||
NotTEC
|
||||
verdict(CheckStatus status)
|
||||
ImportVec
|
||||
createWasmImport(HostFunctions& hfs)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case CheckStatus::Ok:
|
||||
return tesSUCCESS;
|
||||
ImportVec i;
|
||||
|
||||
// The module will not compile, imports what no engine of this ABI serves, does
|
||||
// not export the entry point as `() -> i32`, or asks for more linear memory than
|
||||
// it may have.
|
||||
case CheckStatus::Compile:
|
||||
case CheckStatus::Import:
|
||||
case CheckStatus::EntryPoint:
|
||||
case CheckStatus::Memory:
|
||||
return temBAD_WASM;
|
||||
setCommonHostFunctions(hfs, i);
|
||||
WASM_IMPORT_FUNC2(i, updateData, "set_data", hfs, 1000);
|
||||
|
||||
// The engine panicked: a defect in the engine, reported rather than fatal to
|
||||
// the node, and not the transaction's fault.
|
||||
case CheckStatus::Panic:
|
||||
return telFAILED_PROCESSING;
|
||||
}
|
||||
UNREACHABLE("xrpl::verdict : unknown CheckStatus");
|
||||
return telFAILED_PROCESSING;
|
||||
return i;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::expected<EscrowResult, WasmTER>
|
||||
runEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::int64_t gasLimit,
|
||||
std::string_view funcName) noexcept
|
||||
int64_t gasLimit,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params)
|
||||
{
|
||||
// A run needs a budget to spend. Refused here rather than in the engine because what a
|
||||
// non-positive limit means is a transaction-validity rule; the engine's own budget is
|
||||
// therefore an unsigned quantity with no invalid value to represent.
|
||||
if (gasLimit <= 0)
|
||||
return std::unexpected{WasmTER{.ter = temBAD_AMOUNT, .cost = std::nullopt}};
|
||||
// create VM and set cost limit
|
||||
auto& vm = WasmEngine::instance();
|
||||
// vm.initMaxPages(MAX_PAGES);
|
||||
|
||||
auto const nodeSideFault = std::unexpected{WasmTER{.ter = tecINTERNAL, .cost = std::nullopt}};
|
||||
auto const ret =
|
||||
vm.run(wasmCode, hfs, gasLimit, funcName, params, createWasmImport(hfs), hfs.getJournal());
|
||||
|
||||
return guarded(hfs.getJournal(), nodeSideFault, [&]() -> std::expected<EscrowResult, WasmTER> {
|
||||
// The host caches the current ledger object, the slot table and the
|
||||
// contract's data for the length of one run, so a reused one would answer a
|
||||
// later contract out of an earlier contract's state.
|
||||
if (!hfs.checkSelf())
|
||||
{
|
||||
JLOG(hfs.getJournal().error()) << "wasm: host functions not clean before the run";
|
||||
return nodeSideFault;
|
||||
}
|
||||
if (!ret)
|
||||
{
|
||||
#ifdef DEBUG_OUTPUT
|
||||
std::cout << ", error: " << ret.error().ter << std::endl;
|
||||
#endif
|
||||
// Carries the TER (tecOUT_OF_GAS / tecFAILED_PROCESSING / tecINTERNAL /
|
||||
// temBAD_AMOUNT) and, when meaningful, the gas consumed. The caller is
|
||||
// responsible for writing that gas to tx metadata.
|
||||
return std::unexpected(ret.error());
|
||||
}
|
||||
|
||||
HostContext const ctx{hfs};
|
||||
auto const run = rs::wasm_vm::run_escrow(
|
||||
ctx,
|
||||
rust::Slice<std::uint8_t const>{wasmCode.data(), wasmCode.size()},
|
||||
static_cast<std::uint64_t>(gasLimit),
|
||||
rust::Str{funcName.data(), funcName.size()});
|
||||
|
||||
auto const result = outcome(run);
|
||||
if (!result)
|
||||
{
|
||||
JLOG(hfs.getJournal().warn())
|
||||
<< "wasm: " << std::string_view{run.detail.data(), run.detail.size()}
|
||||
<< ", ter: " << transToken(result.error().ter);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
#ifdef DEBUG_OUTPUT
|
||||
std::cout << ", ret: " << ret->result << ", gas spent: " << ret->cost << std::endl;
|
||||
#endif
|
||||
return EscrowResult{.result = ret->result, .cost = ret->cost};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
preflightEscrowWasm(Bytes const& wasmCode, beast::Journal j, std::string_view funcName) noexcept
|
||||
preflightEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params)
|
||||
{
|
||||
return guarded(j, NotTEC{telFAILED_PROCESSING}, [&]() {
|
||||
auto const checked = rs::wasm_vm::check_escrow(
|
||||
rust::Slice<std::uint8_t const>{wasmCode.data(), wasmCode.size()},
|
||||
rust::Str{funcName.data(), funcName.size()});
|
||||
// create VM and set cost limit
|
||||
auto& vm = WasmEngine::instance();
|
||||
// vm.initMaxPages(MAX_PAGES);
|
||||
|
||||
auto const ter = verdict(checked.status);
|
||||
if (!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j.warn()) << "wasm: "
|
||||
<< std::string_view{checked.detail.data(), checked.detail.size()}
|
||||
<< ", ter: " << transToken(ter);
|
||||
}
|
||||
return ter;
|
||||
});
|
||||
auto const ret =
|
||||
vm.check(wasmCode, hfs, funcName, params, createWasmImport(hfs), hfs.getJournal());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
WasmEngine::WasmEngine() : impl_(std::make_unique<WasmiEngine>())
|
||||
{
|
||||
}
|
||||
|
||||
WasmEngine&
|
||||
WasmEngine::instance()
|
||||
{
|
||||
static WasmEngine e;
|
||||
return e;
|
||||
}
|
||||
|
||||
std::expected<WasmResult<int32_t>, WasmTER>
|
||||
WasmEngine::run(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gasLimit,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j)
|
||||
{
|
||||
return impl_->run(wasmCode, hfs, gasLimit, funcName, params, imports, j);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
WasmEngine::check(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j)
|
||||
{
|
||||
return impl_->check(wasmCode, hfs, funcName, params, imports, j);
|
||||
}
|
||||
|
||||
void*
|
||||
WasmEngine::newTrap(std::string const& msg)
|
||||
{
|
||||
return impl_->newTrap(msg);
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
beast::Journal
|
||||
WasmEngine::getJournal() const
|
||||
{
|
||||
return impl_->getJournal();
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
958
src/libxrpl/tx/wasm/WasmiVM.cpp
Normal file
958
src/libxrpl/tx/wasm/WasmiVM.cpp
Normal file
@@ -0,0 +1,958 @@
|
||||
#include <xrpl/tx/wasm/WasmiVM.h>
|
||||
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
#include <xrpl/tx/wasm/WasmImportsHelper.h>
|
||||
#include <xrpl/tx/wasm/WasmVM.h>
|
||||
|
||||
#include <wasmi/config.h>
|
||||
#include <wasmi/error.h>
|
||||
|
||||
#include <wasm.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <expected>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _DEBUG
|
||||
// #define DEBUG_OUTPUT 1
|
||||
#endif
|
||||
// #define SHOW_CALL_TIME 1
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
wasm_trap_t*
|
||||
HostFuncMain_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
namespace {
|
||||
|
||||
void
|
||||
printWasmError(std::string_view msg, wasm_trap_t* trap, beast::Journal jlog)
|
||||
{
|
||||
#ifdef DEBUG_OUTPUT
|
||||
auto& j = std::cerr;
|
||||
#else
|
||||
auto j = jlog.warn();
|
||||
if (jlog.active(beast::Severity::Warning))
|
||||
#endif
|
||||
{
|
||||
wasm_byte_vec_t errorMessage WASM_EMPTY_VEC;
|
||||
|
||||
if (trap != nullptr)
|
||||
wasm_trap_message(trap, &errorMessage);
|
||||
|
||||
if (errorMessage.size != 0u)
|
||||
{
|
||||
j << "WASMI Error: " << msg << ", "
|
||||
<< std::string_view(errorMessage.data, errorMessage.size - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
j << "WASMI Error: " << msg;
|
||||
}
|
||||
|
||||
if (errorMessage.size != 0u)
|
||||
wasm_byte_vec_delete(&errorMessage);
|
||||
}
|
||||
|
||||
if (trap != nullptr)
|
||||
wasm_trap_delete(trap);
|
||||
|
||||
#ifdef DEBUG_OUTPUT
|
||||
j << std::endl;
|
||||
#endif
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
// Extract a trap's message into a std::string (the only signal the C API gives
|
||||
// for classification; see the trap-signal constants in WasmCommon.h). Does not
|
||||
// take ownership of `trap`.
|
||||
std::string
|
||||
trapMessage(wasm_trap_t* trap)
|
||||
{
|
||||
if (trap == nullptr)
|
||||
return {}; // LCOV_EXCL_LINE
|
||||
wasm_byte_vec_t msg WASM_EMPTY_VEC;
|
||||
wasm_trap_message(trap, &msg);
|
||||
std::string out;
|
||||
if (msg.size != 0u)
|
||||
{
|
||||
// wasm_trap_message NUL-terminates, so drop the trailing NUL.
|
||||
out.assign(msg.data, msg.size - 1);
|
||||
wasm_byte_vec_delete(&msg);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class WasmiRuntimeWrapper : public WasmRuntimeWrapper
|
||||
{
|
||||
InstanceWrapper& iw_;
|
||||
|
||||
public:
|
||||
WasmiRuntimeWrapper(InstanceWrapper& iw) : iw_(iw)
|
||||
{
|
||||
}
|
||||
|
||||
Wmem
|
||||
getMem() override
|
||||
{
|
||||
return iw_.getMem();
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
getGas() override
|
||||
{
|
||||
return iw_.getGas();
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
setGas(std::int64_t gas) override
|
||||
{
|
||||
return iw_.setGas(gas);
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
getTransferLimit() override
|
||||
{
|
||||
return iw_.getTransferLimit();
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
setTransferLimit(std::int64_t x) override
|
||||
{
|
||||
return iw_.setTransferLimit(x);
|
||||
}
|
||||
};
|
||||
|
||||
InstancePtr
|
||||
InstanceWrapper::init(
|
||||
StorePtr& s,
|
||||
ModulePtr& m,
|
||||
WasmExternVec& expt,
|
||||
WasmExternVec const& imports,
|
||||
beast::Journal j)
|
||||
{
|
||||
wasm_trap_t* trap = nullptr;
|
||||
InstancePtr mi = InstancePtr(
|
||||
wasm_instance_new(s.get(), m.get(), imports.get(), &trap), &wasm_instance_delete);
|
||||
|
||||
if (!mi || (trap != nullptr))
|
||||
{
|
||||
printWasmError("can't create instance", trap, j);
|
||||
Throw<std::runtime_error>("can't create instance");
|
||||
}
|
||||
wasm_instance_exports(mi.get(), expt.get());
|
||||
return mi;
|
||||
}
|
||||
|
||||
InstanceWrapper&
|
||||
InstanceWrapper::operator=(InstanceWrapper&& o)
|
||||
{
|
||||
if (this == &o)
|
||||
return *this; // LCOV_EXCL_LINE
|
||||
|
||||
store_ = o.store_;
|
||||
o.store_ = nullptr;
|
||||
exports_ = std::move(o.exports_);
|
||||
memIdx_ = o.memIdx_;
|
||||
o.memIdx_ = -1;
|
||||
instance_ = std::move(o.instance_);
|
||||
|
||||
j_ = o.j_;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
FuncInfo
|
||||
InstanceWrapper::getFunc(std::string_view funcName, WasmExporttypeVec const& exportTypes) const
|
||||
{
|
||||
wasm_func_t const* f = nullptr;
|
||||
wasm_functype_t const* ft = nullptr;
|
||||
|
||||
if (!instance_)
|
||||
Throw<std::runtime_error>("no instance"); // LCOV_EXCL_LINE
|
||||
|
||||
if (exportTypes.empty())
|
||||
Throw<std::runtime_error>("no export"); // LCOV_EXCL_LINE
|
||||
if (exportTypes.size() != exports_.size())
|
||||
Throw<std::runtime_error>("invalid export"); // LCOV_EXCL_LINE
|
||||
|
||||
for (unsigned i = 0; i < exportTypes.size(); ++i)
|
||||
{
|
||||
auto const* expType(exportTypes[i]);
|
||||
|
||||
wasm_name_t const* name = wasm_exporttype_name(expType);
|
||||
wasm_externtype_t const* exnType = wasm_exporttype_type(expType);
|
||||
if (wasm_externtype_kind(exnType) == WASM_EXTERN_FUNC)
|
||||
{
|
||||
if (funcName != std::string_view(name->data, name->size))
|
||||
continue;
|
||||
|
||||
auto const* exn(exports_[i]);
|
||||
if (wasm_extern_kind(exn) != WASM_EXTERN_FUNC)
|
||||
Throw<std::runtime_error>("invalid export"); // LCOV_EXCL_LINE
|
||||
|
||||
ft = wasm_externtype_as_functype_const(exnType);
|
||||
f = wasm_extern_as_func_const(exn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((f == nullptr) || (ft == nullptr))
|
||||
Throw<std::runtime_error>("can't find function <" + std::string(funcName) + ">");
|
||||
|
||||
return {f, ft};
|
||||
}
|
||||
|
||||
Wmem
|
||||
InstanceWrapper::getMem() const
|
||||
{
|
||||
if (memIdx_ >= 0)
|
||||
{
|
||||
auto* e(exports_[memIdx_]);
|
||||
wasm_memory_t* mem = wasm_extern_as_memory(e);
|
||||
return Wmem(wasm_memory_data(mem), wasm_memory_data_size(mem));
|
||||
}
|
||||
|
||||
wasm_memory_t* mem = nullptr;
|
||||
for (int i = 0; i < exports_.size(); ++i)
|
||||
{
|
||||
auto* e(exports_[i]);
|
||||
if (wasm_extern_kind(e) == WASM_EXTERN_MEMORY)
|
||||
{
|
||||
memIdx_ = i;
|
||||
mem = wasm_extern_as_memory(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mem == nullptr)
|
||||
return {}; // LCOV_EXCL_LINE
|
||||
|
||||
return Wmem(wasm_memory_data(mem), wasm_memory_data_size(mem));
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
InstanceWrapper::getGas() const
|
||||
{
|
||||
if (store_ == nullptr)
|
||||
return -1; // LCOV_EXCL_LINE
|
||||
std::uint64_t gas = 0;
|
||||
wasm_store_get_fuel(store_, &gas);
|
||||
return static_cast<std::int64_t>(gas);
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
InstanceWrapper::setGas(std::int64_t gas) const
|
||||
{
|
||||
if (store_ == nullptr)
|
||||
return -1; // LCOV_EXCL_LINE
|
||||
|
||||
if (gas < 0)
|
||||
gas = std::numeric_limits<decltype(gas)>::max();
|
||||
wasmi_error_t* err = wasm_store_set_fuel(store_, static_cast<std::uint64_t>(gas));
|
||||
if (err != nullptr)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
printWasmError("Can't set instance gas", nullptr, j_);
|
||||
wasmi_error_delete(err);
|
||||
return -1;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return gas;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
InstanceWrapper::getTransferLimit() const
|
||||
{
|
||||
if (store_ == nullptr)
|
||||
return -1; // LCOV_EXCL_LINE
|
||||
|
||||
return transferLimit_;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
InstanceWrapper::setTransferLimit(std::int64_t x)
|
||||
{
|
||||
if (store_ == nullptr)
|
||||
return -1; // LCOV_EXCL_LINE
|
||||
if (x < 0)
|
||||
{
|
||||
transferLimit_ = std::numeric_limits<decltype(transferLimit_)>::max();
|
||||
}
|
||||
else
|
||||
{
|
||||
transferLimit_ = x;
|
||||
}
|
||||
|
||||
return transferLimit_;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ModulePtr
|
||||
ModuleWrapper::init(StorePtr& s, Bytes const& wasmBin, beast::Journal j)
|
||||
{
|
||||
wasm_byte_vec_t const code{
|
||||
.size = wasmBin.size(),
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
|
||||
.data = const_cast<char*>(reinterpret_cast<char const*>(wasmBin.data()))};
|
||||
ModulePtr m = ModulePtr(wasm_module_new(s.get(), &code), &wasm_module_delete);
|
||||
if (!m)
|
||||
throw std::runtime_error("can't create module");
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
ModuleWrapper::ModuleWrapper(
|
||||
StorePtr& s,
|
||||
Bytes const& wasmBin,
|
||||
bool instantiate,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j)
|
||||
: module_(init(s, wasmBin, j)), j_(j)
|
||||
{
|
||||
wasm_module_exports(module_.get(), exportTypes_.get());
|
||||
auto wimports = buildImports(s, imports);
|
||||
if (instantiate)
|
||||
{
|
||||
addInstance(s, wimports);
|
||||
}
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
ModuleWrapper&
|
||||
ModuleWrapper::operator=(ModuleWrapper&& o)
|
||||
{
|
||||
if (this == &o)
|
||||
return *this;
|
||||
|
||||
module_ = std::move(o.module_);
|
||||
instanceWrap_ = std::move(o.instanceWrap_);
|
||||
exportTypes_ = std::move(o.exportTypes_);
|
||||
j_ = o.j_;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
static WasmValtypeVec
|
||||
makeImpParams(WasmImportFunc const& imp)
|
||||
{
|
||||
auto const paramSize = imp.params.size();
|
||||
if (paramSize == 0u)
|
||||
return {};
|
||||
|
||||
WasmValtypeVec v(paramSize);
|
||||
|
||||
for (unsigned i = 0; i < paramSize; ++i)
|
||||
{
|
||||
auto const vt = imp.params[i];
|
||||
switch (vt)
|
||||
{
|
||||
case WasmTypes::WtI32:
|
||||
v[i] = wasm_valtype_new_i32();
|
||||
break;
|
||||
case WasmTypes::WtI64:
|
||||
v[i] = wasm_valtype_new_i64();
|
||||
break;
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
throw std::runtime_error("invalid import type");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static WasmValtypeVec
|
||||
makeImpReturn(WasmImportFunc const& imp)
|
||||
{
|
||||
if (!imp.result)
|
||||
return {}; // LCOV_EXCL_LINE
|
||||
|
||||
WasmValtypeVec v(1);
|
||||
switch (*imp.result)
|
||||
{
|
||||
case WasmTypes::WtI32:
|
||||
v[0] = wasm_valtype_new_i32();
|
||||
break;
|
||||
// LCOV_EXCL_START
|
||||
case WasmTypes::WtI64:
|
||||
v[0] = wasm_valtype_new_i64();
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("invalid return type");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
WasmExternVec
|
||||
ModuleWrapper::buildImports(StorePtr& s, ImportVec const& imports) const
|
||||
{
|
||||
WasmImporttypeVec importTypes;
|
||||
wasm_module_imports(module_.get(), importTypes.get());
|
||||
|
||||
if (importTypes.empty())
|
||||
return {};
|
||||
if (imports.empty())
|
||||
Throw<std::runtime_error>("Empty imports");
|
||||
|
||||
WasmExternVec wimports(importTypes.size());
|
||||
|
||||
unsigned impCnt = 0;
|
||||
for (unsigned i = 0; i < importTypes.size(); ++i)
|
||||
{
|
||||
wasm_importtype_t const* importType = importTypes[i];
|
||||
|
||||
// wasm_name_t const* mn = wasm_importtype_module(importtype);
|
||||
// auto modName = std::string_view(mn->data, mn->num_elems);
|
||||
wasm_name_t const* fn = wasm_importtype_name(importType);
|
||||
auto fieldName = std::string_view(fn->data, fn->size);
|
||||
|
||||
wasm_externkind_t const itype = wasm_externtype_kind(wasm_importtype_type(importType));
|
||||
if (itype != WASM_EXTERN_FUNC)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Invalid import type " + std::to_string(itype)); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// for multi-module support
|
||||
// if ((W_ENV != modName) && (W_HOST_LIB != modName))
|
||||
// continue;
|
||||
|
||||
auto const it = imports.find(fieldName);
|
||||
if (it == imports.end())
|
||||
{
|
||||
printWasmError("Import not found: " + std::string(fieldName), nullptr, j_);
|
||||
continue; // print all missed import
|
||||
}
|
||||
|
||||
WasmUserData const& obj = it->second;
|
||||
WasmImportFunc const& imp = obj.second;
|
||||
|
||||
WasmValtypeVec params(makeImpParams(imp));
|
||||
WasmValtypeVec results(makeImpReturn(imp));
|
||||
|
||||
std::unique_ptr<wasm_functype_t, decltype(&wasm_functype_delete)> const ftype(
|
||||
wasm_functype_new(params.get(), results.get()), &wasm_functype_delete);
|
||||
|
||||
params.release();
|
||||
results.release();
|
||||
|
||||
wasm_func_t* func =
|
||||
wasm_func_new_with_env(s.get(), ftype.get(), HostFuncMain_wrap, (void*)&obj, nullptr);
|
||||
if (func == nullptr)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"can't create import function " + std::string(imp.name)); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
wimports[i] = wasm_func_as_extern(func);
|
||||
++impCnt;
|
||||
}
|
||||
|
||||
if (impCnt != importTypes.size())
|
||||
{
|
||||
printWasmError(
|
||||
std::string("Imports not finished: ") + std::to_string(impCnt) + "/" +
|
||||
std::to_string(importTypes.size()),
|
||||
nullptr,
|
||||
j_);
|
||||
Throw<std::runtime_error>("Missing imports");
|
||||
}
|
||||
|
||||
return wimports;
|
||||
}
|
||||
|
||||
wasm_functype_t const*
|
||||
ModuleWrapper::getFuncType(std::string_view funcName) const
|
||||
{
|
||||
for (size_t i = 0; i < exportTypes_.size(); i++)
|
||||
{
|
||||
auto const* expType(exportTypes_[i]);
|
||||
wasm_name_t const* name = wasm_exporttype_name(expType);
|
||||
wasm_externtype_t const* exnType = wasm_exporttype_type(expType);
|
||||
if (wasm_externtype_kind(exnType) == WASM_EXTERN_FUNC &&
|
||||
funcName == std::string_view(name->data, name->size))
|
||||
{
|
||||
return wasm_externtype_as_functype_const(exnType);
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("can't find function <" + std::string(funcName) + ">");
|
||||
}
|
||||
|
||||
// int
|
||||
// my_module_t::delInstance(int i)
|
||||
// {
|
||||
// if (i >= mod_inst.size())
|
||||
// return -1;
|
||||
// if (!mod_inst[i])
|
||||
// mod_inst[i] = my_mod_inst_t();
|
||||
// return i;
|
||||
// }
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// void
|
||||
// WasmiEngine::clearModules()
|
||||
// {
|
||||
// modules.clear();
|
||||
// store.reset(); // to free the memory before creating new store
|
||||
// store = {wasm_store_new(engine.get()), &wasm_store_delete};
|
||||
// }
|
||||
|
||||
std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)>
|
||||
WasmiEngine::init()
|
||||
{
|
||||
wasm_config_t* config = wasm_config_new();
|
||||
if (config == nullptr)
|
||||
{
|
||||
return std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)>{
|
||||
nullptr, &wasm_engine_delete}; // LCOV_EXCL_LINE
|
||||
}
|
||||
wasmi_config_consume_fuel_set(config, true);
|
||||
wasmi_config_ignore_custom_sections_set(config, true);
|
||||
wasmi_config_wasm_mutable_globals_set(config, false);
|
||||
wasmi_config_wasm_multi_value_set(config, false);
|
||||
wasmi_config_wasm_sign_extension_set(config, false);
|
||||
wasmi_config_wasm_saturating_float_to_int_set(config, false);
|
||||
wasmi_config_wasm_bulk_memory_set(config, false);
|
||||
wasmi_config_wasm_reference_types_set(config, false);
|
||||
wasmi_config_wasm_tail_call_set(config, false);
|
||||
wasmi_config_wasm_extended_const_set(config, false);
|
||||
wasmi_config_floats_set(config, false);
|
||||
wasmi_config_wasm_multi_memory_set(config, false);
|
||||
wasmi_config_wasm_custom_page_sizes_set(config, false);
|
||||
wasmi_config_wasm_memory64_set(config, false);
|
||||
wasmi_config_wasm_wide_arithmetic_set(config, false);
|
||||
|
||||
return std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)>(
|
||||
wasm_engine_new_with_config(config), &wasm_engine_delete);
|
||||
}
|
||||
|
||||
int
|
||||
WasmiEngine::addModule(
|
||||
Bytes const& wasmCode,
|
||||
bool instantiate,
|
||||
ImportVec const& imports,
|
||||
int64_t gas)
|
||||
{
|
||||
moduleWrap_.reset();
|
||||
store_.reset(); // to free the memory before creating new store
|
||||
store_ = {wasm_store_new_with_memory_max_pages(engine_.get(), maxPages), &wasm_store_delete};
|
||||
|
||||
if (gas < 0)
|
||||
gas = std::numeric_limits<decltype(gas)>::max();
|
||||
wasmi_error_t* err = wasm_store_set_fuel(store_.get(), static_cast<std::uint64_t>(gas));
|
||||
if (err != nullptr)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
printWasmError("Error setting gas", nullptr, j_);
|
||||
wasmi_error_delete(err);
|
||||
throw std::runtime_error("can't set gas");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
moduleWrap_ = std::make_unique<ModuleWrapper>(store_, wasmCode, instantiate, imports, j_);
|
||||
|
||||
if (!moduleWrap_)
|
||||
throw std::runtime_error("can't create module wrapper"); // LCOV_EXCL_LINE
|
||||
|
||||
return moduleWrap_ ? 0 : -1;
|
||||
}
|
||||
|
||||
// int
|
||||
// WasmiEngine::addInstance()
|
||||
// {
|
||||
// return module->addInstance(store.get());
|
||||
// }
|
||||
|
||||
std::vector<wasm_val_t>
|
||||
WasmiEngine::convertParams(std::vector<WasmParam> const& params)
|
||||
{
|
||||
std::vector<wasm_val_t> v;
|
||||
v.reserve(params.size());
|
||||
for (auto const& p : params)
|
||||
{
|
||||
switch (p.type)
|
||||
{
|
||||
case WasmTypes::WtI32:
|
||||
v.push_back(WASM_I32_VAL(p.of.i32));
|
||||
break;
|
||||
// LCOV_EXCL_START
|
||||
case WasmTypes::WtI64:
|
||||
v.push_back(WASM_I64_VAL(p.of.i64));
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"unknown parameter type: " + std::to_string(static_cast<int>(p.type)));
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
int
|
||||
WasmiEngine::compareParamTypes(wasm_valtype_vec_t const* ftp, std::vector<wasm_val_t> const& p)
|
||||
{
|
||||
if (ftp->size != p.size())
|
||||
return std::min(ftp->size, p.size());
|
||||
|
||||
for (unsigned i = 0; i < ftp->size; ++i)
|
||||
{
|
||||
auto const t1 = wasm_valtype_kind(ftp->data[i]);
|
||||
auto const t2 = p[i].kind;
|
||||
if (t1 != t2)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
void
|
||||
WasmiEngine::addParam(std::vector<wasm_val_t>& in, int32_t p)
|
||||
{
|
||||
in.emplace_back();
|
||||
auto& el(in.back());
|
||||
memset(&el, 0, sizeof(el));
|
||||
el = WASM_I32_VAL(p); // WASM_I32;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
void
|
||||
WasmiEngine::addParam(std::vector<wasm_val_t>& in, int64_t p)
|
||||
{
|
||||
in.emplace_back();
|
||||
auto& el(in.back());
|
||||
el = WASM_I64_VAL(p);
|
||||
}
|
||||
|
||||
template <int NR, class... Types>
|
||||
WasmiResult
|
||||
WasmiEngine::call(std::string_view func, Types&&... args)
|
||||
{
|
||||
// Lookup our export function
|
||||
auto f = getFunc(func);
|
||||
return call<NR>(f, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <int NR, class... Types>
|
||||
WasmiResult
|
||||
WasmiEngine::call(FuncInfo const& f, Types&&... args)
|
||||
{
|
||||
std::vector<wasm_val_t> in;
|
||||
return call<NR>(f, in, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
#ifdef SHOW_CALL_TIME
|
||||
static inline uint64_t
|
||||
usecs()
|
||||
{
|
||||
uint64_t x = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::high_resolution_clock::now().time_since_epoch())
|
||||
.count();
|
||||
return x;
|
||||
}
|
||||
#endif
|
||||
|
||||
template <int NR, class... Types>
|
||||
WasmiResult
|
||||
WasmiEngine::call(FuncInfo const& f, std::vector<wasm_val_t>& in)
|
||||
{
|
||||
WasmiResult ret(NR);
|
||||
wasm_val_vec_t const inv = in.empty() ? wasm_val_vec_t WASM_EMPTY_VEC
|
||||
: wasm_val_vec_t{.size = in.size(), .data = in.data()};
|
||||
|
||||
#ifdef SHOW_CALL_TIME
|
||||
auto const start = usecs();
|
||||
#endif
|
||||
|
||||
wasm_trap_t* trap = wasm_func_call(f.first, &inv, ret.r.get());
|
||||
|
||||
#ifdef SHOW_CALL_TIME
|
||||
auto const finish = usecs();
|
||||
auto const delta_ms = (finish - start) / 1000;
|
||||
std::cout << "wasm_func_call: " << delta_ms << "ms" << std::endl;
|
||||
#endif
|
||||
|
||||
if (trap)
|
||||
{
|
||||
// Classify the trap into a TER by matching tokens as substrings of the
|
||||
// message (see the trap-signal constants in WasmCommon.h for why).
|
||||
std::string const msg = trapMessage(trap);
|
||||
auto const has = [&msg](std::string_view token) { return msg.contains(token); };
|
||||
if (has(hfErrInternal))
|
||||
{
|
||||
ret.ter = tecINTERNAL;
|
||||
}
|
||||
else if (has(hfErrOutOfGas) || has(wasmiTrapOutOfFuel))
|
||||
{
|
||||
ret.ter = tecOUT_OF_GAS;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.ter = tecFAILED_PROCESSING;
|
||||
}
|
||||
printWasmError("failure to call func", trap, j_);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <int NR, class... Types>
|
||||
WasmiResult
|
||||
WasmiEngine::call(FuncInfo const& f, std::vector<wasm_val_t>& in, std::int32_t p, Types&&... args)
|
||||
{
|
||||
addParam(in, p);
|
||||
return call<NR>(f, in, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <int NR, class... Types>
|
||||
WasmiResult
|
||||
WasmiEngine::call(FuncInfo const& f, std::vector<wasm_val_t>& in, std::int64_t p, Types&&... args)
|
||||
{
|
||||
addParam(in, p);
|
||||
return call<NR>(f, in, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <int NR, class... Types>
|
||||
WasmiResult
|
||||
WasmiEngine::call(FuncInfo const& f, std::vector<wasm_val_t>& in, Bytes const& p, Types&&... args)
|
||||
{
|
||||
return call<NR>(f, in, p.data(), p.size(), std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
static inline void
|
||||
checkImports(ImportVec const& imports, HostFunctions* hfs)
|
||||
{
|
||||
for (auto const& obj : imports)
|
||||
{
|
||||
if (hfs != &obj.second.first.get())
|
||||
Throw<std::runtime_error>("Imports hf unsync");
|
||||
}
|
||||
}
|
||||
|
||||
std::expected<WasmResult<int32_t>, WasmTER>
|
||||
WasmiEngine::run(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gas,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (gas <= 0)
|
||||
return std::unexpected(WasmTER{.ter = temBAD_AMOUNT, .cost = std::nullopt});
|
||||
|
||||
try
|
||||
{
|
||||
checkImports(imports, &hfs);
|
||||
return runHlp(wasmCode, hfs, gas, funcName, params, imports, j);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
printWasmError(std::string("exception: ") + e.what(), nullptr, j);
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
printWasmError(std::string("exception: unknown"), nullptr, j);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
// An exception escaping the engine is an xrpld-side fault -> tecINTERNAL,
|
||||
// no gas. Genuine wasm faults don't throw; they surface as traps in runHlp.
|
||||
return std::unexpected(WasmTER{.ter = tecINTERNAL, .cost = std::nullopt});
|
||||
}
|
||||
|
||||
std::expected<WasmResult<int32_t>, WasmTER>
|
||||
WasmiEngine::runHlp(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gas,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j)
|
||||
{
|
||||
// currently only 1 module support, possible parallel UT run
|
||||
std::scoped_lock const lg(m_);
|
||||
j_ = j;
|
||||
|
||||
if (wasmCode.empty())
|
||||
throw std::runtime_error("empty module");
|
||||
if (!hfs.checkSelf())
|
||||
throw std::runtime_error("hfs isn't clean");
|
||||
|
||||
// Create and instantiate the module.
|
||||
[[maybe_unused]] int const m = addModule(wasmCode, true, imports, gas);
|
||||
|
||||
if (!moduleWrap_ || !moduleWrap_->getInstance())
|
||||
throw std::runtime_error("no instance"); // LCOV_EXCL_LINE
|
||||
|
||||
auto clearRT = [](HostFunctions* p) { p->resetRT(); };
|
||||
std::unique_ptr<HostFunctions, decltype(clearRT)> const clearGuard(&hfs, clearRT);
|
||||
WasmiRuntimeWrapper iw(getRT());
|
||||
hfs.setRT(iw);
|
||||
|
||||
// Call main
|
||||
auto const f = getFunc(!funcName.empty() ? funcName : "_start");
|
||||
auto const* ftp = wasm_functype_params(f.second);
|
||||
|
||||
// not const because passed directly to VM function (which accept non
|
||||
// const)
|
||||
auto p = convertParams(params);
|
||||
|
||||
if (int const comp = compareParamTypes(ftp, p); comp >= 0)
|
||||
throw std::runtime_error("invalid parameter type #" + std::to_string(comp));
|
||||
|
||||
auto const res = call<1>(f, p);
|
||||
|
||||
if (gas == -1)
|
||||
gas = std::numeric_limits<decltype(gas)>::max();
|
||||
|
||||
if (res.ter.has_value())
|
||||
{
|
||||
// call() already classified the trap (see WasmiEngine::call).
|
||||
// tecINTERNAL is an xrpld-side bug: report no gas.
|
||||
if (*res.ter == tecINTERNAL)
|
||||
return std::unexpected(WasmTER{.ter = tecINTERNAL, .cost = std::nullopt});
|
||||
|
||||
// Out-of-gas / wasm faults report gas (caller writes it to metadata).
|
||||
// Force fuel to 0 on out-of-gas so cost is the full limit (wasmi leaves
|
||||
// nonzero leftover fuel on its own out-of-fuel trap).
|
||||
if (*res.ter == tecOUT_OF_GAS)
|
||||
iw.setGas(0);
|
||||
|
||||
return std::unexpected(WasmTER{.ter = *res.ter, .cost = gas - moduleWrap_->getGas()});
|
||||
}
|
||||
|
||||
if (res.r.empty())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"<" + std::string(funcName) + "> return nothing"); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (res.r[0].kind != WASM_I32)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"<" + std::string(funcName) +
|
||||
"> return type mismatch, ret: " + std::to_string(static_cast<int>(res.r[0].kind)));
|
||||
}
|
||||
|
||||
WasmResult<int32_t> const ret{.result = res.r[0].of.i32, .cost = gas - moduleWrap_->getGas()};
|
||||
|
||||
// #ifdef DEBUG_OUTPUT
|
||||
// auto& j = std::cerr;
|
||||
// #else
|
||||
// auto j = j_.debug();
|
||||
// #endif
|
||||
// j << "WASMI Res: " << ret.result << " cost: " << ret.cost << std::endl;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
WasmiEngine::check(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j)
|
||||
{
|
||||
try
|
||||
{
|
||||
checkImports(imports, &hfs);
|
||||
return checkHlp(wasmCode, hfs, funcName, params, imports, j);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
printWasmError(std::string("exception: ") + e.what(), nullptr, j);
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
printWasmError(std::string("exception: unknown"), nullptr, j);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
return temBAD_WASM;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
WasmiEngine::checkHlp(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j)
|
||||
{
|
||||
// currently only 1 module support, possible parallel UT run
|
||||
std::scoped_lock const lg(m_);
|
||||
j_ = j;
|
||||
|
||||
// Create and instantiate the module.
|
||||
if (wasmCode.empty())
|
||||
throw std::runtime_error("empty module");
|
||||
|
||||
int const m = addModule(wasmCode, false, imports, -1);
|
||||
if ((m < 0) || !moduleWrap_)
|
||||
throw std::runtime_error("no module"); // LCOV_EXCL_LINE
|
||||
|
||||
// Looking for a func and compare parameter types
|
||||
auto const f = moduleWrap_->getFuncType(!funcName.empty() ? funcName : "_start");
|
||||
auto const* ftp = wasm_functype_params(f);
|
||||
auto const p = convertParams(params);
|
||||
|
||||
if (int const comp = compareParamTypes(ftp, p); comp >= 0)
|
||||
throw std::runtime_error("invalid parameter type #" + std::to_string(comp));
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
WasmiEngine::newTrap(std::string const& txt)
|
||||
{
|
||||
static char empty[1] = {0};
|
||||
wasm_message_t msg = {.size = 1, .data = empty};
|
||||
|
||||
if (!txt.empty())
|
||||
wasm_name_new(&msg, txt.size() + 1, txt.c_str()); // include 0
|
||||
|
||||
wasm_trap_t* trap = wasm_trap_new(store_.get(), &msg); // NOLINT
|
||||
|
||||
if (!txt.empty())
|
||||
wasm_byte_vec_delete(&msg);
|
||||
|
||||
return trap;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <xrpl/ledger/Ledger.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
@@ -31,7 +32,9 @@
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
@@ -45,10 +48,17 @@ struct FeeSettingsFields
|
||||
std::optional<XRPAmount> baseFeeDrops = std::nullopt;
|
||||
std::optional<XRPAmount> reserveBaseDrops = std::nullopt;
|
||||
std::optional<XRPAmount> reserveIncrementDrops = std::nullopt;
|
||||
std::optional<std::uint32_t> gasLimit = std::nullopt;
|
||||
std::optional<std::uint32_t> bytecodeSizeLimit = std::nullopt;
|
||||
std::optional<std::uint32_t> gasPrice = std::nullopt;
|
||||
};
|
||||
|
||||
STTx
|
||||
createFeeTx(Rules const& rules, std::uint32_t seq, FeeSettingsFields const& fields)
|
||||
createFeeTx(
|
||||
Rules const& rules,
|
||||
std::uint32_t seq,
|
||||
FeeSettingsFields const& fields,
|
||||
bool forceAllFields = false)
|
||||
{
|
||||
auto fill = [&](auto& obj) {
|
||||
obj.setAccountID(sfAccount, AccountID());
|
||||
@@ -76,6 +86,13 @@ createFeeTx(Rules const& rules, std::uint32_t seq, FeeSettingsFields const& fiel
|
||||
obj.setFieldU32(
|
||||
sfReferenceFeeUnits, fields.referenceFeeUnits ? *fields.referenceFeeUnits : 0);
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow) || forceAllFields)
|
||||
{
|
||||
obj.setFieldU32(sfGasLimit, fields.gasLimit ? *fields.gasLimit : 0);
|
||||
obj.setFieldU32(
|
||||
sfBytecodeSizeLimit, fields.bytecodeSizeLimit ? *fields.bytecodeSizeLimit : 0);
|
||||
obj.setFieldU32(sfGasPrice, fields.gasPrice ? *fields.gasPrice : 0);
|
||||
}
|
||||
};
|
||||
return STTx(ttFEE, fill);
|
||||
}
|
||||
@@ -124,6 +141,12 @@ createInvalidFeeTx(
|
||||
obj.setFieldU32(sfReserveIncrement, 50000);
|
||||
obj.setFieldU32(sfReferenceFeeUnits, 10);
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
obj.setFieldU32(sfGasLimit, 100 + uniqueValue);
|
||||
obj.setFieldU32(sfBytecodeSizeLimit, 200 + uniqueValue);
|
||||
obj.setFieldU32(sfGasPrice, 300 + uniqueValue);
|
||||
}
|
||||
}
|
||||
// If missingRequiredFields is true, we don't add the required fields
|
||||
// (default behavior)
|
||||
@@ -131,11 +154,11 @@ createInvalidFeeTx(
|
||||
return STTx(ttFEE, fill);
|
||||
}
|
||||
|
||||
bool
|
||||
TER
|
||||
applyFeeAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx)
|
||||
{
|
||||
auto const res = apply(env.app(), view, tx, ApplyFlags::TapNone, env.journal);
|
||||
return isTesSuccess(res.ter);
|
||||
return res.ter;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -186,6 +209,21 @@ verifyFeeObject(
|
||||
if (!checkEquality(sfReferenceFeeUnits, expected.referenceFeeUnits))
|
||||
return false;
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
if (!checkEquality(sfGasLimit, expected.gasLimit.value_or(0)))
|
||||
return false;
|
||||
if (!checkEquality(sfBytecodeSizeLimit, expected.bytecodeSizeLimit.value_or(0)))
|
||||
return false;
|
||||
if (!checkEquality(sfGasPrice, expected.gasPrice.value_or(0)))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (feeObject->isFieldPresent(sfGasLimit) ||
|
||||
feeObject->isFieldPresent(sfBytecodeSizeLimit) || feeObject->isFieldPresent(sfGasPrice))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -208,6 +246,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
void
|
||||
testSetup()
|
||||
{
|
||||
testcase("FeeVote setup");
|
||||
FeeSetup const defaultSetup;
|
||||
{
|
||||
// defaults
|
||||
@@ -216,35 +255,63 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee);
|
||||
BEAST_EXPECT(setup.accountReserve == defaultSetup.accountReserve);
|
||||
BEAST_EXPECT(setup.ownerReserve == defaultSetup.ownerReserve);
|
||||
BEAST_EXPECT(setup.gasLimit == defaultSetup.gasLimit);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == defaultSetup.bytecodeSizeLimit);
|
||||
BEAST_EXPECT(setup.gasPrice == defaultSetup.gasPrice);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = 50", "account_reserve = 1234567", "owner_reserve = 1234"});
|
||||
{"reference_fee = 50",
|
||||
"account_reserve = 1234567",
|
||||
"owner_reserve = 1234",
|
||||
"gas_limit = 100",
|
||||
"bytecode_size_limit = 200",
|
||||
"gas_price = 300"});
|
||||
auto setup = setupFeeVote(config);
|
||||
BEAST_EXPECT(setup.referenceFee == 50);
|
||||
BEAST_EXPECT(setup.accountReserve == 1234567);
|
||||
BEAST_EXPECT(setup.ownerReserve == 1234);
|
||||
BEAST_EXPECT(setup.gasLimit == 100);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == 200);
|
||||
BEAST_EXPECT(setup.gasPrice == 300);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = blah", "account_reserve = yada", "owner_reserve = foo"});
|
||||
{"reference_fee = blah",
|
||||
"account_reserve = yada",
|
||||
"owner_reserve = foo",
|
||||
"gas_limit = bar",
|
||||
"bytecode_size_limit = baz",
|
||||
"gas_price = qux"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setupFeeVote(config);
|
||||
BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee);
|
||||
BEAST_EXPECT(setup.accountReserve == defaultSetup.accountReserve);
|
||||
BEAST_EXPECT(setup.ownerReserve == defaultSetup.ownerReserve);
|
||||
BEAST_EXPECT(setup.gasLimit == defaultSetup.gasLimit);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == defaultSetup.bytecodeSizeLimit);
|
||||
BEAST_EXPECT(setup.gasPrice == defaultSetup.gasPrice);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = -50", "account_reserve = -1234567", "owner_reserve = -1234"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
{"reference_fee = -50",
|
||||
"account_reserve = -1234567",
|
||||
"owner_reserve = -1234",
|
||||
"gas_limit = -100",
|
||||
"bytecode_size_limit = -200",
|
||||
"gas_price = -300"});
|
||||
// Negative gas/bytecode limit values wrap past their maximum and are
|
||||
// ignored. Other uint32_t fields keep the existing behavior.
|
||||
auto setup = setupFeeVote(config);
|
||||
BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee);
|
||||
BEAST_EXPECT(setup.accountReserve == static_cast<std::uint32_t>(-1234567));
|
||||
BEAST_EXPECT(setup.ownerReserve == static_cast<std::uint32_t>(-1234));
|
||||
BEAST_EXPECT(setup.gasLimit == defaultSetup.gasLimit);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == defaultSetup.bytecodeSizeLimit);
|
||||
BEAST_EXPECT(setup.gasPrice == static_cast<std::uint32_t>(-300));
|
||||
}
|
||||
{
|
||||
auto const big64 = std::to_string(
|
||||
@@ -253,12 +320,36 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
config.append(
|
||||
{"reference_fee = " + big64,
|
||||
"account_reserve = " + big64,
|
||||
"owner_reserve = " + big64});
|
||||
"owner_reserve = " + big64,
|
||||
"gas_limit = " + big64,
|
||||
"bytecode_size_limit = " + big64,
|
||||
"gas_price = " + big64});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setupFeeVote(config);
|
||||
BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee);
|
||||
BEAST_EXPECT(setup.accountReserve == defaultSetup.accountReserve);
|
||||
BEAST_EXPECT(setup.ownerReserve == defaultSetup.ownerReserve);
|
||||
BEAST_EXPECT(setup.gasLimit == defaultSetup.gasLimit);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == defaultSetup.bytecodeSizeLimit);
|
||||
BEAST_EXPECT(setup.gasPrice == defaultSetup.gasPrice);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"gas_limit = " + std::to_string(kMaxGasLimit + 1),
|
||||
"bytecode_size_limit = " + std::to_string(kMaxBytecodeSizeLimit + 1)});
|
||||
auto const setup = setupFeeVote(config);
|
||||
BEAST_EXPECT(setup.gasLimit == defaultSetup.gasLimit);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == defaultSetup.bytecodeSizeLimit);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"gas_limit = " + std::to_string(kMaxGasLimit),
|
||||
"bytecode_size_limit = " + std::to_string(kMaxBytecodeSizeLimit)});
|
||||
auto const setup = setupFeeVote(config);
|
||||
BEAST_EXPECT(setup.gasLimit == kMaxGasLimit);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == kMaxBytecodeSizeLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +360,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
// Test with XRPFees disabled (legacy format)
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureXRPFees - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -290,7 +381,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields);
|
||||
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx)));
|
||||
accum.apply(*ledger);
|
||||
|
||||
// Verify fee object was created/updated correctly
|
||||
@@ -299,7 +390,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
// Test with XRPFees enabled (new format)
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -318,12 +409,105 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields);
|
||||
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx)));
|
||||
accum.apply(*ledger);
|
||||
|
||||
// Verify fee object was created/updated correctly
|
||||
BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields));
|
||||
}
|
||||
|
||||
// Test with both XRPFees and SmartEscrow enabled
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments());
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
env.app().config().fees.toFees(),
|
||||
std::vector<uint256>{},
|
||||
env.app().getNodeFamily());
|
||||
|
||||
// Create the next ledger to apply transaction to
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().getTimeKeeper().closeTime());
|
||||
|
||||
FeeSettingsFields const fields{
|
||||
.baseFeeDrops = XRPAmount{10},
|
||||
.reserveBaseDrops = XRPAmount{200000},
|
||||
.reserveIncrementDrops = XRPAmount{50000},
|
||||
.gasLimit = 100,
|
||||
.bytecodeSizeLimit = 200,
|
||||
.gasPrice = 300};
|
||||
// Test successful fee transaction with new fields
|
||||
auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields);
|
||||
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx)));
|
||||
accum.apply(*ledger);
|
||||
|
||||
// Verify fee object was created/updated correctly
|
||||
BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields));
|
||||
}
|
||||
|
||||
// Test that Smart Escrow limits reject values above their maximums.
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments());
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
env.app().config().fees.toFees(),
|
||||
std::vector<uint256>{},
|
||||
env.app().getNodeFamily());
|
||||
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().getTimeKeeper().closeTime());
|
||||
|
||||
auto testBadFields = [&](FeeSettingsFields const& fields) {
|
||||
auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields);
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, feeTx)));
|
||||
};
|
||||
|
||||
testBadFields(
|
||||
{.baseFeeDrops = XRPAmount{10},
|
||||
.reserveBaseDrops = XRPAmount{200000},
|
||||
.reserveIncrementDrops = XRPAmount{50000},
|
||||
.gasLimit = kMaxGasLimit + 1,
|
||||
.bytecodeSizeLimit = kMaxBytecodeSizeLimit,
|
||||
.gasPrice = 300});
|
||||
testBadFields(
|
||||
{.baseFeeDrops = XRPAmount{10},
|
||||
.reserveBaseDrops = XRPAmount{200000},
|
||||
.reserveIncrementDrops = XRPAmount{50000},
|
||||
.gasLimit = kMaxGasLimit,
|
||||
.bytecodeSizeLimit = kMaxBytecodeSizeLimit + 1,
|
||||
.gasPrice = 300});
|
||||
}
|
||||
|
||||
// Test that the Smart Escrow fields are rejected if the
|
||||
// feature is disabled
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
env.app().config().fees.toFees(),
|
||||
std::vector<uint256>{},
|
||||
env.app().getNodeFamily());
|
||||
|
||||
// Create the next ledger to apply transaction to
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().getTimeKeeper().closeTime());
|
||||
|
||||
FeeSettingsFields const fields{
|
||||
.baseFeeDrops = XRPAmount{10},
|
||||
.reserveBaseDrops = XRPAmount{200000},
|
||||
.reserveIncrementDrops = XRPAmount{50000},
|
||||
.gasLimit = 100,
|
||||
.bytecodeSizeLimit = 200,
|
||||
.gasPrice = 300};
|
||||
// Test successful fee transaction with new fields
|
||||
auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields, true);
|
||||
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, feeTx)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -332,7 +516,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
testcase("Fee Transaction Validation");
|
||||
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureXRPFees - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -346,15 +530,15 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
// Test transaction with missing required legacy fields
|
||||
auto invalidTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), true, false, 1);
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx));
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx)));
|
||||
|
||||
// Test transaction with new format fields when XRPFees is disabled
|
||||
auto disallowedTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), false, true, 2);
|
||||
BEAST_EXPECT(!applyFeeAndTestResult(env, accum, disallowedTx));
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, disallowedTx)));
|
||||
}
|
||||
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -368,11 +552,33 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
// Test transaction with missing required new fields
|
||||
auto invalidTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), true, false, 3);
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx));
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx)));
|
||||
|
||||
// Test transaction with legacy fields when XRPFees is enabled
|
||||
auto disallowedTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), false, true, 4);
|
||||
BEAST_EXPECT(!applyFeeAndTestResult(env, accum, disallowedTx));
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, disallowedTx)));
|
||||
}
|
||||
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees | featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
env.app().config().fees.toFees(),
|
||||
std::vector<uint256>{},
|
||||
env.app().getNodeFamily());
|
||||
|
||||
// Create the next ledger to apply transaction to
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().getTimeKeeper().closeTime());
|
||||
|
||||
// Test transaction with missing required new fields
|
||||
auto invalidTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), true, false, 5);
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx)));
|
||||
|
||||
// Test transaction with legacy fields when XRPFees is enabled
|
||||
auto disallowedTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), false, true, 6);
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, disallowedTx)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +587,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("Pseudo Transaction Properties");
|
||||
|
||||
jtx::Env env(*this, jtx::testableAmendments());
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -411,7 +617,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
// But can be applied to a closed ledger
|
||||
{
|
||||
OpenView closedAccum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, closedAccum, feeTx));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, closedAccum, feeTx)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +626,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("Multiple Fee Updates");
|
||||
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -438,7 +644,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx1));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx1)));
|
||||
accum.apply(*ledger);
|
||||
}
|
||||
|
||||
@@ -455,7 +661,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx2));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx2)));
|
||||
accum.apply(*ledger);
|
||||
}
|
||||
|
||||
@@ -468,7 +674,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("Wrong Ledger Sequence");
|
||||
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -491,7 +697,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
// The transaction should still succeed as long as other fields are
|
||||
// valid
|
||||
// The ledger sequence field is only used for informational purposes
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx)));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -499,7 +705,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("Partial Field Updates");
|
||||
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -517,7 +723,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx1));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx1)));
|
||||
accum.apply(*ledger);
|
||||
}
|
||||
|
||||
@@ -532,7 +738,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx2));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx2)));
|
||||
accum.apply(*ledger);
|
||||
}
|
||||
|
||||
@@ -545,7 +751,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("Single Invalid Transaction");
|
||||
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -567,7 +773,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
});
|
||||
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx));
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx)));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -584,7 +790,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
// Test with XRPFees enabled
|
||||
{
|
||||
Env env(*this, testableAmendments() | featureXRPFees);
|
||||
Env env(*this, testableAmendments() - featureSmartEscrow);
|
||||
auto feeVote = makeFeeVote(setup, env.app().getJournal("FeeVote"));
|
||||
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
@@ -614,7 +820,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
// Test with XRPFees disabled (legacy format)
|
||||
{
|
||||
Env env(*this, testableAmendments() - featureXRPFees);
|
||||
Env env(*this, testableAmendments() - featureXRPFees - featureSmartEscrow);
|
||||
auto feeVote = makeFeeVote(setup, env.app().getJournal("FeeVote"));
|
||||
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
@@ -654,7 +860,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
setup.accountReserve = 1234567;
|
||||
setup.ownerReserve = 7654321;
|
||||
|
||||
Env env(*this, testableAmendments() | featureXRPFees);
|
||||
Env env(*this, testableAmendments() - featureSmartEscrow);
|
||||
|
||||
// establish what the current fees are
|
||||
BEAST_EXPECT(env.current()->fees().base == XRPAmount{UNIT_TEST_REFERENCE_FEE});
|
||||
@@ -729,6 +935,156 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
feeTx.getFieldAmount(sfReserveIncrementDrops) == XRPAmount{setup.ownerReserve});
|
||||
}
|
||||
|
||||
void
|
||||
testDoVotingSmartEscrow()
|
||||
{
|
||||
testcase("doVoting with Smart Escrow");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, testableAmendments() | featureXRPFees | featureSmartEscrow);
|
||||
|
||||
// establish what the current fees are
|
||||
BEAST_EXPECT(env.current()->fees().base == XRPAmount{UNIT_TEST_REFERENCE_FEE});
|
||||
BEAST_EXPECT(env.current()->fees().reserve == XRPAmount{200'000'000});
|
||||
BEAST_EXPECT(env.current()->fees().increment == XRPAmount{50'000'000});
|
||||
BEAST_EXPECT(env.current()->fees().gasLimit == 0);
|
||||
BEAST_EXPECT(env.current()->fees().bytecodeSizeLimit == 0);
|
||||
BEAST_EXPECT(env.current()->fees().gasPrice == 0);
|
||||
|
||||
auto const createFeeTxFromVoting =
|
||||
[&](FeeSetup const& setup) -> std::pair<STTx, std::shared_ptr<Ledger>> {
|
||||
auto feeVote = makeFeeVote(setup, env.app().getJournal("FeeVote"));
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
env.app().config().fees.toFees(),
|
||||
std::vector<uint256>{},
|
||||
env.app().getNodeFamily());
|
||||
|
||||
// doVoting requires a flag ledger (every 256th ledger)
|
||||
// We need to create a ledger at sequence 256 to make it a flag
|
||||
// ledger
|
||||
for (int i = 0; i < 256 - 1; ++i)
|
||||
{
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().getTimeKeeper().closeTime());
|
||||
}
|
||||
BEAST_EXPECT(ledger->isFlagLedger());
|
||||
|
||||
// Create some mock validations with fee votes
|
||||
std::vector<std::shared_ptr<STValidation>> validations;
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
auto sec = randomSecretKey();
|
||||
auto pub = derivePublicKey(KeyType::Secp256k1, sec);
|
||||
|
||||
auto val = std::make_shared<STValidation>(
|
||||
env.app().getTimeKeeper().now(),
|
||||
pub,
|
||||
sec,
|
||||
calcNodeID(pub),
|
||||
[&](STValidation& v) {
|
||||
v.setFieldU32(sfLedgerSequence, ledger->seq());
|
||||
// Vote for different fees than current
|
||||
v.setFieldAmount(sfBaseFeeDrops, XRPAmount{setup.referenceFee});
|
||||
v.setFieldAmount(sfReserveBaseDrops, XRPAmount{setup.accountReserve});
|
||||
v.setFieldAmount(sfReserveIncrementDrops, XRPAmount{setup.ownerReserve});
|
||||
v.setFieldU32(sfGasLimit, setup.gasLimit);
|
||||
v.setFieldU32(sfBytecodeSizeLimit, setup.bytecodeSizeLimit);
|
||||
v.setFieldU32(sfGasPrice, setup.gasPrice);
|
||||
});
|
||||
if (i % 2)
|
||||
val->setTrusted();
|
||||
validations.push_back(val);
|
||||
}
|
||||
|
||||
auto txSet =
|
||||
std::make_shared<SHAMap>(SHAMapType::TRANSACTION, env.app().getNodeFamily());
|
||||
|
||||
// This should not throw since we have a flag ledger
|
||||
feeVote->doVoting(ledger, validations, txSet);
|
||||
|
||||
auto const txs = getTxs(txSet);
|
||||
BEAST_EXPECT(txs.size() == 1);
|
||||
return {txs[0], ledger};
|
||||
};
|
||||
|
||||
auto checkFeeTx = [&](FeeSetup const& setup,
|
||||
STTx const& feeTx,
|
||||
std::shared_ptr<Ledger> const& ledger,
|
||||
std::source_location const loc = std::source_location::current()) {
|
||||
auto const line = " (" + std::to_string(loc.line()) + ")";
|
||||
BEAST_EXPECTS(feeTx.getTxnType() == ttFEE, line);
|
||||
|
||||
BEAST_EXPECTS(feeTx.getAccountID(sfAccount) == AccountID(), line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfLedgerSequence) == ledger->seq() + 1, line);
|
||||
|
||||
BEAST_EXPECTS(feeTx.isFieldPresent(sfBaseFeeDrops), line);
|
||||
BEAST_EXPECTS(feeTx.isFieldPresent(sfReserveBaseDrops), line);
|
||||
BEAST_EXPECTS(feeTx.isFieldPresent(sfReserveIncrementDrops), line);
|
||||
|
||||
// The legacy fields should NOT be present
|
||||
BEAST_EXPECTS(!feeTx.isFieldPresent(sfBaseFee), line);
|
||||
BEAST_EXPECTS(!feeTx.isFieldPresent(sfReserveBase), line);
|
||||
BEAST_EXPECTS(!feeTx.isFieldPresent(sfReserveIncrement), line);
|
||||
BEAST_EXPECTS(!feeTx.isFieldPresent(sfReferenceFeeUnits), line);
|
||||
|
||||
// Check the values
|
||||
BEAST_EXPECTS(
|
||||
feeTx.getFieldAmount(sfBaseFeeDrops) == XRPAmount{setup.referenceFee}, line);
|
||||
BEAST_EXPECTS(
|
||||
feeTx.getFieldAmount(sfReserveBaseDrops) == XRPAmount{setup.accountReserve}, line);
|
||||
BEAST_EXPECTS(
|
||||
feeTx.getFieldAmount(sfReserveIncrementDrops) == XRPAmount{setup.ownerReserve},
|
||||
line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfGasLimit) == setup.gasLimit, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfBytecodeSizeLimit) == setup.bytecodeSizeLimit, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfGasPrice) == setup.gasPrice, line);
|
||||
};
|
||||
|
||||
{
|
||||
FeeSetup setup;
|
||||
setup.referenceFee = 42;
|
||||
setup.accountReserve = 1234567;
|
||||
setup.ownerReserve = 7654321;
|
||||
setup.gasLimit = 100;
|
||||
setup.bytecodeSizeLimit = 200;
|
||||
setup.gasPrice = 300;
|
||||
auto const [feeTx, ledger] = createFeeTxFromVoting(setup);
|
||||
|
||||
checkFeeTx(setup, feeTx, ledger);
|
||||
}
|
||||
|
||||
{
|
||||
FeeSetup setup;
|
||||
setup.referenceFee = 42;
|
||||
setup.accountReserve = 1234567;
|
||||
setup.ownerReserve = 7654321;
|
||||
setup.gasLimit = 0;
|
||||
setup.bytecodeSizeLimit = 0;
|
||||
setup.gasPrice = 300;
|
||||
auto const [feeTx, ledger] = createFeeTxFromVoting(setup);
|
||||
|
||||
checkFeeTx(setup, feeTx, ledger);
|
||||
}
|
||||
|
||||
{
|
||||
FeeSetup setup;
|
||||
setup.referenceFee = 42;
|
||||
setup.accountReserve = 1234567;
|
||||
setup.ownerReserve = 7654321;
|
||||
setup.gasLimit = kMaxGasLimit + 1;
|
||||
setup.bytecodeSizeLimit = kMaxBytecodeSizeLimit + 1;
|
||||
setup.gasPrice = 300;
|
||||
auto const [feeTx, ledger] = createFeeTxFromVoting(setup);
|
||||
|
||||
setup.gasLimit = ledger->fees().gasLimit;
|
||||
setup.bytecodeSizeLimit = ledger->fees().bytecodeSizeLimit;
|
||||
checkFeeTx(setup, feeTx, ledger);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -742,6 +1098,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
testSingleInvalidTransaction();
|
||||
testDoValidation();
|
||||
testDoVoting();
|
||||
testDoVotingSmartEscrow();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/escrow.h>
|
||||
@@ -3930,36 +3930,28 @@ struct HostFuncImpl_test : public beast::unit_test::Suite
|
||||
|
||||
int const normalExp = 18;
|
||||
|
||||
Bytes const floatIntMin = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00}; // -2^63 (rounds to nearest: -(2^63-1)) Bytes const floatIntZero = {0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}; // 0 Bytes const floatIntMax =
|
||||
{0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}; // 2^63-1 Bytes const
|
||||
floatUIntMax = {0x19, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x00, 0x00, 0x00, 0x01}; //
|
||||
2^64-1
|
||||
Bytes const floatIntMin = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}; // -2^63 (rounds to nearest: -(2^63-1))
|
||||
Bytes const floatIntZero = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}; // 0
|
||||
Bytes const floatIntMax = {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}; // 2^63-1
|
||||
Bytes const floatUIntMax = {0x19, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x00, 0x00, 0x00, 0x01}; // 2^64-1
|
||||
|
||||
Bytes const floatMaxExp = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0x00, 0x00,
|
||||
0x80, 0x00}; // 1e(Number::kMaxExponent + normalExp) Bytes const floatPreMaxExp = {0x0D, 0xE0,
|
||||
0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF}; // 1e(Number::kMaxExponent + normalExp
|
||||
- 1) Bytes const floatMinusMaxExp = {0xF2, 0x1F, 0x49, 0x4C, 0x58, 0x9C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x80, 0x00}; // -1e(Number::kMaxExponent + normalExp) Bytes const floatMinExp = {0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}; // 1e(Number::kMinExponent -
|
||||
normalExp) Bytes const floatMax = {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
|
||||
0x00, 0x80, 0x00}; // Number::kMaxRep e(Number::kMaxExponent - normalExp)
|
||||
Bytes const floatMaxExp = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00}; // 1e(Number::kMaxExponent + normalExp)
|
||||
Bytes const floatPreMaxExp = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF}; // 1e(Number::kMaxExponent + normalExp - 1)
|
||||
Bytes const floatMinusMaxExp = {0xF2, 0x1F, 0x49, 0x4C, 0x58, 0x9C, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00}; // -1e(Number::kMaxExponent + normalExp)
|
||||
Bytes const floatMinExp = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}; // 1e(Number::kMinExponent - normalExp)
|
||||
Bytes const floatMax = {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x80, 0x00}; // Number::kMaxRep e(Number::kMaxExponent - normalExp)
|
||||
|
||||
Bytes const floatMaxIOU = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x63, 0xFF, 0x9C, 0x00, 0x00,
|
||||
0x00, 0x4E}; // 9999999999999999e(96) Bytes const floatMinIOU = {0x0D, 0xE0, 0xB6, 0xB3,
|
||||
0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x9D}; // 1e(-96 - 3 + normalExp = -81)
|
||||
Bytes const floatMaxIOU = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x63, 0xFF, 0x9C, 0x00, 0x00, 0x00, 0x4E}; // 9999999999999999e(96)
|
||||
Bytes const floatMinIOU = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x9D}; // 1e(-96 - 3 + normalExp = -81)
|
||||
|
||||
Bytes const float1 = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF,
|
||||
0xFF, 0xEE}; // 1 Bytes const floatMinus1 = {0xF2, 0x1F, 0x49, 0x4C, 0x58, 0x9C, 0x00,
|
||||
0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // -1 Bytes const float1More = {0x0D, 0xE0, 0xB6, 0xB3,
|
||||
0xA7, 0x64, 0x03, 0xE8, 0xFF, 0xFF, 0xFF, 0xEE}; // 1.000 000 000 000 001 Bytes const float2 =
|
||||
{0x1B, 0xC1, 0x6D, 0x67, 0x4E, 0xC8, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // 2 Bytes const float10
|
||||
= {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEF}; // 10 Bytes const
|
||||
floatPi = {0x2B, 0x99, 0x2D, 0xDF, 0xA2, 0x32, 0x48, 0xE8, 0xFF, 0xFF, 0xFF, 0xEE};
|
||||
// 3.141592653589793 Bytes const floatInvalidZero = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x81, 0x00, 0x00, 0x00}; // INVALID Bytes const floatMinus3 = {0xD6, 0x5D, 0xDB,
|
||||
0xE5, 0x09, 0xD4, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // -3
|
||||
Bytes const float1 = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // 1
|
||||
Bytes const floatMinus1 = {0xF2, 0x1F, 0x49, 0x4C, 0x58, 0x9C, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // -1
|
||||
Bytes const float1More = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x03, 0xE8, 0xFF, 0xFF, 0xFF, 0xEE}; // 1.000 000 000 000 001
|
||||
Bytes const float2 = {0x1B, 0xC1, 0x6D, 0x67, 0x4E, 0xC8, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // 2
|
||||
Bytes const float10 = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEF}; // 10
|
||||
Bytes const floatPi = {0x2B, 0x99, 0x2D, 0xDF, 0xA2, 0x32, 0x48, 0xE8, 0xFF, 0xFF, 0xFF, 0xEE}; // 3.141592653589793
|
||||
Bytes const floatInvalidZero = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00}; // INVALID
|
||||
Bytes const floatMinus3 = {0xD6, 0x5D, 0xDB, 0xE5, 0x09, 0xD4, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // -3
|
||||
|
||||
std::string const invalid = "invalid_data";
|
||||
|
||||
@@ -6444,4 +6436,3 @@ floatPi = {0x2B, 0x99, 0x2D, 0xDF, 0xA2, 0x32, 0x48, 0xE8, 0xFF, 0xF
|
||||
BEAST_DEFINE_TESTSUITE(HostFuncImpl, app, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
*/
|
||||
|
||||
@@ -43,6 +43,12 @@ struct PseudoTx_test : public beast::unit_test::Suite
|
||||
obj[sfReserveIncrement] = 0;
|
||||
obj[sfReferenceFeeUnits] = 0;
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
obj[sfGasLimit] = 0;
|
||||
obj[sfBytecodeSizeLimit] = 0;
|
||||
obj[sfGasPrice] = 0;
|
||||
}
|
||||
});
|
||||
|
||||
res.emplace_back(ttAMENDMENT, [&](auto& obj) {
|
||||
@@ -107,7 +113,9 @@ struct PseudoTx_test : public beast::unit_test::Suite
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
FeatureBitset const xrpFees{featureXRPFees};
|
||||
|
||||
testPrevented(all - featureXRPFees - featureSmartEscrow);
|
||||
testPrevented(all - featureXRPFees);
|
||||
testPrevented(all - featureSmartEscrow);
|
||||
testPrevented(all);
|
||||
testAllowed();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -379,7 +379,7 @@ fn test_any_ledger_object_functions() -> i32 {
|
||||
let escrow_finish = EscrowFinish;
|
||||
let account_id = escrow_finish.get_account().unwrap();
|
||||
|
||||
// Test 4.1: cache_ledger_obj() - Cache a ledger object
|
||||
// Test 4.1: cache_le() - Cache a ledger object
|
||||
let mut keylet_buffer = [0u8; 32];
|
||||
let keylet_result = unsafe {
|
||||
host::accountroot_id(
|
||||
@@ -402,7 +402,7 @@ fn test_any_ledger_object_functions() -> i32 {
|
||||
|
||||
if cache_result <= 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: cache_ledger_obj failed (expected with test fixtures):",
|
||||
"INFO: cache_le failed (expected with test fixtures):",
|
||||
cache_result as i64,
|
||||
);
|
||||
// Test fixtures may not contain the account object - this is expected
|
||||
@@ -411,7 +411,7 @@ fn test_any_ledger_object_functions() -> i32 {
|
||||
// Test 4.2-4.5 with invalid slot (should fail gracefully)
|
||||
let mut test_buffer = [0u8; 32];
|
||||
|
||||
// Test get_ledger_obj_field with invalid slot
|
||||
// Test le_field with invalid slot
|
||||
let field_result = unsafe {
|
||||
host::le_field(
|
||||
1,
|
||||
@@ -422,12 +422,12 @@ fn test_any_ledger_object_functions() -> i32 {
|
||||
};
|
||||
if field_result < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_ledger_obj_field failed as expected (no cached object):",
|
||||
"INFO: le_field failed as expected (no cached object):",
|
||||
field_result as i64,
|
||||
);
|
||||
}
|
||||
|
||||
// Test get_ledger_obj_nested_field with invalid slot
|
||||
// Test le_inner_field with invalid slot
|
||||
let locator = [
|
||||
0x01_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8,
|
||||
]; // Two int32s in little-endian: [1, 0]
|
||||
@@ -442,16 +442,16 @@ fn test_any_ledger_object_functions() -> i32 {
|
||||
};
|
||||
if nested_result < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_ledger_obj_nested_field failed as expected:",
|
||||
"INFO: le_inner_field failed as expected:",
|
||||
nested_result as i64,
|
||||
);
|
||||
}
|
||||
|
||||
// Test get_ledger_obj_array_len with invalid slot
|
||||
// Test le_inner_arr_len with invalid slot
|
||||
let array_result = unsafe { host::le_arr_len(1, sfield::Signers.into()) };
|
||||
if array_result < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_ledger_obj_array_len failed as expected:",
|
||||
"INFO: le_inner_arr_len failed as expected:",
|
||||
array_result as i64,
|
||||
);
|
||||
}
|
||||
@@ -474,7 +474,7 @@ fn test_any_ledger_object_functions() -> i32 {
|
||||
let slot = cache_result;
|
||||
let _ = trace_num("Successfully cached object in slot:", slot as i64);
|
||||
|
||||
// Test 4.2: get_ledger_obj_field() - Access field from cached object
|
||||
// Test 4.2: le_field() - Access field from cached object
|
||||
let mut cached_balance_buffer = [0u8; 8];
|
||||
let cached_balance_result = unsafe {
|
||||
host::le_field(
|
||||
@@ -487,7 +487,7 @@ fn test_any_ledger_object_functions() -> i32 {
|
||||
|
||||
if cached_balance_result <= 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_ledger_obj_field(Balance) failed:",
|
||||
"INFO: le_field(Balance) failed:",
|
||||
cached_balance_result as i64,
|
||||
);
|
||||
} else if cached_balance_result == 8 {
|
||||
@@ -510,7 +510,7 @@ fn test_any_ledger_object_functions() -> i32 {
|
||||
);
|
||||
}
|
||||
|
||||
// Test 4.3: get_ledger_obj_nested_field() - Nested field from cached object
|
||||
// Test 4.3: le_inner_field() - Nested field from cached object
|
||||
let locator = [
|
||||
0x01_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8,
|
||||
]; // Two int32s in little-endian: [1, 0]
|
||||
@@ -527,7 +527,7 @@ fn test_any_ledger_object_functions() -> i32 {
|
||||
|
||||
if cached_nested_result < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_ledger_obj_nested_field not applicable:",
|
||||
"INFO: le_inner_field not applicable:",
|
||||
cached_nested_result as i64,
|
||||
);
|
||||
} else {
|
||||
@@ -538,7 +538,7 @@ fn test_any_ledger_object_functions() -> i32 {
|
||||
);
|
||||
}
|
||||
|
||||
// Test 4.4: get_ledger_obj_array_len() - Array length from cached object
|
||||
// Test 4.4: le_inner_arr_len() - Array length from cached object
|
||||
let cached_array_len = unsafe { host::le_arr_len(slot, sfield::Signers.into()) };
|
||||
let _ = trace_num(
|
||||
"Cached object Signers array length:",
|
||||
|
||||
@@ -185,13 +185,28 @@ def process_c(project_name):
|
||||
|
||||
|
||||
def wat_to_wasm(wat_path, wasm_path):
|
||||
build_cmd = ["wat2wasm", wat_path, "-o", wasm_path]
|
||||
build_cmd = ["wat2wasm", "--enable-all", wat_path, "-o", wasm_path]
|
||||
try:
|
||||
subprocess.run(build_cmd, check=True)
|
||||
print(f"WASM file for {os.path.basename(wat_path)} has been built.")
|
||||
return
|
||||
except FileNotFoundError:
|
||||
print("exec error: wat2wasm is required to build WAT fixtures")
|
||||
sys.exit(1)
|
||||
except subprocess.CalledProcessError:
|
||||
# wat2wasm (wabt) does not support some proposal text syntax such as
|
||||
# the GC instructions, so fall back to wasm-tools which does.
|
||||
pass
|
||||
|
||||
fallback_cmd = ["wasm-tools", "parse", wat_path, "-o", wasm_path]
|
||||
try:
|
||||
subprocess.run(fallback_cmd, check=True)
|
||||
print(
|
||||
f"WASM file for {os.path.basename(wat_path)} has been built with wasm-tools."
|
||||
)
|
||||
except FileNotFoundError:
|
||||
print("exec error: wasm-tools is required to build this WAT fixture")
|
||||
sys.exit(1)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"exec error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
34
src/test/app/wasm_fixtures/disableFloat.wat
Normal file
34
src/test/app/wasm_fixtures/disableFloat.wat
Normal file
@@ -0,0 +1,34 @@
|
||||
(module
|
||||
(type (;0;) (func))
|
||||
(type (;1;) (func (result i32)))
|
||||
(func (;0;) (type 0))
|
||||
(func (;1;) (type 1) (result i32)
|
||||
f32.const -2048
|
||||
f32.const 2050
|
||||
f32.sub
|
||||
drop
|
||||
i32.const 1)
|
||||
(memory (;0;) 2)
|
||||
(global (;0;) i32 (i32.const 1024))
|
||||
(global (;1;) i32 (i32.const 1024))
|
||||
(global (;2;) i32 (i32.const 2048))
|
||||
(global (;3;) i32 (i32.const 2048))
|
||||
(global (;4;) i32 (i32.const 67584))
|
||||
(global (;5;) i32 (i32.const 1024))
|
||||
(global (;6;) i32 (i32.const 67584))
|
||||
(global (;7;) i32 (i32.const 131072))
|
||||
(global (;8;) i32 (i32.const 0))
|
||||
(global (;9;) i32 (i32.const 1))
|
||||
(export "memory" (memory 0))
|
||||
(export "__wasm_call_ctors" (func 0))
|
||||
(export "escrow_finish" (func 1))
|
||||
(export "buf" (global 0))
|
||||
(export "__dso_handle" (global 1))
|
||||
(export "__data_end" (global 2))
|
||||
(export "__stack_low" (global 3))
|
||||
(export "__stack_high" (global 4))
|
||||
(export "__global_base" (global 5))
|
||||
(export "__heap_base" (global 6))
|
||||
(export "__heap_end" (global 7))
|
||||
(export "__memory_base" (global 8))
|
||||
(export "__table_base" (global 9)))
|
||||
11
src/test/app/wasm_fixtures/fib.c
Normal file
11
src/test/app/wasm_fixtures/fib.c
Normal file
@@ -0,0 +1,11 @@
|
||||
// typedef long long mint;
|
||||
typedef int mint;
|
||||
|
||||
mint fib(mint n)
|
||||
{
|
||||
if (!n)
|
||||
return 0;
|
||||
if (n <= 2)
|
||||
return 1;
|
||||
return fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
2240
src/test/app/wasm_fixtures/fixture_functions_5k.cpp
Normal file
2240
src/test/app/wasm_fixtures/fixture_functions_5k.cpp
Normal file
File diff suppressed because it is too large
Load Diff
2128
src/test/app/wasm_fixtures/fixture_locals_10k.cpp
Normal file
2128
src/test/app/wasm_fixtures/fixture_locals_10k.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,23 @@
|
||||
// Not built. The only reader of these blobs is the disabled suite in Wasm_test.cpp, so they
|
||||
// are left out of the build and cost neither a translation unit nor static-init time.
|
||||
// Regenerate the hex with copyFixtures.py.
|
||||
//
|
||||
// TODO: consider moving these to separate files (and figure out the build)
|
||||
|
||||
/*
|
||||
#include <test/app/wasm_fixtures/fixtures.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
extern std::string const kFibWasmHex =
|
||||
"0061736d0100000001090260000060017f017f030302000105030100020638097f004180080b7f004180080b7f0041"
|
||||
"80080b7f00418088040b7f004180080b7f00418088040b7f00418080080b7f0041000b7f0041010b07a7010c066d65"
|
||||
"6d6f72790200115f5f7761736d5f63616c6c5f63746f727300000366696200010c5f5f64736f5f68616e646c650300"
|
||||
"0a5f5f646174615f656e6403010b5f5f737461636b5f6c6f7703020c5f5f737461636b5f6869676803030d5f5f676c"
|
||||
"6f62616c5f6261736503040b5f5f686561705f6261736503050a5f5f686561705f656e6403060d5f5f6d656d6f7279"
|
||||
"5f6261736503070c5f5f7461626c655f6261736503080a440202000b3f01017f200045044041000f0b200041034804"
|
||||
"4041010f0b200041026a21000340200041036b100120016a2101200041026b220041044a0d000b200141016a0b007f"
|
||||
"0970726f647563657273010c70726f6365737365642d62790105636c616e675f31392e312e352d776173692d73646b"
|
||||
"202868747470733a2f2f6769746875622e636f6d2f6c6c766d2f6c6c766d2d70726f6a656374206162346235613264"
|
||||
"62353832393538616631656533303861373930636664623432626432343732302900490f7461726765745f66656174"
|
||||
"75726573042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f7265666572656e63652d747970"
|
||||
"65732b0a6d756c746976616c7565";
|
||||
|
||||
extern std::string const kLedgerSqnWasmHex =
|
||||
"0061736d01000000010e0360027f7f017f6000006000017f02120103656e760a6c6467725f696e6465780000030302"
|
||||
"01020503010002063f0a7f01418088040b7f004180080b7f004180080b7f004180080b7f00418088040b7f00418008"
|
||||
@@ -204,6 +213,10 @@ extern std::string const kAllHostFunctionsWasmHex =
|
||||
"39352e30202835393830373631366520323032362d30342d313429002c0f7461726765745f6665617475726573022b"
|
||||
"0f6d757461626c652d676c6f62616c732b087369676e2d657874";
|
||||
|
||||
extern std::string const kDeepRecursionHex =
|
||||
"0061736d010000000105016000017f030201000608017f0141c0843d0b0711010d657363726f775f66696e69736800"
|
||||
"000a16011400230045044041010f0b230041016b240010000b";
|
||||
|
||||
extern std::string const kAllKeyletsWasmHex =
|
||||
"0061736d0100000001500a60067f7f7f7f7f7f017f60047f7f7f7f017f60087f7f7f7f7f7f7f7f017f60047f7f7f7f"
|
||||
"0060037f7f7f017f60037f7f7e017f60057f7f7f7f7f017f6000017f60037f7f7f0060067f7f7f7f7f7e00029f0418"
|
||||
@@ -634,6 +647,487 @@ extern std::string const kCodecovTestsWasmHex =
|
||||
"6365737365642d6279010572757374631d312e39352e30202835393830373631366520323032362d30342d31342900"
|
||||
"2c0f7461726765745f6665617475726573022b0f6d757461626c652d676c6f62616c732b087369676e2d657874";
|
||||
|
||||
extern std::string const kFloatTestsWasmHex =
|
||||
"0061736d0100000001490960057f7f7f7f7f017f60077f7f7f7f7f7f7f017f60067f7f7f7f7f7f017f60047e7f7f7f"
|
||||
"017f60057e7f7f7f7f017f60047f7f7f7f017f60037f7f7e017f60037f7f7f006000017f02ea021008686f73745f6c"
|
||||
"6962057472616365000008686f73745f6c69620e666c6f61745f66726f6d5f696e74000308686f73745f6c69620f66"
|
||||
"6c6f61745f66726f6d5f75696e74000003656e7613666c6f61745f66726f6d5f6d616e745f657870000408686f7374"
|
||||
"5f6c696209666c6f61745f636d70000508686f73745f6c696209666c6f61745f616464000108686f73745f6c696209"
|
||||
"666c6f61745f737562000108686f73745f6c69620a666c6f61745f6d756c74000108686f73745f6c696209666c6f61"
|
||||
"745f646976000108686f73745f6c696209666c6f61745f706f77000208686f73745f6c69620974726163655f6e756d"
|
||||
"000608686f73745f6c69620a666c6f61745f726f6f74000203656e760c666c6f61745f746f5f696e74000003656e76"
|
||||
"11666c6f61745f746f5f6d616e745f657870000203656e7613666c6f61745f66726f6d5f7374616d6f756e74000003"
|
||||
"656e7613666c6f61745f66726f6d5f73746e756d6265720000030302070805030100110619037f01418080c0000b7f"
|
||||
"00418599c0000b7f00419099c0000b073504066d656d6f727902000d657363726f775f66696e69736800110a5f5f64"
|
||||
"6174615f656e6403010b5f5f686561705f6261736503020aec20021f002000200141014100410010001a418080c000"
|
||||
"41022002410c410110001a0bc920020c7f017e230041f0006b2200240041ee8ac000411d41014100410010001a2000"
|
||||
"4100360268200042003703600240428ce000200041e0006a2202410c410010012201410c460440418b8bc000411720"
|
||||
"02101041a28bc000411e2002410c410110001a0c010b41c08bc000411e41014100410010001a0b2000428ce0003703"
|
||||
"500240200041d0006a4108200041e0006a2202410c41001002410c4604402001410c46210741de8bc0004117200210"
|
||||
"100c010b41f58bc000411e41014100410010001a0b024042fb004102200041e0006a2201410c41001003410c460440"
|
||||
"41938cc0004121200110100c010b41b48cc000412841014100410010001a410021070b41dc8cc000411541be80c000"
|
||||
"101041f18cc0004116418881c0001010418280c000411741014100410010001a200041003602682000420037036002"
|
||||
"404201200041e0006a2202410c410010012201410c460440419980c000410f200210100c010b41a880c00041164101"
|
||||
"4100410010001a0b027f200041e0006a410c41be80c000410c100445044041ca80c000411b41014100410010001a20"
|
||||
"01410c460c010b41e580c000412341014100410010001a41000b21080240200041e0006a410c418881c000410c1004"
|
||||
"4101460440419481c000412341014100410010001a0c010b4100210841b781c000412c41014100410010001a0b0240"
|
||||
"418881c000410c200041e0006a410c1004410246044041e381c000412341014100410010001a0c010b410021084186"
|
||||
"82c000412c41014100410010001a0b419c93c000412041014100410010001a200041c680c000280000360258200041"
|
||||
"be80c000290000370350410921030340200041d0006a2201410c41be80c000410c2001410c410010051a200341016b"
|
||||
"22030d000b2000410036026820004200370360420a200041e0006a410c41001001410c46220945044041bc93c00041"
|
||||
"1741014100410010001a0b0240200041e0006a410c200041d0006a410c100445044041d393c0004114410141004100"
|
||||
"10001a0c010b4100210941e793c000411641014100410010001a0b410b21030340200041d0006a2201410c41be80c0"
|
||||
"00410c2001410c410010061a200341016b22030d000b02402001410c418881c000410c100445044041fd93c0004119"
|
||||
"41014100410010001a0c010b41002109419694c000411b41014100410010001a0b41878dc000411f41014100410010"
|
||||
"001a2000410036021020004200370308420a200041086a410c410010011a200041c680c000280000360220200041be"
|
||||
"80c000290000370318410621030340200041186a2201410c200041086a410c2001410c410010071a200341016b2203"
|
||||
"0d000b200041003602582000420037035042c0843d200041d0006a2202410c410010011a02402002410c2001410c10"
|
||||
"04220145044041a68dc000411941014100410010001a0c010b41bf8dc000411b41014100410010001a0b200145210a"
|
||||
"410721030340200041186a2201410c200041086a410c2001410c410010081a200341016b22030d000b200041003602"
|
||||
"68200042003703604201417f200041e0006a2202410c410010031a02402001410c2002410c100445044041da8dc000"
|
||||
"411741014100410010001a0c010b41f18dc000411941014100410010001a4100210a0b41b282c00041174101410041"
|
||||
"0010001a200041003602202000420037031841be80c000410c4103200041186a2202410c410010091a41c982c00041"
|
||||
"1220021010418881c000410c41062002410c410010091a41db82c00041182002101020004100360258200042003703"
|
||||
"504209200041d0006a2201410c410010011a2001410c41022002410c410010091a41f382c000411420021010200141"
|
||||
"0c41002002410c410010091a418783c00041172002101020004100360268200042003703604200200041e0006a2203"
|
||||
"410c410010011a2003410c41022002410c410010091a419e83c00041142002101041b283c00041382003410c410020"
|
||||
"02410c41001009ac100a1a41ea83c000411841014100410010001a200041003602202000420037031842092002410c"
|
||||
"410010011a20004100360258200042003703502002410c41022001410c4100100b1a418284c0004112200110102002"
|
||||
"410c41032001410c4100100b1a419484c000411220011010200041003602682000420037036042c0843d2003410c41"
|
||||
"0010011a2003410c41032001410c4100100b1a41a684c0004118200110102003410c41062001410c4100100b1a41be"
|
||||
"84c000411c2001101041da84c000411a41014100410010001a20004100360258200042003703502000410036026820"
|
||||
"004200370360420a2003410c410010011a41be80c000410c2003410c2001410c410010081a41f484c0004119200110"
|
||||
"1041be80c000410c2001410c2001410c410010081a418d85c000410f2001101002402003410c2001410c1004220b45"
|
||||
"0440419c85c000411441014100410010001a0c010b41b085c000411641014100410010001a0b4100210141c685c000"
|
||||
"411a41014100410010001a20004200370308024041be80c000410c200041086a41084100100c220241084604402000"
|
||||
"290308220c42015104404101210141e085c000411741014100410010001a0c020b41f785c000411941014100410010"
|
||||
"001a419086c0004108200c100a1a0c010b419886c000412441014100410010001a41bc86c000410f2002ac100a1a0b"
|
||||
"410021030240418881c000410c200041086a41084100100c220241084604402000290308220c427f51044041cb86c0"
|
||||
"00411841014100410010001a200121030c020b41e386c000411a41014100410010001a419086c0004108200c100a1a"
|
||||
"0c010b41fd86c000412541014100410010001a41bc86c000410f2002ac100a1a0b4100210120004100360220200042"
|
||||
"0037031842ffffffffffffffffff00200041186a2202410c410010011a02402002410c200041086a41084100100c22"
|
||||
"0241084604402000290308220c42ffffffffffffffffff0051044041a287c000411e41014100410010001a20032101"
|
||||
"0c020b41c087c000412041014100410010001a41e087c000410d42ffffffffffffffffff00100a1a419086c0004108"
|
||||
"200c100a1a0c010b41ed87c000412b41014100410010001a41bc86c000410f2002ac100a1a0b410021022000410036"
|
||||
"0258200042003703504200200041d0006a2203410c410010011a02402003410c200041086a41084100100c22034108"
|
||||
"4604402000290308220c500440419888c000411741014100410010001a200121020c020b41af88c000411941014100"
|
||||
"410010001a419086c0004108200c100a1a0c010b41c888c000412441014100410010001a41bc86c000410f2003ac10"
|
||||
"0a1a0b4100210320004100360268200042003703604201417f200041e0006a2201410c410010031a02402001410c20"
|
||||
"0041086a41084100100c220141084604402000290308220c50044041ec88c000412541014100410010001a20022103"
|
||||
"0c020b419189c000412741014100410010001a419086c0004108200c100a1a0c010b41b889c0004132410141004100"
|
||||
"10001a41bc86c000410f2001ac100a1a0b0240200041e0006a410c200041086a41084101100c220141084604402000"
|
||||
"290308220c50044041ea89c000412741014100410010001a0c020b4100210341918ac000412941014100410010001a"
|
||||
"419086c0004108200c100a1a0c010b4100210341ba8ac000413441014100410010001a41bc86c000410f2001ac100a"
|
||||
"1a0b41002101418a8ec000411f41014100410010001a2000420037032820004100360234024041be80c000410c2000"
|
||||
"41286a4108200041346a4104100d2202410c46044020002802342202416e462000290328220c42808090bbbad6adf0"
|
||||
"0d517145044041c58ec000411e41014100410010001a41e38ec000412f200c100a1a41928fc000411f2002ac100a1a"
|
||||
"0c020b4101210141a98ec000411c41014100410010001a0c010b41b18fc000412941014100410010001a41bc86c000"
|
||||
"410f2002ac100a1a0b2000420037033841002102200041003602440240418881c000410c200041386a4108200041c4"
|
||||
"006a4104100d2204410c46044020002802442204416e462000290338220c428080f0c4c5a9d28f72517145044041f7"
|
||||
"8fc000411f41014100410010001a419690c0004130200c100a1a41928fc000411f2004ac100a1a0c020b41da8fc000"
|
||||
"411d41014100410010001a200121020c010b41c690c000412a41014100410010001a41bc86c000410f2004ac100a1a"
|
||||
"0b410021012000410036025820004200370350420a200041d0006a2204410c410010011a2000420037030820004100"
|
||||
"36024802402004410c200041086a4108200041c8006a4104100d2204410c46044020002802482204416f4620002903"
|
||||
"08220c42808090bbbad6adf00d5171450440418d91c000411f41014100410010001a41e38ec000412f200c100a1a41"
|
||||
"ac91c000411f2004ac100a1a0c020b41f090c000411d41014100410010001a200221010c010b41cb91c000412a4101"
|
||||
"4100410010001a41bc86c000410f2004ac100a1a0b4100210220004100360268200042003703604200200041e0006a"
|
||||
"2204410c410010011a200042003703182000410036024c02402004410c200041186a4108200041cc006a4104100d22"
|
||||
"04410c4604402000290318220c50200028024c22044180808080784671450440419192c000411e4101410041001000"
|
||||
"1a41af92c000411d200c100a1a41cc92c00041272004ac100a1a0c020b41f591c000411c41014100410010001a2001"
|
||||
"21020c010b41f392c000412941014100410010001a41bc86c000410f2004ac100a1a0b4100210141b194c000412141"
|
||||
"014100410010001a200042c0808080d0a0fdf000370018200041003602682000420037036002400240200041186a41"
|
||||
"08200041e0006a2205410c4100100e2204410c46044041d294c000412220051010200042003703502005410c200041"
|
||||
"d0006a41084100100c22044108470d012000290350220c4280c2d72f5104404101210141f494c000411d4101410041"
|
||||
"0010001a0c030b419195c000411f41014100410010001a41b095c000411c200c100a1a0c020b418096c000411f4101"
|
||||
"4100410010001a419f96c00041102004ac100a1a0c010b41cc95c000413441014100410010001a41bc86c000410f20"
|
||||
"04ac100a1a0b4100210441af96c000412141014100410010001a200041ffffff8f7f36005820004281eceedce494cc"
|
||||
"f9c000370050200041003602682000420037036002400240200041d0006a410c200041e0006a2206410c4100100f22"
|
||||
"05410c46044041d096c000411c20061010200042003703182006410c200041186a41084100100c22054108470d0120"
|
||||
"00290318220c42fb005104404101210441ec96c000411b41014100410010001a0c030b418797c000411d4101410041"
|
||||
"0010001a41a497c0004116200c100a1a0c020b41ec97c000411d41014100410010001a419f96c00041102005ac100a"
|
||||
"1a0c010b41ba97c000413241014100410010001a41bc86c000410f2005ac100a1a0b41002105024041be80c000410c"
|
||||
"200041e0006a2206410c4100100f410c460440418998c000411a200610102006410c41be80c000410c100445044041"
|
||||
"a398c000412041014100410010001a200421050c020b41c398c000412241014100410010001a0c010b41e598c00041"
|
||||
"2041014100410010001a0b200041f0006a2400200b452007200871200971200a71200371200271200171200571710b"
|
||||
"0b8f190100418080c0000b851920200a24242420746573745f666c6f61745f636d70202424242020666c6f61742066"
|
||||
"726f6d20313a2020666c6f61742066726f6d20313a206661696c65640de0b6b3a7640000ffffffee2020666c6f6174"
|
||||
"2066726f6d2031203d3d20464c4f41545f4f4e452020666c6f61742066726f6d203120213d20464c4f41545f4f4e45"
|
||||
"2c206661696c6564f21f494c589c0000ffffffee2020666c6f61742066726f6d2031203e20464c4f41545f4e454741"
|
||||
"544956455f4f4e452020666c6f61742066726f6d203120213e20464c4f41545f4e454741544956455f4f4e452c2066"
|
||||
"61696c65642020464c4f41545f4e454741544956455f4f4e45203c20666c6f61742066726f6d20312020464c4f4154"
|
||||
"5f4e454741544956455f4f4e4520213c20666c6f61742066726f6d20312c206661696c65640a24242420746573745f"
|
||||
"666c6f61745f706f77202424242020666c6f61742063756265206f6620313a2020666c6f61742036746820706f7765"
|
||||
"72206f66202d313a2020666c6f617420737175617265206f6620393a2020666c6f61742030746820706f776572206f"
|
||||
"6620393a2020666c6f617420737175617265206f6620303a2020666c6f61742030746820706f776572206f66203020"
|
||||
"28657870656374696e6720494e56414c49445f504152414d53206572726f72293a0a24242420746573745f666c6f61"
|
||||
"745f726f6f74202424242020666c6f61742073717274206f6620393a2020666c6f61742063627274206f6620393a20"
|
||||
"20666c6f61742063627274206f6620313030303030303a2020666c6f61742036746820726f6f74206f662031303030"
|
||||
"3030303a0a24242420746573745f666c6f61745f696e76657274202424242020696e76657274206120666c6f617420"
|
||||
"66726f6d2031303a2020696e7665727420616761696e3a2020696e766572742074776963653a20676f6f642020696e"
|
||||
"766572742074776963653a206661696c65640a24242420746573745f666c6f61745f746f5f696e7420242424202066"
|
||||
"6c6f61745f746f5f696e742831293a20676f6f642020666c6f61745f746f5f696e742831293a206661696c65642020"
|
||||
"2020676f743a2020666c6f61745f746f5f696e742831293a206661696c65642077697468206572726f722020202065"
|
||||
"72726f7220636f64653a2020666c6f61745f746f5f696e74282d31293a20676f6f642020666c6f61745f746f5f696e"
|
||||
"74282d31293a206661696c65642020666c6f61745f746f5f696e74282d31293a206661696c65642077697468206572"
|
||||
"726f722020666c6f61745f746f5f696e74286936343a3a4d4158293a20676f6f642020666c6f61745f746f5f696e74"
|
||||
"286936343a3a4d4158293a206661696c65642020202065787065637465643a2020666c6f61745f746f5f696e742869"
|
||||
"36343a3a4d4158293a206661696c65642077697468206572726f722020666c6f61745f746f5f696e742830293a2067"
|
||||
"6f6f642020666c6f61745f746f5f696e742830293a206661696c65642020666c6f61745f746f5f696e742830293a20"
|
||||
"6661696c65642077697468206572726f722020666c6f61745f746f5f696e7428302e312c20746f5f6e656172657374"
|
||||
"293a20676f6f642020666c6f61745f746f5f696e7428302e312c20746f5f6e656172657374293a206661696c656420"
|
||||
"20666c6f61745f746f5f696e7428302e312c20746f5f6e656172657374293a206661696c6564207769746820657272"
|
||||
"6f722020666c6f61745f746f5f696e7428302e312c20746f77617264735f7a65726f293a20676f6f642020666c6f61"
|
||||
"745f746f5f696e7428302e312c20746f77617264735f7a65726f293a206661696c65642020666c6f61745f746f5f69"
|
||||
"6e7428302e312c20746f77617264735f7a65726f293a206661696c65642077697468206572726f720a242424207465"
|
||||
"73745f666c6f61745f66726f6d5f7761736d202424242020666c6f61742066726f6d206936342031323330303a2020"
|
||||
"666c6f61742066726f6d20693634203132333030206173204845583a2020666c6f61742066726f6d20693634203132"
|
||||
"3330303a206661696c65642020666c6f61742066726f6d207536342031323330303a2020666c6f61742066726f6d20"
|
||||
"7536342031323330303a206661696c65642020666c6f61742066726f6d2065787020322c206d616e74697373612031"
|
||||
"32333a2020666c6f61742066726f6d2065787020322c206d616e7469737361203132333a206661696c65642020666c"
|
||||
"6f61742066726f6d20636f6e737420313a2020666c6f61742066726f6d20636f6e7374202d313a0a24242420746573"
|
||||
"745f666c6f61745f6d756c745f6469766964652024242420207265706561746564206d756c7469706c793a20676f6f"
|
||||
"6420207265706561746564206d756c7469706c793a206661696c656420207265706561746564206469766964653a20"
|
||||
"676f6f6420207265706561746564206469766964653a206661696c65640a24242420746573745f666c6f61745f746f"
|
||||
"5f6d616e745f657870202424242020666c6f61745f746f5f6d616e745f6578702831293a20676f6f642020666c6f61"
|
||||
"745f746f5f6d616e745f6578702831293a206661696c6564202020206578706563746564206d616e74697373612031"
|
||||
"3030303030303030303030303030303030302c20676f743a202020206578706563746564206578706f6e656e74202d"
|
||||
"31382c20676f743a2020666c6f61745f746f5f6d616e745f6578702831293a206661696c6564207769746820657272"
|
||||
"6f722020666c6f61745f746f5f6d616e745f657870282d31293a20676f6f642020666c6f61745f746f5f6d616e745f"
|
||||
"657870282d31293a206661696c6564202020206578706563746564206d616e7469737361202d313030303030303030"
|
||||
"303030303030303030302c20676f743a2020666c6f61745f746f5f6d616e745f657870282d31293a206661696c6564"
|
||||
"2077697468206572726f722020666c6f61745f746f5f6d616e745f657870283130293a20676f6f642020666c6f6174"
|
||||
"5f746f5f6d616e745f657870283130293a206661696c6564202020206578706563746564206578706f6e656e74202d"
|
||||
"31372c20676f743a2020666c6f61745f746f5f6d616e745f657870283130293a206661696c65642077697468206572"
|
||||
"726f722020666c6f61745f746f5f6d616e745f6578702830293a20676f6f642020666c6f61745f746f5f6d616e745f"
|
||||
"6578702830293a206661696c6564202020206578706563746564206d616e746973736120302c20676f743a20202020"
|
||||
"6578706563746564206578706f6e656e74202d323134373438333634382c20676f743a2020666c6f61745f746f5f6d"
|
||||
"616e745f6578702830293a206661696c65642077697468206572726f720a24242420746573745f666c6f61745f6164"
|
||||
"645f7375627472616374202424242020666c6f61742066726f6d2031303a206661696c656420207265706561746564"
|
||||
"206164643a20676f6f6420207265706561746564206164643a206661696c6564202072657065617465642073756274"
|
||||
"726163743a20676f6f64202072657065617465642073756274726163743a206661696c65640a24242420746573745f"
|
||||
"666c6f61745f66726f6d5f7374616d6f756e74202424242020666c6f61742066726f6d2058525020616d6f756e7420"
|
||||
"2831303020585250293a202058525020616d6f756e7420636f6e76657273696f6e3a20676f6f64202058525020616d"
|
||||
"6f756e7420636f6e76657273696f6e3a206661696c6564202020206578706563746564203130303030303030302c20"
|
||||
"676f743a202058525020616d6f756e7420636f6e76657273696f6e3a206661696c6564202d20666c6f61745f746f5f"
|
||||
"696e74206572726f722020666c6f61742066726f6d2058525020616d6f756e743a206661696c656420202020726573"
|
||||
"756c745f73697a653a0a24242420746573745f666c6f61745f66726f6d5f73746e756d626572202424242020666c6f"
|
||||
"61742066726f6d2053544e756d6265722028313233293a202053544e756d62657220636f6e76657273696f6e3a2067"
|
||||
"6f6f64202053544e756d62657220636f6e76657273696f6e3a206661696c6564202020206578706563746564203132"
|
||||
"332c20676f743a202053544e756d62657220636f6e76657273696f6e3a206661696c6564202d20666c6f61745f746f"
|
||||
"5f696e74206572726f722020666c6f61742066726f6d2053544e756d6265723a206661696c65642020666c6f617420"
|
||||
"66726f6d2053544e756d626572202831293a202053544e756d626572283129203d3d20464c4f41545f4f4e453a2067"
|
||||
"6f6f64202053544e756d626572283129203d3d20464c4f41545f4f4e453a206661696c65642020666c6f6174206672"
|
||||
"6f6d2053544e756d6265722831293a206661696c6564004d0970726f64756365727302086c616e6775616765010452"
|
||||
"757374000c70726f6365737365642d6279010572757374631d312e39352e3020283539383037363136652032303236"
|
||||
"2d30342d313429002c0f7461726765745f6665617475726573022b0f6d757461626c652d676c6f62616c732b087369"
|
||||
"676e2d657874";
|
||||
|
||||
extern std::string const kFloat0Hex =
|
||||
"0061736d0100000001290560057f7f7f7f7f017f60047e7f7f7f017f60077f7f7f7f7f7f7f017f60047f7f7f7f017f"
|
||||
"6000017f02560408686f73745f6c6962057472616365000008686f73745f6c69620e666c6f61745f66726f6d5f696e"
|
||||
"74000108686f73745f6c696209666c6f61745f737562000208686f73745f6c696209666c6f61745f636d7000030302"
|
||||
"010405030100110619037f01418080c0000b7f0041e980c0000b7f0041f080c0000b073504066d656d6f727902000d"
|
||||
"657363726f775f66696e69736800040a5f5f646174615f656e6403010b5f5f686561705f6261736503020acc0101c9"
|
||||
"0101027f230041206b22002400418080c000411541014100410010001a200041003602082000420037030020004100"
|
||||
"3602182000420037031002400240420a2000410c41001001410c4604402000410c2000410c200041106a2201410c41"
|
||||
"001002410c470d012001410c419580c000410c100345044041a180c000411a41014100410010001a0c030b41bb80c0"
|
||||
"00411941014100410010001a0c020b41d480c000411541014100410010001a0c010b41d480c0004115410141004100"
|
||||
"10001a0b200041206a240041010b0b720100418080c0000b690a24242420746573745f666c6f61745f302024242400"
|
||||
"00000000000000800000002020464c4f41545f5a45524f20636f6d706172653a20676f6f642020464c4f41545f5a45"
|
||||
"524f20636f6d706172653a206261642020666c6f61742031302d31303a206661696c6564004d0970726f6475636572"
|
||||
"7302086c616e6775616765010452757374000c70726f6365737365642d6279010572757374631d312e39352e302028"
|
||||
"35393830373631366520323032362d30342d313429002c0f7461726765745f6665617475726573022b0f6d75746162"
|
||||
"6c652d676c6f62616c732b087369676e2d657874";
|
||||
|
||||
extern std::string const kDisabledFloatHex =
|
||||
"0061736d010000000108026000006000017f03030200010503010002063e0a7f004180080b7f004180080b7f004180"
|
||||
"100b7f004180100b7f00418090040b7f004180080b7f00418090040b7f00418080080b7f0041000b7f0041010b07b7"
|
||||
"010d066d656d6f72790200115f5f7761736d5f63616c6c5f63746f727300000d657363726f775f66696e6973680001"
|
||||
"0362756603000c5f5f64736f5f68616e646c6503010a5f5f646174615f656e6403020b5f5f737461636b5f6c6f7703"
|
||||
"030c5f5f737461636b5f6869676803040d5f5f676c6f62616c5f6261736503050b5f5f686561705f6261736503060a"
|
||||
"5f5f686561705f656e6403070d5f5f6d656d6f72795f6261736503080c5f5f7461626c655f6261736503090a150202"
|
||||
"000b100043000000c54300200045931a41010b";
|
||||
|
||||
extern std::string const kMemoryPointerAtLimitHex =
|
||||
"0061736d010000000105016000017f0302010005030100010711010d657363726f775f66696e69736800000a0e010c"
|
||||
"0041ffff032d00001a41010b";
|
||||
|
||||
extern std::string const kMemoryPointerOverLimitHex =
|
||||
"0061736d010000000105016000017f0302010005030100010711010d657363726f775f66696e69736800000a0e010c"
|
||||
"00418080042d00001a41010b";
|
||||
|
||||
extern std::string const kMemoryOffsetOverLimitHex =
|
||||
"0061736d010000000105016000017f030201000503010001071a02066d656d6f727902000d657363726f775f66696e"
|
||||
"69736800000a0e010c00410028028080041a41010b";
|
||||
|
||||
extern std::string const kMemoryEndOfWordOverLimitHex =
|
||||
"0061736d010000000105016000017f030201000503010001071a02066d656d6f727902000d657363726f775f66696e"
|
||||
"69736800000a0e010c0041feff032802001a41010b";
|
||||
|
||||
extern std::string const kMemoryGrow0To1PageHex =
|
||||
"0061736d010000000105016000017f030201000503010000071a02066d656d6f727902000d657363726f775f66696e"
|
||||
"69736800000a0b010900410140001a41010b";
|
||||
|
||||
extern std::string const kMemoryGrow1To0PageHex =
|
||||
"0061736d010000000105016000017f030201000503010001071a02066d656d6f727902000d657363726f775f66696e"
|
||||
"69736800000a13011100417f4000417f460440417f0f0b41010b";
|
||||
|
||||
extern std::string const kMemoryLastByteOf8MbHex =
|
||||
"0061736d010000000105016000017f030201000506010180018001071a02066d656d6f727902000d657363726f775f"
|
||||
"66696e69736800000a0f010d0041ffffff032d00001a41010b";
|
||||
|
||||
extern std::string const kMemoryGrow1MoreThan8MbHex =
|
||||
"0061736d010000000105016000017f03020100050401008001071a02066d656d6f727902000d657363726f775f6669"
|
||||
"6e69736800000a1301110041014000417f460440417f0f0b41010b";
|
||||
|
||||
extern std::string const kMemoryGrow0MoreThan8MbHex =
|
||||
"0061736d010000000105016000017f03020100050401008001071a02066d656d6f727902000d657363726f775f6669"
|
||||
"6e69736800000a1301110041004000417f460440417f0f0b41010b";
|
||||
|
||||
extern std::string const kMemoryInit1MoreThan8MbHex =
|
||||
"0061736d010000000105016000017f030201000506010181018101071a02066d656d6f727902000d657363726f775f"
|
||||
"66696e69736800000a0f010d0041ffffff032d00001a41010b";
|
||||
|
||||
extern std::string const kMemoryNegativeAddressHex =
|
||||
"0061736d010000000105016000017f030201000506010180018001071a02066d656d6f727902000d657363726f775f"
|
||||
"66696e69736800000a0c010a00417f2d00001a41010b";
|
||||
|
||||
extern std::string const kTable64ElementsHex =
|
||||
"0061736d010000000108026000006000017f03030200010404017000400711010d657363726f775f66696e69736800"
|
||||
"010946010041000b400000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"00000000000000000000000000000000000000000000000000000a090202000b040041010b";
|
||||
|
||||
extern std::string const kTable65ElementsHex =
|
||||
"0061736d010000000108026000006000017f03030200010404017000410711010d657363726f775f66696e69736800"
|
||||
"010947010041000b410000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"0000000000000000000000000000000000000000000000000000000a090202000b040041010b";
|
||||
|
||||
extern std::string const kTable2TablesHex =
|
||||
"0061736d010000000108026000006000017f030302000104090270010101700101010711010d657363726f775f6669"
|
||||
"6e6973680001090f020041000b0100020141000b0001000a090202000b040041010b";
|
||||
|
||||
extern std::string const kTable0ElementsHex =
|
||||
"0061736d010000000105016000017f030201000404017000000711010d657363726f775f66696e69736800000a0601"
|
||||
"040041010b";
|
||||
|
||||
extern std::string const kTableUintMaxHex =
|
||||
"0061736d010000000105016000017f030201000408017000ffffffff0f0711010d657363726f775f66696e69736800"
|
||||
"000a0601040041010b";
|
||||
|
||||
extern std::string const kProposalMutableGlobalHex =
|
||||
"0061736d010000000105016000017f030201000606017f0141000b071b0207636f756e74657203000d657363726f77"
|
||||
"5f66696e69736800000a0d010b00230041016a240041010b";
|
||||
|
||||
extern std::string const kProposalGcStructNewHex =
|
||||
"0061736d01000000010b026000017f5f027f017f01030201000711010d657363726f775f66696e69736800000a0a01"
|
||||
"0800fb01011a41010b";
|
||||
|
||||
extern std::string const kProposalMultiValueHex =
|
||||
"0061736d010000000110036000027f7f6000017f60027f7f017f03030200010711010d657363726f775f66696e6973"
|
||||
"6800010a14020600410a41140b0b00100002026a411e460b0b";
|
||||
|
||||
extern std::string const kProposalSignExtHex =
|
||||
"0061736d010000000105016000017f030201000711010d657363726f775f66696e69736800000a0b01090041ff01c0"
|
||||
"417f460b";
|
||||
|
||||
extern std::string const kProposalFloatToIntHex =
|
||||
"0061736d010000000105016000017f030201000711010d657363726f775f66696e69736800000a1201100043f90215"
|
||||
"50fc0041ffffffff07460b";
|
||||
|
||||
extern std::string const kProposalBulkMemoryHex =
|
||||
"0061736d010000000105016000017f030201000503010001071a02066d656d6f727902000d657363726f775f66696e"
|
||||
"69736800000a1f011d004100412a3a000041e40041004101fc0a000041e4002d0000412a460b";
|
||||
|
||||
extern std::string const kProposalRefTypesHex =
|
||||
"0061736d010000000105016000017f020f0103656e76057461626c65016f0001030201000711010d657363726f775f"
|
||||
"66696e69736800000a0c010a004100d06f260041010b";
|
||||
|
||||
extern std::string const kProposalTailCallHex =
|
||||
"0061736d010000000105016000017f03030200000711010d657363726f775f66696e69736800010a0b02040041010b"
|
||||
"040012000b";
|
||||
|
||||
extern std::string const kProposalExtendedConstHex =
|
||||
"0061736d010000000105016000017f030201000609017f00410a41206a0b0711010d657363726f775f66696e697368"
|
||||
"00000a090107002300412a460b";
|
||||
|
||||
extern std::string const kProposalMultiMemoryHex =
|
||||
"0061736d010000000105016000017f03020100050502000000010711010d657363726f775f66696e69736800000a06"
|
||||
"0104003f010b";
|
||||
|
||||
extern std::string const kProposalCustomPageSizesHex =
|
||||
"0061736d010000000105016000017f030201000504010801000711010d657363726f775f66696e69736800000a0601"
|
||||
"040041010b";
|
||||
|
||||
extern std::string const kProposalMemory64Hex =
|
||||
"0061736d010000000105016000017f0302010005030104010711010d657363726f775f66696e69736800000a10010e"
|
||||
"004200412a3a00003f004201510b";
|
||||
|
||||
extern std::string const kProposalWideArithmeticHex =
|
||||
"0061736d010000000105016000017f030201000711010d657363726f775f66696e69736800000a0e010c0042014202"
|
||||
"fc161a1a41010b";
|
||||
|
||||
extern std::string const kTrapDivideBy0Hex =
|
||||
"0061736d010000000105016000017f030201000711010d657363726f775f66696e69736800000a0c010a00412a4100"
|
||||
"6d1a41010b";
|
||||
|
||||
extern std::string const kTrapIntOverflowHex =
|
||||
"0061736d010000000105016000017f030201000711010d657363726f775f66696e69736800000a0d010b0041808080"
|
||||
"8078417f6d0b";
|
||||
|
||||
extern std::string const kTrapUnreachableHex =
|
||||
"0061736d010000000105016000017f030201000711010d657363726f775f66696e69736800000a070105000041010"
|
||||
"b";
|
||||
|
||||
extern std::string const kTrapNullCallHex =
|
||||
"0061736d010000000105016000017f030201000404017000010711010d657363726f775f66696e69736800000a0901"
|
||||
"070041001100000b";
|
||||
|
||||
extern std::string const kTrapFuncSigMismatchHex =
|
||||
"0061736d010000000108026000006000017f03030200010404017000010711010d657363726f775f66696e69736800"
|
||||
"010907010041000b01000a0d020300010b070041001101000b";
|
||||
|
||||
extern std::string const kWasiGetTimeHex =
|
||||
"0061736d01000000010c0260037f7e7f017f6000017f02290116776173695f736e617073686f745f70726576696577"
|
||||
"310e636c6f636b5f74696d655f6765740000030201010503010001071a02066d656d6f727902000d657363726f775f"
|
||||
"66696e69736800010a16011400410042e8074100100045047f410105417f0b0b";
|
||||
|
||||
extern std::string const kWasiPrintHex =
|
||||
"0061736d01000000010d0260047f7f7f7f017f6000017f02230116776173695f736e617073686f745f707265766965"
|
||||
"77310866645f77726974650000030201010503010001071a02066d656d6f727902000d657363726f775f66696e6973"
|
||||
"6800010a1d011b01017f411821004101410041012000100045047f410105417f0b0b0b1e030041100b0648656c6c6f"
|
||||
"0a0041000b04100000000041040b0406000000";
|
||||
|
||||
// The following several wasm hex strings are for testing wasm section
|
||||
// corruption cases. They are illegal hence do not have corresponding
|
||||
// rust or wat sources.
|
||||
// Wasm code magic number is "0061736d", and the only valid version is 1.
|
||||
|
||||
extern std::string const kBadMagicNumberHex = "1061736d01000000";
|
||||
extern std::string const kBadVersionNumberHex = "0061736d02000000";
|
||||
|
||||
// Corruption Test: lyingHeader
|
||||
// Scenario: A section declares it is 2GB long, but the file ends immediately.
|
||||
// Attack: Buffer pre-allocation DoS (OOM).
|
||||
// # Magic (00 61 73 6d) + Version (01 00 00 00)
|
||||
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
|
||||
// # Type Section (ID 1)
|
||||
// # Size: LEB128 encoded 2GB (0x80 0x80 0x80 0x80 0x08)
|
||||
// data += b'\x01\x80\x80\x80\x80\x08'
|
||||
extern std::string const kLyingHeaderHex = "0061736d01000000018080808008";
|
||||
|
||||
// Corruption Test: neverEndingNumber
|
||||
// Scenario: An LEB128 integer that never has a stop bit (byte < 0x80).
|
||||
// Attack: Infinite loop in parser or read out of bounds.
|
||||
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
|
||||
// # Type Section (ID 1), Size 5
|
||||
// data += b'\x01\x05'
|
||||
// # Vector count: Infinite stream of 0x80 (100 bytes)
|
||||
// data += b'\x80' * 100
|
||||
extern std::string const kNeverEndingNumberHex =
|
||||
"0061736d01000000010580808080808080808080808080808080808080808080808080808080808080808080808080"
|
||||
"8080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080"
|
||||
"80808080808080808080808080808080";
|
||||
|
||||
// Corruption Test: vectorLie
|
||||
// Scenario: A vector declares it has 4 billion items, but provides none.
|
||||
// Attack: Vector pre-allocation DoS (OOM).
|
||||
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
|
||||
// # Type Section (ID 1)
|
||||
// # Size 5 (just enough for the count bytes)
|
||||
// data += b'\x01\x05'
|
||||
// # Vector Count: 0xFF 0xFF 0xFF 0xFF 0x0F (4,294,967,295 items)
|
||||
// data += b'\xff\xff\xff\xff\x0f'
|
||||
// # No actual items follow...
|
||||
extern std::string const kVectorLieHex = "0061736d010000000105ffffffff0f";
|
||||
|
||||
// Corruption Test: sectionOrdering
|
||||
// Scenario: Sections appear out of order
|
||||
// (Code section before Function section).
|
||||
// Attack: Parser state confusion / potential null pointer deref.
|
||||
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
|
||||
// # Code Section (ID 10) - usually last
|
||||
// # Size 2, Count 0
|
||||
// data += b'\x0a\x02\x00\x0b'
|
||||
// # Function Section (ID 3) - usually 3rd
|
||||
// data += b'\x03\x02\x00\x00'
|
||||
extern std::string const kSectionOrderingHex = "0061736d010000000a02000b03020000";
|
||||
|
||||
// Corruption Test: ghostPayload
|
||||
// Scenario: Valid headers, but file is truncated in the middle of a payload.
|
||||
// Attack: Read out of bounds panic.
|
||||
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
|
||||
// # Type Section (ID 1), Size 10
|
||||
// data += b'\x01\x0a'
|
||||
// # Content: Count 1
|
||||
// data += b'\x01'
|
||||
// # Start of a type definition (0x60 = func)
|
||||
// data += b'\x60'
|
||||
// # File ends abruptly here (missing params/results)
|
||||
extern std::string const kGhostPayloadHex = "0061736d01000000010a0160";
|
||||
|
||||
// Corruption Test: junkAfterSection
|
||||
// Scenario: Section declares size X, but logical content finishes at X-5.
|
||||
// Attack: Validation bypass if parser stops early,
|
||||
// or panic if strict check missing.
|
||||
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
|
||||
// # Type Section (ID 1), Size 10 bytes
|
||||
// data += b'\x01\x0a'
|
||||
// # Real content: Count 1, (func -> void) = 4 bytes
|
||||
// # \x01 (count) \x60 (func) \x00 (0 params) \x00 (0 results)
|
||||
// data += b'\x01\x60\x00\x00'
|
||||
// # Remaining 6 bytes are junk padding within the section size
|
||||
// data += b'\x00' * 6
|
||||
extern std::string const kJunkAfterSectionHex = "0061736d01000000010a01600000000000000000";
|
||||
|
||||
// Corruption Test: invalidSectionId
|
||||
// Scenario: A section ID that doesn't exist (0xFF).
|
||||
// Attack: Default case handling / unhandled enum variant.
|
||||
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
|
||||
// # Section ID 0xFF, Size 1
|
||||
// data += b'\xff\x01\x00'
|
||||
extern std::string const kInvalidSectionIdHex = "0061736d01000000ff0100";
|
||||
|
||||
// Corruption Test: localVariableBomb
|
||||
// Scenario: A function declares 4 billion local variables.
|
||||
// Attack: Stack Overflow / OOM during function init (memset).
|
||||
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
|
||||
// # 1. Type Section: (func) -> ()
|
||||
// data += b'\x01\x04\x01\x60\x00\x00'
|
||||
// # 3. Function Section: 1 function of type 0
|
||||
// data += b'\x03\x02\x01\x00'
|
||||
// # 10. Code Section
|
||||
// # ID 10, Size 15 (estimated), Count 1
|
||||
// data += b'\x0a\x0f\x01'
|
||||
// # Function Body Size: 13 bytes
|
||||
// data += b'\x0d'
|
||||
// # Local Declarations Count: 1 entry
|
||||
// data += b'\x01'
|
||||
// # The Bomb: 4,294,967,295 locals of type i32
|
||||
// # Count: 0xFF 0xFF 0xFF 0xFF 0x0F
|
||||
// # Type: 0x7F (i32)
|
||||
// data += b'\xff\xff\xff\xff\x0f\x7f'
|
||||
// # Instruction: end (0x0b)
|
||||
// data += b'\x0b'
|
||||
extern std::string const kLocalVariableBombHex =
|
||||
"0061736d01000000010401600000030201000a0f010d01ffffffff0f7f0b";
|
||||
|
||||
extern std::string const kInfiniteLoopWasmHex =
|
||||
"0061736d010000000108026000006000017f030302000105030100020638097f004180080b7f004180080b7f004180"
|
||||
"080b7f00418088040b7f004180080b7f00418088040b7f00418080080b7f0041000b7f0041010b07a8010c066d656d"
|
||||
"6f72790200115f5f7761736d5f63616c6c5f63746f72730000046c6f6f7000010c5f5f64736f5f68616e646c650300"
|
||||
"0a5f5f646174615f656e6403010b5f5f737461636b5f6c6f7703020c5f5f737461636b5f6869676803030d5f5f676c"
|
||||
"6f62616c5f6261736503040b5f5f686561705f6261736503050a5f5f686561705f656e6403060d5f5f6d656d6f7279"
|
||||
"5f6261736503070c5f5f7461626c655f6261736503080a270202000b220041fc87044100360200034041fc870441fc"
|
||||
"870428020041016a3602000c000b000b007f0970726f647563657273010c70726f6365737365642d62790105636c61"
|
||||
"6e675f31392e312e352d776173692d73646b202868747470733a2f2f6769746875622e636f6d2f6c6c766d2f6c6c76"
|
||||
"6d2d70726f6a6563742061623462356132646235383239353861663165653330386137393063666462343262643234"
|
||||
"3732302900490f7461726765745f6665617475726573042b0f6d757461626c652d676c6f62616c732b087369676e2d"
|
||||
"6578742b0f7265666572656e63652d74797065732b0a6d756c746976616c7565";
|
||||
|
||||
extern std::string const kStartLoopHex =
|
||||
"0061736d010000000108026000006000017f030302000107190205737461727400000d657363726f775f66696e6973"
|
||||
"6800010801000a0e02070003400c000b0b040041010b";
|
||||
|
||||
extern std::string const kBadAlignWasmHex =
|
||||
"0061736d01000000011b046000017f60057f7f7f7f7f017f60067f7f7f7f7f7f017f60000002260203656e760f666c"
|
||||
"6f61745f66726f6d5f75696e74000103656e7608636865636b5f6964000203050403000000050301000306470b7f00"
|
||||
@@ -654,4 +1148,247 @@ extern std::string const kBadAlignWasmHex =
|
||||
"32393538616631656533303861373930636664623432626432343732302900490f7461726765745f66656174757265"
|
||||
"73042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f7265666572656e63652d74797065732b"
|
||||
"0a6d756c746976616c7565";
|
||||
*/
|
||||
|
||||
extern std::string const kThousandParamsHex =
|
||||
"0061736d0100000001f1070260000060e8077f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f017f030302000105030100020638097f"
|
||||
"004180080b7f004180080b7f004180080b7f00418088040b7f004180080b7f00418088040b7f00418080080b7f0041"
|
||||
"000b7f0041010b07a8010c066d656d6f72790200115f5f7761736d5f63616c6c5f63746f7273000004746573740001"
|
||||
"0c5f5f64736f5f68616e646c6503000a5f5f646174615f656e6403010b5f5f737461636b5f6c6f7703020c5f5f7374"
|
||||
"61636b5f6869676803030d5f5f676c6f62616c5f6261736503040b5f5f686561705f6261736503050a5f5f68656170"
|
||||
"5f656e6403060d5f5f6d656d6f72795f6261736503070c5f5f7461626c655f6261736503080aa71e0202000ba11e00"
|
||||
"200020016a20026a20036a20046a20056a20066a20076a20086a20096a200a6a200b6a200c6a200d6a200e6a200f6a"
|
||||
"20106a20116a20126a20136a20146a20156a20166a20176a20186a20196a201a6a201b6a201c6a201d6a201e6a201f"
|
||||
"6a20206a20216a20226a20236a20246a20256a20266a20276a20286a20296a202a6a202b6a202c6a202d6a202e6a20"
|
||||
"2f6a20306a20316a20326a20336a20346a20356a20366a20376a20386a20396a203a6a203b6a203c6a203d6a203e6a"
|
||||
"203f6a20406a20416a20426a20436a20446a20456a20466a20476a20486a20496a204a6a204b6a204c6a204d6a204e"
|
||||
"6a204f6a20506a20516a20526a20536a20546a20556a20566a20576a20586a20596a205a6a205b6a205c6a205d6a20"
|
||||
"5e6a205f6a20606a20616a20626a20636a20646a20656a20666a20676a20686a20696a206a6a206b6a206c6a206d6a"
|
||||
"206e6a206f6a20706a20716a20726a20736a20746a20756a20766a20776a20786a20796a207a6a207b6a207c6a207d"
|
||||
"6a207e6a207f6a2080016a2081016a2082016a2083016a2084016a2085016a2086016a2087016a2088016a2089016a"
|
||||
"208a016a208b016a208c016a208d016a208e016a208f016a2090016a2091016a2092016a2093016a2094016a209501"
|
||||
"6a2096016a2097016a2098016a2099016a209a016a209b016a209c016a209d016a209e016a209f016a20a0016a20a1"
|
||||
"016a20a2016a20a3016a20a4016a20a5016a20a6016a20a7016a20a8016a20a9016a20aa016a20ab016a20ac016a20"
|
||||
"ad016a20ae016a20af016a20b0016a20b1016a20b2016a20b3016a20b4016a20b5016a20b6016a20b7016a20b8016a"
|
||||
"20b9016a20ba016a20bb016a20bc016a20bd016a20be016a20bf016a20c0016a20c1016a20c2016a20c3016a20c401"
|
||||
"6a20c5016a20c6016a20c7016a20c8016a20c9016a20ca016a20cb016a20cc016a20cd016a20ce016a20cf016a20d0"
|
||||
"016a20d1016a20d2016a20d3016a20d4016a20d5016a20d6016a20d7016a20d8016a20d9016a20da016a20db016a20"
|
||||
"dc016a20dd016a20de016a20df016a20e0016a20e1016a20e2016a20e3016a20e4016a20e5016a20e6016a20e7016a"
|
||||
"20e8016a20e9016a20ea016a20eb016a20ec016a20ed016a20ee016a20ef016a20f0016a20f1016a20f2016a20f301"
|
||||
"6a20f4016a20f5016a20f6016a20f7016a20f8016a20f9016a20fa016a20fb016a20fc016a20fd016a20fe016a20ff"
|
||||
"016a2080026a2081026a2082026a2083026a2084026a2085026a2086026a2087026a2088026a2089026a208a026a20"
|
||||
"8b026a208c026a208d026a208e026a208f026a2090026a2091026a2092026a2093026a2094026a2095026a2096026a"
|
||||
"2097026a2098026a2099026a209a026a209b026a209c026a209d026a209e026a209f026a20a0026a20a1026a20a202"
|
||||
"6a20a3026a20a4026a20a5026a20a6026a20a7026a20a8026a20a9026a20aa026a20ab026a20ac026a20ad026a20ae"
|
||||
"026a20af026a20b0026a20b1026a20b2026a20b3026a20b4026a20b5026a20b6026a20b7026a20b8026a20b9026a20"
|
||||
"ba026a20bb026a20bc026a20bd026a20be026a20bf026a20c0026a20c1026a20c2026a20c3026a20c4026a20c5026a"
|
||||
"20c6026a20c7026a20c8026a20c9026a20ca026a20cb026a20cc026a20cd026a20ce026a20cf026a20d0026a20d102"
|
||||
"6a20d2026a20d3026a20d4026a20d5026a20d6026a20d7026a20d8026a20d9026a20da026a20db026a20dc026a20dd"
|
||||
"026a20de026a20df026a20e0026a20e1026a20e2026a20e3026a20e4026a20e5026a20e6026a20e7026a20e8026a20"
|
||||
"e9026a20ea026a20eb026a20ec026a20ed026a20ee026a20ef026a20f0026a20f1026a20f2026a20f3026a20f4026a"
|
||||
"20f5026a20f6026a20f7026a20f8026a20f9026a20fa026a20fb026a20fc026a20fd026a20fe026a20ff026a208003"
|
||||
"6a2081036a2082036a2083036a2084036a2085036a2086036a2087036a2088036a2089036a208a036a208b036a208c"
|
||||
"036a208d036a208e036a208f036a2090036a2091036a2092036a2093036a2094036a2095036a2096036a2097036a20"
|
||||
"98036a2099036a209a036a209b036a209c036a209d036a209e036a209f036a20a0036a20a1036a20a2036a20a3036a"
|
||||
"20a4036a20a5036a20a6036a20a7036a20a8036a20a9036a20aa036a20ab036a20ac036a20ad036a20ae036a20af03"
|
||||
"6a20b0036a20b1036a20b2036a20b3036a20b4036a20b5036a20b6036a20b7036a20b8036a20b9036a20ba036a20bb"
|
||||
"036a20bc036a20bd036a20be036a20bf036a20c0036a20c1036a20c2036a20c3036a20c4036a20c5036a20c6036a20"
|
||||
"c7036a20c8036a20c9036a20ca036a20cb036a20cc036a20cd036a20ce036a20cf036a20d0036a20d1036a20d2036a"
|
||||
"20d3036a20d4036a20d5036a20d6036a20d7036a20d8036a20d9036a20da036a20db036a20dc036a20dd036a20de03"
|
||||
"6a20df036a20e0036a20e1036a20e2036a20e3036a20e4036a20e5036a20e6036a20e7036a20e8036a20e9036a20ea"
|
||||
"036a20eb036a20ec036a20ed036a20ee036a20ef036a20f0036a20f1036a20f2036a20f3036a20f4036a20f5036a20"
|
||||
"f6036a20f7036a20f8036a20f9036a20fa036a20fb036a20fc036a20fd036a20fe036a20ff036a2080046a2081046a"
|
||||
"2082046a2083046a2084046a2085046a2086046a2087046a2088046a2089046a208a046a208b046a208c046a208d04"
|
||||
"6a208e046a208f046a2090046a2091046a2092046a2093046a2094046a2095046a2096046a2097046a2098046a2099"
|
||||
"046a209a046a209b046a209c046a209d046a209e046a209f046a20a0046a20a1046a20a2046a20a3046a20a4046a20"
|
||||
"a5046a20a6046a20a7046a20a8046a20a9046a20aa046a20ab046a20ac046a20ad046a20ae046a20af046a20b0046a"
|
||||
"20b1046a20b2046a20b3046a20b4046a20b5046a20b6046a20b7046a20b8046a20b9046a20ba046a20bb046a20bc04"
|
||||
"6a20bd046a20be046a20bf046a20c0046a20c1046a20c2046a20c3046a20c4046a20c5046a20c6046a20c7046a20c8"
|
||||
"046a20c9046a20ca046a20cb046a20cc046a20cd046a20ce046a20cf046a20d0046a20d1046a20d2046a20d3046a20"
|
||||
"d4046a20d5046a20d6046a20d7046a20d8046a20d9046a20da046a20db046a20dc046a20dd046a20de046a20df046a"
|
||||
"20e0046a20e1046a20e2046a20e3046a20e4046a20e5046a20e6046a20e7046a20e8046a20e9046a20ea046a20eb04"
|
||||
"6a20ec046a20ed046a20ee046a20ef046a20f0046a20f1046a20f2046a20f3046a20f4046a20f5046a20f6046a20f7"
|
||||
"046a20f8046a20f9046a20fa046a20fb046a20fc046a20fd046a20fe046a20ff046a2080056a2081056a2082056a20"
|
||||
"83056a2084056a2085056a2086056a2087056a2088056a2089056a208a056a208b056a208c056a208d056a208e056a"
|
||||
"208f056a2090056a2091056a2092056a2093056a2094056a2095056a2096056a2097056a2098056a2099056a209a05"
|
||||
"6a209b056a209c056a209d056a209e056a209f056a20a0056a20a1056a20a2056a20a3056a20a4056a20a5056a20a6"
|
||||
"056a20a7056a20a8056a20a9056a20aa056a20ab056a20ac056a20ad056a20ae056a20af056a20b0056a20b1056a20"
|
||||
"b2056a20b3056a20b4056a20b5056a20b6056a20b7056a20b8056a20b9056a20ba056a20bb056a20bc056a20bd056a"
|
||||
"20be056a20bf056a20c0056a20c1056a20c2056a20c3056a20c4056a20c5056a20c6056a20c7056a20c8056a20c905"
|
||||
"6a20ca056a20cb056a20cc056a20cd056a20ce056a20cf056a20d0056a20d1056a20d2056a20d3056a20d4056a20d5"
|
||||
"056a20d6056a20d7056a20d8056a20d9056a20da056a20db056a20dc056a20dd056a20de056a20df056a20e0056a20"
|
||||
"e1056a20e2056a20e3056a20e4056a20e5056a20e6056a20e7056a20e8056a20e9056a20ea056a20eb056a20ec056a"
|
||||
"20ed056a20ee056a20ef056a20f0056a20f1056a20f2056a20f3056a20f4056a20f5056a20f6056a20f7056a20f805"
|
||||
"6a20f9056a20fa056a20fb056a20fc056a20fd056a20fe056a20ff056a2080066a2081066a2082066a2083066a2084"
|
||||
"066a2085066a2086066a2087066a2088066a2089066a208a066a208b066a208c066a208d066a208e066a208f066a20"
|
||||
"90066a2091066a2092066a2093066a2094066a2095066a2096066a2097066a2098066a2099066a209a066a209b066a"
|
||||
"209c066a209d066a209e066a209f066a20a0066a20a1066a20a2066a20a3066a20a4066a20a5066a20a6066a20a706"
|
||||
"6a20a8066a20a9066a20aa066a20ab066a20ac066a20ad066a20ae066a20af066a20b0066a20b1066a20b2066a20b3"
|
||||
"066a20b4066a20b5066a20b6066a20b7066a20b8066a20b9066a20ba066a20bb066a20bc066a20bd066a20be066a20"
|
||||
"bf066a20c0066a20c1066a20c2066a20c3066a20c4066a20c5066a20c6066a20c7066a20c8066a20c9066a20ca066a"
|
||||
"20cb066a20cc066a20cd066a20ce066a20cf066a20d0066a20d1066a20d2066a20d3066a20d4066a20d5066a20d606"
|
||||
"6a20d7066a20d8066a20d9066a20da066a20db066a20dc066a20dd066a20de066a20df066a20e0066a20e1066a20e2"
|
||||
"066a20e3066a20e4066a20e5066a20e6066a20e7066a20e8066a20e9066a20ea066a20eb066a20ec066a20ed066a20"
|
||||
"ee066a20ef066a20f0066a20f1066a20f2066a20f3066a20f4066a20f5066a20f6066a20f7066a20f8066a20f9066a"
|
||||
"20fa066a20fb066a20fc066a20fd066a20fe066a20ff066a2080076a2081076a2082076a2083076a2084076a208507"
|
||||
"6a2086076a2087076a2088076a2089076a208a076a208b076a208c076a208d076a208e076a208f076a2090076a2091"
|
||||
"076a2092076a2093076a2094076a2095076a2096076a2097076a2098076a2099076a209a076a209b076a209c076a20"
|
||||
"9d076a209e076a209f076a20a0076a20a1076a20a2076a20a3076a20a4076a20a5076a20a6076a20a7076a20a8076a"
|
||||
"20a9076a20aa076a20ab076a20ac076a20ad076a20ae076a20af076a20b0076a20b1076a20b2076a20b3076a20b407"
|
||||
"6a20b5076a20b6076a20b7076a20b8076a20b9076a20ba076a20bb076a20bc076a20bd076a20be076a20bf076a20c0"
|
||||
"076a20c1076a20c2076a20c3076a20c4076a20c5076a20c6076a20c7076a20c8076a20c9076a20ca076a20cb076a20"
|
||||
"cc076a20cd076a20ce076a20cf076a20d0076a20d1076a20d2076a20d3076a20d4076a20d5076a20d6076a20d7076a"
|
||||
"20d8076a20d9076a20da076a20db076a20dc076a20dd076a20de076a20df076a20e0076a20e1076a20e2076a20e307"
|
||||
"6a20e4076a20e5076a20e6076a20e7076a0b007f0970726f647563657273010c70726f6365737365642d6279010563"
|
||||
"6c616e675f31392e312e352d776173692d73646b202868747470733a2f2f6769746875622e636f6d2f6c6c766d2f6c"
|
||||
"6c766d2d70726f6a656374206162346235613264623538323935386166316565333038613739306366646234326264"
|
||||
"32343732302900490f7461726765745f6665617475726573042b0f6d757461626c652d676c6f62616c732b08736967"
|
||||
"6e2d6578742b0f7265666572656e63652d74797065732b0a6d756c746976616c7565";
|
||||
|
||||
extern std::string const kThousand1ParamsHex =
|
||||
"0061736d0100000001f2070260000060e9077f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f"
|
||||
"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f017f03030200010503010002063809"
|
||||
"7f004180080b7f004180080b7f004180080b7f00418088040b7f004180080b7f00418088040b7f00418080080b7f00"
|
||||
"41000b7f0041010b07a8010c066d656d6f72790200115f5f7761736d5f63616c6c5f63746f72730000047465737400"
|
||||
"010c5f5f64736f5f68616e646c6503000a5f5f646174615f656e6403010b5f5f737461636b5f6c6f7703020c5f5f73"
|
||||
"7461636b5f6869676803030d5f5f676c6f62616c5f6261736503040b5f5f686561705f6261736503050a5f5f686561"
|
||||
"705f656e6403060d5f5f6d656d6f72795f6261736503070c5f5f7461626c655f6261736503080aab1e0202000ba51e"
|
||||
"00200020016a20026a20036a20046a20056a20066a20076a20086a20096a200a6a200b6a200c6a200d6a200e6a200f"
|
||||
"6a20106a20116a20126a20136a20146a20156a20166a20176a20186a20196a201a6a201b6a201c6a201d6a201e6a20"
|
||||
"1f6a20206a20216a20226a20236a20246a20256a20266a20276a20286a20296a202a6a202b6a202c6a202d6a202e6a"
|
||||
"202f6a20306a20316a20326a20336a20346a20356a20366a20376a20386a20396a203a6a203b6a203c6a203d6a203e"
|
||||
"6a203f6a20406a20416a20426a20436a20446a20456a20466a20476a20486a20496a204a6a204b6a204c6a204d6a20"
|
||||
"4e6a204f6a20506a20516a20526a20536a20546a20556a20566a20576a20586a20596a205a6a205b6a205c6a205d6a"
|
||||
"205e6a205f6a20606a20616a20626a20636a20646a20656a20666a20676a20686a20696a206a6a206b6a206c6a206d"
|
||||
"6a206e6a206f6a20706a20716a20726a20736a20746a20756a20766a20776a20786a20796a207a6a207b6a207c6a20"
|
||||
"7d6a207e6a207f6a2080016a2081016a2082016a2083016a2084016a2085016a2086016a2087016a2088016a208901"
|
||||
"6a208a016a208b016a208c016a208d016a208e016a208f016a2090016a2091016a2092016a2093016a2094016a2095"
|
||||
"016a2096016a2097016a2098016a2099016a209a016a209b016a209c016a209d016a209e016a209f016a20a0016a20"
|
||||
"a1016a20a2016a20a3016a20a4016a20a5016a20a6016a20a7016a20a8016a20a9016a20aa016a20ab016a20ac016a"
|
||||
"20ad016a20ae016a20af016a20b0016a20b1016a20b2016a20b3016a20b4016a20b5016a20b6016a20b7016a20b801"
|
||||
"6a20b9016a20ba016a20bb016a20bc016a20bd016a20be016a20bf016a20c0016a20c1016a20c2016a20c3016a20c4"
|
||||
"016a20c5016a20c6016a20c7016a20c8016a20c9016a20ca016a20cb016a20cc016a20cd016a20ce016a20cf016a20"
|
||||
"d0016a20d1016a20d2016a20d3016a20d4016a20d5016a20d6016a20d7016a20d8016a20d9016a20da016a20db016a"
|
||||
"20dc016a20dd016a20de016a20df016a20e0016a20e1016a20e2016a20e3016a20e4016a20e5016a20e6016a20e701"
|
||||
"6a20e8016a20e9016a20ea016a20eb016a20ec016a20ed016a20ee016a20ef016a20f0016a20f1016a20f2016a20f3"
|
||||
"016a20f4016a20f5016a20f6016a20f7016a20f8016a20f9016a20fa016a20fb016a20fc016a20fd016a20fe016a20"
|
||||
"ff016a2080026a2081026a2082026a2083026a2084026a2085026a2086026a2087026a2088026a2089026a208a026a"
|
||||
"208b026a208c026a208d026a208e026a208f026a2090026a2091026a2092026a2093026a2094026a2095026a209602"
|
||||
"6a2097026a2098026a2099026a209a026a209b026a209c026a209d026a209e026a209f026a20a0026a20a1026a20a2"
|
||||
"026a20a3026a20a4026a20a5026a20a6026a20a7026a20a8026a20a9026a20aa026a20ab026a20ac026a20ad026a20"
|
||||
"ae026a20af026a20b0026a20b1026a20b2026a20b3026a20b4026a20b5026a20b6026a20b7026a20b8026a20b9026a"
|
||||
"20ba026a20bb026a20bc026a20bd026a20be026a20bf026a20c0026a20c1026a20c2026a20c3026a20c4026a20c502"
|
||||
"6a20c6026a20c7026a20c8026a20c9026a20ca026a20cb026a20cc026a20cd026a20ce026a20cf026a20d0026a20d1"
|
||||
"026a20d2026a20d3026a20d4026a20d5026a20d6026a20d7026a20d8026a20d9026a20da026a20db026a20dc026a20"
|
||||
"dd026a20de026a20df026a20e0026a20e1026a20e2026a20e3026a20e4026a20e5026a20e6026a20e7026a20e8026a"
|
||||
"20e9026a20ea026a20eb026a20ec026a20ed026a20ee026a20ef026a20f0026a20f1026a20f2026a20f3026a20f402"
|
||||
"6a20f5026a20f6026a20f7026a20f8026a20f9026a20fa026a20fb026a20fc026a20fd026a20fe026a20ff026a2080"
|
||||
"036a2081036a2082036a2083036a2084036a2085036a2086036a2087036a2088036a2089036a208a036a208b036a20"
|
||||
"8c036a208d036a208e036a208f036a2090036a2091036a2092036a2093036a2094036a2095036a2096036a2097036a"
|
||||
"2098036a2099036a209a036a209b036a209c036a209d036a209e036a209f036a20a0036a20a1036a20a2036a20a303"
|
||||
"6a20a4036a20a5036a20a6036a20a7036a20a8036a20a9036a20aa036a20ab036a20ac036a20ad036a20ae036a20af"
|
||||
"036a20b0036a20b1036a20b2036a20b3036a20b4036a20b5036a20b6036a20b7036a20b8036a20b9036a20ba036a20"
|
||||
"bb036a20bc036a20bd036a20be036a20bf036a20c0036a20c1036a20c2036a20c3036a20c4036a20c5036a20c6036a"
|
||||
"20c7036a20c8036a20c9036a20ca036a20cb036a20cc036a20cd036a20ce036a20cf036a20d0036a20d1036a20d203"
|
||||
"6a20d3036a20d4036a20d5036a20d6036a20d7036a20d8036a20d9036a20da036a20db036a20dc036a20dd036a20de"
|
||||
"036a20df036a20e0036a20e1036a20e2036a20e3036a20e4036a20e5036a20e6036a20e7036a20e8036a20e9036a20"
|
||||
"ea036a20eb036a20ec036a20ed036a20ee036a20ef036a20f0036a20f1036a20f2036a20f3036a20f4036a20f5036a"
|
||||
"20f6036a20f7036a20f8036a20f9036a20fa036a20fb036a20fc036a20fd036a20fe036a20ff036a2080046a208104"
|
||||
"6a2082046a2083046a2084046a2085046a2086046a2087046a2088046a2089046a208a046a208b046a208c046a208d"
|
||||
"046a208e046a208f046a2090046a2091046a2092046a2093046a2094046a2095046a2096046a2097046a2098046a20"
|
||||
"99046a209a046a209b046a209c046a209d046a209e046a209f046a20a0046a20a1046a20a2046a20a3046a20a4046a"
|
||||
"20a5046a20a6046a20a7046a20a8046a20a9046a20aa046a20ab046a20ac046a20ad046a20ae046a20af046a20b004"
|
||||
"6a20b1046a20b2046a20b3046a20b4046a20b5046a20b6046a20b7046a20b8046a20b9046a20ba046a20bb046a20bc"
|
||||
"046a20bd046a20be046a20bf046a20c0046a20c1046a20c2046a20c3046a20c4046a20c5046a20c6046a20c7046a20"
|
||||
"c8046a20c9046a20ca046a20cb046a20cc046a20cd046a20ce046a20cf046a20d0046a20d1046a20d2046a20d3046a"
|
||||
"20d4046a20d5046a20d6046a20d7046a20d8046a20d9046a20da046a20db046a20dc046a20dd046a20de046a20df04"
|
||||
"6a20e0046a20e1046a20e2046a20e3046a20e4046a20e5046a20e6046a20e7046a20e8046a20e9046a20ea046a20eb"
|
||||
"046a20ec046a20ed046a20ee046a20ef046a20f0046a20f1046a20f2046a20f3046a20f4046a20f5046a20f6046a20"
|
||||
"f7046a20f8046a20f9046a20fa046a20fb046a20fc046a20fd046a20fe046a20ff046a2080056a2081056a2082056a"
|
||||
"2083056a2084056a2085056a2086056a2087056a2088056a2089056a208a056a208b056a208c056a208d056a208e05"
|
||||
"6a208f056a2090056a2091056a2092056a2093056a2094056a2095056a2096056a2097056a2098056a2099056a209a"
|
||||
"056a209b056a209c056a209d056a209e056a209f056a20a0056a20a1056a20a2056a20a3056a20a4056a20a5056a20"
|
||||
"a6056a20a7056a20a8056a20a9056a20aa056a20ab056a20ac056a20ad056a20ae056a20af056a20b0056a20b1056a"
|
||||
"20b2056a20b3056a20b4056a20b5056a20b6056a20b7056a20b8056a20b9056a20ba056a20bb056a20bc056a20bd05"
|
||||
"6a20be056a20bf056a20c0056a20c1056a20c2056a20c3056a20c4056a20c5056a20c6056a20c7056a20c8056a20c9"
|
||||
"056a20ca056a20cb056a20cc056a20cd056a20ce056a20cf056a20d0056a20d1056a20d2056a20d3056a20d4056a20"
|
||||
"d5056a20d6056a20d7056a20d8056a20d9056a20da056a20db056a20dc056a20dd056a20de056a20df056a20e0056a"
|
||||
"20e1056a20e2056a20e3056a20e4056a20e5056a20e6056a20e7056a20e8056a20e9056a20ea056a20eb056a20ec05"
|
||||
"6a20ed056a20ee056a20ef056a20f0056a20f1056a20f2056a20f3056a20f4056a20f5056a20f6056a20f7056a20f8"
|
||||
"056a20f9056a20fa056a20fb056a20fc056a20fd056a20fe056a20ff056a2080066a2081066a2082066a2083066a20"
|
||||
"84066a2085066a2086066a2087066a2088066a2089066a208a066a208b066a208c066a208d066a208e066a208f066a"
|
||||
"2090066a2091066a2092066a2093066a2094066a2095066a2096066a2097066a2098066a2099066a209a066a209b06"
|
||||
"6a209c066a209d066a209e066a209f066a20a0066a20a1066a20a2066a20a3066a20a4066a20a5066a20a6066a20a7"
|
||||
"066a20a8066a20a9066a20aa066a20ab066a20ac066a20ad066a20ae066a20af066a20b0066a20b1066a20b2066a20"
|
||||
"b3066a20b4066a20b5066a20b6066a20b7066a20b8066a20b9066a20ba066a20bb066a20bc066a20bd066a20be066a"
|
||||
"20bf066a20c0066a20c1066a20c2066a20c3066a20c4066a20c5066a20c6066a20c7066a20c8066a20c9066a20ca06"
|
||||
"6a20cb066a20cc066a20cd066a20ce066a20cf066a20d0066a20d1066a20d2066a20d3066a20d4066a20d5066a20d6"
|
||||
"066a20d7066a20d8066a20d9066a20da066a20db066a20dc066a20dd066a20de066a20df066a20e0066a20e1066a20"
|
||||
"e2066a20e3066a20e4066a20e5066a20e6066a20e7066a20e8066a20e9066a20ea066a20eb066a20ec066a20ed066a"
|
||||
"20ee066a20ef066a20f0066a20f1066a20f2066a20f3066a20f4066a20f5066a20f6066a20f7066a20f8066a20f906"
|
||||
"6a20fa066a20fb066a20fc066a20fd066a20fe066a20ff066a2080076a2081076a2082076a2083076a2084076a2085"
|
||||
"076a2086076a2087076a2088076a2089076a208a076a208b076a208c076a208d076a208e076a208f076a2090076a20"
|
||||
"91076a2092076a2093076a2094076a2095076a2096076a2097076a2098076a2099076a209a076a209b076a209c076a"
|
||||
"209d076a209e076a209f076a20a0076a20a1076a20a2076a20a3076a20a4076a20a5076a20a6076a20a7076a20a807"
|
||||
"6a20a9076a20aa076a20ab076a20ac076a20ad076a20ae076a20af076a20b0076a20b1076a20b2076a20b3076a20b4"
|
||||
"076a20b5076a20b6076a20b7076a20b8076a20b9076a20ba076a20bb076a20bc076a20bd076a20be076a20bf076a20"
|
||||
"c0076a20c1076a20c2076a20c3076a20c4076a20c5076a20c6076a20c7076a20c8076a20c9076a20ca076a20cb076a"
|
||||
"20cc076a20cd076a20ce076a20cf076a20d0076a20d1076a20d2076a20d3076a20d4076a20d5076a20d6076a20d707"
|
||||
"6a20d8076a20d9076a20da076a20db076a20dc076a20dd076a20de076a20df076a20e0076a20e1076a20e2076a20e3"
|
||||
"076a20e4076a20e5076a20e6076a20e7076a20e8076a0b007f0970726f647563657273010c70726f6365737365642d"
|
||||
"62790105636c616e675f31392e312e352d776173692d73646b202868747470733a2f2f6769746875622e636f6d2f6c"
|
||||
"6c766d2f6c6c766d2d70726f6a65637420616234623561326462353832393538616631656533303861373930636664"
|
||||
"623432626432343732302900490f7461726765745f6665617475726573042b0f6d757461626c652d676c6f62616c73"
|
||||
"2b087369676e2d6578742b0f7265666572656e63652d74797065732b0a6d756c746976616c7565";
|
||||
|
||||
extern std::string const kOpcReservedHex =
|
||||
"0061736d010000000105016000017f03030200000404017000010503010001060b027f0141000b7e0142000b071401"
|
||||
"10616c6c5f696e737472756374696f6e7300010907010041000b01000a53020400412a0b4c02017f017e0101010101"
|
||||
"0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"
|
||||
"01010101010101010101010101010101410b0b0b0a010041000b0474657374";
|
||||
|
||||
extern std::string const kImpExpHex =
|
||||
"0061736d0100000001100360027f7f017f6000017f60017f017f02330203656e760e6765745f6c65646765725f7371"
|
||||
"6e000003656e76166765745f706172656e745f6c65646765725f686173680000030403010201050301000107310406"
|
||||
"6d656d6f72790200096578705f66756e63310002096578705f66756e633200030c746573745f696d706f7274730004"
|
||||
"0a2b03040041010b0700200041026c0b1c01027f4120410410001a41202802002100410041201001210120000b";
|
||||
|
||||
@@ -9,4 +9,75 @@ extern std::string const kAllHostFunctionsWasmHex;
|
||||
extern std::string const kAllKeyletsWasmHex;
|
||||
extern std::string const kCodecovTestsWasmHex;
|
||||
|
||||
extern std::string const kFibWasmHex;
|
||||
|
||||
extern std::string const kFloatTestsWasmHex;
|
||||
extern std::string const kFloat0Hex;
|
||||
extern std::string const kDisabledFloatHex;
|
||||
|
||||
extern std::string const kMemoryPointerAtLimitHex;
|
||||
extern std::string const kMemoryPointerOverLimitHex;
|
||||
extern std::string const kMemoryOffsetOverLimitHex;
|
||||
extern std::string const kMemoryEndOfWordOverLimitHex;
|
||||
extern std::string const kMemoryGrow0To1PageHex;
|
||||
extern std::string const kMemoryGrow1To0PageHex;
|
||||
extern std::string const kMemoryLastByteOf8MbHex;
|
||||
extern std::string const kMemoryGrow1MoreThan8MbHex;
|
||||
extern std::string const kMemoryGrow0MoreThan8MbHex;
|
||||
extern std::string const kMemoryInit1MoreThan8MbHex;
|
||||
extern std::string const kMemoryNegativeAddressHex;
|
||||
|
||||
extern std::string const kTable64ElementsHex;
|
||||
extern std::string const kTable65ElementsHex;
|
||||
extern std::string const kTable2TablesHex;
|
||||
extern std::string const kTable0ElementsHex;
|
||||
extern std::string const kTableUintMaxHex;
|
||||
|
||||
extern std::string const kProposalMutableGlobalHex;
|
||||
extern std::string const kProposalGcStructNewHex;
|
||||
extern std::string const kProposalMultiValueHex;
|
||||
extern std::string const kProposalSignExtHex;
|
||||
extern std::string const kProposalFloatToIntHex;
|
||||
extern std::string const kProposalBulkMemoryHex;
|
||||
extern std::string const kProposalRefTypesHex;
|
||||
extern std::string const kProposalTailCallHex;
|
||||
extern std::string const kProposalExtendedConstHex;
|
||||
extern std::string const kProposalMultiMemoryHex;
|
||||
extern std::string const kProposalCustomPageSizesHex;
|
||||
extern std::string const kProposalMemory64Hex;
|
||||
extern std::string const kProposalWideArithmeticHex;
|
||||
|
||||
extern std::string const kTrapDivideBy0Hex;
|
||||
extern std::string const kTrapIntOverflowHex;
|
||||
extern std::string const kTrapUnreachableHex;
|
||||
extern std::string const kTrapNullCallHex;
|
||||
extern std::string const kTrapFuncSigMismatchHex;
|
||||
|
||||
extern std::string const kWasiGetTimeHex;
|
||||
extern std::string const kWasiPrintHex;
|
||||
|
||||
extern std::string const kBadMagicNumberHex;
|
||||
extern std::string const kBadVersionNumberHex;
|
||||
extern std::string const kLyingHeaderHex;
|
||||
extern std::string const kNeverEndingNumberHex;
|
||||
extern std::string const kVectorLieHex;
|
||||
extern std::string const kSectionOrderingHex;
|
||||
extern std::string const kGhostPayloadHex;
|
||||
extern std::string const kJunkAfterSectionHex;
|
||||
extern std::string const kInvalidSectionIdHex;
|
||||
extern std::string const kLocalVariableBombHex;
|
||||
|
||||
extern std::string const kDeepRecursionHex;
|
||||
extern std::string const kInfiniteLoopWasmHex;
|
||||
extern std::string const kStartLoopHex;
|
||||
|
||||
extern std::string const kBadAlignWasmHex;
|
||||
|
||||
extern std::string const kThousandParamsHex;
|
||||
extern std::string const kThousand1ParamsHex;
|
||||
extern std::string const kLocals10kHex;
|
||||
extern std::string const kFunctions5kHex;
|
||||
|
||||
extern std::string const kOpcReservedHex;
|
||||
|
||||
extern std::string const kImpExpHex;
|
||||
|
||||
171
src/test/app/wasm_fixtures/float_0/Cargo.lock
generated
Normal file
171
src/test/app/wasm_fixtures/float_0/Cargo.lock
generated
Normal file
@@ -0,0 +1,171 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bs58"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float_0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"xrpl-wasm-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.183"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-macros"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#6b35fe45ac70bad38914e7f319d31d7947e05e25"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"sha2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-stdlib"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#6b35fe45ac70bad38914e7f319d31d7947e05e25"
|
||||
dependencies = [
|
||||
"xrpl-macros",
|
||||
]
|
||||
21
src/test/app/wasm_fixtures/float_0/Cargo.toml
Normal file
21
src/test/app/wasm_fixtures/float_0/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "float_0"
|
||||
version = "0.0.1"
|
||||
edition = "2024"
|
||||
|
||||
# This empty workspace definition keeps this project independent of the parent workspace
|
||||
[workspace]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 's'
|
||||
panic = "abort"
|
||||
|
||||
[dependencies]
|
||||
xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-wasm-stdlib", branch = "renames" }
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
70
src/test/app/wasm_fixtures/float_0/src/lib.rs
Normal file
70
src/test/app/wasm_fixtures/float_0/src/lib.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
#![cfg_attr(target_arch = "wasm32", no_std)]
|
||||
|
||||
use xrpl_std::host::trace::trace;
|
||||
use xrpl_std::host::{float_cmp, float_from_int, float_sub, FLOAT_ROUNDING_MODES_TO_NEAREST};
|
||||
|
||||
// Float size constant (8 bytes mantissa + 4 bytes exponent)
|
||||
const FLOAT_SIZE: usize = 12;
|
||||
|
||||
// FLOAT_ZERO constant
|
||||
const FLOAT_ZERO: [u8; FLOAT_SIZE] = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn escrow_finish() -> i32 {
|
||||
let _ = trace("\n$$$ test_float_0 $$$");
|
||||
|
||||
// Test: 10 - 10 should equal 0
|
||||
let mut f10: [u8; FLOAT_SIZE] = [0u8; FLOAT_SIZE];
|
||||
let mut f_result: [u8; FLOAT_SIZE] = [0u8; FLOAT_SIZE];
|
||||
|
||||
// Create float from 10
|
||||
if FLOAT_SIZE as i32
|
||||
!= unsafe {
|
||||
float_from_int(
|
||||
10,
|
||||
f10.as_mut_ptr(),
|
||||
FLOAT_SIZE,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
}
|
||||
{
|
||||
let _ = trace(" float 10-10: failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Subtract: 10 - 10 = 0
|
||||
if FLOAT_SIZE as i32
|
||||
!= unsafe {
|
||||
float_sub(
|
||||
f10.as_ptr(),
|
||||
FLOAT_SIZE,
|
||||
f10.as_ptr(),
|
||||
FLOAT_SIZE,
|
||||
f_result.as_mut_ptr(),
|
||||
FLOAT_SIZE,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
}
|
||||
{
|
||||
let _ = trace(" float 10-10: failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Compare result with FLOAT_ZERO constant
|
||||
if 0 == unsafe {
|
||||
float_cmp(
|
||||
f_result.as_ptr(),
|
||||
FLOAT_SIZE,
|
||||
FLOAT_ZERO.as_ptr(),
|
||||
FLOAT_SIZE,
|
||||
)
|
||||
} {
|
||||
let _ = trace(" FLOAT_ZERO compare: good");
|
||||
} else {
|
||||
let _ = trace(" FLOAT_ZERO compare: bad");
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
171
src/test/app/wasm_fixtures/float_tests/Cargo.lock
generated
Normal file
171
src/test/app/wasm_fixtures/float_tests/Cargo.lock
generated
Normal file
@@ -0,0 +1,171 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bs58"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float_tests"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"xrpl-wasm-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-macros"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#6b35fe45ac70bad38914e7f319d31d7947e05e25"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"sha2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-stdlib"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#6b35fe45ac70bad38914e7f319d31d7947e05e25"
|
||||
dependencies = [
|
||||
"xrpl-macros",
|
||||
]
|
||||
21
src/test/app/wasm_fixtures/float_tests/Cargo.toml
Normal file
21
src/test/app/wasm_fixtures/float_tests/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "float_tests"
|
||||
version = "0.0.1"
|
||||
edition = "2024"
|
||||
|
||||
# This empty workspace definition keeps this project independent of the parent workspace
|
||||
[workspace]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 's'
|
||||
panic = "abort"
|
||||
|
||||
[dependencies]
|
||||
xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-wasm-stdlib", branch = "renames" }
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
1112
src/test/app/wasm_fixtures/float_tests/src/lib.rs
Normal file
1112
src/test/app/wasm_fixtures/float_tests/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
7
src/test/app/wasm_fixtures/infiniteLoop.c
Normal file
7
src/test/app/wasm_fixtures/infiniteLoop.c
Normal file
@@ -0,0 +1,7 @@
|
||||
int loop()
|
||||
{
|
||||
int volatile x = 0;
|
||||
while (1)
|
||||
x++;
|
||||
return x;
|
||||
}
|
||||
264
src/test/app/wasm_fixtures/thousand1_params.c
Normal file
264
src/test/app/wasm_fixtures/thousand1_params.c
Normal file
@@ -0,0 +1,264 @@
|
||||
// clang-format off
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t test(
|
||||
int32_t p0, int32_t p1, int32_t p2, int32_t p3, int32_t p4, int32_t p5, int32_t p6, int32_t p7
|
||||
, int32_t p8, int32_t p9, int32_t p10, int32_t p11, int32_t p12, int32_t p13, int32_t p14, int32_t p15
|
||||
, int32_t p16, int32_t p17, int32_t p18, int32_t p19, int32_t p20, int32_t p21, int32_t p22, int32_t p23
|
||||
, int32_t p24, int32_t p25, int32_t p26, int32_t p27, int32_t p28, int32_t p29, int32_t p30, int32_t p31
|
||||
, int32_t p32, int32_t p33, int32_t p34, int32_t p35, int32_t p36, int32_t p37, int32_t p38, int32_t p39
|
||||
, int32_t p40, int32_t p41, int32_t p42, int32_t p43, int32_t p44, int32_t p45, int32_t p46, int32_t p47
|
||||
, int32_t p48, int32_t p49, int32_t p50, int32_t p51, int32_t p52, int32_t p53, int32_t p54, int32_t p55
|
||||
, int32_t p56, int32_t p57, int32_t p58, int32_t p59, int32_t p60, int32_t p61, int32_t p62, int32_t p63
|
||||
, int32_t p64, int32_t p65, int32_t p66, int32_t p67, int32_t p68, int32_t p69, int32_t p70, int32_t p71
|
||||
, int32_t p72, int32_t p73, int32_t p74, int32_t p75, int32_t p76, int32_t p77, int32_t p78, int32_t p79
|
||||
, int32_t p80, int32_t p81, int32_t p82, int32_t p83, int32_t p84, int32_t p85, int32_t p86, int32_t p87
|
||||
, int32_t p88, int32_t p89, int32_t p90, int32_t p91, int32_t p92, int32_t p93, int32_t p94, int32_t p95
|
||||
, int32_t p96, int32_t p97, int32_t p98, int32_t p99, int32_t p100, int32_t p101, int32_t p102, int32_t p103
|
||||
, int32_t p104, int32_t p105, int32_t p106, int32_t p107, int32_t p108, int32_t p109, int32_t p110, int32_t p111
|
||||
, int32_t p112, int32_t p113, int32_t p114, int32_t p115, int32_t p116, int32_t p117, int32_t p118, int32_t p119
|
||||
, int32_t p120, int32_t p121, int32_t p122, int32_t p123, int32_t p124, int32_t p125, int32_t p126, int32_t p127
|
||||
, int32_t p128, int32_t p129, int32_t p130, int32_t p131, int32_t p132, int32_t p133, int32_t p134, int32_t p135
|
||||
, int32_t p136, int32_t p137, int32_t p138, int32_t p139, int32_t p140, int32_t p141, int32_t p142, int32_t p143
|
||||
, int32_t p144, int32_t p145, int32_t p146, int32_t p147, int32_t p148, int32_t p149, int32_t p150, int32_t p151
|
||||
, int32_t p152, int32_t p153, int32_t p154, int32_t p155, int32_t p156, int32_t p157, int32_t p158, int32_t p159
|
||||
, int32_t p160, int32_t p161, int32_t p162, int32_t p163, int32_t p164, int32_t p165, int32_t p166, int32_t p167
|
||||
, int32_t p168, int32_t p169, int32_t p170, int32_t p171, int32_t p172, int32_t p173, int32_t p174, int32_t p175
|
||||
, int32_t p176, int32_t p177, int32_t p178, int32_t p179, int32_t p180, int32_t p181, int32_t p182, int32_t p183
|
||||
, int32_t p184, int32_t p185, int32_t p186, int32_t p187, int32_t p188, int32_t p189, int32_t p190, int32_t p191
|
||||
, int32_t p192, int32_t p193, int32_t p194, int32_t p195, int32_t p196, int32_t p197, int32_t p198, int32_t p199
|
||||
, int32_t p200, int32_t p201, int32_t p202, int32_t p203, int32_t p204, int32_t p205, int32_t p206, int32_t p207
|
||||
, int32_t p208, int32_t p209, int32_t p210, int32_t p211, int32_t p212, int32_t p213, int32_t p214, int32_t p215
|
||||
, int32_t p216, int32_t p217, int32_t p218, int32_t p219, int32_t p220, int32_t p221, int32_t p222, int32_t p223
|
||||
, int32_t p224, int32_t p225, int32_t p226, int32_t p227, int32_t p228, int32_t p229, int32_t p230, int32_t p231
|
||||
, int32_t p232, int32_t p233, int32_t p234, int32_t p235, int32_t p236, int32_t p237, int32_t p238, int32_t p239
|
||||
, int32_t p240, int32_t p241, int32_t p242, int32_t p243, int32_t p244, int32_t p245, int32_t p246, int32_t p247
|
||||
, int32_t p248, int32_t p249, int32_t p250, int32_t p251, int32_t p252, int32_t p253, int32_t p254, int32_t p255
|
||||
, int32_t p256, int32_t p257, int32_t p258, int32_t p259, int32_t p260, int32_t p261, int32_t p262, int32_t p263
|
||||
, int32_t p264, int32_t p265, int32_t p266, int32_t p267, int32_t p268, int32_t p269, int32_t p270, int32_t p271
|
||||
, int32_t p272, int32_t p273, int32_t p274, int32_t p275, int32_t p276, int32_t p277, int32_t p278, int32_t p279
|
||||
, int32_t p280, int32_t p281, int32_t p282, int32_t p283, int32_t p284, int32_t p285, int32_t p286, int32_t p287
|
||||
, int32_t p288, int32_t p289, int32_t p290, int32_t p291, int32_t p292, int32_t p293, int32_t p294, int32_t p295
|
||||
, int32_t p296, int32_t p297, int32_t p298, int32_t p299, int32_t p300, int32_t p301, int32_t p302, int32_t p303
|
||||
, int32_t p304, int32_t p305, int32_t p306, int32_t p307, int32_t p308, int32_t p309, int32_t p310, int32_t p311
|
||||
, int32_t p312, int32_t p313, int32_t p314, int32_t p315, int32_t p316, int32_t p317, int32_t p318, int32_t p319
|
||||
, int32_t p320, int32_t p321, int32_t p322, int32_t p323, int32_t p324, int32_t p325, int32_t p326, int32_t p327
|
||||
, int32_t p328, int32_t p329, int32_t p330, int32_t p331, int32_t p332, int32_t p333, int32_t p334, int32_t p335
|
||||
, int32_t p336, int32_t p337, int32_t p338, int32_t p339, int32_t p340, int32_t p341, int32_t p342, int32_t p343
|
||||
, int32_t p344, int32_t p345, int32_t p346, int32_t p347, int32_t p348, int32_t p349, int32_t p350, int32_t p351
|
||||
, int32_t p352, int32_t p353, int32_t p354, int32_t p355, int32_t p356, int32_t p357, int32_t p358, int32_t p359
|
||||
, int32_t p360, int32_t p361, int32_t p362, int32_t p363, int32_t p364, int32_t p365, int32_t p366, int32_t p367
|
||||
, int32_t p368, int32_t p369, int32_t p370, int32_t p371, int32_t p372, int32_t p373, int32_t p374, int32_t p375
|
||||
, int32_t p376, int32_t p377, int32_t p378, int32_t p379, int32_t p380, int32_t p381, int32_t p382, int32_t p383
|
||||
, int32_t p384, int32_t p385, int32_t p386, int32_t p387, int32_t p388, int32_t p389, int32_t p390, int32_t p391
|
||||
, int32_t p392, int32_t p393, int32_t p394, int32_t p395, int32_t p396, int32_t p397, int32_t p398, int32_t p399
|
||||
, int32_t p400, int32_t p401, int32_t p402, int32_t p403, int32_t p404, int32_t p405, int32_t p406, int32_t p407
|
||||
, int32_t p408, int32_t p409, int32_t p410, int32_t p411, int32_t p412, int32_t p413, int32_t p414, int32_t p415
|
||||
, int32_t p416, int32_t p417, int32_t p418, int32_t p419, int32_t p420, int32_t p421, int32_t p422, int32_t p423
|
||||
, int32_t p424, int32_t p425, int32_t p426, int32_t p427, int32_t p428, int32_t p429, int32_t p430, int32_t p431
|
||||
, int32_t p432, int32_t p433, int32_t p434, int32_t p435, int32_t p436, int32_t p437, int32_t p438, int32_t p439
|
||||
, int32_t p440, int32_t p441, int32_t p442, int32_t p443, int32_t p444, int32_t p445, int32_t p446, int32_t p447
|
||||
, int32_t p448, int32_t p449, int32_t p450, int32_t p451, int32_t p452, int32_t p453, int32_t p454, int32_t p455
|
||||
, int32_t p456, int32_t p457, int32_t p458, int32_t p459, int32_t p460, int32_t p461, int32_t p462, int32_t p463
|
||||
, int32_t p464, int32_t p465, int32_t p466, int32_t p467, int32_t p468, int32_t p469, int32_t p470, int32_t p471
|
||||
, int32_t p472, int32_t p473, int32_t p474, int32_t p475, int32_t p476, int32_t p477, int32_t p478, int32_t p479
|
||||
, int32_t p480, int32_t p481, int32_t p482, int32_t p483, int32_t p484, int32_t p485, int32_t p486, int32_t p487
|
||||
, int32_t p488, int32_t p489, int32_t p490, int32_t p491, int32_t p492, int32_t p493, int32_t p494, int32_t p495
|
||||
, int32_t p496, int32_t p497, int32_t p498, int32_t p499, int32_t p500, int32_t p501, int32_t p502, int32_t p503
|
||||
, int32_t p504, int32_t p505, int32_t p506, int32_t p507, int32_t p508, int32_t p509, int32_t p510, int32_t p511
|
||||
, int32_t p512, int32_t p513, int32_t p514, int32_t p515, int32_t p516, int32_t p517, int32_t p518, int32_t p519
|
||||
, int32_t p520, int32_t p521, int32_t p522, int32_t p523, int32_t p524, int32_t p525, int32_t p526, int32_t p527
|
||||
, int32_t p528, int32_t p529, int32_t p530, int32_t p531, int32_t p532, int32_t p533, int32_t p534, int32_t p535
|
||||
, int32_t p536, int32_t p537, int32_t p538, int32_t p539, int32_t p540, int32_t p541, int32_t p542, int32_t p543
|
||||
, int32_t p544, int32_t p545, int32_t p546, int32_t p547, int32_t p548, int32_t p549, int32_t p550, int32_t p551
|
||||
, int32_t p552, int32_t p553, int32_t p554, int32_t p555, int32_t p556, int32_t p557, int32_t p558, int32_t p559
|
||||
, int32_t p560, int32_t p561, int32_t p562, int32_t p563, int32_t p564, int32_t p565, int32_t p566, int32_t p567
|
||||
, int32_t p568, int32_t p569, int32_t p570, int32_t p571, int32_t p572, int32_t p573, int32_t p574, int32_t p575
|
||||
, int32_t p576, int32_t p577, int32_t p578, int32_t p579, int32_t p580, int32_t p581, int32_t p582, int32_t p583
|
||||
, int32_t p584, int32_t p585, int32_t p586, int32_t p587, int32_t p588, int32_t p589, int32_t p590, int32_t p591
|
||||
, int32_t p592, int32_t p593, int32_t p594, int32_t p595, int32_t p596, int32_t p597, int32_t p598, int32_t p599
|
||||
, int32_t p600, int32_t p601, int32_t p602, int32_t p603, int32_t p604, int32_t p605, int32_t p606, int32_t p607
|
||||
, int32_t p608, int32_t p609, int32_t p610, int32_t p611, int32_t p612, int32_t p613, int32_t p614, int32_t p615
|
||||
, int32_t p616, int32_t p617, int32_t p618, int32_t p619, int32_t p620, int32_t p621, int32_t p622, int32_t p623
|
||||
, int32_t p624, int32_t p625, int32_t p626, int32_t p627, int32_t p628, int32_t p629, int32_t p630, int32_t p631
|
||||
, int32_t p632, int32_t p633, int32_t p634, int32_t p635, int32_t p636, int32_t p637, int32_t p638, int32_t p639
|
||||
, int32_t p640, int32_t p641, int32_t p642, int32_t p643, int32_t p644, int32_t p645, int32_t p646, int32_t p647
|
||||
, int32_t p648, int32_t p649, int32_t p650, int32_t p651, int32_t p652, int32_t p653, int32_t p654, int32_t p655
|
||||
, int32_t p656, int32_t p657, int32_t p658, int32_t p659, int32_t p660, int32_t p661, int32_t p662, int32_t p663
|
||||
, int32_t p664, int32_t p665, int32_t p666, int32_t p667, int32_t p668, int32_t p669, int32_t p670, int32_t p671
|
||||
, int32_t p672, int32_t p673, int32_t p674, int32_t p675, int32_t p676, int32_t p677, int32_t p678, int32_t p679
|
||||
, int32_t p680, int32_t p681, int32_t p682, int32_t p683, int32_t p684, int32_t p685, int32_t p686, int32_t p687
|
||||
, int32_t p688, int32_t p689, int32_t p690, int32_t p691, int32_t p692, int32_t p693, int32_t p694, int32_t p695
|
||||
, int32_t p696, int32_t p697, int32_t p698, int32_t p699, int32_t p700, int32_t p701, int32_t p702, int32_t p703
|
||||
, int32_t p704, int32_t p705, int32_t p706, int32_t p707, int32_t p708, int32_t p709, int32_t p710, int32_t p711
|
||||
, int32_t p712, int32_t p713, int32_t p714, int32_t p715, int32_t p716, int32_t p717, int32_t p718, int32_t p719
|
||||
, int32_t p720, int32_t p721, int32_t p722, int32_t p723, int32_t p724, int32_t p725, int32_t p726, int32_t p727
|
||||
, int32_t p728, int32_t p729, int32_t p730, int32_t p731, int32_t p732, int32_t p733, int32_t p734, int32_t p735
|
||||
, int32_t p736, int32_t p737, int32_t p738, int32_t p739, int32_t p740, int32_t p741, int32_t p742, int32_t p743
|
||||
, int32_t p744, int32_t p745, int32_t p746, int32_t p747, int32_t p748, int32_t p749, int32_t p750, int32_t p751
|
||||
, int32_t p752, int32_t p753, int32_t p754, int32_t p755, int32_t p756, int32_t p757, int32_t p758, int32_t p759
|
||||
, int32_t p760, int32_t p761, int32_t p762, int32_t p763, int32_t p764, int32_t p765, int32_t p766, int32_t p767
|
||||
, int32_t p768, int32_t p769, int32_t p770, int32_t p771, int32_t p772, int32_t p773, int32_t p774, int32_t p775
|
||||
, int32_t p776, int32_t p777, int32_t p778, int32_t p779, int32_t p780, int32_t p781, int32_t p782, int32_t p783
|
||||
, int32_t p784, int32_t p785, int32_t p786, int32_t p787, int32_t p788, int32_t p789, int32_t p790, int32_t p791
|
||||
, int32_t p792, int32_t p793, int32_t p794, int32_t p795, int32_t p796, int32_t p797, int32_t p798, int32_t p799
|
||||
, int32_t p800, int32_t p801, int32_t p802, int32_t p803, int32_t p804, int32_t p805, int32_t p806, int32_t p807
|
||||
, int32_t p808, int32_t p809, int32_t p810, int32_t p811, int32_t p812, int32_t p813, int32_t p814, int32_t p815
|
||||
, int32_t p816, int32_t p817, int32_t p818, int32_t p819, int32_t p820, int32_t p821, int32_t p822, int32_t p823
|
||||
, int32_t p824, int32_t p825, int32_t p826, int32_t p827, int32_t p828, int32_t p829, int32_t p830, int32_t p831
|
||||
, int32_t p832, int32_t p833, int32_t p834, int32_t p835, int32_t p836, int32_t p837, int32_t p838, int32_t p839
|
||||
, int32_t p840, int32_t p841, int32_t p842, int32_t p843, int32_t p844, int32_t p845, int32_t p846, int32_t p847
|
||||
, int32_t p848, int32_t p849, int32_t p850, int32_t p851, int32_t p852, int32_t p853, int32_t p854, int32_t p855
|
||||
, int32_t p856, int32_t p857, int32_t p858, int32_t p859, int32_t p860, int32_t p861, int32_t p862, int32_t p863
|
||||
, int32_t p864, int32_t p865, int32_t p866, int32_t p867, int32_t p868, int32_t p869, int32_t p870, int32_t p871
|
||||
, int32_t p872, int32_t p873, int32_t p874, int32_t p875, int32_t p876, int32_t p877, int32_t p878, int32_t p879
|
||||
, int32_t p880, int32_t p881, int32_t p882, int32_t p883, int32_t p884, int32_t p885, int32_t p886, int32_t p887
|
||||
, int32_t p888, int32_t p889, int32_t p890, int32_t p891, int32_t p892, int32_t p893, int32_t p894, int32_t p895
|
||||
, int32_t p896, int32_t p897, int32_t p898, int32_t p899, int32_t p900, int32_t p901, int32_t p902, int32_t p903
|
||||
, int32_t p904, int32_t p905, int32_t p906, int32_t p907, int32_t p908, int32_t p909, int32_t p910, int32_t p911
|
||||
, int32_t p912, int32_t p913, int32_t p914, int32_t p915, int32_t p916, int32_t p917, int32_t p918, int32_t p919
|
||||
, int32_t p920, int32_t p921, int32_t p922, int32_t p923, int32_t p924, int32_t p925, int32_t p926, int32_t p927
|
||||
, int32_t p928, int32_t p929, int32_t p930, int32_t p931, int32_t p932, int32_t p933, int32_t p934, int32_t p935
|
||||
, int32_t p936, int32_t p937, int32_t p938, int32_t p939, int32_t p940, int32_t p941, int32_t p942, int32_t p943
|
||||
, int32_t p944, int32_t p945, int32_t p946, int32_t p947, int32_t p948, int32_t p949, int32_t p950, int32_t p951
|
||||
, int32_t p952, int32_t p953, int32_t p954, int32_t p955, int32_t p956, int32_t p957, int32_t p958, int32_t p959
|
||||
, int32_t p960, int32_t p961, int32_t p962, int32_t p963, int32_t p964, int32_t p965, int32_t p966, int32_t p967
|
||||
, int32_t p968, int32_t p969, int32_t p970, int32_t p971, int32_t p972, int32_t p973, int32_t p974, int32_t p975
|
||||
, int32_t p976, int32_t p977, int32_t p978, int32_t p979, int32_t p980, int32_t p981, int32_t p982, int32_t p983
|
||||
, int32_t p984, int32_t p985, int32_t p986, int32_t p987, int32_t p988, int32_t p989, int32_t p990, int32_t p991
|
||||
, int32_t p992, int32_t p993, int32_t p994, int32_t p995, int32_t p996, int32_t p997, int32_t p998, int32_t p999
|
||||
, int32_t p1000
|
||||
)
|
||||
{
|
||||
int32_t x;
|
||||
x = p0 + p1 + p2 + p3 + p4 + p5 + p6 + p7
|
||||
+ p8 + p9 + p10 + p11 + p12 + p13 + p14 + p15
|
||||
+ p16 + p17 + p18 + p19 + p20 + p21 + p22 + p23
|
||||
+ p24 + p25 + p26 + p27 + p28 + p29 + p30 + p31
|
||||
+ p32 + p33 + p34 + p35 + p36 + p37 + p38 + p39
|
||||
+ p40 + p41 + p42 + p43 + p44 + p45 + p46 + p47
|
||||
+ p48 + p49 + p50 + p51 + p52 + p53 + p54 + p55
|
||||
+ p56 + p57 + p58 + p59 + p60 + p61 + p62 + p63
|
||||
+ p64 + p65 + p66 + p67 + p68 + p69 + p70 + p71
|
||||
+ p72 + p73 + p74 + p75 + p76 + p77 + p78 + p79
|
||||
+ p80 + p81 + p82 + p83 + p84 + p85 + p86 + p87
|
||||
+ p88 + p89 + p90 + p91 + p92 + p93 + p94 + p95
|
||||
+ p96 + p97 + p98 + p99 + p100 + p101 + p102 + p103
|
||||
+ p104 + p105 + p106 + p107 + p108 + p109 + p110 + p111
|
||||
+ p112 + p113 + p114 + p115 + p116 + p117 + p118 + p119
|
||||
+ p120 + p121 + p122 + p123 + p124 + p125 + p126 + p127
|
||||
+ p128 + p129 + p130 + p131 + p132 + p133 + p134 + p135
|
||||
+ p136 + p137 + p138 + p139 + p140 + p141 + p142 + p143
|
||||
+ p144 + p145 + p146 + p147 + p148 + p149 + p150 + p151
|
||||
+ p152 + p153 + p154 + p155 + p156 + p157 + p158 + p159
|
||||
+ p160 + p161 + p162 + p163 + p164 + p165 + p166 + p167
|
||||
+ p168 + p169 + p170 + p171 + p172 + p173 + p174 + p175
|
||||
+ p176 + p177 + p178 + p179 + p180 + p181 + p182 + p183
|
||||
+ p184 + p185 + p186 + p187 + p188 + p189 + p190 + p191
|
||||
+ p192 + p193 + p194 + p195 + p196 + p197 + p198 + p199
|
||||
+ p200 + p201 + p202 + p203 + p204 + p205 + p206 + p207
|
||||
+ p208 + p209 + p210 + p211 + p212 + p213 + p214 + p215
|
||||
+ p216 + p217 + p218 + p219 + p220 + p221 + p222 + p223
|
||||
+ p224 + p225 + p226 + p227 + p228 + p229 + p230 + p231
|
||||
+ p232 + p233 + p234 + p235 + p236 + p237 + p238 + p239
|
||||
+ p240 + p241 + p242 + p243 + p244 + p245 + p246 + p247
|
||||
+ p248 + p249 + p250 + p251 + p252 + p253 + p254 + p255
|
||||
+ p256 + p257 + p258 + p259 + p260 + p261 + p262 + p263
|
||||
+ p264 + p265 + p266 + p267 + p268 + p269 + p270 + p271
|
||||
+ p272 + p273 + p274 + p275 + p276 + p277 + p278 + p279
|
||||
+ p280 + p281 + p282 + p283 + p284 + p285 + p286 + p287
|
||||
+ p288 + p289 + p290 + p291 + p292 + p293 + p294 + p295
|
||||
+ p296 + p297 + p298 + p299 + p300 + p301 + p302 + p303
|
||||
+ p304 + p305 + p306 + p307 + p308 + p309 + p310 + p311
|
||||
+ p312 + p313 + p314 + p315 + p316 + p317 + p318 + p319
|
||||
+ p320 + p321 + p322 + p323 + p324 + p325 + p326 + p327
|
||||
+ p328 + p329 + p330 + p331 + p332 + p333 + p334 + p335
|
||||
+ p336 + p337 + p338 + p339 + p340 + p341 + p342 + p343
|
||||
+ p344 + p345 + p346 + p347 + p348 + p349 + p350 + p351
|
||||
+ p352 + p353 + p354 + p355 + p356 + p357 + p358 + p359
|
||||
+ p360 + p361 + p362 + p363 + p364 + p365 + p366 + p367
|
||||
+ p368 + p369 + p370 + p371 + p372 + p373 + p374 + p375
|
||||
+ p376 + p377 + p378 + p379 + p380 + p381 + p382 + p383
|
||||
+ p384 + p385 + p386 + p387 + p388 + p389 + p390 + p391
|
||||
+ p392 + p393 + p394 + p395 + p396 + p397 + p398 + p399
|
||||
+ p400 + p401 + p402 + p403 + p404 + p405 + p406 + p407
|
||||
+ p408 + p409 + p410 + p411 + p412 + p413 + p414 + p415
|
||||
+ p416 + p417 + p418 + p419 + p420 + p421 + p422 + p423
|
||||
+ p424 + p425 + p426 + p427 + p428 + p429 + p430 + p431
|
||||
+ p432 + p433 + p434 + p435 + p436 + p437 + p438 + p439
|
||||
+ p440 + p441 + p442 + p443 + p444 + p445 + p446 + p447
|
||||
+ p448 + p449 + p450 + p451 + p452 + p453 + p454 + p455
|
||||
+ p456 + p457 + p458 + p459 + p460 + p461 + p462 + p463
|
||||
+ p464 + p465 + p466 + p467 + p468 + p469 + p470 + p471
|
||||
+ p472 + p473 + p474 + p475 + p476 + p477 + p478 + p479
|
||||
+ p480 + p481 + p482 + p483 + p484 + p485 + p486 + p487
|
||||
+ p488 + p489 + p490 + p491 + p492 + p493 + p494 + p495
|
||||
+ p496 + p497 + p498 + p499 + p500 + p501 + p502 + p503
|
||||
+ p504 + p505 + p506 + p507 + p508 + p509 + p510 + p511
|
||||
+ p512 + p513 + p514 + p515 + p516 + p517 + p518 + p519
|
||||
+ p520 + p521 + p522 + p523 + p524 + p525 + p526 + p527
|
||||
+ p528 + p529 + p530 + p531 + p532 + p533 + p534 + p535
|
||||
+ p536 + p537 + p538 + p539 + p540 + p541 + p542 + p543
|
||||
+ p544 + p545 + p546 + p547 + p548 + p549 + p550 + p551
|
||||
+ p552 + p553 + p554 + p555 + p556 + p557 + p558 + p559
|
||||
+ p560 + p561 + p562 + p563 + p564 + p565 + p566 + p567
|
||||
+ p568 + p569 + p570 + p571 + p572 + p573 + p574 + p575
|
||||
+ p576 + p577 + p578 + p579 + p580 + p581 + p582 + p583
|
||||
+ p584 + p585 + p586 + p587 + p588 + p589 + p590 + p591
|
||||
+ p592 + p593 + p594 + p595 + p596 + p597 + p598 + p599
|
||||
+ p600 + p601 + p602 + p603 + p604 + p605 + p606 + p607
|
||||
+ p608 + p609 + p610 + p611 + p612 + p613 + p614 + p615
|
||||
+ p616 + p617 + p618 + p619 + p620 + p621 + p622 + p623
|
||||
+ p624 + p625 + p626 + p627 + p628 + p629 + p630 + p631
|
||||
+ p632 + p633 + p634 + p635 + p636 + p637 + p638 + p639
|
||||
+ p640 + p641 + p642 + p643 + p644 + p645 + p646 + p647
|
||||
+ p648 + p649 + p650 + p651 + p652 + p653 + p654 + p655
|
||||
+ p656 + p657 + p658 + p659 + p660 + p661 + p662 + p663
|
||||
+ p664 + p665 + p666 + p667 + p668 + p669 + p670 + p671
|
||||
+ p672 + p673 + p674 + p675 + p676 + p677 + p678 + p679
|
||||
+ p680 + p681 + p682 + p683 + p684 + p685 + p686 + p687
|
||||
+ p688 + p689 + p690 + p691 + p692 + p693 + p694 + p695
|
||||
+ p696 + p697 + p698 + p699 + p700 + p701 + p702 + p703
|
||||
+ p704 + p705 + p706 + p707 + p708 + p709 + p710 + p711
|
||||
+ p712 + p713 + p714 + p715 + p716 + p717 + p718 + p719
|
||||
+ p720 + p721 + p722 + p723 + p724 + p725 + p726 + p727
|
||||
+ p728 + p729 + p730 + p731 + p732 + p733 + p734 + p735
|
||||
+ p736 + p737 + p738 + p739 + p740 + p741 + p742 + p743
|
||||
+ p744 + p745 + p746 + p747 + p748 + p749 + p750 + p751
|
||||
+ p752 + p753 + p754 + p755 + p756 + p757 + p758 + p759
|
||||
+ p760 + p761 + p762 + p763 + p764 + p765 + p766 + p767
|
||||
+ p768 + p769 + p770 + p771 + p772 + p773 + p774 + p775
|
||||
+ p776 + p777 + p778 + p779 + p780 + p781 + p782 + p783
|
||||
+ p784 + p785 + p786 + p787 + p788 + p789 + p790 + p791
|
||||
+ p792 + p793 + p794 + p795 + p796 + p797 + p798 + p799
|
||||
+ p800 + p801 + p802 + p803 + p804 + p805 + p806 + p807
|
||||
+ p808 + p809 + p810 + p811 + p812 + p813 + p814 + p815
|
||||
+ p816 + p817 + p818 + p819 + p820 + p821 + p822 + p823
|
||||
+ p824 + p825 + p826 + p827 + p828 + p829 + p830 + p831
|
||||
+ p832 + p833 + p834 + p835 + p836 + p837 + p838 + p839
|
||||
+ p840 + p841 + p842 + p843 + p844 + p845 + p846 + p847
|
||||
+ p848 + p849 + p850 + p851 + p852 + p853 + p854 + p855
|
||||
+ p856 + p857 + p858 + p859 + p860 + p861 + p862 + p863
|
||||
+ p864 + p865 + p866 + p867 + p868 + p869 + p870 + p871
|
||||
+ p872 + p873 + p874 + p875 + p876 + p877 + p878 + p879
|
||||
+ p880 + p881 + p882 + p883 + p884 + p885 + p886 + p887
|
||||
+ p888 + p889 + p890 + p891 + p892 + p893 + p894 + p895
|
||||
+ p896 + p897 + p898 + p899 + p900 + p901 + p902 + p903
|
||||
+ p904 + p905 + p906 + p907 + p908 + p909 + p910 + p911
|
||||
+ p912 + p913 + p914 + p915 + p916 + p917 + p918 + p919
|
||||
+ p920 + p921 + p922 + p923 + p924 + p925 + p926 + p927
|
||||
+ p928 + p929 + p930 + p931 + p932 + p933 + p934 + p935
|
||||
+ p936 + p937 + p938 + p939 + p940 + p941 + p942 + p943
|
||||
+ p944 + p945 + p946 + p947 + p948 + p949 + p950 + p951
|
||||
+ p952 + p953 + p954 + p955 + p956 + p957 + p958 + p959
|
||||
+ p960 + p961 + p962 + p963 + p964 + p965 + p966 + p967
|
||||
+ p968 + p969 + p970 + p971 + p972 + p973 + p974 + p975
|
||||
+ p976 + p977 + p978 + p979 + p980 + p981 + p982 + p983
|
||||
+ p984 + p985 + p986 + p987 + p988 + p989 + p990 + p991
|
||||
+ p992 + p993 + p994 + p995 + p996 + p997 + p998 + p999
|
||||
+ p1000;
|
||||
return x;
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
262
src/test/app/wasm_fixtures/thousand_params.c
Normal file
262
src/test/app/wasm_fixtures/thousand_params.c
Normal file
@@ -0,0 +1,262 @@
|
||||
// clang-format off
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t test(
|
||||
int32_t p0, int32_t p1, int32_t p2, int32_t p3, int32_t p4, int32_t p5, int32_t p6, int32_t p7
|
||||
, int32_t p8, int32_t p9, int32_t p10, int32_t p11, int32_t p12, int32_t p13, int32_t p14, int32_t p15
|
||||
, int32_t p16, int32_t p17, int32_t p18, int32_t p19, int32_t p20, int32_t p21, int32_t p22, int32_t p23
|
||||
, int32_t p24, int32_t p25, int32_t p26, int32_t p27, int32_t p28, int32_t p29, int32_t p30, int32_t p31
|
||||
, int32_t p32, int32_t p33, int32_t p34, int32_t p35, int32_t p36, int32_t p37, int32_t p38, int32_t p39
|
||||
, int32_t p40, int32_t p41, int32_t p42, int32_t p43, int32_t p44, int32_t p45, int32_t p46, int32_t p47
|
||||
, int32_t p48, int32_t p49, int32_t p50, int32_t p51, int32_t p52, int32_t p53, int32_t p54, int32_t p55
|
||||
, int32_t p56, int32_t p57, int32_t p58, int32_t p59, int32_t p60, int32_t p61, int32_t p62, int32_t p63
|
||||
, int32_t p64, int32_t p65, int32_t p66, int32_t p67, int32_t p68, int32_t p69, int32_t p70, int32_t p71
|
||||
, int32_t p72, int32_t p73, int32_t p74, int32_t p75, int32_t p76, int32_t p77, int32_t p78, int32_t p79
|
||||
, int32_t p80, int32_t p81, int32_t p82, int32_t p83, int32_t p84, int32_t p85, int32_t p86, int32_t p87
|
||||
, int32_t p88, int32_t p89, int32_t p90, int32_t p91, int32_t p92, int32_t p93, int32_t p94, int32_t p95
|
||||
, int32_t p96, int32_t p97, int32_t p98, int32_t p99, int32_t p100, int32_t p101, int32_t p102, int32_t p103
|
||||
, int32_t p104, int32_t p105, int32_t p106, int32_t p107, int32_t p108, int32_t p109, int32_t p110, int32_t p111
|
||||
, int32_t p112, int32_t p113, int32_t p114, int32_t p115, int32_t p116, int32_t p117, int32_t p118, int32_t p119
|
||||
, int32_t p120, int32_t p121, int32_t p122, int32_t p123, int32_t p124, int32_t p125, int32_t p126, int32_t p127
|
||||
, int32_t p128, int32_t p129, int32_t p130, int32_t p131, int32_t p132, int32_t p133, int32_t p134, int32_t p135
|
||||
, int32_t p136, int32_t p137, int32_t p138, int32_t p139, int32_t p140, int32_t p141, int32_t p142, int32_t p143
|
||||
, int32_t p144, int32_t p145, int32_t p146, int32_t p147, int32_t p148, int32_t p149, int32_t p150, int32_t p151
|
||||
, int32_t p152, int32_t p153, int32_t p154, int32_t p155, int32_t p156, int32_t p157, int32_t p158, int32_t p159
|
||||
, int32_t p160, int32_t p161, int32_t p162, int32_t p163, int32_t p164, int32_t p165, int32_t p166, int32_t p167
|
||||
, int32_t p168, int32_t p169, int32_t p170, int32_t p171, int32_t p172, int32_t p173, int32_t p174, int32_t p175
|
||||
, int32_t p176, int32_t p177, int32_t p178, int32_t p179, int32_t p180, int32_t p181, int32_t p182, int32_t p183
|
||||
, int32_t p184, int32_t p185, int32_t p186, int32_t p187, int32_t p188, int32_t p189, int32_t p190, int32_t p191
|
||||
, int32_t p192, int32_t p193, int32_t p194, int32_t p195, int32_t p196, int32_t p197, int32_t p198, int32_t p199
|
||||
, int32_t p200, int32_t p201, int32_t p202, int32_t p203, int32_t p204, int32_t p205, int32_t p206, int32_t p207
|
||||
, int32_t p208, int32_t p209, int32_t p210, int32_t p211, int32_t p212, int32_t p213, int32_t p214, int32_t p215
|
||||
, int32_t p216, int32_t p217, int32_t p218, int32_t p219, int32_t p220, int32_t p221, int32_t p222, int32_t p223
|
||||
, int32_t p224, int32_t p225, int32_t p226, int32_t p227, int32_t p228, int32_t p229, int32_t p230, int32_t p231
|
||||
, int32_t p232, int32_t p233, int32_t p234, int32_t p235, int32_t p236, int32_t p237, int32_t p238, int32_t p239
|
||||
, int32_t p240, int32_t p241, int32_t p242, int32_t p243, int32_t p244, int32_t p245, int32_t p246, int32_t p247
|
||||
, int32_t p248, int32_t p249, int32_t p250, int32_t p251, int32_t p252, int32_t p253, int32_t p254, int32_t p255
|
||||
, int32_t p256, int32_t p257, int32_t p258, int32_t p259, int32_t p260, int32_t p261, int32_t p262, int32_t p263
|
||||
, int32_t p264, int32_t p265, int32_t p266, int32_t p267, int32_t p268, int32_t p269, int32_t p270, int32_t p271
|
||||
, int32_t p272, int32_t p273, int32_t p274, int32_t p275, int32_t p276, int32_t p277, int32_t p278, int32_t p279
|
||||
, int32_t p280, int32_t p281, int32_t p282, int32_t p283, int32_t p284, int32_t p285, int32_t p286, int32_t p287
|
||||
, int32_t p288, int32_t p289, int32_t p290, int32_t p291, int32_t p292, int32_t p293, int32_t p294, int32_t p295
|
||||
, int32_t p296, int32_t p297, int32_t p298, int32_t p299, int32_t p300, int32_t p301, int32_t p302, int32_t p303
|
||||
, int32_t p304, int32_t p305, int32_t p306, int32_t p307, int32_t p308, int32_t p309, int32_t p310, int32_t p311
|
||||
, int32_t p312, int32_t p313, int32_t p314, int32_t p315, int32_t p316, int32_t p317, int32_t p318, int32_t p319
|
||||
, int32_t p320, int32_t p321, int32_t p322, int32_t p323, int32_t p324, int32_t p325, int32_t p326, int32_t p327
|
||||
, int32_t p328, int32_t p329, int32_t p330, int32_t p331, int32_t p332, int32_t p333, int32_t p334, int32_t p335
|
||||
, int32_t p336, int32_t p337, int32_t p338, int32_t p339, int32_t p340, int32_t p341, int32_t p342, int32_t p343
|
||||
, int32_t p344, int32_t p345, int32_t p346, int32_t p347, int32_t p348, int32_t p349, int32_t p350, int32_t p351
|
||||
, int32_t p352, int32_t p353, int32_t p354, int32_t p355, int32_t p356, int32_t p357, int32_t p358, int32_t p359
|
||||
, int32_t p360, int32_t p361, int32_t p362, int32_t p363, int32_t p364, int32_t p365, int32_t p366, int32_t p367
|
||||
, int32_t p368, int32_t p369, int32_t p370, int32_t p371, int32_t p372, int32_t p373, int32_t p374, int32_t p375
|
||||
, int32_t p376, int32_t p377, int32_t p378, int32_t p379, int32_t p380, int32_t p381, int32_t p382, int32_t p383
|
||||
, int32_t p384, int32_t p385, int32_t p386, int32_t p387, int32_t p388, int32_t p389, int32_t p390, int32_t p391
|
||||
, int32_t p392, int32_t p393, int32_t p394, int32_t p395, int32_t p396, int32_t p397, int32_t p398, int32_t p399
|
||||
, int32_t p400, int32_t p401, int32_t p402, int32_t p403, int32_t p404, int32_t p405, int32_t p406, int32_t p407
|
||||
, int32_t p408, int32_t p409, int32_t p410, int32_t p411, int32_t p412, int32_t p413, int32_t p414, int32_t p415
|
||||
, int32_t p416, int32_t p417, int32_t p418, int32_t p419, int32_t p420, int32_t p421, int32_t p422, int32_t p423
|
||||
, int32_t p424, int32_t p425, int32_t p426, int32_t p427, int32_t p428, int32_t p429, int32_t p430, int32_t p431
|
||||
, int32_t p432, int32_t p433, int32_t p434, int32_t p435, int32_t p436, int32_t p437, int32_t p438, int32_t p439
|
||||
, int32_t p440, int32_t p441, int32_t p442, int32_t p443, int32_t p444, int32_t p445, int32_t p446, int32_t p447
|
||||
, int32_t p448, int32_t p449, int32_t p450, int32_t p451, int32_t p452, int32_t p453, int32_t p454, int32_t p455
|
||||
, int32_t p456, int32_t p457, int32_t p458, int32_t p459, int32_t p460, int32_t p461, int32_t p462, int32_t p463
|
||||
, int32_t p464, int32_t p465, int32_t p466, int32_t p467, int32_t p468, int32_t p469, int32_t p470, int32_t p471
|
||||
, int32_t p472, int32_t p473, int32_t p474, int32_t p475, int32_t p476, int32_t p477, int32_t p478, int32_t p479
|
||||
, int32_t p480, int32_t p481, int32_t p482, int32_t p483, int32_t p484, int32_t p485, int32_t p486, int32_t p487
|
||||
, int32_t p488, int32_t p489, int32_t p490, int32_t p491, int32_t p492, int32_t p493, int32_t p494, int32_t p495
|
||||
, int32_t p496, int32_t p497, int32_t p498, int32_t p499, int32_t p500, int32_t p501, int32_t p502, int32_t p503
|
||||
, int32_t p504, int32_t p505, int32_t p506, int32_t p507, int32_t p508, int32_t p509, int32_t p510, int32_t p511
|
||||
, int32_t p512, int32_t p513, int32_t p514, int32_t p515, int32_t p516, int32_t p517, int32_t p518, int32_t p519
|
||||
, int32_t p520, int32_t p521, int32_t p522, int32_t p523, int32_t p524, int32_t p525, int32_t p526, int32_t p527
|
||||
, int32_t p528, int32_t p529, int32_t p530, int32_t p531, int32_t p532, int32_t p533, int32_t p534, int32_t p535
|
||||
, int32_t p536, int32_t p537, int32_t p538, int32_t p539, int32_t p540, int32_t p541, int32_t p542, int32_t p543
|
||||
, int32_t p544, int32_t p545, int32_t p546, int32_t p547, int32_t p548, int32_t p549, int32_t p550, int32_t p551
|
||||
, int32_t p552, int32_t p553, int32_t p554, int32_t p555, int32_t p556, int32_t p557, int32_t p558, int32_t p559
|
||||
, int32_t p560, int32_t p561, int32_t p562, int32_t p563, int32_t p564, int32_t p565, int32_t p566, int32_t p567
|
||||
, int32_t p568, int32_t p569, int32_t p570, int32_t p571, int32_t p572, int32_t p573, int32_t p574, int32_t p575
|
||||
, int32_t p576, int32_t p577, int32_t p578, int32_t p579, int32_t p580, int32_t p581, int32_t p582, int32_t p583
|
||||
, int32_t p584, int32_t p585, int32_t p586, int32_t p587, int32_t p588, int32_t p589, int32_t p590, int32_t p591
|
||||
, int32_t p592, int32_t p593, int32_t p594, int32_t p595, int32_t p596, int32_t p597, int32_t p598, int32_t p599
|
||||
, int32_t p600, int32_t p601, int32_t p602, int32_t p603, int32_t p604, int32_t p605, int32_t p606, int32_t p607
|
||||
, int32_t p608, int32_t p609, int32_t p610, int32_t p611, int32_t p612, int32_t p613, int32_t p614, int32_t p615
|
||||
, int32_t p616, int32_t p617, int32_t p618, int32_t p619, int32_t p620, int32_t p621, int32_t p622, int32_t p623
|
||||
, int32_t p624, int32_t p625, int32_t p626, int32_t p627, int32_t p628, int32_t p629, int32_t p630, int32_t p631
|
||||
, int32_t p632, int32_t p633, int32_t p634, int32_t p635, int32_t p636, int32_t p637, int32_t p638, int32_t p639
|
||||
, int32_t p640, int32_t p641, int32_t p642, int32_t p643, int32_t p644, int32_t p645, int32_t p646, int32_t p647
|
||||
, int32_t p648, int32_t p649, int32_t p650, int32_t p651, int32_t p652, int32_t p653, int32_t p654, int32_t p655
|
||||
, int32_t p656, int32_t p657, int32_t p658, int32_t p659, int32_t p660, int32_t p661, int32_t p662, int32_t p663
|
||||
, int32_t p664, int32_t p665, int32_t p666, int32_t p667, int32_t p668, int32_t p669, int32_t p670, int32_t p671
|
||||
, int32_t p672, int32_t p673, int32_t p674, int32_t p675, int32_t p676, int32_t p677, int32_t p678, int32_t p679
|
||||
, int32_t p680, int32_t p681, int32_t p682, int32_t p683, int32_t p684, int32_t p685, int32_t p686, int32_t p687
|
||||
, int32_t p688, int32_t p689, int32_t p690, int32_t p691, int32_t p692, int32_t p693, int32_t p694, int32_t p695
|
||||
, int32_t p696, int32_t p697, int32_t p698, int32_t p699, int32_t p700, int32_t p701, int32_t p702, int32_t p703
|
||||
, int32_t p704, int32_t p705, int32_t p706, int32_t p707, int32_t p708, int32_t p709, int32_t p710, int32_t p711
|
||||
, int32_t p712, int32_t p713, int32_t p714, int32_t p715, int32_t p716, int32_t p717, int32_t p718, int32_t p719
|
||||
, int32_t p720, int32_t p721, int32_t p722, int32_t p723, int32_t p724, int32_t p725, int32_t p726, int32_t p727
|
||||
, int32_t p728, int32_t p729, int32_t p730, int32_t p731, int32_t p732, int32_t p733, int32_t p734, int32_t p735
|
||||
, int32_t p736, int32_t p737, int32_t p738, int32_t p739, int32_t p740, int32_t p741, int32_t p742, int32_t p743
|
||||
, int32_t p744, int32_t p745, int32_t p746, int32_t p747, int32_t p748, int32_t p749, int32_t p750, int32_t p751
|
||||
, int32_t p752, int32_t p753, int32_t p754, int32_t p755, int32_t p756, int32_t p757, int32_t p758, int32_t p759
|
||||
, int32_t p760, int32_t p761, int32_t p762, int32_t p763, int32_t p764, int32_t p765, int32_t p766, int32_t p767
|
||||
, int32_t p768, int32_t p769, int32_t p770, int32_t p771, int32_t p772, int32_t p773, int32_t p774, int32_t p775
|
||||
, int32_t p776, int32_t p777, int32_t p778, int32_t p779, int32_t p780, int32_t p781, int32_t p782, int32_t p783
|
||||
, int32_t p784, int32_t p785, int32_t p786, int32_t p787, int32_t p788, int32_t p789, int32_t p790, int32_t p791
|
||||
, int32_t p792, int32_t p793, int32_t p794, int32_t p795, int32_t p796, int32_t p797, int32_t p798, int32_t p799
|
||||
, int32_t p800, int32_t p801, int32_t p802, int32_t p803, int32_t p804, int32_t p805, int32_t p806, int32_t p807
|
||||
, int32_t p808, int32_t p809, int32_t p810, int32_t p811, int32_t p812, int32_t p813, int32_t p814, int32_t p815
|
||||
, int32_t p816, int32_t p817, int32_t p818, int32_t p819, int32_t p820, int32_t p821, int32_t p822, int32_t p823
|
||||
, int32_t p824, int32_t p825, int32_t p826, int32_t p827, int32_t p828, int32_t p829, int32_t p830, int32_t p831
|
||||
, int32_t p832, int32_t p833, int32_t p834, int32_t p835, int32_t p836, int32_t p837, int32_t p838, int32_t p839
|
||||
, int32_t p840, int32_t p841, int32_t p842, int32_t p843, int32_t p844, int32_t p845, int32_t p846, int32_t p847
|
||||
, int32_t p848, int32_t p849, int32_t p850, int32_t p851, int32_t p852, int32_t p853, int32_t p854, int32_t p855
|
||||
, int32_t p856, int32_t p857, int32_t p858, int32_t p859, int32_t p860, int32_t p861, int32_t p862, int32_t p863
|
||||
, int32_t p864, int32_t p865, int32_t p866, int32_t p867, int32_t p868, int32_t p869, int32_t p870, int32_t p871
|
||||
, int32_t p872, int32_t p873, int32_t p874, int32_t p875, int32_t p876, int32_t p877, int32_t p878, int32_t p879
|
||||
, int32_t p880, int32_t p881, int32_t p882, int32_t p883, int32_t p884, int32_t p885, int32_t p886, int32_t p887
|
||||
, int32_t p888, int32_t p889, int32_t p890, int32_t p891, int32_t p892, int32_t p893, int32_t p894, int32_t p895
|
||||
, int32_t p896, int32_t p897, int32_t p898, int32_t p899, int32_t p900, int32_t p901, int32_t p902, int32_t p903
|
||||
, int32_t p904, int32_t p905, int32_t p906, int32_t p907, int32_t p908, int32_t p909, int32_t p910, int32_t p911
|
||||
, int32_t p912, int32_t p913, int32_t p914, int32_t p915, int32_t p916, int32_t p917, int32_t p918, int32_t p919
|
||||
, int32_t p920, int32_t p921, int32_t p922, int32_t p923, int32_t p924, int32_t p925, int32_t p926, int32_t p927
|
||||
, int32_t p928, int32_t p929, int32_t p930, int32_t p931, int32_t p932, int32_t p933, int32_t p934, int32_t p935
|
||||
, int32_t p936, int32_t p937, int32_t p938, int32_t p939, int32_t p940, int32_t p941, int32_t p942, int32_t p943
|
||||
, int32_t p944, int32_t p945, int32_t p946, int32_t p947, int32_t p948, int32_t p949, int32_t p950, int32_t p951
|
||||
, int32_t p952, int32_t p953, int32_t p954, int32_t p955, int32_t p956, int32_t p957, int32_t p958, int32_t p959
|
||||
, int32_t p960, int32_t p961, int32_t p962, int32_t p963, int32_t p964, int32_t p965, int32_t p966, int32_t p967
|
||||
, int32_t p968, int32_t p969, int32_t p970, int32_t p971, int32_t p972, int32_t p973, int32_t p974, int32_t p975
|
||||
, int32_t p976, int32_t p977, int32_t p978, int32_t p979, int32_t p980, int32_t p981, int32_t p982, int32_t p983
|
||||
, int32_t p984, int32_t p985, int32_t p986, int32_t p987, int32_t p988, int32_t p989, int32_t p990, int32_t p991
|
||||
, int32_t p992, int32_t p993, int32_t p994, int32_t p995, int32_t p996, int32_t p997, int32_t p998, int32_t p999
|
||||
)
|
||||
{
|
||||
int32_t x;
|
||||
x = p0 + p1 + p2 + p3 + p4 + p5 + p6 + p7
|
||||
+ p8 + p9 + p10 + p11 + p12 + p13 + p14 + p15
|
||||
+ p16 + p17 + p18 + p19 + p20 + p21 + p22 + p23
|
||||
+ p24 + p25 + p26 + p27 + p28 + p29 + p30 + p31
|
||||
+ p32 + p33 + p34 + p35 + p36 + p37 + p38 + p39
|
||||
+ p40 + p41 + p42 + p43 + p44 + p45 + p46 + p47
|
||||
+ p48 + p49 + p50 + p51 + p52 + p53 + p54 + p55
|
||||
+ p56 + p57 + p58 + p59 + p60 + p61 + p62 + p63
|
||||
+ p64 + p65 + p66 + p67 + p68 + p69 + p70 + p71
|
||||
+ p72 + p73 + p74 + p75 + p76 + p77 + p78 + p79
|
||||
+ p80 + p81 + p82 + p83 + p84 + p85 + p86 + p87
|
||||
+ p88 + p89 + p90 + p91 + p92 + p93 + p94 + p95
|
||||
+ p96 + p97 + p98 + p99 + p100 + p101 + p102 + p103
|
||||
+ p104 + p105 + p106 + p107 + p108 + p109 + p110 + p111
|
||||
+ p112 + p113 + p114 + p115 + p116 + p117 + p118 + p119
|
||||
+ p120 + p121 + p122 + p123 + p124 + p125 + p126 + p127
|
||||
+ p128 + p129 + p130 + p131 + p132 + p133 + p134 + p135
|
||||
+ p136 + p137 + p138 + p139 + p140 + p141 + p142 + p143
|
||||
+ p144 + p145 + p146 + p147 + p148 + p149 + p150 + p151
|
||||
+ p152 + p153 + p154 + p155 + p156 + p157 + p158 + p159
|
||||
+ p160 + p161 + p162 + p163 + p164 + p165 + p166 + p167
|
||||
+ p168 + p169 + p170 + p171 + p172 + p173 + p174 + p175
|
||||
+ p176 + p177 + p178 + p179 + p180 + p181 + p182 + p183
|
||||
+ p184 + p185 + p186 + p187 + p188 + p189 + p190 + p191
|
||||
+ p192 + p193 + p194 + p195 + p196 + p197 + p198 + p199
|
||||
+ p200 + p201 + p202 + p203 + p204 + p205 + p206 + p207
|
||||
+ p208 + p209 + p210 + p211 + p212 + p213 + p214 + p215
|
||||
+ p216 + p217 + p218 + p219 + p220 + p221 + p222 + p223
|
||||
+ p224 + p225 + p226 + p227 + p228 + p229 + p230 + p231
|
||||
+ p232 + p233 + p234 + p235 + p236 + p237 + p238 + p239
|
||||
+ p240 + p241 + p242 + p243 + p244 + p245 + p246 + p247
|
||||
+ p248 + p249 + p250 + p251 + p252 + p253 + p254 + p255
|
||||
+ p256 + p257 + p258 + p259 + p260 + p261 + p262 + p263
|
||||
+ p264 + p265 + p266 + p267 + p268 + p269 + p270 + p271
|
||||
+ p272 + p273 + p274 + p275 + p276 + p277 + p278 + p279
|
||||
+ p280 + p281 + p282 + p283 + p284 + p285 + p286 + p287
|
||||
+ p288 + p289 + p290 + p291 + p292 + p293 + p294 + p295
|
||||
+ p296 + p297 + p298 + p299 + p300 + p301 + p302 + p303
|
||||
+ p304 + p305 + p306 + p307 + p308 + p309 + p310 + p311
|
||||
+ p312 + p313 + p314 + p315 + p316 + p317 + p318 + p319
|
||||
+ p320 + p321 + p322 + p323 + p324 + p325 + p326 + p327
|
||||
+ p328 + p329 + p330 + p331 + p332 + p333 + p334 + p335
|
||||
+ p336 + p337 + p338 + p339 + p340 + p341 + p342 + p343
|
||||
+ p344 + p345 + p346 + p347 + p348 + p349 + p350 + p351
|
||||
+ p352 + p353 + p354 + p355 + p356 + p357 + p358 + p359
|
||||
+ p360 + p361 + p362 + p363 + p364 + p365 + p366 + p367
|
||||
+ p368 + p369 + p370 + p371 + p372 + p373 + p374 + p375
|
||||
+ p376 + p377 + p378 + p379 + p380 + p381 + p382 + p383
|
||||
+ p384 + p385 + p386 + p387 + p388 + p389 + p390 + p391
|
||||
+ p392 + p393 + p394 + p395 + p396 + p397 + p398 + p399
|
||||
+ p400 + p401 + p402 + p403 + p404 + p405 + p406 + p407
|
||||
+ p408 + p409 + p410 + p411 + p412 + p413 + p414 + p415
|
||||
+ p416 + p417 + p418 + p419 + p420 + p421 + p422 + p423
|
||||
+ p424 + p425 + p426 + p427 + p428 + p429 + p430 + p431
|
||||
+ p432 + p433 + p434 + p435 + p436 + p437 + p438 + p439
|
||||
+ p440 + p441 + p442 + p443 + p444 + p445 + p446 + p447
|
||||
+ p448 + p449 + p450 + p451 + p452 + p453 + p454 + p455
|
||||
+ p456 + p457 + p458 + p459 + p460 + p461 + p462 + p463
|
||||
+ p464 + p465 + p466 + p467 + p468 + p469 + p470 + p471
|
||||
+ p472 + p473 + p474 + p475 + p476 + p477 + p478 + p479
|
||||
+ p480 + p481 + p482 + p483 + p484 + p485 + p486 + p487
|
||||
+ p488 + p489 + p490 + p491 + p492 + p493 + p494 + p495
|
||||
+ p496 + p497 + p498 + p499 + p500 + p501 + p502 + p503
|
||||
+ p504 + p505 + p506 + p507 + p508 + p509 + p510 + p511
|
||||
+ p512 + p513 + p514 + p515 + p516 + p517 + p518 + p519
|
||||
+ p520 + p521 + p522 + p523 + p524 + p525 + p526 + p527
|
||||
+ p528 + p529 + p530 + p531 + p532 + p533 + p534 + p535
|
||||
+ p536 + p537 + p538 + p539 + p540 + p541 + p542 + p543
|
||||
+ p544 + p545 + p546 + p547 + p548 + p549 + p550 + p551
|
||||
+ p552 + p553 + p554 + p555 + p556 + p557 + p558 + p559
|
||||
+ p560 + p561 + p562 + p563 + p564 + p565 + p566 + p567
|
||||
+ p568 + p569 + p570 + p571 + p572 + p573 + p574 + p575
|
||||
+ p576 + p577 + p578 + p579 + p580 + p581 + p582 + p583
|
||||
+ p584 + p585 + p586 + p587 + p588 + p589 + p590 + p591
|
||||
+ p592 + p593 + p594 + p595 + p596 + p597 + p598 + p599
|
||||
+ p600 + p601 + p602 + p603 + p604 + p605 + p606 + p607
|
||||
+ p608 + p609 + p610 + p611 + p612 + p613 + p614 + p615
|
||||
+ p616 + p617 + p618 + p619 + p620 + p621 + p622 + p623
|
||||
+ p624 + p625 + p626 + p627 + p628 + p629 + p630 + p631
|
||||
+ p632 + p633 + p634 + p635 + p636 + p637 + p638 + p639
|
||||
+ p640 + p641 + p642 + p643 + p644 + p645 + p646 + p647
|
||||
+ p648 + p649 + p650 + p651 + p652 + p653 + p654 + p655
|
||||
+ p656 + p657 + p658 + p659 + p660 + p661 + p662 + p663
|
||||
+ p664 + p665 + p666 + p667 + p668 + p669 + p670 + p671
|
||||
+ p672 + p673 + p674 + p675 + p676 + p677 + p678 + p679
|
||||
+ p680 + p681 + p682 + p683 + p684 + p685 + p686 + p687
|
||||
+ p688 + p689 + p690 + p691 + p692 + p693 + p694 + p695
|
||||
+ p696 + p697 + p698 + p699 + p700 + p701 + p702 + p703
|
||||
+ p704 + p705 + p706 + p707 + p708 + p709 + p710 + p711
|
||||
+ p712 + p713 + p714 + p715 + p716 + p717 + p718 + p719
|
||||
+ p720 + p721 + p722 + p723 + p724 + p725 + p726 + p727
|
||||
+ p728 + p729 + p730 + p731 + p732 + p733 + p734 + p735
|
||||
+ p736 + p737 + p738 + p739 + p740 + p741 + p742 + p743
|
||||
+ p744 + p745 + p746 + p747 + p748 + p749 + p750 + p751
|
||||
+ p752 + p753 + p754 + p755 + p756 + p757 + p758 + p759
|
||||
+ p760 + p761 + p762 + p763 + p764 + p765 + p766 + p767
|
||||
+ p768 + p769 + p770 + p771 + p772 + p773 + p774 + p775
|
||||
+ p776 + p777 + p778 + p779 + p780 + p781 + p782 + p783
|
||||
+ p784 + p785 + p786 + p787 + p788 + p789 + p790 + p791
|
||||
+ p792 + p793 + p794 + p795 + p796 + p797 + p798 + p799
|
||||
+ p800 + p801 + p802 + p803 + p804 + p805 + p806 + p807
|
||||
+ p808 + p809 + p810 + p811 + p812 + p813 + p814 + p815
|
||||
+ p816 + p817 + p818 + p819 + p820 + p821 + p822 + p823
|
||||
+ p824 + p825 + p826 + p827 + p828 + p829 + p830 + p831
|
||||
+ p832 + p833 + p834 + p835 + p836 + p837 + p838 + p839
|
||||
+ p840 + p841 + p842 + p843 + p844 + p845 + p846 + p847
|
||||
+ p848 + p849 + p850 + p851 + p852 + p853 + p854 + p855
|
||||
+ p856 + p857 + p858 + p859 + p860 + p861 + p862 + p863
|
||||
+ p864 + p865 + p866 + p867 + p868 + p869 + p870 + p871
|
||||
+ p872 + p873 + p874 + p875 + p876 + p877 + p878 + p879
|
||||
+ p880 + p881 + p882 + p883 + p884 + p885 + p886 + p887
|
||||
+ p888 + p889 + p890 + p891 + p892 + p893 + p894 + p895
|
||||
+ p896 + p897 + p898 + p899 + p900 + p901 + p902 + p903
|
||||
+ p904 + p905 + p906 + p907 + p908 + p909 + p910 + p911
|
||||
+ p912 + p913 + p914 + p915 + p916 + p917 + p918 + p919
|
||||
+ p920 + p921 + p922 + p923 + p924 + p925 + p926 + p927
|
||||
+ p928 + p929 + p930 + p931 + p932 + p933 + p934 + p935
|
||||
+ p936 + p937 + p938 + p939 + p940 + p941 + p942 + p943
|
||||
+ p944 + p945 + p946 + p947 + p948 + p949 + p950 + p951
|
||||
+ p952 + p953 + p954 + p955 + p956 + p957 + p958 + p959
|
||||
+ p960 + p961 + p962 + p963 + p964 + p965 + p966 + p967
|
||||
+ p968 + p969 + p970 + p971 + p972 + p973 + p974 + p975
|
||||
+ p976 + p977 + p978 + p979 + p980 + p981 + p982 + p983
|
||||
+ p984 + p985 + p986 + p987 + p988 + p989 + p990 + p991
|
||||
+ p992 + p993 + p994 + p995 + p996 + p997 + p998 + p999;
|
||||
return x;
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
13
src/test/app/wasm_fixtures/wat/custom_page_sizes.wat
Normal file
13
src/test/app/wasm_fixtures/wat/custom_page_sizes.wat
Normal file
@@ -0,0 +1,13 @@
|
||||
(module
|
||||
;; Define a memory with 1 initial page.
|
||||
;; CRITICAL: We explicitly set the page size to 1 byte.
|
||||
;; Standard Wasm implies (pagesize 65536).
|
||||
(memory 1 (pagesize 1))
|
||||
|
||||
(func $escrow_finish (result i32)
|
||||
;; If this module instantiates, the runtime accepted the custom page size.
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
(export "escrow_finish" (func $escrow_finish))
|
||||
)
|
||||
29
src/test/app/wasm_fixtures/wat/deep_recursion.wat
Normal file
29
src/test/app/wasm_fixtures/wat/deep_recursion.wat
Normal file
@@ -0,0 +1,29 @@
|
||||
(module
|
||||
;; Define a Mutable Global Variable to act as our counter.
|
||||
;; We initialize it to 1,000,000.
|
||||
(global $counter (mut i32) (i32.const 1000000))
|
||||
|
||||
(func $escrow_finish (result i32)
|
||||
;; 1. Check if counter == 0 (Base Case)
|
||||
global.get $counter
|
||||
i32.eqz
|
||||
if
|
||||
;; If counter is 0, we are done. Return 1.
|
||||
i32.const 1
|
||||
return
|
||||
end
|
||||
|
||||
;; 2. Decrement the Global Counter
|
||||
global.get $counter
|
||||
i32.const 1
|
||||
i32.sub
|
||||
global.set $counter
|
||||
|
||||
;; 3. Recursive Step: Call SELF
|
||||
;; This puts an i32 (1) on the stack when it returns.
|
||||
call $escrow_finish
|
||||
)
|
||||
|
||||
;; Export the only function we have
|
||||
(export "escrow_finish" (func $escrow_finish))
|
||||
)
|
||||
BIN
src/test/app/wasm_fixtures/wat/functions_5k.zip
Normal file
BIN
src/test/app/wasm_fixtures/wat/functions_5k.zip
Normal file
Binary file not shown.
BIN
src/test/app/wasm_fixtures/wat/locals_10k.zip
Normal file
BIN
src/test/app/wasm_fixtures/wat/locals_10k.zip
Normal file
Binary file not shown.
21
src/test/app/wasm_fixtures/wat/memory64.wat
Normal file
21
src/test/app/wasm_fixtures/wat/memory64.wat
Normal file
@@ -0,0 +1,21 @@
|
||||
(module
|
||||
;; Define a 64-bit memory (index type i64)
|
||||
;; Start with 1 page.
|
||||
(memory i64 1)
|
||||
|
||||
(func $escrow_finish (result i32)
|
||||
;; 1. Perform a store using a 64-bit address.
|
||||
;; Even if the value is small (0), the type MUST be i64.
|
||||
i64.const 0 ;; Address (64-bit)
|
||||
i32.const 42 ;; Value (32-bit)
|
||||
i32.store8 ;; Opcode doesn't change, but validation rules do.
|
||||
|
||||
;; 2. check memory size
|
||||
;; memory.size now returns an i64.
|
||||
memory.size
|
||||
i64.const 1
|
||||
i64.eq ;; Returns i32 (1 if true)
|
||||
)
|
||||
|
||||
(export "escrow_finish" (func $escrow_finish))
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
(module
|
||||
;; 1. Define Memory: 1 Page = 64KB = 65,536 bytes
|
||||
(memory 1)
|
||||
|
||||
;; Export memory so the host can inspect it if needed
|
||||
(export "memory" (memory 0))
|
||||
|
||||
(func $test_straddle (result i32)
|
||||
;; Push the address onto the stack.
|
||||
;; 65534 is valid, but it is only 2 bytes away from the end.
|
||||
i32.const 65534
|
||||
|
||||
;; Attempt to load an i32 (4 bytes) from that address.
|
||||
;; This requires bytes 65534, 65535, 65536, and 65537.
|
||||
;; Since 65536 is the first invalid byte, this MUST trap.
|
||||
i32.load
|
||||
|
||||
;; Clean up the stack.
|
||||
;; The load pushed a value, but we don't care what it is.
|
||||
drop
|
||||
|
||||
;; Return 1 to signal "I survived the memory access"
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
;; Export the function so you can call it from your host (JS, Python, etc.)
|
||||
(export "escrow_finish" (func $test_straddle))
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
(module
|
||||
;; Start at your limit: 128 pages (8MB)
|
||||
(memory 128)
|
||||
(export "memory" (memory 0))
|
||||
|
||||
(func $try_grow_beyond_limit (result i32)
|
||||
;; Attempt to grow by 0 page
|
||||
i32.const 0
|
||||
memory.grow
|
||||
|
||||
;; memory.grow returns:
|
||||
;; -1 if the growth failed (Correct behavior for your limit)
|
||||
;; 128 (old size) if growth succeeded (Means limit was bypassed)
|
||||
|
||||
;; Check if result == -1
|
||||
i32.const -1
|
||||
i32.eq
|
||||
if
|
||||
;; Growth FAILED (Host blocked it). Return -1.
|
||||
i32.const -1
|
||||
return
|
||||
end
|
||||
|
||||
;; Growth SUCCEEDED (Host allowed it). Return 1.
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
(export "escrow_finish" (func $try_grow_beyond_limit))
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user