mirror of
https://github.com/XRPLF/rippled.git
synced 2026-02-04 05:55:25 +00:00
Compare commits
185 Commits
copilot/co
...
ripple/se/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f625fb993a | ||
|
|
0418ffb26a | ||
|
|
b2627039f6 | ||
|
|
6c1a92fe93 | ||
|
|
7813683091 | ||
|
|
b814a09a08 | ||
|
|
6d369e0f02 | ||
|
|
b182430178 | ||
|
|
fe31cdc9f6 | ||
|
|
ff4520cc45 | ||
|
|
8f97ec3bde | ||
|
|
e85e7b1b1a | ||
|
|
69c61b2235 | ||
|
|
6ae0e860ff | ||
|
|
c077e7f073 | ||
|
|
803a344c65 | ||
|
|
4eb34f381a | ||
|
|
72fffb6e51 | ||
|
|
f7ee580f01 | ||
|
|
122d405750 | ||
|
|
c1c1b4ea67 | ||
|
|
977caea0a5 | ||
|
|
d7ed6d6512 | ||
|
|
f1f2e2629f | ||
|
|
917c610f96 | ||
|
|
317e533d81 | ||
|
|
4160677878 | ||
|
|
4621e4eda3 | ||
|
|
df98db1452 | ||
|
|
981ac7abf4 | ||
|
|
673476ef1b | ||
|
|
8bc6f9cd70 | ||
|
|
ba5debfecd | ||
|
|
f4a27c9b6d | ||
|
|
fd1cb318e3 | ||
|
|
43c80edaf4 | ||
|
|
8c3544a58c | ||
|
|
ed5139d4e3 | ||
|
|
42494dd4cf | ||
|
|
ce84cc8b44 | ||
|
|
9a9a7aab01 | ||
|
|
209a1a6ffa | ||
|
|
384b3608d7 | ||
|
|
fc35a9f9c8 | ||
|
|
c5e50aa221 | ||
|
|
074b1f00d5 | ||
|
|
7a9d245950 | ||
|
|
1809fe07f2 | ||
|
|
409c67494a | ||
|
|
c626b6403a | ||
|
|
81cbc91927 | ||
|
|
e1513570df | ||
|
|
1c812a6c4d | ||
|
|
0724927799 | ||
|
|
d83ec96848 | ||
|
|
f0d0739528 | ||
|
|
375dd50b35 | ||
|
|
419d53ec4c | ||
|
|
d4d70d5675 | ||
|
|
6ab15f8377 | ||
|
|
91f3d51f3d | ||
|
|
9ed60b45f8 | ||
|
|
d5c53dcfd2 | ||
|
|
103379836a | ||
|
|
e94321fb41 | ||
|
|
bbc28b3b1c | ||
|
|
843e981c8a | ||
|
|
5aab274b7a | ||
|
|
2c30e41191 | ||
|
|
8ea5106b0b | ||
|
|
f57f67a8ae | ||
|
|
397bc8781e | ||
|
|
a98269f049 | ||
|
|
8bb8c2e38b | ||
|
|
b66bc47ca9 | ||
|
|
0e9c7458bb | ||
|
|
36ecd3b52b | ||
|
|
1d89940653 | ||
|
|
1a1a6806ec | ||
|
|
1977df9c2e | ||
|
|
9d1f51b01a | ||
|
|
6c95548df5 | ||
|
|
69ab39d658 | ||
|
|
b9eb66eecc | ||
|
|
827ecc6e3a | ||
|
|
881087dd3d | ||
|
|
90e0bbd0fc | ||
|
|
b57df290de | ||
|
|
8a403f1241 | ||
|
|
6d2640871d | ||
|
|
27ac30208d | ||
|
|
c145598ff9 | ||
|
|
50e5608d86 | ||
|
|
e40a4df777 | ||
|
|
7a7b96107c | ||
|
|
500bb68831 | ||
|
|
95d78a8600 | ||
|
|
53eb0f60bc | ||
|
|
41205ae928 | ||
|
|
c33b0ae463 | ||
|
|
16087c9680 | ||
|
|
56bc6d58f6 | ||
|
|
ef5d335e09 | ||
|
|
25c3060fef | ||
|
|
ce9f0b38a4 | ||
|
|
35f7cbf772 | ||
|
|
578413859c | ||
|
|
0db564d261 | ||
|
|
427b7ea104 | ||
|
|
3195eb16b2 | ||
|
|
7bf6878b4b | ||
|
|
eed280d169 | ||
|
|
0bc1a115ff | ||
|
|
334bcfa5ef | ||
|
|
106dea4559 | ||
|
|
3ffdcf8114 | ||
|
|
4021a7eb28 | ||
|
|
0690fda0f1 | ||
|
|
d0cc48c6d3 | ||
|
|
d66e3c949e | ||
|
|
aebec5378c | ||
|
|
0c65a386b5 | ||
|
|
67e2c1b563 | ||
|
|
29f5430881 | ||
|
|
209ee25c32 | ||
|
|
101f285bcd | ||
|
|
af6beb1d7c | ||
|
|
ce19c13059 | ||
|
|
286dc6322b | ||
|
|
c9346cd40d | ||
|
|
43caa1ef29 | ||
|
|
1c5683ec78 | ||
|
|
9bee155d59 | ||
|
|
f34b05f4de | ||
|
|
97ce25f4ce | ||
|
|
9e14c14a26 | ||
|
|
fe601308e7 | ||
|
|
c507880d8f | ||
|
|
3f8328bbf8 | ||
|
|
51f1be7f5b | ||
|
|
c10a5f9ef6 | ||
|
|
3c141de695 | ||
|
|
f57b855d74 | ||
|
|
da2b9455f2 | ||
|
|
86525d8583 | ||
|
|
51ee06429b | ||
|
|
cb622488c0 | ||
|
|
32f971fec6 | ||
|
|
ca85d09f02 | ||
|
|
8dea76baa4 | ||
|
|
299fbe04c4 | ||
|
|
57fc1df7d7 | ||
|
|
e59f5f3b01 | ||
|
|
c8b06e7de1 | ||
|
|
eaba76f9e6 | ||
|
|
cb702cc238 | ||
|
|
c3fd52c177 | ||
|
|
b69b4a0a4a | ||
|
|
50d6072a73 | ||
|
|
d24cd50e61 | ||
|
|
737fab5471 | ||
|
|
5a6c4e8ae0 | ||
|
|
9f5875158c | ||
|
|
c3dc33c861 | ||
|
|
7420f47658 | ||
|
|
6be8f2124c | ||
|
|
edfed06001 | ||
|
|
1c646dba91 | ||
|
|
6781068058 | ||
|
|
cfe57c1dfe | ||
|
|
c34d09a971 | ||
|
|
ebd90c4742 | ||
|
|
ba52d34828 | ||
|
|
1b6312afb3 | ||
|
|
bf32dc2e72 | ||
|
|
a15d65f7a2 | ||
|
|
2de8488855 | ||
|
|
129aa4bfaa | ||
|
|
b1d70db63b | ||
|
|
f03c3aafe4 | ||
|
|
51a9f106d1 | ||
|
|
bfc048e3fe | ||
|
|
83418644f7 | ||
|
|
dbc9dd5bfc | ||
|
|
45ab15d4b5 |
@@ -272,6 +272,7 @@ words:
|
||||
- venv
|
||||
- vfalco
|
||||
- vinnie
|
||||
- wasmi
|
||||
- wextra
|
||||
- wptr
|
||||
- writeme
|
||||
|
||||
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
@@ -1,8 +1,2 @@
|
||||
# Allow anyone to review any change by default.
|
||||
*
|
||||
|
||||
# Require the rpc-reviewers team to review changes to the rpc code.
|
||||
include/xrpl/protocol/ @xrplf/rpc-reviewers
|
||||
src/libxrpl/protocol/ @xrplf/rpc-reviewers
|
||||
src/xrpld/rpc/ @xrplf/rpc-reviewers
|
||||
src/xrpld/app/misc/ @xrplf/rpc-reviewers
|
||||
|
||||
@@ -153,6 +153,7 @@ tests.libxrpl > xrpl.json
|
||||
tests.libxrpl > xrpl.net
|
||||
xrpl.core > xrpl.basics
|
||||
xrpl.core > xrpl.json
|
||||
xrpl.core > xrpl.ledger
|
||||
xrpl.json > xrpl.basics
|
||||
xrpl.ledger > xrpl.basics
|
||||
xrpl.ledger > xrpl.protocol
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@282890f46d6921249d5659dd38babcb0bd8aef48
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@320be44621ca2a080f05aeb15817c44b84518108
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-ab4d1f0" }'
|
||||
|
||||
2
.github/workflows/publish-docs.yml
vendored
2
.github/workflows/publish-docs.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/get-nproc@2ece4ec6ab7de266859a6f053571425b2bd684b6
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
with:
|
||||
subtract: ${{ env.NPROC_SUBTRACT }}
|
||||
|
||||
@@ -101,13 +101,13 @@ jobs:
|
||||
steps:
|
||||
- name: Cleanup workspace (macOS and Windows)
|
||||
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
|
||||
uses: XRPLF/actions/cleanup-workspace@2ece4ec6ab7de266859a6f053571425b2bd684b6
|
||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@f05cab7b8541eee6473aa42beb9d2fe35608a190
|
||||
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||
with:
|
||||
enable_ccache: ${{ inputs.ccache_enabled }}
|
||||
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
uses: ./.github/actions/print-env
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/get-nproc@2ece4ec6ab7de266859a6f053571425b2bd684b6
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
with:
|
||||
subtract: ${{ inputs.nproc_subtract }}
|
||||
|
||||
6
.github/workflows/upload-conan-deps.yml
vendored
6
.github/workflows/upload-conan-deps.yml
vendored
@@ -64,13 +64,13 @@ jobs:
|
||||
steps:
|
||||
- name: Cleanup workspace (macOS and Windows)
|
||||
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
|
||||
uses: XRPLF/actions/cleanup-workspace@2ece4ec6ab7de266859a6f053571425b2bd684b6
|
||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@f05cab7b8541eee6473aa42beb9d2fe35608a190
|
||||
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
uses: ./.github/actions/print-env
|
||||
|
||||
- name: Get number of processors
|
||||
uses: XRPLF/actions/get-nproc@2ece4ec6ab7de266859a6f053571425b2bd684b6
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
with:
|
||||
subtract: ${{ env.NPROC_SUBTRACT }}
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -64,6 +64,9 @@ DerivedData
|
||||
/.vs/
|
||||
/.vscode/
|
||||
|
||||
# zed IDE.
|
||||
/.zed/
|
||||
|
||||
# AI tools.
|
||||
/.augment
|
||||
/.claude
|
||||
|
||||
200
API-CHANGELOG.md
200
API-CHANGELOG.md
@@ -6,90 +6,85 @@ For info about how [API versioning](https://xrpl.org/request-formatting.html#api
|
||||
|
||||
The API version controls the API behavior you see. This includes what properties you see in responses, what parameters you're permitted to send in requests, and so on. You specify the API version in each of your requests. When a breaking change is introduced to the `rippled` API, a new version is released. To avoid breaking your code, you should set (or increase) your version when you're ready to upgrade.
|
||||
|
||||
The [commandline](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/request-formatting/#commandline-format) always uses the latest API version. The command line is intended for ad-hoc usage by humans, not programs or automated scripts. The command line is not meant for use in production code.
|
||||
|
||||
For a log of breaking changes, see the **API Version [number]** headings. In general, breaking changes are associated with a particular API Version number. For non-breaking changes, scroll to the **XRP Ledger version [x.y.z]** headings. Non-breaking changes are associated with a particular XRP Ledger (`rippled`) release.
|
||||
|
||||
## API Version 3 (Beta)
|
||||
|
||||
API version 3 is currently a beta API. It requires enabling `[beta_rpc_api]` in the rippled configuration to use. See [API-VERSION-3.md](API-VERSION-3.md) for the full list of changes in API version 3.
|
||||
|
||||
## API Version 2
|
||||
|
||||
API version 2 is available in `rippled` version 2.0.0 and later. To use this API, clients specify `"api_version" : 2` in each request.
|
||||
|
||||
#### Removed methods
|
||||
|
||||
In API version 2, the following deprecated methods are no longer available: (https://github.com/XRPLF/rippled/pull/4759)
|
||||
|
||||
- `tx_history` - Instead, use other methods such as `account_tx` or `ledger` with the `transactions` field set to `true`.
|
||||
- `ledger_header` - Instead, use the `ledger` method.
|
||||
|
||||
#### Modifications to JSON transaction element in V2
|
||||
|
||||
In API version 2, JSON elements for transaction output have been changed and made consistent for all methods which output transactions. (https://github.com/XRPLF/rippled/pull/4775)
|
||||
This helps to unify the JSON serialization format of transactions. (https://github.com/XRPLF/clio/issues/722, https://github.com/XRPLF/rippled/issues/4727)
|
||||
|
||||
- JSON transaction element is named `tx_json`
|
||||
- Binary transaction element is named `tx_blob`
|
||||
- JSON transaction metadata element is named `meta`
|
||||
- Binary transaction metadata element is named `meta_blob`
|
||||
|
||||
Additionally, these elements are now consistently available next to `tx_json` (i.e. sibling elements), where possible:
|
||||
|
||||
- `hash` - Transaction ID. This data was stored inside transaction output in API version 1, but in API version 2 is a sibling element.
|
||||
- `ledger_index` - Ledger index (only set on validated ledgers)
|
||||
- `ledger_hash` - Ledger hash (only set on closed or validated ledgers)
|
||||
- `close_time_iso` - Ledger close time expressed in ISO 8601 time format (only set on validated ledgers)
|
||||
- `validated` - Bool element set to `true` if the transaction is in a validated ledger, otherwise `false`
|
||||
|
||||
This change affects the following methods:
|
||||
|
||||
- `tx` - Transaction data moved into element `tx_json` (was inline inside `result`) or, if binary output was requested, moved from `tx` to `tx_blob`. Renamed binary transaction metadata element (if it was requested) from `meta` to `meta_blob`. Changed location of `hash` and added new elements
|
||||
- `account_tx` - Renamed transaction element from `tx` to `tx_json`. Renamed binary transaction metadata element (if it was requested) from `meta` to `meta_blob`. Changed location of `hash` and added new elements
|
||||
- `transaction_entry` - Renamed transaction metadata element from `metadata` to `meta`. Changed location of `hash` and added new elements
|
||||
- `subscribe` - Renamed transaction element from `transaction` to `tx_json`. Changed location of `hash` and added new elements
|
||||
- `sign`, `sign_for`, `submit` and `submit_multisigned` - Changed location of `hash` element.
|
||||
|
||||
#### Modification to `Payment` transaction JSON schema
|
||||
|
||||
When reading Payments, the `Amount` field should generally **not** be used. Instead, use [delivered_amount](https://xrpl.org/partial-payments.html#the-delivered_amount-field) to see the amount that the Payment delivered. To clarify its meaning, the `Amount` field is being renamed to `DeliverMax`. (https://github.com/XRPLF/rippled/pull/4733)
|
||||
|
||||
- In `Payment` transaction type, JSON RPC field `Amount` is renamed to `DeliverMax`. To enable smooth client transition, `Amount` is still handled, as described below: (https://github.com/XRPLF/rippled/pull/4733)
|
||||
- On JSON RPC input (e.g. `submit_multisigned` etc. methods), `Amount` is recognized as an alias to `DeliverMax` for both API version 1 and version 2 clients.
|
||||
- On JSON RPC input, submitting both `Amount` and `DeliverMax` fields is allowed _only_ if they are identical; otherwise such input is rejected with `rpcINVALID_PARAMS` error.
|
||||
- On JSON RPC output (e.g. `subscribe`, `account_tx` etc. methods), `DeliverMax` is present in both API version 1 and version 2.
|
||||
- On JSON RPC output, `Amount` is only present in API version 1 and _not_ in version 2.
|
||||
|
||||
#### Modifications to account_info response
|
||||
|
||||
- `signer_lists` is returned in the root of the response. (In API version 1, it was nested under `account_data`.) (https://github.com/XRPLF/rippled/pull/3770)
|
||||
- When using an invalid `signer_lists` value, the API now returns an "invalidParams" error. (https://github.com/XRPLF/rippled/pull/4585)
|
||||
- (`signer_lists` must be a boolean. In API version 1, strings were accepted and may return a normal response - i.e. as if `signer_lists` were `true`.)
|
||||
|
||||
#### Modifications to [account_tx](https://xrpl.org/account_tx.html#account_tx) response
|
||||
|
||||
- Using `ledger_index_min`, `ledger_index_max`, and `ledger_index` returns `invalidParams` because if you use `ledger_index_min` or `ledger_index_max`, then it does not make sense to also specify `ledger_index`. In API version 1, no error was returned. (https://github.com/XRPLF/rippled/pull/4571)
|
||||
- The same applies for `ledger_index_min`, `ledger_index_max`, and `ledger_hash`. (https://github.com/XRPLF/rippled/issues/4545#issuecomment-1565065579)
|
||||
- Using a `ledger_index_min` or `ledger_index_max` beyond the range of ledgers that the server has:
|
||||
- returns `lgrIdxMalformed` in API version 2. Previously, in API version 1, no error was returned. (https://github.com/XRPLF/rippled/issues/4288)
|
||||
- Attempting to use a non-boolean value (such as a string) for the `binary` or `forward` parameters returns `invalidParams` (`rpcINVALID_PARAMS`). Previously, in API version 1, no error was returned. (https://github.com/XRPLF/rippled/pull/4620)
|
||||
|
||||
#### Modifications to [noripple_check](https://xrpl.org/noripple_check.html#noripple_check) response
|
||||
|
||||
- Attempting to use a non-boolean value (such as a string) for the `transactions` parameter returns `invalidParams` (`rpcINVALID_PARAMS`). Previously, in API version 1, no error was returned. (https://github.com/XRPLF/rippled/pull/4620)
|
||||
API version 2 is available in `rippled` version 2.0.0 and later. See [API-VERSION-2.md](API-VERSION-2.md) for the full list of changes in API version 2.
|
||||
|
||||
## API Version 1
|
||||
|
||||
This version is supported by all `rippled` versions. For WebSocket and HTTP JSON-RPC requests, it is currently the default API version used when no `api_version` is specified.
|
||||
|
||||
The [commandline](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/request-formatting/#commandline-format) always uses the latest API version. The command line is intended for ad-hoc usage by humans, not programs or automated scripts. The command line is not meant for use in production code.
|
||||
## XRP Ledger server version 3.1.0
|
||||
|
||||
### Inconsistency: server_info - network_id
|
||||
[Version 3.1.0](https://github.com/XRPLF/rippled/releases/tag/3.1.0) was released on Jan 27, 2026.
|
||||
|
||||
The `network_id` field was added in the `server_info` response in version 1.5.0 (2019), but it is not returned in [reporting mode](https://xrpl.org/rippled-server-modes.html#reporting-mode). However, use of reporting mode is now discouraged, in favor of using [Clio](https://github.com/XRPLF/clio) instead.
|
||||
### Additions in 3.1.0
|
||||
|
||||
- `vault_info`: New RPC method to retrieve information about a specific vault (part of XLS-66 Lending Protocol). ([#6156](https://github.com/XRPLF/rippled/pull/6156))
|
||||
|
||||
## XRP Ledger server version 3.0.0
|
||||
|
||||
[Version 3.0.0](https://github.com/XRPLF/rippled/releases/tag/3.0.0) was released on Dec 9, 2025.
|
||||
|
||||
### Additions in 3.0.0
|
||||
|
||||
- `ledger_entry`: Supports all ledger entry types with dedicated parsers. ([#5237](https://github.com/XRPLF/rippled/pull/5237))
|
||||
- `ledger_entry`: New error codes `entryNotFound` and `unexpectedLedgerType` for more specific error handling. ([#5237](https://github.com/XRPLF/rippled/pull/5237))
|
||||
- `ledger_entry`: Improved error messages with more context (e.g., specifying which field is invalid or missing). ([#5237](https://github.com/XRPLF/rippled/pull/5237))
|
||||
- `ledger_entry`: Assorted bug fixes in RPC processing. ([#5237](https://github.com/XRPLF/rippled/pull/5237))
|
||||
- `simulate`: Supports additional metadata in the response. ([#5754](https://github.com/XRPLF/rippled/pull/5754))
|
||||
|
||||
## XRP Ledger server version 2.6.2
|
||||
|
||||
[Version 2.6.2](https://github.com/XRPLF/rippled/releases/tag/2.6.2) was released on Nov 19, 2025.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 2.6.1
|
||||
|
||||
[Version 2.6.1](https://github.com/XRPLF/rippled/releases/tag/2.6.1) was released on Sep 30, 2025.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 2.6.0
|
||||
|
||||
[Version 2.6.0](https://github.com/XRPLF/rippled/releases/tag/2.6.0) was released on Aug 27, 2025.
|
||||
|
||||
### Additions in 2.6.0
|
||||
|
||||
- `account_info`: Added `allowTrustLineLocking` flag in response. ([#5525](https://github.com/XRPLF/rippled/pull/5525))
|
||||
- `ledger`: Removed the type filter from the RPC command. ([#4934](https://github.com/XRPLF/rippled/pull/4934))
|
||||
- `subscribe` (`validations` stream): `network_id` is now included. ([#5579](https://github.com/XRPLF/rippled/pull/5579))
|
||||
- `subscribe` (`transactions` stream): `nftoken_id`, `nftoken_ids`, and `offer_id` are now included in transaction metadata. ([#5230](https://github.com/XRPLF/rippled/pull/5230))
|
||||
|
||||
## XRP Ledger server version 2.5.1
|
||||
|
||||
[Version 2.5.1](https://github.com/XRPLF/rippled/releases/tag/2.5.1) was released on Sep 17, 2025.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 2.5.0
|
||||
|
||||
As of 2025-04-04, version 2.5.0 is in development. You can use a pre-release version by building from source or [using the `nightly` package](https://xrpl.org/docs/infrastructure/installation/install-rippled-on-ubuntu).
|
||||
[Version 2.5.0](https://github.com/XRPLF/rippled/releases/tag/2.5.0) was released on Jun 24, 2025.
|
||||
|
||||
### Additions and bugfixes in 2.5.0
|
||||
|
||||
- `channel_authorize`: If `signing_support` is not enabled in the config, the RPC is disabled.
|
||||
- `tx`: Added `ctid` field to the response and improved error handling. ([#4738](https://github.com/XRPLF/rippled/pull/4738))
|
||||
- `ledger_entry`: Improved error messages in `permissioned_domain`. ([#5344](https://github.com/XRPLF/rippled/pull/5344))
|
||||
- `simulate`: Improved multi-sign usage. ([#5479](https://github.com/XRPLF/rippled/pull/5479))
|
||||
- `channel_authorize`: If `signing_support` is not enabled in the config, the RPC is disabled. ([#5385](https://github.com/XRPLF/rippled/pull/5385))
|
||||
- `subscribe` (admin): Removed webhook queue limit to prevent dropping notifications; reduced HTTP timeout from 10 minutes to 30 seconds. ([#5163](https://github.com/XRPLF/rippled/pull/5163))
|
||||
- `ledger_data` (gRPC): Fixed crashing issue with some invalid markers. ([#5137](https://github.com/XRPLF/rippled/pull/5137))
|
||||
- `account_lines`: Fixed error with `no_ripple` and `no_ripple_peer` sometimes showing up incorrectly. ([#5345](https://github.com/XRPLF/rippled/pull/5345))
|
||||
- `account_tx`: Fixed issue with incorrect CTIDs. ([#5408](https://github.com/XRPLF/rippled/pull/5408))
|
||||
|
||||
## XRP Ledger server version 2.4.0
|
||||
|
||||
@@ -97,11 +92,19 @@ As of 2025-04-04, version 2.5.0 is in development. You can use a pre-release ver
|
||||
|
||||
### Additions and bugfixes in 2.4.0
|
||||
|
||||
- `ledger_entry`: `state` is added an alias for `ripple_state`.
|
||||
- `ledger_entry`: Enables case-insensitive filtering by canonical name in addition to case-sensitive filtering by RPC name.
|
||||
- `validators`: Added new field `validator_list_threshold` in response.
|
||||
- `simulate`: A new RPC that executes a [dry run of a transaction submission](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069d-simulate#2-rpc-simulate)
|
||||
- Signing methods autofill fees better and properly handle transactions that don't have a base fee, and will also autofill the `NetworkID` field.
|
||||
- `simulate`: A new RPC that executes a [dry run of a transaction submission](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069d-simulate#2-rpc-simulate). ([#5069](https://github.com/XRPLF/rippled/pull/5069))
|
||||
- Signing methods (`sign`, `sign_for`, `submit`): Autofill fees better, properly handle transactions without a base fee, and autofill the `NetworkID` field. ([#5069](https://github.com/XRPLF/rippled/pull/5069))
|
||||
- `ledger_entry`: `state` is added as an alias for `ripple_state`. ([#5199](https://github.com/XRPLF/rippled/pull/5199))
|
||||
- `ledger`, `ledger_data`, `account_objects`: Support filtering ledger entry types by their canonical names (case-insensitive). ([#5271](https://github.com/XRPLF/rippled/pull/5271))
|
||||
- `validators`: Added new field `validator_list_threshold` in response. ([#5112](https://github.com/XRPLF/rippled/pull/5112))
|
||||
- `server_info`: Added git commit hash info on admin connection. ([#5225](https://github.com/XRPLF/rippled/pull/5225))
|
||||
- `server_definitions`: Changed larger `UInt` serialized types to `Hash`. ([#5231](https://github.com/XRPLF/rippled/pull/5231))
|
||||
|
||||
## XRP Ledger server version 2.3.1
|
||||
|
||||
[Version 2.3.1](https://github.com/XRPLF/rippled/releases/tag/2.3.1) was released on Jan 29, 2025.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 2.3.0
|
||||
|
||||
@@ -109,19 +112,30 @@ As of 2025-04-04, version 2.5.0 is in development. You can use a pre-release ver
|
||||
|
||||
### Breaking changes in 2.3.0
|
||||
|
||||
- `book_changes`: If the requested ledger version is not available on this node, a `ledgerNotFound` error is returned and the node does not attempt to acquire the ledger from the p2p network (as with other non-admin RPCs).
|
||||
|
||||
Admins can still attempt to retrieve old ledgers with the `ledger_request` RPC.
|
||||
- `book_changes`: If the requested ledger version is not available on this node, a `ledgerNotFound` error is returned and the node does not attempt to acquire the ledger from the p2p network (as with other non-admin RPCs). Admins can still attempt to retrieve old ledgers with the `ledger_request` RPC.
|
||||
|
||||
### Additions and bugfixes in 2.3.0
|
||||
|
||||
- `book_changes`: Returns a `validated` field in its response, which was missing in prior versions.
|
||||
- `book_changes`: Returns a `validated` field in its response. ([#5096](https://github.com/XRPLF/rippled/pull/5096))
|
||||
- `book_changes`: Accepts shortcut strings (`current`, `closed`, `validated`) for the `ledger_index` parameter. ([#5096](https://github.com/XRPLF/rippled/pull/5096))
|
||||
- `server_definitions`: Include `index` in response. ([#5190](https://github.com/XRPLF/rippled/pull/5190))
|
||||
- `account_nfts`: Fix issue where unassociated marker would return incorrect results. ([#5045](https://github.com/XRPLF/rippled/pull/5045))
|
||||
- `account_objects`: Fix issue where invalid marker would not return an error. ([#5046](https://github.com/XRPLF/rippled/pull/5046))
|
||||
- `account_objects`: Disallow filtering by ledger entry types that an account cannot hold. ([#5056](https://github.com/XRPLF/rippled/pull/5056))
|
||||
- `tx`: Allow lowercase CTID. ([#5049](https://github.com/XRPLF/rippled/pull/5049))
|
||||
- `feature`: Better error handling for invalid values of `feature`. ([#5063](https://github.com/XRPLF/rippled/pull/5063))
|
||||
|
||||
## XRP Ledger server version 2.2.0
|
||||
|
||||
[Version 2.2.0](https://github.com/XRPLF/rippled/releases/tag/2.2.0) was released on Jun 5, 2024. The following additions are non-breaking (because they are purely additive):
|
||||
|
||||
- The `feature` method now has a non-admin mode for users. (It was previously only available to admin connections.) The method returns an updated list of amendments, including their names and other information. ([#4781](https://github.com/XRPLF/rippled/pull/4781))
|
||||
- `feature`: Add a non-admin mode for users. (It was previously only available to admin connections.) The method returns an updated list of amendments, including their names and other information. ([#4781](https://github.com/XRPLF/rippled/pull/4781))
|
||||
|
||||
## XRP Ledger server version 2.0.1
|
||||
|
||||
[Version 2.0.1](https://github.com/XRPLF/rippled/releases/tag/2.0.1) was released on Jan 29, 2024. The following additions are non-breaking:
|
||||
|
||||
- `path_find`: Fixes unbounded memory growth. ([#4822](https://github.com/XRPLF/rippled/pull/4822))
|
||||
|
||||
## XRP Ledger server version 2.0.0
|
||||
|
||||
@@ -129,24 +143,18 @@ Admins can still attempt to retrieve old ledgers with the `ledger_request` RPC.
|
||||
|
||||
- `server_definitions`: A new RPC that generates a `definitions.json`-like output that can be used in XRPL libraries.
|
||||
- In `Payment` transactions, `DeliverMax` has been added. This is a replacement for the `Amount` field, which should not be used. Typically, the `delivered_amount` (in transaction metadata) should be used. To ease the transition, `DeliverMax` is present regardless of API version, since adding a field is non-breaking.
|
||||
- API version 2 has been moved from beta to supported, meaning that it is generally available (regardless of the `beta_rpc_api` setting).
|
||||
|
||||
## XRP Ledger server version 2.2.0
|
||||
|
||||
The following is a non-breaking addition to the API.
|
||||
|
||||
- The `feature` method now has a non-admin mode for users. (It was previously only available to admin connections.) The method returns an updated list of amendments, including their names and other information. ([#4781](https://github.com/XRPLF/rippled/pull/4781))
|
||||
- API version 2 has been moved from beta to supported, meaning that it is generally available (regardless of the `beta_rpc_api` setting). The full list of changes is in [API-VERSION-2.md](API-VERSION-2.md).
|
||||
|
||||
## XRP Ledger server version 1.12.0
|
||||
|
||||
[Version 1.12.0](https://github.com/XRPLF/rippled/releases/tag/1.12.0) was released on Sep 6, 2023. The following additions are non-breaking (because they are purely additive).
|
||||
[Version 1.12.0](https://github.com/XRPLF/rippled/releases/tag/1.12.0) was released on Sep 6, 2023. The following additions are non-breaking (because they are purely additive):
|
||||
|
||||
- `server_info`: Added `ports`, an array which advertises the RPC and WebSocket ports. This information is also included in the `/crawl` endpoint (which calls `server_info` internally). `grpc` and `peer` ports are also included. (https://github.com/XRPLF/rippled/pull/4427)
|
||||
- `server_info`: Added `ports`, an array which advertises the RPC and WebSocket ports. This information is also included in the `/crawl` endpoint (which calls `server_info` internally). `grpc` and `peer` ports are also included. ([#4427](https://github.com/XRPLF/rippled/pull/4427))
|
||||
- `ports` contains objects, each containing a `port` for the listening port (a number string), and a `protocol` array listing the supported protocols on that port.
|
||||
- This allows crawlers to build a more detailed topology without needing to port-scan nodes.
|
||||
- (For peers and other non-admin clients, the info about admin ports is excluded.)
|
||||
- Clawback: The following additions are gated by the Clawback amendment (`featureClawback`). (https://github.com/XRPLF/rippled/pull/4553)
|
||||
- Adds an [AccountRoot flag](https://xrpl.org/accountroot.html#accountroot-flags) called `lsfAllowTrustLineClawback` (https://github.com/XRPLF/rippled/pull/4617)
|
||||
- Clawback: The following additions are gated by the Clawback amendment (`featureClawback`). ([#4553](https://github.com/XRPLF/rippled/pull/4553))
|
||||
- Adds an [AccountRoot flag](https://xrpl.org/accountroot.html#accountroot-flags) called `lsfAllowTrustLineClawback`. ([#4617](https://github.com/XRPLF/rippled/pull/4617))
|
||||
- Adds the corresponding `asfAllowTrustLineClawback` [AccountSet Flag](https://xrpl.org/accountset.html#accountset-flags) as well.
|
||||
- Clawback is disabled by default, so if an issuer desires the ability to claw back funds, they must use an `AccountSet` transaction to set the AllowTrustLineClawback flag. They must do this before creating any trust lines, offers, escrows, payment channels, or checks.
|
||||
- Adds the [Clawback transaction type](https://github.com/XRPLF/XRPL-Standards/blob/master/XLS-39d-clawback/README.md#331-clawback-transaction), containing these fields:
|
||||
@@ -181,16 +189,16 @@ The following is a non-breaking addition to the API.
|
||||
|
||||
### Breaking changes in 1.11
|
||||
|
||||
- Added the ability to mark amendments as obsolete. For the `feature` admin API, there is a new possible value for the `vetoed` field. (https://github.com/XRPLF/rippled/pull/4291)
|
||||
- Added the ability to mark amendments as obsolete. For the `feature` admin API, there is a new possible value for the `vetoed` field. ([#4291](https://github.com/XRPLF/rippled/pull/4291))
|
||||
- The value of `vetoed` can now be `true`, `false`, or `"Obsolete"`.
|
||||
- Removed the acceptance of seeds or public keys in place of account addresses. (https://github.com/XRPLF/rippled/pull/4404)
|
||||
- Removed the acceptance of seeds or public keys in place of account addresses. ([#4404](https://github.com/XRPLF/rippled/pull/4404))
|
||||
- This simplifies the API and encourages better security practices (i.e. seeds should never be sent over the network).
|
||||
- For the `ledger_data` method, when all entries are filtered out, the `state` field of the response is now an empty list (in other words, an empty array, `[]`). (Previously, it would return `null`.) While this is technically a breaking change, the new behavior is consistent with the documentation, so this is considered only a bug fix. (https://github.com/XRPLF/rippled/pull/4398)
|
||||
- For the `ledger_data` method, when all entries are filtered out, the `state` field of the response is now an empty list (in other words, an empty array, `[]`). (Previously, it would return `null`.) While this is technically a breaking change, the new behavior is consistent with the documentation, so this is considered only a bug fix. ([#4398](https://github.com/XRPLF/rippled/pull/4398))
|
||||
- If and when the `fixNFTokenRemint` amendment activates, there will be a new AccountRoot field, `FirstNFTSequence`. This field is set to the current account sequence when the account issues their first NFT. If an account has not issued any NFTs, then the field is not set. ([#4406](https://github.com/XRPLF/rippled/pull/4406))
|
||||
- There is a new account deletion restriction: an account can only be deleted if `FirstNFTSequence` + `MintedNFTokens` + `256` is less than the current ledger sequence.
|
||||
- This is potentially a breaking change if clients have logic for determining whether an account can be deleted.
|
||||
- NetworkID
|
||||
- For sidechains and networks with a network ID greater than 1024, there is a new [transaction common field](https://xrpl.org/transaction-common-fields.html), `NetworkID`. (https://github.com/XRPLF/rippled/pull/4370)
|
||||
- For sidechains and networks with a network ID greater than 1024, there is a new [transaction common field](https://xrpl.org/transaction-common-fields.html), `NetworkID`. ([#4370](https://github.com/XRPLF/rippled/pull/4370))
|
||||
- This field helps to prevent replay attacks and is now required for chains whose network ID is 1025 or higher.
|
||||
- The field must be omitted for Mainnet, so there is no change for Mainnet users.
|
||||
- There are three new local error codes:
|
||||
@@ -200,10 +208,10 @@ The following is a non-breaking addition to the API.
|
||||
|
||||
### Additions and bug fixes in 1.11
|
||||
|
||||
- Added `nftoken_id`, `nftoken_ids` and `offer_id` meta fields into NFT `tx` and `account_tx` responses. (https://github.com/XRPLF/rippled/pull/4447)
|
||||
- Added an `account_flags` object to the `account_info` method response. (https://github.com/XRPLF/rippled/pull/4459)
|
||||
- Added `NFTokenPages` to the `account_objects` RPC. (https://github.com/XRPLF/rippled/pull/4352)
|
||||
- Fixed: `marker` returned from the `account_lines` command would not work on subsequent commands. (https://github.com/XRPLF/rippled/pull/4361)
|
||||
- Added `nftoken_id`, `nftoken_ids` and `offer_id` meta fields into NFT `tx` and `account_tx` responses. ([#4447](https://github.com/XRPLF/rippled/pull/4447))
|
||||
- Added an `account_flags` object to the `account_info` method response. ([#4459](https://github.com/XRPLF/rippled/pull/4459))
|
||||
- Added `NFTokenPages` to the `account_objects` RPC. ([#4352](https://github.com/XRPLF/rippled/pull/4352))
|
||||
- Fixed: `marker` returned from the `account_lines` command would not work on subsequent commands. ([#4361](https://github.com/XRPLF/rippled/pull/4361))
|
||||
|
||||
## XRP Ledger server version 1.10.0
|
||||
|
||||
|
||||
66
API-VERSION-2.md
Normal file
66
API-VERSION-2.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# API Version 2
|
||||
|
||||
API version 2 is available in `rippled` version 2.0.0 and later. To use this API, clients specify `"api_version" : 2` in each request.
|
||||
|
||||
For info about how [API versioning](https://xrpl.org/request-formatting.html#api-versioning) works, including examples, please view the [XLS-22d spec](https://github.com/XRPLF/XRPL-Standards/discussions/54). For details about the implementation of API versioning, view the [implementation PR](https://github.com/XRPLF/rippled/pull/3155). API versioning ensures existing integrations and users continue to receive existing behavior, while those that request a higher API version will experience new behavior.
|
||||
|
||||
## Removed methods
|
||||
|
||||
In API version 2, the following deprecated methods are no longer available: ([#4759](https://github.com/XRPLF/rippled/pull/4759))
|
||||
|
||||
- `tx_history` - Instead, use other methods such as `account_tx` or `ledger` with the `transactions` field set to `true`.
|
||||
- `ledger_header` - Instead, use the `ledger` method.
|
||||
|
||||
## Modifications to JSON transaction element in API version 2
|
||||
|
||||
In API version 2, JSON elements for transaction output have been changed and made consistent for all methods which output transactions. ([#4775](https://github.com/XRPLF/rippled/pull/4775))
|
||||
This helps to unify the JSON serialization format of transactions. ([clio#722](https://github.com/XRPLF/clio/issues/722), [#4727](https://github.com/XRPLF/rippled/issues/4727))
|
||||
|
||||
- JSON transaction element is named `tx_json`
|
||||
- Binary transaction element is named `tx_blob`
|
||||
- JSON transaction metadata element is named `meta`
|
||||
- Binary transaction metadata element is named `meta_blob`
|
||||
|
||||
Additionally, these elements are now consistently available next to `tx_json` (i.e. sibling elements), where possible:
|
||||
|
||||
- `hash` - Transaction ID. This data was stored inside transaction output in API version 1, but in API version 2 is a sibling element.
|
||||
- `ledger_index` - Ledger index (only set on validated ledgers)
|
||||
- `ledger_hash` - Ledger hash (only set on closed or validated ledgers)
|
||||
- `close_time_iso` - Ledger close time expressed in ISO 8601 time format (only set on validated ledgers)
|
||||
- `validated` - Bool element set to `true` if the transaction is in a validated ledger, otherwise `false`
|
||||
|
||||
This change affects the following methods:
|
||||
|
||||
- `tx` - Transaction data moved into element `tx_json` (was inline inside `result`) or, if binary output was requested, moved from `tx` to `tx_blob`. Renamed binary transaction metadata element (if it was requested) from `meta` to `meta_blob`. Changed location of `hash` and added new elements
|
||||
- `account_tx` - Renamed transaction element from `tx` to `tx_json`. Renamed binary transaction metadata element (if it was requested) from `meta` to `meta_blob`. Changed location of `hash` and added new elements
|
||||
- `transaction_entry` - Renamed transaction metadata element from `metadata` to `meta`. Changed location of `hash` and added new elements
|
||||
- `subscribe` - Renamed transaction element from `transaction` to `tx_json`. Changed location of `hash` and added new elements
|
||||
- `sign`, `sign_for`, `submit` and `submit_multisigned` - Changed location of `hash` element.
|
||||
|
||||
## Modifications to `Payment` transaction JSON schema
|
||||
|
||||
When reading Payments, the `Amount` field should generally **not** be used. Instead, use [delivered_amount](https://xrpl.org/partial-payments.html#the-delivered_amount-field) to see the amount that the Payment delivered. To clarify its meaning, the `Amount` field is being renamed to `DeliverMax`. ([#4733](https://github.com/XRPLF/rippled/pull/4733))
|
||||
|
||||
- In `Payment` transaction type, JSON RPC field `Amount` is renamed to `DeliverMax`. To enable smooth client transition, `Amount` is still handled, as described below: ([#4733](https://github.com/XRPLF/rippled/pull/4733))
|
||||
- On JSON RPC input (e.g. `submit_multisigned` etc. methods), `Amount` is recognized as an alias to `DeliverMax` for both API version 1 and version 2 clients.
|
||||
- On JSON RPC input, submitting both `Amount` and `DeliverMax` fields is allowed _only_ if they are identical; otherwise such input is rejected with `rpcINVALID_PARAMS` error.
|
||||
- On JSON RPC output (e.g. `subscribe`, `account_tx` etc. methods), `DeliverMax` is present in both API version 1 and version 2.
|
||||
- On JSON RPC output, `Amount` is only present in API version 1 and _not_ in version 2.
|
||||
|
||||
## Modifications to account_info response
|
||||
|
||||
- `signer_lists` is returned in the root of the response. (In API version 1, it was nested under `account_data`.) ([#3770](https://github.com/XRPLF/rippled/pull/3770))
|
||||
- When using an invalid `signer_lists` value, the API now returns an "invalidParams" error. ([#4585](https://github.com/XRPLF/rippled/pull/4585))
|
||||
- (`signer_lists` must be a boolean. In API version 1, strings were accepted and may return a normal response - i.e. as if `signer_lists` were `true`.)
|
||||
|
||||
## Modifications to [account_tx](https://xrpl.org/account_tx.html#account_tx) response
|
||||
|
||||
- Using `ledger_index_min`, `ledger_index_max`, and `ledger_index` returns `invalidParams` because if you use `ledger_index_min` or `ledger_index_max`, then it does not make sense to also specify `ledger_index`. In API version 1, no error was returned. ([#4571](https://github.com/XRPLF/rippled/pull/4571))
|
||||
- The same applies for `ledger_index_min`, `ledger_index_max`, and `ledger_hash`. ([#4545](https://github.com/XRPLF/rippled/issues/4545#issuecomment-1565065579))
|
||||
- Using a `ledger_index_min` or `ledger_index_max` beyond the range of ledgers that the server has:
|
||||
- returns `lgrIdxMalformed` in API version 2. Previously, in API version 1, no error was returned. ([#4288](https://github.com/XRPLF/rippled/issues/4288))
|
||||
- Attempting to use a non-boolean value (such as a string) for the `binary` or `forward` parameters returns `invalidParams` (`rpcINVALID_PARAMS`). Previously, in API version 1, no error was returned. ([#4620](https://github.com/XRPLF/rippled/pull/4620))
|
||||
|
||||
## Modifications to [noripple_check](https://xrpl.org/noripple_check.html#noripple_check) response
|
||||
|
||||
- Attempting to use a non-boolean value (such as a string) for the `transactions` parameter returns `invalidParams` (`rpcINVALID_PARAMS`). Previously, in API version 1, no error was returned. ([#4620](https://github.com/XRPLF/rippled/pull/4620))
|
||||
27
API-VERSION-3.md
Normal file
27
API-VERSION-3.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# API Version 3
|
||||
|
||||
API version 3 is currently a **beta API**. It requires enabling `[beta_rpc_api]` in the rippled configuration to use. To use this API, clients specify `"api_version" : 3` in each request.
|
||||
|
||||
For info about how [API versioning](https://xrpl.org/request-formatting.html#api-versioning) works, including examples, please view the [XLS-22d spec](https://github.com/XRPLF/XRPL-Standards/discussions/54). For details about the implementation of API versioning, view the [implementation PR](https://github.com/XRPLF/rippled/pull/3155). API versioning ensures existing integrations and users continue to receive existing behavior, while those that request a higher API version will experience new behavior.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### Modifications to `amm_info`
|
||||
|
||||
The order of error checks has been changed to provide more specific error messages. ([#4924](https://github.com/XRPLF/rippled/pull/4924))
|
||||
|
||||
- **Before (API v2)**: When sending an invalid account or asset to `amm_info` while other parameters are not set as expected, the method returns a generic `rpcINVALID_PARAMS` error.
|
||||
- **After (API v3)**: The same scenario returns a more specific error: `rpcISSUE_MALFORMED` for malformed assets or `rpcACT_MALFORMED` for malformed accounts.
|
||||
|
||||
### Modifications to `ledger_entry`
|
||||
|
||||
Added support for string shortcuts to look up fixed-location ledger entries using the `"index"` parameter. ([#5644](https://github.com/XRPLF/rippled/pull/5644))
|
||||
|
||||
In API version 3, the following string values can be used with the `"index"` parameter:
|
||||
|
||||
- `"index": "amendments"` - Returns the `Amendments` ledger entry
|
||||
- `"index": "fee"` - Returns the `FeeSettings` ledger entry
|
||||
- `"index": "nunl"` - Returns the `NegativeUNL` ledger entry
|
||||
- `"index": "hashes"` - Returns the "short" `LedgerHashes` ledger entry (recent ledger hashes)
|
||||
|
||||
These shortcuts are only available in API version 3 and later. In API versions 1 and 2, these string values would result in an error.
|
||||
@@ -103,6 +103,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(
|
||||
|
||||
@@ -872,7 +872,8 @@ git push --delete upstream-push master-next
|
||||
11. [Create a new release on
|
||||
Github](https://github.com/XRPLF/rippled/releases). Be sure that
|
||||
"Set as the latest release" is checked.
|
||||
12. Finally [reverse merge the release into `develop`](#follow-up-reverse-merge).
|
||||
12. Open a PR to update the [API-CHANGELOG](API-CHANGELOG.md) and `API-VERSION-[n].md` with the changes for this release (if any are missing).
|
||||
13. Finally, [reverse merge the release into `develop`](#follow-up-reverse-merge).
|
||||
|
||||
#### Special cases: point releases, hotfixes, etc.
|
||||
|
||||
|
||||
@@ -1290,6 +1290,39 @@
|
||||
# Example:
|
||||
# owner_reserve = 2000000 # 2 XRP
|
||||
#
|
||||
# extension_compute_limit = <gas>
|
||||
#
|
||||
# The extension compute 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:
|
||||
# extension_compute_limit = 1000000 # 1 million gas
|
||||
#
|
||||
# extension_size_limit = <bytes>
|
||||
#
|
||||
# The extension 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:
|
||||
# extension_size_limit = 100000 # 100 kb
|
||||
#
|
||||
# gas_price = <bytes>
|
||||
#
|
||||
# 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
|
||||
|
||||
@@ -51,7 +51,7 @@ set(is_amd64 FALSE)
|
||||
set(is_arm64 FALSE)
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
|
||||
set(is_amd64 TRUE)
|
||||
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
|
||||
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64")
|
||||
set(is_arm64 TRUE)
|
||||
else ()
|
||||
message(FATAL_ERROR "Unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}")
|
||||
|
||||
@@ -45,6 +45,7 @@ target_link_libraries(
|
||||
Xrpl::opts
|
||||
Xrpl::syslibs
|
||||
secp256k1::secp256k1
|
||||
wasmi::wasmi
|
||||
xrpl.libpb
|
||||
xxHash::xxhash
|
||||
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"requires": [
|
||||
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
|
||||
"wasmi/1.0.6#407c9db14601a8af1c7dd3b388f3e4cd%1768164779.349",
|
||||
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1765850149.926",
|
||||
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1765850149.46",
|
||||
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878",
|
||||
|
||||
@@ -35,6 +35,7 @@ class Xrpl(ConanFile):
|
||||
"openssl/3.5.4",
|
||||
"secp256k1/0.7.0",
|
||||
"soci/4.0.3",
|
||||
"wasmi/1.0.6",
|
||||
"zlib/1.3.1",
|
||||
]
|
||||
|
||||
@@ -215,6 +216,7 @@ class Xrpl(ConanFile):
|
||||
"soci::soci",
|
||||
"secp256k1::secp256k1",
|
||||
"sqlite3::sqlite",
|
||||
"wasmi::wasmi",
|
||||
"xxhash::xxhash",
|
||||
"zlib::zlib",
|
||||
]
|
||||
|
||||
@@ -717,6 +717,10 @@ abs(Number x) noexcept
|
||||
Number
|
||||
power(Number const& f, unsigned n);
|
||||
|
||||
// logarithm with base 10
|
||||
Number
|
||||
log10(Number const& value, int iterations = 50);
|
||||
|
||||
// Returns f^(1/d)
|
||||
// Uses Newton–Raphson iterations until the result stops changing
|
||||
// to find the root of the polynomial g(x) = x^d - f
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <cctype>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace beast {
|
||||
@@ -180,7 +179,7 @@ split_commas(FwdIt first, FwdIt last)
|
||||
|
||||
template <class Result = std::vector<std::string>>
|
||||
Result
|
||||
split_commas(std::string_view const& s)
|
||||
split_commas(boost::beast::string_view const& s)
|
||||
{
|
||||
return split_commas(s.begin(), s.end());
|
||||
}
|
||||
|
||||
202
include/xrpl/core/ServiceRegistry.h
Normal file
202
include/xrpl/core/ServiceRegistry.h
Normal file
@@ -0,0 +1,202 @@
|
||||
#ifndef XRPL_CORE_SERVICEREGISTRY_H_INCLUDED
|
||||
#define XRPL_CORE_SERVICEREGISTRY_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/SHAMapHash.h>
|
||||
#include <xrpl/basics/TaggedCache.h>
|
||||
#include <xrpl/ledger/CachedSLEs.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Forward declarations
|
||||
namespace NodeStore {
|
||||
class Database;
|
||||
}
|
||||
namespace Resource {
|
||||
class Manager;
|
||||
}
|
||||
namespace perf {
|
||||
class PerfLog;
|
||||
}
|
||||
|
||||
class AcceptedLedger;
|
||||
class AmendmentTable;
|
||||
class Cluster;
|
||||
class CollectorManager;
|
||||
class DatabaseCon;
|
||||
class Family;
|
||||
class HashRouter;
|
||||
class InboundLedgers;
|
||||
class InboundTransactions;
|
||||
class JobQueue;
|
||||
class LedgerCleaner;
|
||||
class LedgerMaster;
|
||||
class LedgerReplayer;
|
||||
class LoadFeeTrack;
|
||||
class LoadManager;
|
||||
class ManifestCache;
|
||||
class NetworkOPs;
|
||||
class OpenLedger;
|
||||
class OrderBookDB;
|
||||
class Overlay;
|
||||
class PathRequests;
|
||||
class PeerReservationTable;
|
||||
class PendingSaves;
|
||||
class RelationalDatabase;
|
||||
class ServerHandler;
|
||||
class SHAMapStore;
|
||||
class TimeKeeper;
|
||||
class TransactionMaster;
|
||||
class TxQ;
|
||||
class ValidatorList;
|
||||
class ValidatorSite;
|
||||
|
||||
template <class Adaptor>
|
||||
class Validations;
|
||||
class RCLValidationsAdaptor;
|
||||
using RCLValidations = Validations<RCLValidationsAdaptor>;
|
||||
|
||||
using NodeCache = TaggedCache<SHAMapHash, Blob>;
|
||||
|
||||
/** Service registry for dependency injection.
|
||||
|
||||
This abstract interface provides access to various services and components
|
||||
used throughout the application. It separates the service locator pattern
|
||||
from the Application lifecycle management.
|
||||
|
||||
Components that need access to services can hold a reference to
|
||||
ServiceRegistry rather than Application when they only need service
|
||||
access and not lifecycle management.
|
||||
|
||||
*/
|
||||
class ServiceRegistry
|
||||
{
|
||||
public:
|
||||
ServiceRegistry() = default;
|
||||
virtual ~ServiceRegistry() = default;
|
||||
|
||||
// Core infrastructure services
|
||||
virtual CollectorManager&
|
||||
getCollectorManager() = 0;
|
||||
|
||||
virtual Family&
|
||||
getNodeFamily() = 0;
|
||||
|
||||
virtual TimeKeeper&
|
||||
timeKeeper() = 0;
|
||||
|
||||
virtual JobQueue&
|
||||
getJobQueue() = 0;
|
||||
|
||||
virtual NodeCache&
|
||||
getTempNodeCache() = 0;
|
||||
|
||||
virtual CachedSLEs&
|
||||
cachedSLEs() = 0;
|
||||
|
||||
// Protocol and validation services
|
||||
virtual AmendmentTable&
|
||||
getAmendmentTable() = 0;
|
||||
|
||||
virtual HashRouter&
|
||||
getHashRouter() = 0;
|
||||
|
||||
virtual LoadFeeTrack&
|
||||
getFeeTrack() = 0;
|
||||
|
||||
virtual LoadManager&
|
||||
getLoadManager() = 0;
|
||||
|
||||
virtual RCLValidations&
|
||||
getValidations() = 0;
|
||||
|
||||
virtual ValidatorList&
|
||||
validators() = 0;
|
||||
|
||||
virtual ValidatorSite&
|
||||
validatorSites() = 0;
|
||||
|
||||
virtual ManifestCache&
|
||||
validatorManifests() = 0;
|
||||
|
||||
virtual ManifestCache&
|
||||
publisherManifests() = 0;
|
||||
|
||||
// Network services
|
||||
virtual Overlay&
|
||||
overlay() = 0;
|
||||
|
||||
virtual Cluster&
|
||||
cluster() = 0;
|
||||
|
||||
virtual PeerReservationTable&
|
||||
peerReservations() = 0;
|
||||
|
||||
virtual Resource::Manager&
|
||||
getResourceManager() = 0;
|
||||
|
||||
// Storage services
|
||||
virtual NodeStore::Database&
|
||||
getNodeStore() = 0;
|
||||
|
||||
virtual SHAMapStore&
|
||||
getSHAMapStore() = 0;
|
||||
|
||||
virtual RelationalDatabase&
|
||||
getRelationalDatabase() = 0;
|
||||
|
||||
// Ledger services
|
||||
virtual InboundLedgers&
|
||||
getInboundLedgers() = 0;
|
||||
|
||||
virtual InboundTransactions&
|
||||
getInboundTransactions() = 0;
|
||||
|
||||
virtual TaggedCache<uint256, AcceptedLedger>&
|
||||
getAcceptedLedgerCache() = 0;
|
||||
|
||||
virtual LedgerMaster&
|
||||
getLedgerMaster() = 0;
|
||||
|
||||
virtual LedgerCleaner&
|
||||
getLedgerCleaner() = 0;
|
||||
|
||||
virtual LedgerReplayer&
|
||||
getLedgerReplayer() = 0;
|
||||
|
||||
virtual PendingSaves&
|
||||
pendingSaves() = 0;
|
||||
|
||||
virtual OpenLedger&
|
||||
openLedger() = 0;
|
||||
|
||||
virtual OpenLedger const&
|
||||
openLedger() const = 0;
|
||||
|
||||
// Transaction and operation services
|
||||
virtual NetworkOPs&
|
||||
getOPs() = 0;
|
||||
|
||||
virtual OrderBookDB&
|
||||
getOrderBookDB() = 0;
|
||||
|
||||
virtual TransactionMaster&
|
||||
getMasterTransaction() = 0;
|
||||
|
||||
virtual TxQ&
|
||||
getTxQ() = 0;
|
||||
|
||||
virtual PathRequests&
|
||||
getPathRequests() = 0;
|
||||
|
||||
// Server services
|
||||
virtual ServerHandler&
|
||||
getServerHandler() = 0;
|
||||
|
||||
virtual perf::PerfLog&
|
||||
getPerfLog() = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
#endif
|
||||
@@ -1,20 +1,21 @@
|
||||
#ifndef XRPL_JSON_OUTPUT_H_INCLUDED
|
||||
#define XRPL_JSON_OUTPUT_H_INCLUDED
|
||||
|
||||
#include <boost/beast/core/string.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Json {
|
||||
|
||||
class Value;
|
||||
|
||||
using Output = std::function<void(std::string_view const&)>;
|
||||
using Output = std::function<void(boost::beast::string_view const&)>;
|
||||
|
||||
inline Output
|
||||
stringOutput(std::string& s)
|
||||
{
|
||||
return [&](std::string_view const& b) { s.append(b.data(), b.size()); };
|
||||
return [&](boost::beast::string_view const& b) { s.append(b.data(), b.size()); };
|
||||
}
|
||||
|
||||
/** Writes a minimal representation of a Json value to an Output in O(n) time.
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
constexpr std::uint32_t MICRO_DROPS_PER_DROP{1'000'000};
|
||||
|
||||
/** Reflects the fee settings for a particular ledger.
|
||||
|
||||
The fees are always the same for any transactions applied
|
||||
@@ -12,9 +14,12 @@ namespace xrpl {
|
||||
*/
|
||||
struct Fees
|
||||
{
|
||||
XRPAmount base{0}; // Reference tx cost (drops)
|
||||
XRPAmount reserve{0}; // Reserve base (drops)
|
||||
XRPAmount increment{0}; // Reserve increment (drops)
|
||||
XRPAmount base{0}; // Reference tx cost (drops)
|
||||
XRPAmount reserve{0}; // Reserve base (drops)
|
||||
XRPAmount increment{0}; // Reserve increment (drops)
|
||||
std::uint32_t extensionComputeLimit{0}; // Extension compute limit (instructions)
|
||||
std::uint32_t extensionSizeLimit{0}; // Extension size limit (bytes)
|
||||
std::uint32_t gasPrice{0}; // price of WASM gas (micro-drops)
|
||||
|
||||
explicit Fees() = default;
|
||||
Fees(Fees const&) = default;
|
||||
|
||||
@@ -251,6 +251,13 @@ std::uint8_t constexpr vaultMaximumIOUScale = 18;
|
||||
* another vault; counted from 0 */
|
||||
std::uint8_t constexpr maxAssetCheckDepth = 5;
|
||||
|
||||
/** The maximum length of a Data field in Escrow object that can be updated by
|
||||
* Wasm code */
|
||||
std::size_t constexpr maxWasmDataLength = 4 * 1024;
|
||||
|
||||
/** The maximum length of a parameters passed from Wasm code*/
|
||||
std::size_t constexpr maxWasmParamLength = 1024;
|
||||
|
||||
/** A ledger index. */
|
||||
using LedgerIndex = std::uint32_t;
|
||||
|
||||
|
||||
@@ -122,6 +122,8 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temARRAY_TOO_LARGE,
|
||||
temBAD_TRANSFER_FEE,
|
||||
temINVALID_INNER_BATCH,
|
||||
|
||||
temBAD_WASM,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FEATURE(SmartEscrow, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo)
|
||||
@@ -33,7 +35,6 @@ XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo
|
||||
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
|
||||
// Check flags in Credential transactions
|
||||
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -302,6 +302,11 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// Smart Escrow fields
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
|
||||
{sfPreviousTxnID, soeOPTIONAL},
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
}))
|
||||
@@ -578,7 +583,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
|
||||
// The unrounded true total value of the loan.
|
||||
//
|
||||
// - TrueTotalPrincipalOutstanding can be computed using the algorithm
|
||||
// in the ripple::detail::loanPrincipalFromPeriodicPayment function.
|
||||
// in the xrpl::detail::loanPrincipalFromPeriodicPayment function.
|
||||
//
|
||||
// - TrueTotalInterestOutstanding = TrueTotalLoanValue -
|
||||
// TrueTotalPrincipalOutstanding
|
||||
|
||||
@@ -114,6 +114,9 @@ TYPED_SFIELD(sfInterestRate, UINT32, 65) // 1/10 basis points (bi
|
||||
TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 69)
|
||||
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 70)
|
||||
TYPED_SFIELD(sfGasPrice, UINT32, 71)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
|
||||
@@ -1092,6 +1092,10 @@ TRANSACTION(ttFEE, 101, SetFee,
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// Smart Escrow fields
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the network's negative UNL
|
||||
|
||||
@@ -255,6 +255,9 @@ JSS(expected_date_UTC); // out: any (warnings)
|
||||
JSS(expected_ledger_size); // out: TxQ
|
||||
JSS(expiration); // out: AccountOffers, AccountChannels,
|
||||
// ValidatorList, amm_info
|
||||
JSS(extension_compute); // out: NetworkOps
|
||||
JSS(extension_size); // out: NetworkOps
|
||||
JSS(gas_price); // out: NetworkOps
|
||||
JSS(fail_hard); // in: Sign, Submit
|
||||
JSS(failed); // out: InboundLedger
|
||||
JSS(feature); // in: Feature
|
||||
@@ -709,11 +712,11 @@ JSS(write_load); // out: GetCounts
|
||||
#pragma push_macro("LEDGER_ENTRY_DUPLICATE")
|
||||
#undef LEDGER_ENTRY_DUPLICATE
|
||||
|
||||
#define LEDGER_ENTRY(tag, value, name, rpcName, ...) \
|
||||
JSS(name); \
|
||||
#define LEDGER_ENTRY(tag, value, name, rpcName, fields) \
|
||||
JSS(name); \
|
||||
JSS(rpcName);
|
||||
|
||||
#define LEDGER_ENTRY_DUPLICATE(tag, value, name, rpcName, ...) JSS(rpcName);
|
||||
#define LEDGER_ENTRY_DUPLICATE(tag, value, name, rpcName, fields) JSS(rpcName);
|
||||
|
||||
#include <xrpl/protocol/detail/ledger_entries.macro>
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -50,7 +49,7 @@ private:
|
||||
bool ping_active_ = false;
|
||||
boost::beast::websocket::ping_data payload_;
|
||||
error_code ec_;
|
||||
std::function<void(boost::beast::websocket::frame_type, std::string_view)> control_callback_;
|
||||
std::function<void(boost::beast::websocket::frame_type, boost::beast::string_view)> control_callback_;
|
||||
|
||||
public:
|
||||
template <class Body, class Headers>
|
||||
@@ -138,7 +137,7 @@ protected:
|
||||
on_ping(error_code const& ec);
|
||||
|
||||
void
|
||||
on_ping_pong(boost::beast::websocket::frame_type kind, std::string_view payload);
|
||||
on_ping_pong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload);
|
||||
|
||||
void
|
||||
on_timer(error_code ec);
|
||||
@@ -391,11 +390,11 @@ BaseWSPeer<Handler, Impl>::on_ping(error_code const& ec)
|
||||
|
||||
template <class Handler, class Impl>
|
||||
void
|
||||
BaseWSPeer<Handler, Impl>::on_ping_pong(boost::beast::websocket::frame_type kind, std::string_view payload)
|
||||
BaseWSPeer<Handler, Impl>::on_ping_pong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload)
|
||||
{
|
||||
if (kind == boost::beast::websocket::frame_type::pong)
|
||||
{
|
||||
std::string_view p(payload_.begin(), payload_.size());
|
||||
boost::beast::string_view p(payload_.begin());
|
||||
if (payload == p)
|
||||
{
|
||||
close_on_timer_ = false;
|
||||
|
||||
@@ -925,6 +925,73 @@ power(Number const& f, unsigned n)
|
||||
return r;
|
||||
}
|
||||
|
||||
// Series expansion method approximation of ln(x)
|
||||
static Number
|
||||
ln(Number const& x, int iterations = 50)
|
||||
{
|
||||
static Number const N0(0);
|
||||
static Number const N2(2, 0);
|
||||
static Number const N05(5, -1);
|
||||
static Number const LN2(693'147'180'559'945'309ll, -18);
|
||||
|
||||
if (x <= 0)
|
||||
throw std::runtime_error("Not a positive value");
|
||||
else if (x == 1)
|
||||
return N0;
|
||||
|
||||
int exponent = 0;
|
||||
Number mantissa = x;
|
||||
|
||||
while (mantissa >= N2)
|
||||
{
|
||||
mantissa /= 2;
|
||||
exponent += 1;
|
||||
}
|
||||
while (mantissa < N05)
|
||||
{
|
||||
mantissa *= 2;
|
||||
exponent -= 1;
|
||||
}
|
||||
|
||||
Number z = (mantissa - 1) / (mantissa + 1);
|
||||
Number const zz = z * z;
|
||||
Number sum;
|
||||
|
||||
for (int i = 1; i <= iterations; ++i)
|
||||
{
|
||||
sum = sum + z / (2 * i - 1);
|
||||
z = z * zz;
|
||||
}
|
||||
|
||||
return 2 * sum + exponent * LN2;
|
||||
}
|
||||
|
||||
Number
|
||||
log10(Number const& x, int iterations)
|
||||
{
|
||||
static Number const N0(0);
|
||||
static Number const LN10(2'302'585'092'994'046ll, -15);
|
||||
|
||||
if (x <= 0)
|
||||
throw std::runtime_error("Not a positive value");
|
||||
else if (x == 1)
|
||||
return N0;
|
||||
|
||||
if (x <= Number(10))
|
||||
{
|
||||
auto const r = ln(x, iterations) / LN10;
|
||||
return r;
|
||||
}
|
||||
|
||||
// (1 <= normalX < 10)
|
||||
// ln(x) = ln(normalX * 10^norm) = ln(normalX) + norm * ln(10)
|
||||
int diffExp = 15 + x.exponent();
|
||||
Number const normalX = x / Number(1, diffExp);
|
||||
auto const lnX = ln(normalX, iterations) + diffExp * LN10;
|
||||
auto const lgX = lnX / LN10;
|
||||
return lgX;
|
||||
}
|
||||
|
||||
// Returns f^(1/d)
|
||||
// Uses Newton–Raphson iterations until the result stops changing
|
||||
// to find the non-negative root of the polynomial g(x) = x^d - f
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <set>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -88,14 +87,14 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
output(std::string_view const& bytes)
|
||||
output(boost::beast::string_view const& bytes)
|
||||
{
|
||||
markStarted();
|
||||
output_(bytes);
|
||||
}
|
||||
|
||||
void
|
||||
stringOutput(std::string_view const& bytes)
|
||||
stringOutput(boost::beast::string_view const& bytes)
|
||||
{
|
||||
markStarted();
|
||||
std::size_t position = 0, writtenUntil = 0;
|
||||
|
||||
@@ -58,6 +58,10 @@ STValidation::validationFormat()
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// featureSmartEscrow
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -198,6 +198,7 @@ transResults()
|
||||
MAKE_ERROR(temARRAY_TOO_LARGE, "Malformed: Array is too large."),
|
||||
MAKE_ERROR(temBAD_TRANSFER_FEE, "Malformed: Transfer fee is outside valid range."),
|
||||
MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."),
|
||||
MAKE_ERROR(temBAD_WASM, "Malformed: Provided WASM code is invalid."),
|
||||
|
||||
MAKE_ERROR(terRETRY, "Retry transaction."),
|
||||
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <source_location>
|
||||
|
||||
namespace xrpl {
|
||||
namespace test {
|
||||
|
||||
@@ -24,10 +26,13 @@ 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> extensionComputeLimit = std::nullopt;
|
||||
std::optional<std::uint32_t> extensionSizeLimit = 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());
|
||||
@@ -49,6 +54,12 @@ createFeeTx(Rules const& rules, std::uint32_t seq, FeeSettingsFields const& fiel
|
||||
obj.setFieldU32(sfReserveIncrement, fields.reserveIncrement ? *fields.reserveIncrement : 0);
|
||||
obj.setFieldU32(sfReferenceFeeUnits, fields.referenceFeeUnits ? *fields.referenceFeeUnits : 0);
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow) || forceAllFields)
|
||||
{
|
||||
obj.setFieldU32(sfExtensionComputeLimit, fields.extensionComputeLimit ? *fields.extensionComputeLimit : 0);
|
||||
obj.setFieldU32(sfExtensionSizeLimit, fields.extensionSizeLimit ? *fields.extensionSizeLimit : 0);
|
||||
obj.setFieldU32(sfGasPrice, fields.gasPrice ? *fields.gasPrice : 0);
|
||||
}
|
||||
};
|
||||
return STTx(ttFEE, fill);
|
||||
}
|
||||
@@ -97,6 +108,12 @@ createInvalidFeeTx(
|
||||
obj.setFieldU32(sfReserveIncrement, 50000);
|
||||
obj.setFieldU32(sfReferenceFeeUnits, 10);
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
obj.setFieldU32(sfExtensionComputeLimit, 100 + uniqueValue);
|
||||
obj.setFieldU32(sfExtensionSizeLimit, 200 + uniqueValue);
|
||||
obj.setFieldU32(sfGasPrice, 300 + uniqueValue);
|
||||
}
|
||||
}
|
||||
// If missingRequiredFields is true, we don't add the required fields
|
||||
// (default behavior)
|
||||
@@ -104,11 +121,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 res.ter == tesSUCCESS;
|
||||
return res.ter;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -153,6 +170,21 @@ verifyFeeObject(std::shared_ptr<Ledger const> const& ledger, Rules const& rules,
|
||||
if (!checkEquality(sfReferenceFeeUnits, expected.referenceFeeUnits))
|
||||
return false;
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
if (!checkEquality(sfExtensionComputeLimit, expected.extensionComputeLimit.value_or(0)))
|
||||
return false;
|
||||
if (!checkEquality(sfExtensionSizeLimit, expected.extensionSizeLimit.value_or(0)))
|
||||
return false;
|
||||
if (!checkEquality(sfGasPrice, expected.gasPrice.value_or(0)))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (feeObject->isFieldPresent(sfExtensionComputeLimit) || feeObject->isFieldPresent(sfExtensionSizeLimit) ||
|
||||
feeObject->isFieldPresent(sfGasPrice))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -175,6 +207,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
void
|
||||
testSetup()
|
||||
{
|
||||
testcase("FeeVote setup");
|
||||
FeeSetup const defaultSetup;
|
||||
{
|
||||
// defaults
|
||||
@@ -183,43 +216,82 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
|
||||
BEAST_EXPECT(setup.extension_compute_limit == defaultSetup.extension_compute_limit);
|
||||
BEAST_EXPECT(setup.extension_size_limit == defaultSetup.extension_size_limit);
|
||||
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append({"reference_fee = 50", "account_reserve = 1234567", "owner_reserve = 1234"});
|
||||
config.append(
|
||||
{"reference_fee = 50",
|
||||
"account_reserve = 1234567",
|
||||
"owner_reserve = 1234",
|
||||
"extension_compute_limit = 100",
|
||||
"extension_size_limit = 200",
|
||||
" gas_price = 300"});
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == 50);
|
||||
BEAST_EXPECT(setup.account_reserve == 1234567);
|
||||
BEAST_EXPECT(setup.owner_reserve == 1234);
|
||||
BEAST_EXPECT(setup.extension_compute_limit == 100);
|
||||
BEAST_EXPECT(setup.extension_size_limit == 200);
|
||||
BEAST_EXPECT(setup.gas_price == 300);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append({"reference_fee = blah", "account_reserve = yada", "owner_reserve = foo"});
|
||||
config.append(
|
||||
{"reference_fee = blah",
|
||||
"account_reserve = yada",
|
||||
"owner_reserve = foo",
|
||||
"extension_compute_limit = bar",
|
||||
"extension_size_limit = baz",
|
||||
"gas_price = qux"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
|
||||
BEAST_EXPECT(setup.extension_compute_limit == defaultSetup.extension_compute_limit);
|
||||
BEAST_EXPECT(setup.extension_size_limit == defaultSetup.extension_size_limit);
|
||||
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append({"reference_fee = -50", "account_reserve = -1234567", "owner_reserve = -1234"});
|
||||
config.append(
|
||||
{"reference_fee = -50",
|
||||
"account_reserve = -1234567",
|
||||
"owner_reserve = -1234",
|
||||
"extension_compute_limit = -100",
|
||||
"extension_size_limit = -200",
|
||||
"gas_price = -300"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == static_cast<std::uint32_t>(-1234567));
|
||||
BEAST_EXPECT(setup.owner_reserve == static_cast<std::uint32_t>(-1234));
|
||||
BEAST_EXPECT(setup.extension_compute_limit == static_cast<std::uint32_t>(-100));
|
||||
BEAST_EXPECT(setup.extension_size_limit == static_cast<std::uint32_t>(-200));
|
||||
BEAST_EXPECT(setup.gas_price == static_cast<std::uint32_t>(-300));
|
||||
}
|
||||
{
|
||||
auto const big64 =
|
||||
std::to_string(static_cast<std::uint64_t>(std::numeric_limits<XRPAmount::value_type>::max()) + 1);
|
||||
Section config;
|
||||
config.append({"reference_fee = " + big64, "account_reserve = " + big64, "owner_reserve = " + big64});
|
||||
config.append(
|
||||
{"reference_fee = " + big64,
|
||||
"account_reserve = " + big64,
|
||||
"owner_reserve = " + big64,
|
||||
"extension_compute_limit = " + big64,
|
||||
"extension_size_limit = " + big64,
|
||||
"gas_price = " + big64});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
|
||||
BEAST_EXPECT(setup.extension_compute_limit == defaultSetup.extension_compute_limit);
|
||||
BEAST_EXPECT(setup.extension_size_limit == defaultSetup.extension_size_limit);
|
||||
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +302,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
|
||||
// Test with XRPFees disabled (legacy format)
|
||||
{
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureXRPFees - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -244,7 +316,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
|
||||
@@ -253,7 +325,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
|
||||
// Test with XRPFees enabled (new format)
|
||||
{
|
||||
jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -268,12 +340,63 @@ 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::testable_amendments());
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
// Create the next ledger to apply transaction to
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().timeKeeper().closeTime());
|
||||
|
||||
FeeSettingsFields fields{
|
||||
.baseFeeDrops = XRPAmount{10},
|
||||
.reserveBaseDrops = XRPAmount{200000},
|
||||
.reserveIncrementDrops = XRPAmount{50000},
|
||||
.extensionComputeLimit = 100,
|
||||
.extensionSizeLimit = 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 the Smart Escrow fields are rejected if the
|
||||
// feature is disabled
|
||||
{
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
// Create the next ledger to apply transaction to
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().timeKeeper().closeTime());
|
||||
|
||||
FeeSettingsFields fields{
|
||||
.baseFeeDrops = XRPAmount{10},
|
||||
.reserveBaseDrops = XRPAmount{200000},
|
||||
.reserveIncrementDrops = XRPAmount{50000},
|
||||
.extensionComputeLimit = 100,
|
||||
.extensionSizeLimit = 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
|
||||
@@ -282,7 +405,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
testcase("Fee Transaction Validation");
|
||||
|
||||
{
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureXRPFees - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -292,15 +415,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::testable_amendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -310,11 +433,29 @@ 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::testable_amendments() | featureXRPFees | featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
// Create the next ledger to apply transaction to
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().timeKeeper().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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +464,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Pseudo Transaction Properties");
|
||||
|
||||
jtx::Env env(*this, jtx::testable_amendments());
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -349,7 +490,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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +499,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Multiple Fee Updates");
|
||||
|
||||
jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -372,7 +513,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);
|
||||
}
|
||||
|
||||
@@ -389,7 +530,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);
|
||||
}
|
||||
|
||||
@@ -402,7 +543,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Wrong Ledger Sequence");
|
||||
|
||||
jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -421,7 +562,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
|
||||
@@ -429,7 +570,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Partial Field Updates");
|
||||
|
||||
jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -443,7 +584,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);
|
||||
}
|
||||
|
||||
@@ -457,7 +598,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);
|
||||
}
|
||||
|
||||
@@ -470,7 +611,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Single Invalid Transaction");
|
||||
|
||||
jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -488,7 +629,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
|
||||
@@ -505,7 +646,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
|
||||
// Test with XRPFees enabled
|
||||
{
|
||||
Env env(*this, testable_amendments() | featureXRPFees);
|
||||
Env env(*this, testable_amendments() - featureSmartEscrow);
|
||||
auto feeVote = make_FeeVote(setup, env.app().journal("FeeVote"));
|
||||
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
@@ -531,7 +672,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
|
||||
// Test with XRPFees disabled (legacy format)
|
||||
{
|
||||
Env env(*this, testable_amendments() - featureXRPFees);
|
||||
Env env(*this, testable_amendments() - featureXRPFees - featureSmartEscrow);
|
||||
auto feeVote = make_FeeVote(setup, env.app().journal("FeeVote"));
|
||||
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
@@ -567,7 +708,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
setup.account_reserve = 1234567;
|
||||
setup.owner_reserve = 7654321;
|
||||
|
||||
Env env(*this, testable_amendments() | featureXRPFees);
|
||||
Env env(*this, testable_amendments() - featureSmartEscrow);
|
||||
|
||||
// establish what the current fees are
|
||||
BEAST_EXPECT(env.current()->fees().base == XRPAmount{UNIT_TEST_REFERENCE_FEE});
|
||||
@@ -637,6 +778,127 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(feeTx.getFieldAmount(sfReserveIncrementDrops) == XRPAmount{setup.owner_reserve});
|
||||
}
|
||||
|
||||
void
|
||||
testDoVotingSmartEscrow()
|
||||
{
|
||||
testcase("doVoting with Smart Escrow");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, testable_amendments() | 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().extensionComputeLimit == 0);
|
||||
BEAST_EXPECT(env.current()->fees().extensionSizeLimit == 0);
|
||||
BEAST_EXPECT(env.current()->fees().gasPrice == 0);
|
||||
|
||||
auto const createFeeTxFromVoting = [&](FeeSetup const& setup) -> std::pair<STTx, std::shared_ptr<Ledger>> {
|
||||
auto feeVote = make_FeeVote(setup, env.app().journal("FeeVote"));
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), 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().timeKeeper().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().timeKeeper().now(), pub, sec, calcNodeID(pub), [&](STValidation& v) {
|
||||
v.setFieldU32(sfLedgerSequence, ledger->seq());
|
||||
// Vote for different fees than current
|
||||
v.setFieldAmount(sfBaseFeeDrops, XRPAmount{setup.reference_fee});
|
||||
v.setFieldAmount(sfReserveBaseDrops, XRPAmount{setup.account_reserve});
|
||||
v.setFieldAmount(sfReserveIncrementDrops, XRPAmount{setup.owner_reserve});
|
||||
v.setFieldU32(sfExtensionComputeLimit, setup.extension_compute_limit);
|
||||
v.setFieldU32(sfExtensionSizeLimit, setup.extension_size_limit);
|
||||
v.setFieldU32(sfGasPrice, setup.gas_price);
|
||||
});
|
||||
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.reference_fee}, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldAmount(sfReserveBaseDrops) == XRPAmount{setup.account_reserve}, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldAmount(sfReserveIncrementDrops) == XRPAmount{setup.owner_reserve}, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfExtensionComputeLimit) == setup.extension_compute_limit, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfExtensionSizeLimit) == setup.extension_size_limit, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfGasPrice) == setup.gas_price, line);
|
||||
};
|
||||
|
||||
{
|
||||
FeeSetup setup;
|
||||
setup.reference_fee = 42;
|
||||
setup.account_reserve = 1234567;
|
||||
setup.owner_reserve = 7654321;
|
||||
setup.extension_compute_limit = 100;
|
||||
setup.extension_size_limit = 200;
|
||||
setup.gas_price = 300;
|
||||
auto const [feeTx, ledger] = createFeeTxFromVoting(setup);
|
||||
|
||||
checkFeeTx(setup, feeTx, ledger);
|
||||
}
|
||||
|
||||
{
|
||||
FeeSetup setup;
|
||||
setup.reference_fee = 42;
|
||||
setup.account_reserve = 1234567;
|
||||
setup.owner_reserve = 7654321;
|
||||
setup.extension_compute_limit = 0;
|
||||
setup.extension_size_limit = 0;
|
||||
setup.gas_price = 300;
|
||||
auto const [feeTx, ledger] = createFeeTxFromVoting(setup);
|
||||
|
||||
checkFeeTx(setup, feeTx, ledger);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -650,6 +912,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
testSingleInvalidTransaction();
|
||||
testDoValidation();
|
||||
testDoVoting();
|
||||
testDoVotingSmartEscrow();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
2709
src/test/app/HostFuncImpl_test.cpp
Normal file
2709
src/test/app/HostFuncImpl_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -876,42 +876,48 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
env.fund(XRP(1000), alice, buyer, gw);
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
|
||||
uint256 const nftAlice0ID = token::getNextID(env, alice, 0, tfTransferable);
|
||||
env(token::mint(alice, 0u), txflags(tfTransferable));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
||||
uint8_t aliceCount = 1;
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
|
||||
uint256 const nftXrpOnlyID = token::getNextID(env, alice, 0, tfOnlyXRP | tfTransferable);
|
||||
env(token::mint(alice, 0), txflags(tfOnlyXRP | tfTransferable));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
|
||||
uint256 nftNoXferID = token::getNextID(env, alice, 0);
|
||||
env(token::mint(alice, 0));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
|
||||
// alice creates sell offers for her nfts.
|
||||
uint256 const plainOfferIndex = keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
env(token::createOffer(alice, nftAlice0ID, XRP(10)), txflags(tfSellNFToken));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 2);
|
||||
aliceCount++;
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
|
||||
uint256 const audOfferIndex = keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
env(token::createOffer(alice, nftAlice0ID, gwAUD(30)), txflags(tfSellNFToken));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 3);
|
||||
aliceCount++;
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
|
||||
uint256 const xrpOnlyOfferIndex = keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
env(token::createOffer(alice, nftXrpOnlyID, XRP(20)), txflags(tfSellNFToken));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 4);
|
||||
aliceCount++;
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
|
||||
uint256 const noXferOfferIndex = keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
env(token::createOffer(alice, nftNoXferID, XRP(30)), txflags(tfSellNFToken));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 5);
|
||||
aliceCount++;
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
|
||||
// alice creates a sell offer that will expire soon.
|
||||
uint256 const aliceExpOfferIndex = keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
@@ -919,7 +925,17 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
txflags(tfSellNFToken),
|
||||
token::expiration(lastClose(env) + 5));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 6);
|
||||
aliceCount++;
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
|
||||
// buyer creates a Buy offer that will expire soon.
|
||||
uint256 const buyerExpOfferIndex = keylet::nftoffer(buyer, env.seq(buyer)).key;
|
||||
env(token::createOffer(buyer, nftAlice0ID, XRP(40)),
|
||||
token::owner(alice),
|
||||
token::expiration(lastClose(env) + 5));
|
||||
env.close();
|
||||
uint8_t buyerCount = 1;
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// preflight
|
||||
@@ -927,12 +943,12 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
// Set a negative fee.
|
||||
env(token::acceptSellOffer(buyer, noXferOfferIndex), fee(STAmount(10ull, true)), ter(temBAD_FEE));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Set an invalid flag.
|
||||
env(token::acceptSellOffer(buyer, noXferOfferIndex), txflags(0x00008000), ter(temINVALID_FLAG));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Supply nether an sfNFTokenBuyOffer nor an sfNFTokenSellOffer field.
|
||||
{
|
||||
@@ -940,7 +956,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
jv.removeMember(sfNFTokenSellOffer.jsonName);
|
||||
env(jv, ter(temMALFORMED));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
}
|
||||
|
||||
// A buy offer may not contain a sfNFTokenBrokerFee field.
|
||||
@@ -949,7 +965,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
jv[sfNFTokenBrokerFee.jsonName] = STAmount(500000).getJson(JsonOptions::none);
|
||||
env(jv, ter(temMALFORMED));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
}
|
||||
|
||||
// A sell offer may not contain a sfNFTokenBrokerFee field.
|
||||
@@ -958,7 +974,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
jv[sfNFTokenBrokerFee.jsonName] = STAmount(500000).getJson(JsonOptions::none);
|
||||
env(jv, ter(temMALFORMED));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
}
|
||||
|
||||
// A brokered offer may not contain a negative or zero brokerFee.
|
||||
@@ -966,7 +982,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
token::brokerFee(gwAUD(0)),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// preclaim
|
||||
@@ -974,33 +990,48 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
// The buy offer must be non-zero.
|
||||
env(token::acceptBuyOffer(buyer, beast::zero), ter(tecOBJECT_NOT_FOUND));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// The buy offer must be present in the ledger.
|
||||
uint256 const missingOfferIndex = keylet::nftoffer(alice, 1).key;
|
||||
env(token::acceptBuyOffer(buyer, missingOfferIndex), ter(tecOBJECT_NOT_FOUND));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// The buy offer must not have expired.
|
||||
env(token::acceptBuyOffer(buyer, aliceExpOfferIndex), ter(tecEXPIRED));
|
||||
// NOTE: this is only a preclaim check with the
|
||||
// fixExpiredNFTokenOfferRemoval amendment disabled.
|
||||
env(token::acceptBuyOffer(alice, buyerExpOfferIndex), ter(tecEXPIRED));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
buyerCount--;
|
||||
}
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// The sell offer must be non-zero.
|
||||
env(token::acceptSellOffer(buyer, beast::zero), ter(tecOBJECT_NOT_FOUND));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// The sell offer must be present in the ledger.
|
||||
env(token::acceptSellOffer(buyer, missingOfferIndex), ter(tecOBJECT_NOT_FOUND));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// The sell offer must not have expired.
|
||||
// NOTE: this is only a preclaim check with the
|
||||
// fixExpiredNFTokenOfferRemoval amendment disabled.
|
||||
env(token::acceptSellOffer(buyer, aliceExpOfferIndex), ter(tecEXPIRED));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
// Alice's count is decremented by one when the expired offer is
|
||||
// removed.
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
aliceCount--;
|
||||
}
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// preclaim brokered
|
||||
@@ -1012,8 +1043,13 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
env(pay(gw, buyer, gwAUD(30)));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 7);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
aliceCount++;
|
||||
buyerCount++;
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// We're about to exercise offer brokering, so we need
|
||||
// corresponding buy and sell offers.
|
||||
@@ -1022,35 +1058,38 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
uint256 const buyerOfferIndex = keylet::nftoffer(buyer, env.seq(buyer)).key;
|
||||
env(token::createOffer(buyer, nftAlice0ID, gwAUD(29)), token::owner(alice));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
buyerCount++;
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// gw attempts to broker offers that are not for the same token.
|
||||
env(token::brokerOffers(gw, buyerOfferIndex, xrpOnlyOfferIndex), ter(tecNFTOKEN_BUY_SELL_MISMATCH));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// gw attempts to broker offers that are not for the same currency.
|
||||
env(token::brokerOffers(gw, buyerOfferIndex, plainOfferIndex), ter(tecNFTOKEN_BUY_SELL_MISMATCH));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// In a brokered offer, the buyer must offer greater than or
|
||||
// equal to the selling price.
|
||||
env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex), ter(tecINSUFFICIENT_PAYMENT));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Remove buyer's offer.
|
||||
env(token::cancelOffer(buyer, {buyerOfferIndex}));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
buyerCount--;
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
}
|
||||
{
|
||||
// buyer creates a buy offer for one of alice's nfts.
|
||||
uint256 const buyerOfferIndex = keylet::nftoffer(buyer, env.seq(buyer)).key;
|
||||
env(token::createOffer(buyer, nftAlice0ID, gwAUD(31)), token::owner(alice));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
buyerCount++;
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Broker sets their fee in a denomination other than the one
|
||||
// used by the offers
|
||||
@@ -1058,14 +1097,14 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
token::brokerFee(XRP(40)),
|
||||
ter(tecNFTOKEN_BUY_SELL_MISMATCH));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Broker fee way too big.
|
||||
env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
|
||||
token::brokerFee(gwAUD(31)),
|
||||
ter(tecINSUFFICIENT_PAYMENT));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Broker fee is smaller, but still too big once the offer
|
||||
// seller's minimum is taken into account.
|
||||
@@ -1073,12 +1112,13 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
token::brokerFee(gwAUD(1.5)),
|
||||
ter(tecINSUFFICIENT_PAYMENT));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Remove buyer's offer.
|
||||
env(token::cancelOffer(buyer, {buyerOfferIndex}));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
buyerCount--;
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
}
|
||||
//----------------------------------------------------------------------
|
||||
// preclaim buy
|
||||
@@ -1087,17 +1127,18 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
uint256 const buyerOfferIndex = keylet::nftoffer(buyer, env.seq(buyer)).key;
|
||||
env(token::createOffer(buyer, nftAlice0ID, gwAUD(30)), token::owner(alice));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
buyerCount++;
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Don't accept a buy offer if the sell flag is set.
|
||||
env(token::acceptBuyOffer(buyer, plainOfferIndex), ter(tecNFTOKEN_OFFER_TYPE_MISMATCH));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 7);
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
|
||||
// An account can't accept its own offer.
|
||||
env(token::acceptBuyOffer(buyer, buyerOfferIndex), ter(tecCANT_ACCEPT_OWN_NFTOKEN_OFFER));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// An offer acceptor must have enough funds to pay for the offer.
|
||||
env(pay(buyer, gw, gwAUD(30)));
|
||||
@@ -1105,7 +1146,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(env.balance(buyer, gwAUD) == gwAUD(0));
|
||||
env(token::acceptBuyOffer(alice, buyerOfferIndex), ter(tecINSUFFICIENT_FUNDS));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// alice gives her NFT to gw, so alice no longer owns nftAlice0.
|
||||
{
|
||||
@@ -1114,7 +1155,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
env(token::acceptSellOffer(gw, offerIndex));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 7);
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
}
|
||||
env(pay(gw, buyer, gwAUD(30)));
|
||||
env.close();
|
||||
@@ -1122,12 +1163,13 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
// alice can't accept a buy offer for an NFT she no longer owns.
|
||||
env(token::acceptBuyOffer(alice, buyerOfferIndex), ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Remove buyer's offer.
|
||||
env(token::cancelOffer(buyer, {buyerOfferIndex}));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
buyerCount--;
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
}
|
||||
//----------------------------------------------------------------------
|
||||
// preclaim sell
|
||||
@@ -1136,23 +1178,24 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
uint256 const buyerOfferIndex = keylet::nftoffer(buyer, env.seq(buyer)).key;
|
||||
env(token::createOffer(buyer, nftXrpOnlyID, XRP(30)), token::owner(alice));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
buyerCount++;
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Don't accept a sell offer without the sell flag set.
|
||||
env(token::acceptSellOffer(alice, buyerOfferIndex), ter(tecNFTOKEN_OFFER_TYPE_MISMATCH));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 7);
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
|
||||
// An account can't accept its own offer.
|
||||
env(token::acceptSellOffer(alice, plainOfferIndex), ter(tecCANT_ACCEPT_OWN_NFTOKEN_OFFER));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// The seller must currently be in possession of the token they
|
||||
// are selling. alice gave nftAlice0ID to gw.
|
||||
env(token::acceptSellOffer(buyer, plainOfferIndex), ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// gw gives nftAlice0ID back to alice. That allows us to check
|
||||
// buyer attempting to accept one of alice's offers with
|
||||
@@ -1163,14 +1206,14 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
env(token::acceptSellOffer(alice, offerIndex));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 7);
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
|
||||
}
|
||||
env(pay(buyer, gw, gwAUD(30)));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(buyer, gwAUD) == gwAUD(0));
|
||||
env(token::acceptSellOffer(buyer, audOfferIndex), ter(tecINSUFFICIENT_FUNDS));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
@@ -2769,6 +2812,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
uint256 const nftokenID1 = token::getNextID(env, issuer, 0, tfTransferable);
|
||||
env(token::mint(minter, 0), token::issuer(issuer), txflags(tfTransferable));
|
||||
env.close();
|
||||
uint8_t issuerCount, minterCount, buyerCount;
|
||||
|
||||
// Test how adding an Expiration field to an offer affects permissions
|
||||
// for cancelling offers.
|
||||
@@ -2792,9 +2836,12 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
uint256 const offerBuyerToMinter = keylet::nftoffer(buyer, env.seq(buyer)).key;
|
||||
env(token::createOffer(buyer, nftokenID0, drops(1)), token::owner(minter), token::expiration(expiration));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 3);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
issuerCount = 1;
|
||||
minterCount = 3;
|
||||
buyerCount = 1;
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Test who gets to cancel the offers. Anyone outside of the
|
||||
// offer-owner/destination pair should not be able to cancel
|
||||
@@ -2806,32 +2853,36 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
env(token::cancelOffer(buyer, {offerIssuerToMinter}), ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
BEAST_EXPECT(lastClose(env) < expiration);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 3);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// The offer creator can cancel their own unexpired offer.
|
||||
env(token::cancelOffer(minter, {offerMinterToAnyone}));
|
||||
minterCount--;
|
||||
|
||||
// The destination of a sell offer can cancel the NFT owner's
|
||||
// unexpired offer.
|
||||
env(token::cancelOffer(issuer, {offerMinterToIssuer}));
|
||||
minterCount--;
|
||||
|
||||
// Close enough ledgers to get past the expiration.
|
||||
while (lastClose(env) < expiration)
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Anyone can cancel expired offers.
|
||||
env(token::cancelOffer(issuer, {offerBuyerToMinter}));
|
||||
buyerCount--;
|
||||
env(token::cancelOffer(buyer, {offerIssuerToMinter}));
|
||||
issuerCount--;
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
}
|
||||
// Show that:
|
||||
// 1. An unexpired sell offer with an expiration can be accepted.
|
||||
@@ -2844,44 +2895,70 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
env(token::createOffer(minter, nftokenID0, drops(1)),
|
||||
token::expiration(expiration),
|
||||
txflags(tfSellNFToken));
|
||||
minterCount++;
|
||||
|
||||
uint256 const offer1 = keylet::nftoffer(minter, env.seq(minter)).key;
|
||||
env(token::createOffer(minter, nftokenID1, drops(1)),
|
||||
token::expiration(expiration),
|
||||
txflags(tfSellNFToken));
|
||||
minterCount++;
|
||||
env.close();
|
||||
BEAST_EXPECT(lastClose(env) < expiration);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 3);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Anyone can accept an unexpired sell offer.
|
||||
env(token::acceptSellOffer(buyer, offer0));
|
||||
minterCount--;
|
||||
buyerCount++;
|
||||
|
||||
// Close enough ledgers to get past the expiration.
|
||||
while (lastClose(env) < expiration)
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// No one can accept an expired sell offer.
|
||||
env(token::acceptSellOffer(buyer, offer1), ter(tecEXPIRED));
|
||||
env(token::acceptSellOffer(issuer, offer1), ter(tecEXPIRED));
|
||||
|
||||
// With fixExpiredNFTokenOfferRemoval amendment, the first accept
|
||||
// attempt deletes the expired offer. Without the amendment,
|
||||
// the offer remains and we can try to accept it again.
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// After amendment: offer was deleted by first accept attempt
|
||||
minterCount--;
|
||||
env(token::acceptSellOffer(issuer, offer1), ter(tecOBJECT_NOT_FOUND));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Before amendment: offer still exists, second accept also
|
||||
// fails
|
||||
env(token::acceptSellOffer(issuer, offer1), ter(tecEXPIRED));
|
||||
}
|
||||
env.close();
|
||||
|
||||
// The expired sell offer is still in the ledger.
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
// Check if the expired sell offer behavior matches amendment status
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Anyone can cancel the expired sell offer.
|
||||
env(token::cancelOffer(issuer, {offer1}));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
if (!features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// Before amendment: expired offer still exists and needs to be
|
||||
// cancelled
|
||||
env(token::cancelOffer(issuer, {offer1}));
|
||||
env.close();
|
||||
minterCount--;
|
||||
}
|
||||
// Ensure that owner counts are correct with and without the
|
||||
// amendment
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0 && issuerCount == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1 && minterCount == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1 && buyerCount == 1);
|
||||
|
||||
// Transfer nftokenID0 back to minter so we start the next test in
|
||||
// a simple place.
|
||||
@@ -2889,10 +2966,11 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
env(token::createOffer(buyer, nftokenID0, XRP(0)), txflags(tfSellNFToken), token::destination(minter));
|
||||
env.close();
|
||||
env(token::acceptSellOffer(minter, offerSellBack));
|
||||
buyerCount--;
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
}
|
||||
// Show that:
|
||||
// 1. An unexpired buy offer with an expiration can be accepted.
|
||||
@@ -2903,14 +2981,16 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
|
||||
uint256 const offer0 = keylet::nftoffer(buyer, env.seq(buyer)).key;
|
||||
env(token::createOffer(buyer, nftokenID0, drops(1)), token::owner(minter), token::expiration(expiration));
|
||||
buyerCount++;
|
||||
|
||||
uint256 const offer1 = keylet::nftoffer(buyer, env.seq(buyer)).key;
|
||||
env(token::createOffer(buyer, nftokenID1, drops(1)), token::owner(minter), token::expiration(expiration));
|
||||
buyerCount++;
|
||||
env.close();
|
||||
BEAST_EXPECT(lastClose(env) < expiration);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// An unexpired buy offer can be accepted.
|
||||
env(token::acceptBuyOffer(minter, offer0));
|
||||
@@ -2919,26 +2999,48 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
while (lastClose(env) < expiration)
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// An expired buy offer cannot be accepted.
|
||||
env(token::acceptBuyOffer(minter, offer1), ter(tecEXPIRED));
|
||||
env(token::acceptBuyOffer(issuer, offer1), ter(tecEXPIRED));
|
||||
|
||||
// With fixExpiredNFTokenOfferRemoval amendment, the first accept
|
||||
// attempt deletes the expired offer. Without the amendment,
|
||||
// the offer remains and we can try to accept it again.
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// After amendment: offer was deleted by first accept attempt
|
||||
buyerCount--;
|
||||
env(token::acceptBuyOffer(issuer, offer1), ter(tecOBJECT_NOT_FOUND));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Before amendment: offer still exists, second accept also
|
||||
// fails
|
||||
env(token::acceptBuyOffer(issuer, offer1), ter(tecEXPIRED));
|
||||
}
|
||||
env.close();
|
||||
|
||||
// The expired buy offer is still in the ledger.
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
// Check if the expired buy offer behavior matches amendment status
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// Anyone can cancel the expired buy offer.
|
||||
env(token::cancelOffer(issuer, {offer1}));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
if (!features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// Before amendment: expired offer still exists and can be
|
||||
// cancelled
|
||||
env(token::cancelOffer(issuer, {offer1}));
|
||||
env.close();
|
||||
buyerCount--;
|
||||
}
|
||||
// Ensure that owner counts are the same with and without the
|
||||
// amendment
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0 && issuerCount == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1 && minterCount == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1 && buyerCount == 1);
|
||||
|
||||
// Transfer nftokenID0 back to minter so we start the next test in
|
||||
// a simple place.
|
||||
@@ -2947,9 +3049,10 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
env(token::acceptSellOffer(minter, offerSellBack));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
buyerCount--;
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
}
|
||||
// Show that in brokered mode:
|
||||
// 1. An unexpired sell offer with an expiration can be accepted.
|
||||
@@ -2962,50 +3065,74 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
env(token::createOffer(minter, nftokenID0, drops(1)),
|
||||
token::expiration(expiration),
|
||||
txflags(tfSellNFToken));
|
||||
minterCount++;
|
||||
|
||||
uint256 const sellOffer1 = keylet::nftoffer(minter, env.seq(minter)).key;
|
||||
env(token::createOffer(minter, nftokenID1, drops(1)),
|
||||
token::expiration(expiration),
|
||||
txflags(tfSellNFToken));
|
||||
minterCount++;
|
||||
|
||||
uint256 const buyOffer0 = keylet::nftoffer(buyer, env.seq(buyer)).key;
|
||||
env(token::createOffer(buyer, nftokenID0, drops(1)), token::owner(minter));
|
||||
buyerCount++;
|
||||
|
||||
uint256 const buyOffer1 = keylet::nftoffer(buyer, env.seq(buyer)).key;
|
||||
env(token::createOffer(buyer, nftokenID1, drops(1)), token::owner(minter));
|
||||
buyerCount++;
|
||||
|
||||
env.close();
|
||||
BEAST_EXPECT(lastClose(env) < expiration);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 3);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// An unexpired offer can be brokered.
|
||||
env(token::brokerOffers(issuer, buyOffer0, sellOffer0));
|
||||
minterCount--;
|
||||
|
||||
// Close enough ledgers to get past the expiration.
|
||||
while (lastClose(env) < expiration)
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
// If the sell offer is expired it cannot be brokered.
|
||||
env(token::brokerOffers(issuer, buyOffer1, sellOffer1), ter(tecEXPIRED));
|
||||
env.close();
|
||||
|
||||
// The expired sell offer is still in the ledger.
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// With amendment: expired offers are deleted
|
||||
minterCount--;
|
||||
}
|
||||
|
||||
// Anyone can cancel the expired sell offer.
|
||||
env(token::cancelOffer(buyer, {buyOffer1, sellOffer1}));
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// The buy offer was deleted, so no need to cancel it
|
||||
// The sell offer still exists, so we can cancel it
|
||||
env(token::cancelOffer(buyer, {buyOffer1}));
|
||||
buyerCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Anyone can cancel the expired offers
|
||||
env(token::cancelOffer(buyer, {buyOffer1, sellOffer1}));
|
||||
minterCount--;
|
||||
buyerCount--;
|
||||
}
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
// Ensure that owner counts are the same with and without the
|
||||
// amendment
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0 && issuerCount == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1 && minterCount == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1 && buyerCount == 1);
|
||||
|
||||
// Transfer nftokenID0 back to minter so we start the next test in
|
||||
// a simple place.
|
||||
@@ -3014,9 +3141,10 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
env(token::acceptSellOffer(minter, offerSellBack));
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
buyerCount--;
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
|
||||
}
|
||||
// Show that in brokered mode:
|
||||
// 1. An unexpired buy offer with an expiration can be accepted.
|
||||
@@ -3054,17 +3182,28 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
|
||||
// If the buy offer is expired it cannot be brokered.
|
||||
env(token::brokerOffers(issuer, buyOffer1, sellOffer1), ter(tecEXPIRED));
|
||||
env.close();
|
||||
|
||||
// The expired buy offer is still in the ledger.
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
|
||||
// Anyone can cancel the expired buy offer.
|
||||
env(token::cancelOffer(minter, {buyOffer1, sellOffer1}));
|
||||
if (features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// After amendment: expired offers were deleted during broker
|
||||
// attempt
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 1);
|
||||
// The buy offer was deleted, so no need to cancel it
|
||||
// The sell offer still exists, so we can cancel it
|
||||
env(token::cancelOffer(minter, {sellOffer1}));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Before amendment: expired offers still exist in ledger
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
// Anyone can cancel the expired offers
|
||||
env(token::cancelOffer(minter, {buyOffer1, sellOffer1}));
|
||||
}
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
@@ -3122,17 +3261,19 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
|
||||
// If the offers are expired they cannot be brokered.
|
||||
env(token::brokerOffers(issuer, buyOffer1, sellOffer1), ter(tecEXPIRED));
|
||||
env.close();
|
||||
|
||||
// The expired offers are still in the ledger.
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
|
||||
// Anyone can cancel the expired offers.
|
||||
env(token::cancelOffer(issuer, {buyOffer1, sellOffer1}));
|
||||
if (!features[fixExpiredNFTokenOfferRemoval])
|
||||
{
|
||||
// Before amendment: expired offers still exist in ledger
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 2);
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 2);
|
||||
// Anyone can cancel the expired offers
|
||||
env(token::cancelOffer(issuer, {buyOffer1, sellOffer1}));
|
||||
}
|
||||
env.close();
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, minter) == 1);
|
||||
@@ -6736,7 +6877,9 @@ public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testWithFeats(allFeatures - fixNFTokenReserve - featureNFTokenMintOffer - featureDynamicNFT);
|
||||
testWithFeats(
|
||||
allFeatures - fixNFTokenReserve - featureNFTokenMintOffer - featureDynamicNFT -
|
||||
fixExpiredNFTokenOfferRemoval);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6767,6 +6910,15 @@ class NFTokenWOModify_test : public NFTokenBaseUtil_test
|
||||
}
|
||||
};
|
||||
|
||||
class NFTokenWOExpiredOfferRemoval_test : public NFTokenBaseUtil_test
|
||||
{
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testWithFeats(allFeatures - fixExpiredNFTokenOfferRemoval);
|
||||
}
|
||||
};
|
||||
|
||||
class NFTokenAllFeatures_test : public NFTokenBaseUtil_test
|
||||
{
|
||||
void
|
||||
|
||||
@@ -33,6 +33,12 @@ struct PseudoTx_test : public beast::unit_test::suite
|
||||
obj[sfReserveIncrement] = 0;
|
||||
obj[sfReferenceFeeUnits] = 0;
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
obj[sfExtensionComputeLimit] = 0;
|
||||
obj[sfExtensionSizeLimit] = 0;
|
||||
obj[sfGasPrice] = 0;
|
||||
}
|
||||
}));
|
||||
|
||||
res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) {
|
||||
@@ -97,7 +103,9 @@ struct PseudoTx_test : public beast::unit_test::suite
|
||||
FeatureBitset const all{testable_amendments()};
|
||||
FeatureBitset const xrpFees{featureXRPFees};
|
||||
|
||||
testPrevented(all - featureXRPFees - featureSmartEscrow);
|
||||
testPrevented(all - featureXRPFees);
|
||||
testPrevented(all - featureSmartEscrow);
|
||||
testPrevented(all);
|
||||
testAllowed();
|
||||
}
|
||||
|
||||
1264
src/test/app/TestHostFunctions.h
Normal file
1264
src/test/app/TestHostFunctions.h
Normal file
File diff suppressed because it is too large
Load Diff
964
src/test/app/Wasm_test.cpp
Normal file
964
src/test/app/Wasm_test.cpp
Normal file
@@ -0,0 +1,964 @@
|
||||
#ifdef _DEBUG
|
||||
// #define DEBUG_OUTPUT 1
|
||||
#endif
|
||||
|
||||
#include <test/app/TestHostFunctions.h>
|
||||
|
||||
#include <xrpld/app/wasm/HostFuncWrapper.h>
|
||||
|
||||
#include <source_location>
|
||||
|
||||
namespace xrpl {
|
||||
namespace test {
|
||||
|
||||
bool
|
||||
testGetDataIncrement();
|
||||
|
||||
using Add_proto = int32_t(int32_t, int32_t);
|
||||
static wasm_trap_t*
|
||||
Add(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
|
||||
{
|
||||
int32_t Val1 = params->data[0].of.i32;
|
||||
int32_t Val2 = params->data[1].of.i32;
|
||||
// printf("Host function \"Add\": %d + %d\n", Val1, Val2);
|
||||
results->data[0] = WASM_I32_VAL(Val1 + Val2);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> const
|
||||
hexToBytes(std::string const& hex)
|
||||
{
|
||||
auto const ws = boost::algorithm::unhex(hex);
|
||||
return Bytes(ws.begin(), ws.end());
|
||||
}
|
||||
|
||||
std::optional<int32_t>
|
||||
runFinishFunction(std::string const& code)
|
||||
{
|
||||
auto& engine = WasmEngine::instance();
|
||||
auto const wasm = hexToBytes(code);
|
||||
auto const re = engine.run(wasm, "finish");
|
||||
if (re.has_value())
|
||||
{
|
||||
return std::optional<int32_t>(re->result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
struct Wasm_test : public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
checkResult(
|
||||
Expected<WasmResult<int32_t>, TER> re,
|
||||
int32_t expectedResult,
|
||||
int64_t expectedCost,
|
||||
std::source_location const location = std::source_location::current())
|
||||
{
|
||||
auto const lineStr = " (" + std::to_string(location.line()) + ")";
|
||||
if (BEAST_EXPECTS(re.has_value(), transToken(re.error()) + lineStr))
|
||||
{
|
||||
BEAST_EXPECTS(re->result == expectedResult, std::to_string(re->result) + lineStr);
|
||||
BEAST_EXPECTS(re->cost == expectedCost, std::to_string(re->cost) + lineStr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testGetDataHelperFunctions()
|
||||
{
|
||||
testcase("getData helper functions");
|
||||
BEAST_EXPECT(testGetDataIncrement());
|
||||
}
|
||||
|
||||
void
|
||||
testWasmLib()
|
||||
{
|
||||
testcase("wasmtime lib test");
|
||||
// clang-format off
|
||||
/* The WASM module buffer. */
|
||||
Bytes const wasm = {/* WASM header */
|
||||
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
|
||||
/* Type section */
|
||||
0x01, 0x07, 0x01,
|
||||
/* function type {i32, i32} -> {i32} */
|
||||
0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
|
||||
/* Import section */
|
||||
0x02, 0x13, 0x01,
|
||||
/* module name: "extern" */
|
||||
0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E,
|
||||
/* extern name: "func-add" */
|
||||
0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64,
|
||||
/* import desc: func 0 */
|
||||
0x00, 0x00,
|
||||
/* Function section */
|
||||
0x03, 0x02, 0x01, 0x00,
|
||||
/* Export section */
|
||||
0x07, 0x0A, 0x01,
|
||||
/* export name: "addTwo" */
|
||||
0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F,
|
||||
/* export desc: func 0 */
|
||||
0x00, 0x01,
|
||||
/* Code section */
|
||||
0x0A, 0x0A, 0x01,
|
||||
/* code body */
|
||||
0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B};
|
||||
// clang-format on
|
||||
auto& vm = WasmEngine::instance();
|
||||
|
||||
std::shared_ptr<ImportVec> imports(std::make_shared<ImportVec>());
|
||||
WasmImpFunc<Add_proto>(*imports, "func-add", reinterpret_cast<void*>(&Add));
|
||||
|
||||
auto re = vm.run(wasm, "addTwo", wasmParams(1234, 5678), imports);
|
||||
|
||||
// if (res) printf("invokeAdd get the result: %d\n", res.value());
|
||||
|
||||
checkResult(re, 6'912, 59);
|
||||
}
|
||||
|
||||
void
|
||||
testBadWasm()
|
||||
{
|
||||
testcase("bad wasm test");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
std::shared_ptr<HostFunctions> hfs(new HostFunctions(env.journal));
|
||||
|
||||
{
|
||||
auto wasm = hexToBytes("00000000");
|
||||
std::string funcName("mock_escrow");
|
||||
|
||||
auto re = runEscrowWasm(wasm, hfs, funcName, {}, 15);
|
||||
BEAST_EXPECT(!re);
|
||||
}
|
||||
|
||||
{
|
||||
auto wasm = hexToBytes("00112233445566778899AA");
|
||||
std::string funcName("mock_escrow");
|
||||
|
||||
auto const re = preflightEscrowWasm(wasm, hfs, funcName);
|
||||
BEAST_EXPECT(!isTesSuccess(re));
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction wrong function name
|
||||
// pub fn bad() -> bool {
|
||||
// unsafe { host_lib::getLedgerSqn() >= 5 }
|
||||
// }
|
||||
auto const badWasm = hexToBytes(
|
||||
"0061736d010000000105016000017f02190108686f73745f6c69620c6765"
|
||||
"744c656467657253716e00000302010005030100100611027f00418080c0"
|
||||
"000b7f00418080c0000b072b04066d656d6f727902000362616400010a5f"
|
||||
"5f646174615f656e6403000b5f5f686561705f6261736503010a09010700"
|
||||
"100041044a0b004d0970726f64756365727302086c616e67756167650104"
|
||||
"52757374000c70726f6365737365642d6279010572757374631d312e3835"
|
||||
"2e31202834656231363132353020323032352d30332d31352900490f7461"
|
||||
"726765745f6665617475726573042b0f6d757461626c652d676c6f62616c"
|
||||
"732b087369676e2d6578742b0f7265666572656e63652d74797065732b0a"
|
||||
"6d756c746976616c7565");
|
||||
|
||||
auto const re = preflightEscrowWasm(badWasm, hfs, ESCROW_FUNCTION_NAME);
|
||||
BEAST_EXPECT(!isTesSuccess(re));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWasmLedgerSqn()
|
||||
{
|
||||
testcase("Wasm get ledger sequence");
|
||||
|
||||
auto ledgerSqnWasm = hexToBytes(ledgerSqnWasmHex);
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
std::shared_ptr<HostFunctions> hfs(new TestLedgerDataProvider(env));
|
||||
auto imports = std::make_shared<ImportVec>();
|
||||
WASM_IMPORT_FUNC2(*imports, getLedgerSqn, "get_ledger_sqn", hfs.get(), 33);
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
auto re = engine.run(ledgerSqnWasm, ESCROW_FUNCTION_NAME, {}, imports, hfs, 1'000'000, env.journal);
|
||||
|
||||
checkResult(re, 0, 151);
|
||||
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
// empty module - run the same instance
|
||||
re = engine.run({}, ESCROW_FUNCTION_NAME, {}, imports, hfs, 1'000'000, env.journal);
|
||||
|
||||
checkResult(re, 5, 190);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmFib()
|
||||
{
|
||||
testcase("Wasm fibo");
|
||||
|
||||
auto const fibWasm = hexToBytes(fibWasmHex);
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
auto const re = engine.run(fibWasm, "fib", wasmParams(10));
|
||||
|
||||
checkResult(re, 55, 1'137);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmSha()
|
||||
{
|
||||
testcase("Wasm sha");
|
||||
|
||||
auto const sha512Wasm = hexToBytes(sha512PureWasmHex);
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
auto const re = engine.run(sha512Wasm, "sha512_process", wasmParams(sha512PureWasmHex));
|
||||
|
||||
checkResult(re, 34'432, 151'155);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmB58()
|
||||
{
|
||||
testcase("Wasm base58");
|
||||
auto const b58Wasm = hexToBytes(b58WasmHex);
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
Bytes outb;
|
||||
outb.resize(1024);
|
||||
|
||||
auto const minsz = std::min(static_cast<std::uint32_t>(512), static_cast<std::uint32_t>(b58WasmHex.size()));
|
||||
auto const s = std::string_view(b58WasmHex.c_str(), minsz);
|
||||
|
||||
auto const re = engine.run(b58Wasm, "b58enco", wasmParams(outb, s));
|
||||
|
||||
checkResult(re, 700, 2'886'069);
|
||||
}
|
||||
|
||||
void
|
||||
testHFCost()
|
||||
{
|
||||
testcase("wasm test host functions cost");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this);
|
||||
{
|
||||
auto const allHostFuncWasm = hexToBytes(allHostFunctionsWasmHex);
|
||||
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
std::shared_ptr<HostFunctions> hfs(new TestHostFunctions(env, 0));
|
||||
auto imp = createWasmImport(*hfs);
|
||||
for (auto& i : *imp)
|
||||
i.second.gas = 0;
|
||||
|
||||
auto re = engine.run(allHostFuncWasm, ESCROW_FUNCTION_NAME, {}, imp, hfs, 1'000'000, env.journal);
|
||||
|
||||
checkResult(re, 1, 25'503);
|
||||
|
||||
env.close();
|
||||
}
|
||||
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const allHostFuncWasm = hexToBytes(allHostFunctionsWasmHex);
|
||||
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
std::shared_ptr<HostFunctions> hfs(new TestHostFunctions(env, 0));
|
||||
auto const imp = createWasmImport(*hfs);
|
||||
|
||||
auto re = engine.run(allHostFuncWasm, ESCROW_FUNCTION_NAME, {}, imp, hfs, 1'000'000, env.journal);
|
||||
|
||||
checkResult(re, 1, 64'763);
|
||||
|
||||
env.close();
|
||||
}
|
||||
|
||||
// not enough gas
|
||||
{
|
||||
auto const allHostFuncWasm = hexToBytes(allHostFunctionsWasmHex);
|
||||
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
std::shared_ptr<HostFunctions> hfs(new TestHostFunctions(env, 0));
|
||||
auto const imp = createWasmImport(*hfs);
|
||||
|
||||
auto re = engine.run(allHostFuncWasm, ESCROW_FUNCTION_NAME, {}, imp, hfs, 200, env.journal);
|
||||
|
||||
if (BEAST_EXPECT(!re))
|
||||
{
|
||||
BEAST_EXPECTS(re.error() == tecFAILED_PROCESSING, std::to_string(TERtoInt(re.error())));
|
||||
}
|
||||
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testEscrowWasmDN()
|
||||
{
|
||||
testcase("escrow wasm devnet test");
|
||||
|
||||
auto const allHFWasm = hexToBytes(allHostFunctionsWasmHex);
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
{
|
||||
std::shared_ptr<HostFunctions> hfs(new TestHostFunctions(env, 0));
|
||||
auto re = runEscrowWasm(allHFWasm, hfs, ESCROW_FUNCTION_NAME, {}, 100'000);
|
||||
checkResult(re, 1, 64'763);
|
||||
}
|
||||
|
||||
{
|
||||
// max<int64_t>() gas
|
||||
std::shared_ptr<HostFunctions> hfs(new TestHostFunctions(env, 0));
|
||||
auto re = runEscrowWasm(allHFWasm, hfs, ESCROW_FUNCTION_NAME, {}, -1);
|
||||
checkResult(re, 1, 64'763);
|
||||
}
|
||||
|
||||
{ // fail because trying to access nonexistent field
|
||||
struct BadTestHostFunctions : public TestHostFunctions
|
||||
{
|
||||
explicit BadTestHostFunctions(Env& env) : TestHostFunctions(env)
|
||||
{
|
||||
}
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getTxField(SField const& fname) override
|
||||
{
|
||||
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<HostFunctions> hfs(new BadTestHostFunctions(env));
|
||||
auto re = runEscrowWasm(allHFWasm, hfs, ESCROW_FUNCTION_NAME, {}, 100'000);
|
||||
checkResult(re, -201, 28'148);
|
||||
}
|
||||
|
||||
{ // fail because trying to allocate more than MAX_PAGES memory
|
||||
struct BadTestHostFunctions : public TestHostFunctions
|
||||
{
|
||||
explicit BadTestHostFunctions(Env& env) : TestHostFunctions(env)
|
||||
{
|
||||
}
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getTxField(SField const& fname) override
|
||||
{
|
||||
return Bytes((128 + 1) * 64 * 1024, 1);
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<HostFunctions> hfs(new BadTestHostFunctions(env));
|
||||
auto re = runEscrowWasm(allHFWasm, hfs, ESCROW_FUNCTION_NAME, {}, 100'000);
|
||||
checkResult(re, -201, 28'148);
|
||||
}
|
||||
|
||||
{ // fail because recursion too deep
|
||||
|
||||
auto const deepWasm = hexToBytes(deepRecursionHex);
|
||||
|
||||
std::shared_ptr<TestHostFunctionsSink> hfs(new TestHostFunctionsSink(env));
|
||||
std::string funcName("finish");
|
||||
auto re = runEscrowWasm(deepWasm, hfs, funcName, {}, 1'000'000'000);
|
||||
BEAST_EXPECT(!re && re.error());
|
||||
// std::cout << "bad case (deep recursion) result " << re.error()
|
||||
// << std::endl;
|
||||
|
||||
auto const& sink = hfs->getSink();
|
||||
auto countSubstr = [](std::string const& str, std::string const& substr) {
|
||||
std::size_t pos = 0;
|
||||
int occurrences = 0;
|
||||
while ((pos = str.find(substr, pos)) != std::string::npos)
|
||||
{
|
||||
occurrences++;
|
||||
pos += substr.length();
|
||||
}
|
||||
return occurrences;
|
||||
};
|
||||
|
||||
auto const s = sink.messages().str();
|
||||
BEAST_EXPECT(countSubstr(s, "WASMI Error: failure to call func") == 1);
|
||||
BEAST_EXPECT(countSubstr(s, "exception: <finish> failure") > 0);
|
||||
}
|
||||
|
||||
{ // infinite loop
|
||||
auto const infiniteLoopWasm = hexToBytes(infiniteLoopWasmHex);
|
||||
std::string const funcName("loop");
|
||||
std::shared_ptr<HostFunctions> hfs(new TestHostFunctions(env, 0));
|
||||
|
||||
// infinite loop should be caught and fail
|
||||
auto const re = runEscrowWasm(infiniteLoopWasm, hfs, funcName, {}, 1'000'000);
|
||||
if (BEAST_EXPECT(!re.has_value()))
|
||||
{
|
||||
BEAST_EXPECT(re.error() == tecFAILED_PROCESSING);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// expected import not provided
|
||||
auto const lgrSqnWasm = hexToBytes(ledgerSqnWasmHex);
|
||||
std::shared_ptr<HostFunctions> hfs(new TestLedgerDataProvider(env));
|
||||
std::shared_ptr<ImportVec> imports(std::make_shared<ImportVec>());
|
||||
WASM_IMPORT_FUNC2(*imports, getLedgerSqn, "get_ledger_sqn2", hfs.get());
|
||||
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
auto re = engine.run(lgrSqnWasm, ESCROW_FUNCTION_NAME, {}, imports, hfs, 1'000'000, env.journal);
|
||||
|
||||
BEAST_EXPECT(!re);
|
||||
}
|
||||
|
||||
{
|
||||
// bad import format
|
||||
auto const lgrSqnWasm = hexToBytes(ledgerSqnWasmHex);
|
||||
std::shared_ptr<HostFunctions> hfs(new TestLedgerDataProvider(env));
|
||||
std::shared_ptr<ImportVec> imports(std::make_shared<ImportVec>());
|
||||
WASM_IMPORT_FUNC2(*imports, getLedgerSqn, "get_ledger_sqn", hfs.get());
|
||||
(*imports)[0].first = nullptr;
|
||||
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
auto re = engine.run(lgrSqnWasm, ESCROW_FUNCTION_NAME, {}, imports, hfs, 1'000'000, env.journal);
|
||||
|
||||
BEAST_EXPECT(!re);
|
||||
}
|
||||
|
||||
{
|
||||
// bad function name
|
||||
auto const lgrSqnWasm = hexToBytes(ledgerSqnWasmHex);
|
||||
std::shared_ptr<HostFunctions> hfs(new TestLedgerDataProvider(env));
|
||||
std::shared_ptr<ImportVec> imports(std::make_shared<ImportVec>());
|
||||
WASM_IMPORT_FUNC2(*imports, getLedgerSqn, "get_ledger_sqn", hfs.get());
|
||||
|
||||
auto& engine = WasmEngine::instance();
|
||||
auto re = engine.run(lgrSqnWasm, "func1", {}, imports, hfs, 1'000'000, env.journal);
|
||||
|
||||
BEAST_EXPECT(!re);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFloat()
|
||||
{
|
||||
testcase("float point");
|
||||
|
||||
std::string const funcName("finish");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this);
|
||||
{
|
||||
auto const floatTestWasm = hexToBytes(floatTestsWasmHex);
|
||||
|
||||
std::shared_ptr<HostFunctions> hfs(new TestHostFunctions(env, 0));
|
||||
auto re = runEscrowWasm(floatTestWasm, hfs, funcName, {}, 200'000);
|
||||
checkResult(re, 1, 110'699);
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
auto const float0Wasm = hexToBytes(float0Hex);
|
||||
|
||||
std::shared_ptr<HostFunctions> hfs(new TestHostFunctions(env, 0));
|
||||
auto re = runEscrowWasm(float0Wasm, hfs, funcName, {}, 100'000);
|
||||
checkResult(re, 1, 4'259);
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
perfTest()
|
||||
{
|
||||
testcase("Perf test host functions");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
// std::string const funcName("test");
|
||||
auto const perfWasm = hexToBytes(hfPerfTest);
|
||||
|
||||
// std::string const credType = "abcde";
|
||||
// std::string const credType2 = "fghijk";
|
||||
// std::string const credType3 = "0123456";
|
||||
// char const uri[] = "uri";
|
||||
|
||||
Account const alan{"alan"};
|
||||
Account const bob{"bob"};
|
||||
Account const issuer{"issuer"};
|
||||
|
||||
{
|
||||
Env env(*this);
|
||||
// Env env(*this, envconfig(), {}, nullptr,
|
||||
// beast::severities::kTrace);
|
||||
env.fund(XRP(5000), alan, bob, issuer);
|
||||
env.close();
|
||||
|
||||
// // create escrow
|
||||
// auto const seq = env.seq(alan);
|
||||
// auto const k = keylet::escrow(alan, seq);
|
||||
// // auto const allowance = 3'600;
|
||||
// auto escrowCreate = escrow::create(alan, bob, XRP(1000));
|
||||
// XRPAmount txnFees = env.current()->fees().base + 1000;
|
||||
// env(escrowCreate,
|
||||
// escrow::finish_function(wasmHex),
|
||||
// escrow::finish_time(env.now() + 11s),
|
||||
// escrow::cancel_time(env.now() + 100s),
|
||||
// escrow::data("1000000000"), // 1000 XRP in drops
|
||||
// memodata("memo1234567"),
|
||||
// memodata("2memo1234567"),
|
||||
// fee(txnFees));
|
||||
|
||||
// // create depositPreauth
|
||||
// auto const k = keylet::depositPreauth(
|
||||
// bob,
|
||||
// {{issuer.id(), makeSlice(credType)},
|
||||
// {issuer.id(), makeSlice(credType2)},
|
||||
// {issuer.id(), makeSlice(credType3)}});
|
||||
// env(deposit::authCredentials(
|
||||
// bob,
|
||||
// {{issuer, credType},
|
||||
// {issuer, credType2},
|
||||
// {issuer, credType3}}));
|
||||
|
||||
// create nft
|
||||
[[maybe_unused]] uint256 const nft0{token::getNextID(env, alan, 0u)};
|
||||
env(token::mint(alan, 0u));
|
||||
auto const k = keylet::nftoffer(alan, 0);
|
||||
[[maybe_unused]] uint256 const nft1{token::getNextID(env, alan, 0u)};
|
||||
|
||||
env(token::mint(alan, 0u),
|
||||
token::uri("https://github.com/XRPLF/XRPL-Standards/discussions/"
|
||||
"279?id=github.com/XRPLF/XRPL-Standards/discussions/"
|
||||
"279&ut=github.com/XRPLF/XRPL-Standards/discussions/"
|
||||
"279&sid=github.com/XRPLF/XRPL-Standards/discussions/"
|
||||
"279&aot=github.com/XRPLF/XRPL-Standards/disc"));
|
||||
[[maybe_unused]] uint256 const nft2{token::getNextID(env, alan, 0u)};
|
||||
env(token::mint(alan, 0u));
|
||||
env.close();
|
||||
|
||||
std::shared_ptr<HostFunctions> hfs(new PerfHostFunctions(env, k, env.tx()));
|
||||
|
||||
auto re = runEscrowWasm(perfWasm, hfs, ESCROW_FUNCTION_NAME);
|
||||
if (BEAST_EXPECT(re.has_value()))
|
||||
{
|
||||
BEAST_EXPECT(re->result);
|
||||
std::cout << "Res: " << re->result << " cost: " << re->cost << std::endl;
|
||||
}
|
||||
|
||||
// env(escrow::finish(alan, alan, seq),
|
||||
// escrow::comp_allowance(allowance),
|
||||
// fee(txnFees),
|
||||
// ter(tesSUCCESS));
|
||||
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCodecovWasm()
|
||||
{
|
||||
testcase("Codecov wasm test");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
|
||||
auto const codecovWasm = hexToBytes(codecovTestsWasmHex);
|
||||
std::shared_ptr<HostFunctions> hfs(new TestHostFunctions(env, 0));
|
||||
|
||||
auto const allowance = 187'131;
|
||||
auto re = runEscrowWasm(codecovWasm, hfs, ESCROW_FUNCTION_NAME, {}, allowance);
|
||||
|
||||
checkResult(re, 1, allowance);
|
||||
}
|
||||
|
||||
void
|
||||
testDisabledFloat()
|
||||
{
|
||||
testcase("disabled float");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
|
||||
auto disabledFloatWasm = hexToBytes(disabledFloatHex);
|
||||
std::string const funcName("finish");
|
||||
std::shared_ptr<HostFunctions> hfs(new TestHostFunctions(env, 0));
|
||||
|
||||
{
|
||||
// f32 set constant, opcode disabled exception
|
||||
auto const re = runEscrowWasm(disabledFloatWasm, hfs, funcName, {}, 1'000'000);
|
||||
if (BEAST_EXPECT(!re.has_value()))
|
||||
{
|
||||
BEAST_EXPECT(re.error() == tecFAILED_PROCESSING);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// f32 add, can't create module exception
|
||||
disabledFloatWasm[0x117] = 0x92;
|
||||
auto const re = runEscrowWasm(disabledFloatWasm, hfs, funcName, {}, 1'000'000);
|
||||
if (BEAST_EXPECT(!re.has_value()))
|
||||
{
|
||||
BEAST_EXPECT(re.error() == tecFAILED_PROCESSING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWasmMemory()
|
||||
{
|
||||
testcase("Wasm additional memory limit tests");
|
||||
BEAST_EXPECT(runFinishFunction(memoryPointerAtLimitHex).value() == 1);
|
||||
BEAST_EXPECT(runFinishFunction(memoryPointerOverLimitHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(memoryOffsetOverLimitHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(memoryEndOfWordOverLimitHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(memoryGrow0To1PageHex).value() == 1);
|
||||
BEAST_EXPECT(runFinishFunction(memoryGrow1To0PageHex).value() == -1);
|
||||
BEAST_EXPECT(runFinishFunction(memoryLastByteOf8MBHex).value() == 1);
|
||||
BEAST_EXPECT(runFinishFunction(memoryGrow1MoreThan8MBHex).value() == -1);
|
||||
BEAST_EXPECT(runFinishFunction(memoryGrow0MoreThan8MBHex).value() == 1);
|
||||
BEAST_EXPECT(runFinishFunction(memoryInit1MoreThan8MBHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(memoryNegativeAddressHex).has_value() == false);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmTable()
|
||||
{
|
||||
testcase("Wasm table limit tests");
|
||||
BEAST_EXPECT(runFinishFunction(table64ElementsHex).value() == 1);
|
||||
BEAST_EXPECT(runFinishFunction(table65ElementsHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(table2TablesHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(table0ElementsHex).value() == 1);
|
||||
BEAST_EXPECT(runFinishFunction(tableUintMaxHex).has_value() == false);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmProposal()
|
||||
{
|
||||
testcase("Wasm disabled proposal tests");
|
||||
BEAST_EXPECT(runFinishFunction(proposalMutableGlobalHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(proposalGcStructNewHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(proposalMultiValueHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(proposalSignExtHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(proposalFloatToIntHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(proposalBulkMemoryHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(proposalRefTypesHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(proposalTailCallHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(proposalExtendedConstHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(proposalMultiMemoryHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(proposalCustomPageSizesHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(proposalMemory64Hex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(proposalWideArithmeticHex).has_value() == false);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmTrap()
|
||||
{
|
||||
testcase("Wasm trap tests");
|
||||
BEAST_EXPECT(runFinishFunction(trapDivideBy0Hex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(trapIntOverflowHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(trapUnreachableHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(trapNullCallHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(trapFuncSigMismatchHex).has_value() == false);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmWasi()
|
||||
{
|
||||
testcase("Wasm Wasi tests");
|
||||
BEAST_EXPECT(runFinishFunction(wasiGetTimeHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(wasiPrintHex).has_value() == false);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmSectionCorruption()
|
||||
{
|
||||
testcase("Wasm Section Corruption tests");
|
||||
BEAST_EXPECT(runFinishFunction(badMagicNumberHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(badVersionNumberHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(lyingHeaderHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(neverEndingNumberHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(vectorLieHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(sectionOrderingHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(ghostPayloadHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(junkAfterSectionHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(invalidSectionIdHex).has_value() == false);
|
||||
BEAST_EXPECT(runFinishFunction(localVariableBombHex).has_value() == false);
|
||||
}
|
||||
|
||||
void
|
||||
testStartFunctionLoop()
|
||||
{
|
||||
testcase("infinite loop in start function");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env(*this);
|
||||
|
||||
auto const startLoopWasm = hexToBytes(startLoopHex);
|
||||
std::shared_ptr<HostFunctions> hfs(new TestLedgerDataProvider(env));
|
||||
std::shared_ptr<ImportVec> imports(std::make_shared<ImportVec>());
|
||||
|
||||
auto& engine = WasmEngine::instance();
|
||||
auto checkRes = engine.check(startLoopWasm, "finish", {}, imports, hfs, env.journal);
|
||||
BEAST_EXPECTS(checkRes == tesSUCCESS, std::to_string(TERtoInt(checkRes)));
|
||||
|
||||
auto re = engine.run(startLoopWasm, ESCROW_FUNCTION_NAME, {}, imports, hfs, 1'000'000, env.journal);
|
||||
BEAST_EXPECTS(re.error() == tecFAILED_PROCESSING, std::to_string(TERtoInt(re.error())));
|
||||
}
|
||||
|
||||
void
|
||||
testBadAlloc()
|
||||
{
|
||||
testcase("Wasm Bad Alloc");
|
||||
|
||||
// bad_alloc.c
|
||||
auto const badAllocWasm = hexToBytes(badAllocHex);
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
std::shared_ptr<HostFunctions> hfs(new TestLedgerDataProvider(env));
|
||||
|
||||
// std::shared_ptr<ImportVec> imports(std::make_shared<ImportVec>());
|
||||
uint8_t buf1[8] = {7, 8, 9, 10, 11, 12, 13, 14};
|
||||
{ // forged "allocate" return valid address
|
||||
std::vector<WasmParam> params = {{.type = WT_U8V, .of = {.u8v = {.d = buf1, .sz = sizeof(buf1)}}}};
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
auto re = engine.run(badAllocWasm, "test", params, {}, hfs, 1'000'000, env.journal);
|
||||
if (BEAST_EXPECT(re))
|
||||
{
|
||||
BEAST_EXPECTS(re->result == 7, std::to_string(re->result));
|
||||
BEAST_EXPECTS(re->cost == 430, std::to_string(re->cost));
|
||||
}
|
||||
}
|
||||
|
||||
{ // return 0 whithout calling wasm
|
||||
std::vector<WasmParam> params = {{.type = WT_U8V, .of = {.u8v = {.d = buf1, .sz = 0}}}};
|
||||
auto& engine = WasmEngine::instance();
|
||||
auto re = engine.run(badAllocWasm, "test", params, {}, hfs, 1'000'000, env.journal);
|
||||
BEAST_EXPECT(!re) && BEAST_EXPECT(re.error() == tecFAILED_PROCESSING);
|
||||
}
|
||||
|
||||
{ // forged "allocate" return 8Mb (which is more than memory limit)
|
||||
std::vector<WasmParam> params = {{.type = WT_U8V, .of = {.u8v = {.d = buf1, .sz = 1}}}};
|
||||
auto& engine = WasmEngine::instance();
|
||||
auto re = engine.run(badAllocWasm, "test", params, {}, hfs, 1'000'000, env.journal);
|
||||
BEAST_EXPECT(!re) && BEAST_EXPECT(re.error() == tecFAILED_PROCESSING);
|
||||
}
|
||||
|
||||
{ // forged "allocate" return 0
|
||||
std::vector<WasmParam> params = {{.type = WT_U8V, .of = {.u8v = {.d = buf1, .sz = 2}}}};
|
||||
auto& engine = WasmEngine::instance();
|
||||
auto re = engine.run(badAllocWasm, "test", params, {}, hfs, 1'000'000, env.journal);
|
||||
BEAST_EXPECT(!re) && BEAST_EXPECT(re.error() == tecFAILED_PROCESSING);
|
||||
}
|
||||
|
||||
{ // forged "allocate" return -1
|
||||
std::vector<WasmParam> params = {{.type = WT_U8V, .of = {.u8v = {.d = buf1, .sz = 3}}}};
|
||||
auto& engine = WasmEngine::instance();
|
||||
auto re = engine.run(badAllocWasm, "test", params, {}, hfs, 1'000'000, env.journal);
|
||||
|
||||
BEAST_EXPECT(!re) && BEAST_EXPECT(re.error() == tecFAILED_PROCESSING);
|
||||
}
|
||||
|
||||
{
|
||||
std::string what;
|
||||
try
|
||||
{
|
||||
char const test[] = "test";
|
||||
std::size_t sz = std::numeric_limits<int32_t>::max() + 1ull;
|
||||
auto p = wasmParams(std::string_view(test, sz));
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
what = e.what();
|
||||
}
|
||||
BEAST_EXPECT(what.find("can't allocate memory, size: 2147483648") != std::string::npos);
|
||||
}
|
||||
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
testBadAlign()
|
||||
{
|
||||
testcase("Wasm Bad Align");
|
||||
|
||||
// bad_align.c
|
||||
auto const badAlignWasm = hexToBytes(badAlignHex);
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
std::shared_ptr<HostFunctions> hfs(new TestHostFunctions(env, 0));
|
||||
auto imports = createWasmImport(*hfs);
|
||||
|
||||
{ // Calls float_from_uint with bad aligment.
|
||||
// Can be checked through codecov
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
auto re = engine.run(badAlignWasm, "test", {}, imports, hfs, 1'000'000, env.journal);
|
||||
BEAST_EXPECT(re && re->result == 0xbab88d46);
|
||||
}
|
||||
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
testReturnType()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
Env env(*this);
|
||||
std::shared_ptr<HostFunctions> hfs(new TestHostFunctions(env, 0));
|
||||
|
||||
// return int64.
|
||||
{ // (module
|
||||
// (memory (export "memory") 1)
|
||||
// (func (export "finish") (result i64)
|
||||
// i64.const 0x100000000))
|
||||
auto const wasmHex =
|
||||
"0061736d010000000105016000017e030201000503010001"
|
||||
"071302066d656d6f727902000666696e69736800000a0a01"
|
||||
"08004280808080100b";
|
||||
auto const wasm = hexToBytes(wasmHex);
|
||||
auto const re = runEscrowWasm(wasm, hfs, ESCROW_FUNCTION_NAME, {}, 100'000);
|
||||
BEAST_EXPECT(!re);
|
||||
}
|
||||
|
||||
// return void. wasmi return execution error
|
||||
{ //(module
|
||||
// (type (;0;) (func))
|
||||
// (func (;0;) (type 0)
|
||||
// return)
|
||||
// (memory (;0;) 1)
|
||||
// (export "memory" (memory 0))
|
||||
// (export "finish" (func 0)))
|
||||
auto const wasmHex =
|
||||
"0061736d01000000010401600000030201000503010001071302066d656d6f"
|
||||
"727902000666696e69736800000a050103000f0b";
|
||||
auto const wasm = hexToBytes(wasmHex);
|
||||
auto const re = runEscrowWasm(wasm, hfs, ESCROW_FUNCTION_NAME, {}, 100'000);
|
||||
BEAST_EXPECT(!re);
|
||||
}
|
||||
|
||||
// return i32, i32. wasmi doesn't create module
|
||||
{ //(module
|
||||
// (memory (export "memory") 1)
|
||||
// (func (export "finish") (result i32 i32)
|
||||
// i32.const 0x10000000
|
||||
// i32.const 0x100000FF))
|
||||
auto const wasmHex =
|
||||
"0061736d010000000106016000027f7f030201000503010001071302066d65"
|
||||
"6d6f727902000666696e69736800000a10010e0041808080800141ff818080"
|
||||
"010b";
|
||||
auto const wasm = hexToBytes(wasmHex);
|
||||
auto const re = runEscrowWasm(wasm, hfs, ESCROW_FUNCTION_NAME, {}, 100'000);
|
||||
BEAST_EXPECT(!re);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSwapBytes()
|
||||
{
|
||||
testcase("Wasm swap bytes");
|
||||
|
||||
uint64_t const SWAP_DATAU64 = 0x123456789abcdeffull;
|
||||
uint64_t const REVERSE_SWAP_DATAU64 = 0xffdebc9a78563412ull;
|
||||
int64_t const SWAP_DATAI64 = 0x123456789abcdeffll;
|
||||
int64_t const REVERSE_SWAP_DATAI64 = 0xffdebc9a78563412ll;
|
||||
|
||||
uint32_t const SWAP_DATAU32 = 0x12789aff;
|
||||
uint32_t const REVERSE_SWAP_DATAU32 = 0xff9a7812;
|
||||
int32_t const SWAP_DATAI32 = 0x12789aff;
|
||||
int32_t const REVERSE_SWAP_DATAI32 = 0xff9a7812;
|
||||
|
||||
uint16_t const SWAP_DATAU16 = 0x12ff;
|
||||
uint16_t const REVERSE_SWAP_DATAU16 = 0xff12;
|
||||
int16_t const SWAP_DATAI16 = 0x12ff;
|
||||
int16_t const REVERSE_SWAP_DATAI16 = 0xff12;
|
||||
|
||||
uint64_t b1 = SWAP_DATAU64;
|
||||
int64_t b2 = SWAP_DATAI64;
|
||||
b1 = adjustWasmEndianessHlp(b1);
|
||||
b2 = adjustWasmEndianessHlp(b2);
|
||||
BEAST_EXPECT(b1 == REVERSE_SWAP_DATAU64);
|
||||
BEAST_EXPECT(b2 == REVERSE_SWAP_DATAI64);
|
||||
b1 = adjustWasmEndianessHlp(b1);
|
||||
b2 = adjustWasmEndianessHlp(b2);
|
||||
BEAST_EXPECT(b1 == SWAP_DATAU64);
|
||||
BEAST_EXPECT(b2 == SWAP_DATAI64);
|
||||
|
||||
uint32_t b3 = SWAP_DATAU32;
|
||||
int32_t b4 = SWAP_DATAI32;
|
||||
b3 = adjustWasmEndianessHlp(b3);
|
||||
b4 = adjustWasmEndianessHlp(b4);
|
||||
BEAST_EXPECT(b3 == REVERSE_SWAP_DATAU32);
|
||||
BEAST_EXPECT(b4 == REVERSE_SWAP_DATAI32);
|
||||
b3 = adjustWasmEndianessHlp(b3);
|
||||
b4 = adjustWasmEndianessHlp(b4);
|
||||
BEAST_EXPECT(b3 == SWAP_DATAU32);
|
||||
BEAST_EXPECT(b4 == SWAP_DATAI32);
|
||||
|
||||
uint16_t b5 = SWAP_DATAU16;
|
||||
int16_t b6 = SWAP_DATAI16;
|
||||
b5 = adjustWasmEndianessHlp(b5);
|
||||
b6 = adjustWasmEndianessHlp(b6);
|
||||
BEAST_EXPECT(b5 == REVERSE_SWAP_DATAU16);
|
||||
BEAST_EXPECT(b6 == REVERSE_SWAP_DATAI16);
|
||||
b5 = adjustWasmEndianessHlp(b5);
|
||||
b6 = adjustWasmEndianessHlp(b6);
|
||||
BEAST_EXPECT(b5 == SWAP_DATAU16);
|
||||
BEAST_EXPECT(b6 == SWAP_DATAI16);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
testGetDataHelperFunctions();
|
||||
testWasmLib();
|
||||
testBadWasm();
|
||||
testWasmLedgerSqn();
|
||||
|
||||
testWasmFib();
|
||||
testWasmSha();
|
||||
testWasmB58();
|
||||
|
||||
testHFCost();
|
||||
testEscrowWasmDN();
|
||||
testFloat();
|
||||
|
||||
testCodecovWasm();
|
||||
testDisabledFloat();
|
||||
|
||||
testWasmMemory();
|
||||
testWasmTable();
|
||||
testWasmProposal();
|
||||
testWasmTrap();
|
||||
testWasmWasi();
|
||||
testWasmSectionCorruption();
|
||||
|
||||
testStartFunctionLoop();
|
||||
testBadAlloc();
|
||||
testBadAlign();
|
||||
testReturnType();
|
||||
testSwapBytes();
|
||||
|
||||
// perfTest();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Wasm, app, xrpl);
|
||||
|
||||
} // namespace test
|
||||
} // namespace xrpl
|
||||
3
src/test/app/wasm_fixtures/.gitignore
vendored
Normal file
3
src/test/app/wasm_fixtures/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
**/target
|
||||
**/debug
|
||||
*.wasm
|
||||
171
src/test/app/wasm_fixtures/all_host_functions/Cargo.lock
generated
Normal file
171
src/test/app/wasm_fixtures/all_host_functions/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 = "all_host_functions"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"xrpl-wasm-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-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 = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[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.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
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.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-address-macro"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"quote",
|
||||
"sha2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-stdlib"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e"
|
||||
dependencies = [
|
||||
"xrpl-address-macro",
|
||||
]
|
||||
21
src/test/app/wasm_fixtures/all_host_functions/Cargo.toml
Normal file
21
src/test/app/wasm_fixtures/all_host_functions/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "all_host_functions"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
# This empty workspace definition keeps this project independent of the parent workspace
|
||||
[workspace]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-wasm-stdlib" }
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
772
src/test/app/wasm_fixtures/all_host_functions/src/lib.rs
Normal file
772
src/test/app/wasm_fixtures/all_host_functions/src/lib.rs
Normal file
@@ -0,0 +1,772 @@
|
||||
#![cfg_attr(target_arch = "wasm32", no_std)]
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
extern crate std;
|
||||
|
||||
//
|
||||
// Host Functions Test
|
||||
// Tests 26 host functions (across 7 categories)
|
||||
//
|
||||
// With craft you can run this test with:
|
||||
// craft test --project host_functions_test --test-case host_functions_test
|
||||
//
|
||||
// Amount Format Update:
|
||||
// - XRP amounts now return as 8-byte serialized rippled objects
|
||||
// - IOU and MPT amounts return in variable-length serialized format
|
||||
// - Format details: https://xrpl.org/docs/references/protocol/binary-format#amount-fields
|
||||
//
|
||||
// Error Code Ranges:
|
||||
// -100 to -199: Ledger Header Functions (3 functions)
|
||||
// -200 to -299: Transaction Data Functions (5 functions)
|
||||
// -300 to -399: Current Ledger Object Functions (4 functions)
|
||||
// -400 to -499: Any Ledger Object Functions (5 functions)
|
||||
// -500 to -599: Keylet Generation Functions (4 functions)
|
||||
// -600 to -699: Utility Functions (4 functions)
|
||||
// -700 to -799: Data Update Functions (1 function)
|
||||
//
|
||||
|
||||
use xrpl_std::core::current_tx::escrow_finish::EscrowFinish;
|
||||
use xrpl_std::core::current_tx::traits::TransactionCommonFields;
|
||||
use xrpl_std::host;
|
||||
use xrpl_std::host::trace::{trace, trace_account_buf, trace_data, trace_num, DataRepr};
|
||||
use xrpl_std::sfield;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn finish() -> i32 {
|
||||
let _ = trace("=== HOST FUNCTIONS TEST ===");
|
||||
let _ = trace("Testing 26 host functions");
|
||||
|
||||
// Category 1: Ledger Header Data Functions (3 functions)
|
||||
// Error range: -100 to -199
|
||||
match test_ledger_header_functions() {
|
||||
0 => (),
|
||||
err => return err,
|
||||
}
|
||||
|
||||
// Category 2: Transaction Data Functions (5 functions)
|
||||
// Error range: -200 to -299
|
||||
match test_transaction_data_functions() {
|
||||
0 => (),
|
||||
err => return err,
|
||||
}
|
||||
|
||||
// Category 3: Current Ledger Object Functions (4 functions)
|
||||
// Error range: -300 to -399
|
||||
match test_current_ledger_object_functions() {
|
||||
0 => (),
|
||||
err => return err,
|
||||
}
|
||||
|
||||
// Category 4: Any Ledger Object Functions (5 functions)
|
||||
// Error range: -400 to -499
|
||||
match test_any_ledger_object_functions() {
|
||||
0 => (),
|
||||
err => return err,
|
||||
}
|
||||
|
||||
// Category 5: Keylet Generation Functions (4 functions)
|
||||
// Error range: -500 to -599
|
||||
match test_keylet_generation_functions() {
|
||||
0 => (),
|
||||
err => return err,
|
||||
}
|
||||
|
||||
// Category 6: Utility Functions (4 functions)
|
||||
// Error range: -600 to -699
|
||||
match test_utility_functions() {
|
||||
0 => (),
|
||||
err => return err,
|
||||
}
|
||||
|
||||
// Category 7: Data Update Functions (1 function)
|
||||
// Error range: -700 to -799
|
||||
match test_data_update_functions() {
|
||||
0 => (),
|
||||
err => return err,
|
||||
}
|
||||
|
||||
let _ = trace("SUCCESS: All host function tests passed!");
|
||||
1 // Success return code for WASM finish function
|
||||
}
|
||||
|
||||
/// Test Category 1: Ledger Header Data Functions (3 functions)
|
||||
/// - get_ledger_sqn() - Get ledger sequence number
|
||||
/// - get_parent_ledger_time() - Get parent ledger timestamp
|
||||
/// - get_parent_ledger_hash() - Get parent ledger hash
|
||||
fn test_ledger_header_functions() -> i32 {
|
||||
let _ = trace("--- Category 1: Ledger Header Functions ---");
|
||||
|
||||
// Test 1.1: get_ledger_sqn() - should return current ledger sequence number
|
||||
let sqn_result = unsafe { host::get_ledger_sqn() };
|
||||
|
||||
if sqn_result <= 0 {
|
||||
let _ = trace_num("ERROR: get_ledger_sqn failed:", sqn_result as i64);
|
||||
return -101; // Ledger sequence number test failed
|
||||
}
|
||||
let _ = trace_num("Ledger sequence number:", sqn_result as i64);
|
||||
|
||||
// Test 1.2: get_parent_ledger_time() - should return parent ledger timestamp
|
||||
let time_result = unsafe { host::get_parent_ledger_time() };
|
||||
|
||||
if time_result <= 0 {
|
||||
let _ = trace_num("ERROR: get_parent_ledger_time failed:", time_result as i64);
|
||||
return -102; // Parent ledger time test failed
|
||||
}
|
||||
let _ = trace_num("Parent ledger time:", time_result as i64);
|
||||
|
||||
// Test 1.3: get_parent_ledger_hash() - should return parent ledger hash (32 bytes)
|
||||
let mut hash_buffer = [0u8; 32];
|
||||
let hash_result =
|
||||
unsafe { host::get_parent_ledger_hash(hash_buffer.as_mut_ptr(), hash_buffer.len()) };
|
||||
|
||||
if hash_result != 32 {
|
||||
let _ = trace_num(
|
||||
"ERROR: get_parent_ledger_hash wrong length:",
|
||||
hash_result as i64,
|
||||
);
|
||||
return -103; // Parent ledger hash test failed - should be exactly 32 bytes
|
||||
}
|
||||
let _ = trace_data("Parent ledger hash:", &hash_buffer, DataRepr::AsHex);
|
||||
|
||||
let _ = trace("SUCCESS: Ledger header functions");
|
||||
0
|
||||
}
|
||||
|
||||
/// Test Category 2: Transaction Data Functions (5 functions)
|
||||
/// Tests all functions for accessing current transaction data
|
||||
fn test_transaction_data_functions() -> i32 {
|
||||
let _ = trace("--- Category 2: Transaction Data Functions ---");
|
||||
|
||||
// Test 2.1: get_tx_field() - Basic transaction field access
|
||||
// Test with Account field (required, 20 bytes)
|
||||
let mut account_buffer = [0u8; 20];
|
||||
let account_len = unsafe {
|
||||
host::get_tx_field(
|
||||
sfield::Account,
|
||||
account_buffer.as_mut_ptr(),
|
||||
account_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if account_len != 20 {
|
||||
let _ = trace_num(
|
||||
"ERROR: get_tx_field(Account) wrong length:",
|
||||
account_len as i64,
|
||||
);
|
||||
return -201; // Basic transaction field test failed
|
||||
}
|
||||
let _ = trace_account_buf("Transaction Account:", &account_buffer);
|
||||
|
||||
// Test with Fee field (XRP amount - 8 bytes in new serialized format)
|
||||
// New format: XRP amounts are always 8 bytes (positive: value | cPositive flag, negative: just value)
|
||||
let mut fee_buffer = [0u8; 8];
|
||||
let fee_len =
|
||||
unsafe { host::get_tx_field(sfield::Fee, fee_buffer.as_mut_ptr(), fee_buffer.len()) };
|
||||
|
||||
if fee_len != 8 {
|
||||
let _ = trace_num(
|
||||
"ERROR: get_tx_field(Fee) wrong length (expected 8 bytes for XRP):",
|
||||
fee_len as i64,
|
||||
);
|
||||
return -202; // Fee field test failed - XRP amounts should be exactly 8 bytes
|
||||
}
|
||||
let _ = trace_num("Transaction Fee length:", fee_len as i64);
|
||||
let _ = trace_data(
|
||||
"Transaction Fee (serialized XRP amount):",
|
||||
&fee_buffer,
|
||||
DataRepr::AsHex,
|
||||
);
|
||||
|
||||
// Test with Sequence field (required, 4 bytes uint32)
|
||||
let mut seq_buffer = [0u8; 4];
|
||||
let seq_len =
|
||||
unsafe { host::get_tx_field(sfield::Sequence, seq_buffer.as_mut_ptr(), seq_buffer.len()) };
|
||||
|
||||
if seq_len != 4 {
|
||||
let _ = trace_num(
|
||||
"ERROR: get_tx_field(Sequence) wrong length:",
|
||||
seq_len as i64,
|
||||
);
|
||||
return -203; // Sequence field test failed
|
||||
}
|
||||
let _ = trace_data("Transaction Sequence:", &seq_buffer, DataRepr::AsHex);
|
||||
|
||||
// NOTE: get_tx_field2() through get_tx_field6() have been deprecated.
|
||||
// Use get_tx_field() with appropriate parameters for all transaction field access.
|
||||
|
||||
// Test 2.2: get_tx_nested_field() - Nested field access with locator
|
||||
let locator = [0x01, 0x00]; // Simple locator for first element
|
||||
let mut nested_buffer = [0u8; 32];
|
||||
let nested_result = unsafe {
|
||||
host::get_tx_nested_field(
|
||||
locator.as_ptr(),
|
||||
locator.len(),
|
||||
nested_buffer.as_mut_ptr(),
|
||||
nested_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if nested_result < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_tx_nested_field not applicable:",
|
||||
nested_result as i64,
|
||||
);
|
||||
// Expected - locator may not match transaction structure
|
||||
} else {
|
||||
let _ = trace_num("Nested field length:", nested_result as i64);
|
||||
let _ = trace_data(
|
||||
"Nested field:",
|
||||
&nested_buffer[..nested_result as usize],
|
||||
DataRepr::AsHex,
|
||||
);
|
||||
}
|
||||
|
||||
// Test 2.3: get_tx_array_len() - Get array length
|
||||
let signers_len = unsafe { host::get_tx_array_len(sfield::Signers) };
|
||||
let _ = trace_num("Signers array length:", signers_len as i64);
|
||||
|
||||
let memos_len = unsafe { host::get_tx_array_len(sfield::Memos) };
|
||||
let _ = trace_num("Memos array length:", memos_len as i64);
|
||||
|
||||
// Test 2.4: get_tx_nested_array_len() - Get nested array length with locator
|
||||
let nested_array_len =
|
||||
unsafe { host::get_tx_nested_array_len(locator.as_ptr(), locator.len()) };
|
||||
|
||||
if nested_array_len < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_tx_nested_array_len not applicable:",
|
||||
nested_array_len as i64,
|
||||
);
|
||||
} else {
|
||||
let _ = trace_num("Nested array length:", nested_array_len as i64);
|
||||
}
|
||||
|
||||
let _ = trace("SUCCESS: Transaction data functions");
|
||||
0
|
||||
}
|
||||
|
||||
/// Test Category 3: Current Ledger Object Functions (4 functions)
|
||||
/// Tests functions that access the current ledger object being processed
|
||||
fn test_current_ledger_object_functions() -> i32 {
|
||||
let _ = trace("--- Category 3: Current Ledger Object Functions ---");
|
||||
|
||||
// Test 3.1: get_current_ledger_obj_field() - Access field from current ledger object
|
||||
// Test with Balance field (XRP amount - 8 bytes in new serialized format)
|
||||
let mut balance_buffer = [0u8; 8];
|
||||
let balance_result = unsafe {
|
||||
host::get_current_ledger_obj_field(
|
||||
sfield::Balance,
|
||||
balance_buffer.as_mut_ptr(),
|
||||
balance_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if balance_result <= 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_current_ledger_obj_field(Balance) failed (may be expected):",
|
||||
balance_result as i64,
|
||||
);
|
||||
// This might fail if current ledger object doesn't have balance field
|
||||
} else if balance_result == 8 {
|
||||
let _ = trace_num(
|
||||
"Current object balance length (XRP amount):",
|
||||
balance_result as i64,
|
||||
);
|
||||
let _ = trace_data(
|
||||
"Current object balance (serialized XRP amount):",
|
||||
&balance_buffer,
|
||||
DataRepr::AsHex,
|
||||
);
|
||||
} else {
|
||||
let _ = trace_num(
|
||||
"Current object balance length (non-XRP amount):",
|
||||
balance_result as i64,
|
||||
);
|
||||
let _ = trace_data(
|
||||
"Current object balance:",
|
||||
&balance_buffer[..balance_result as usize],
|
||||
DataRepr::AsHex,
|
||||
);
|
||||
}
|
||||
|
||||
// Test with Account field
|
||||
let mut current_account_buffer = [0u8; 20];
|
||||
let current_account_result = unsafe {
|
||||
host::get_current_ledger_obj_field(
|
||||
sfield::Account,
|
||||
current_account_buffer.as_mut_ptr(),
|
||||
current_account_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if current_account_result <= 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_current_ledger_obj_field(Account) failed:",
|
||||
current_account_result as i64,
|
||||
);
|
||||
} else {
|
||||
let _ = trace_account_buf("Current ledger object account:", ¤t_account_buffer);
|
||||
}
|
||||
|
||||
// Test 3.2: get_current_ledger_obj_nested_field() - Nested field access
|
||||
let locator = [0x01, 0x00]; // Simple locator
|
||||
let mut current_nested_buffer = [0u8; 32];
|
||||
let current_nested_result = unsafe {
|
||||
host::get_current_ledger_obj_nested_field(
|
||||
locator.as_ptr(),
|
||||
locator.len(),
|
||||
current_nested_buffer.as_mut_ptr(),
|
||||
current_nested_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if current_nested_result < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_current_ledger_obj_nested_field not applicable:",
|
||||
current_nested_result as i64,
|
||||
);
|
||||
} else {
|
||||
let _ = trace_num("Current nested field length:", current_nested_result as i64);
|
||||
let _ = trace_data(
|
||||
"Current nested field:",
|
||||
¤t_nested_buffer[..current_nested_result as usize],
|
||||
DataRepr::AsHex,
|
||||
);
|
||||
}
|
||||
|
||||
// Test 3.3: get_current_ledger_obj_array_len() - Array length in current object
|
||||
let current_array_len = unsafe { host::get_current_ledger_obj_array_len(sfield::Signers) };
|
||||
let _ = trace_num(
|
||||
"Current object Signers array length:",
|
||||
current_array_len as i64,
|
||||
);
|
||||
|
||||
// Test 3.4: get_current_ledger_obj_nested_array_len() - Nested array length
|
||||
let current_nested_array_len =
|
||||
unsafe { host::get_current_ledger_obj_nested_array_len(locator.as_ptr(), locator.len()) };
|
||||
|
||||
if current_nested_array_len < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_current_ledger_obj_nested_array_len not applicable:",
|
||||
current_nested_array_len as i64,
|
||||
);
|
||||
} else {
|
||||
let _ = trace_num(
|
||||
"Current nested array length:",
|
||||
current_nested_array_len as i64,
|
||||
);
|
||||
}
|
||||
|
||||
let _ = trace("SUCCESS: Current ledger object functions");
|
||||
0
|
||||
}
|
||||
|
||||
/// Test Category 4: Any Ledger Object Functions (5 functions)
|
||||
/// Tests functions that work with cached ledger objects
|
||||
fn test_any_ledger_object_functions() -> i32 {
|
||||
let _ = trace("--- Category 4: Any Ledger Object Functions ---");
|
||||
|
||||
// First we need to cache a ledger object to test the other functions
|
||||
// Get the account from transaction and generate its keylet
|
||||
let escrow_finish = EscrowFinish;
|
||||
let account_id = escrow_finish.get_account().unwrap();
|
||||
|
||||
// Test 4.1: cache_ledger_obj() - Cache a ledger object
|
||||
let mut keylet_buffer = [0u8; 32];
|
||||
let keylet_result = unsafe {
|
||||
host::account_keylet(
|
||||
account_id.0.as_ptr(),
|
||||
account_id.0.len(),
|
||||
keylet_buffer.as_mut_ptr(),
|
||||
keylet_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if keylet_result != 32 {
|
||||
let _ = trace_num(
|
||||
"ERROR: account_keylet failed for caching test:",
|
||||
keylet_result as i64,
|
||||
);
|
||||
return -401; // Keylet generation failed for caching test
|
||||
}
|
||||
|
||||
let cache_result =
|
||||
unsafe { host::cache_ledger_obj(keylet_buffer.as_ptr(), keylet_result as usize, 0) };
|
||||
|
||||
if cache_result <= 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: cache_ledger_obj failed (expected with test fixtures):",
|
||||
cache_result as i64,
|
||||
);
|
||||
// Test fixtures may not contain the account object - this is expected
|
||||
// We'll test the interface but expect failures
|
||||
|
||||
// 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
|
||||
let field_result = unsafe {
|
||||
host::get_ledger_obj_field(
|
||||
1,
|
||||
sfield::Balance,
|
||||
test_buffer.as_mut_ptr(),
|
||||
test_buffer.len(),
|
||||
)
|
||||
};
|
||||
if field_result < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_ledger_obj_field failed as expected (no cached object):",
|
||||
field_result as i64,
|
||||
);
|
||||
}
|
||||
|
||||
// Test get_ledger_obj_nested_field with invalid slot
|
||||
let locator = [0x01, 0x00];
|
||||
let nested_result = unsafe {
|
||||
host::get_ledger_obj_nested_field(
|
||||
1,
|
||||
locator.as_ptr(),
|
||||
locator.len(),
|
||||
test_buffer.as_mut_ptr(),
|
||||
test_buffer.len(),
|
||||
)
|
||||
};
|
||||
if nested_result < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_ledger_obj_nested_field failed as expected:",
|
||||
nested_result as i64,
|
||||
);
|
||||
}
|
||||
|
||||
// Test get_ledger_obj_array_len with invalid slot
|
||||
let array_result = unsafe { host::get_ledger_obj_array_len(1, sfield::Signers) };
|
||||
if array_result < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_ledger_obj_array_len failed as expected:",
|
||||
array_result as i64,
|
||||
);
|
||||
}
|
||||
|
||||
// Test get_ledger_obj_nested_array_len with invalid slot
|
||||
let nested_array_result =
|
||||
unsafe { host::get_ledger_obj_nested_array_len(1, locator.as_ptr(), locator.len()) };
|
||||
if nested_array_result < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_ledger_obj_nested_array_len failed as expected:",
|
||||
nested_array_result as i64,
|
||||
);
|
||||
}
|
||||
|
||||
let _ = trace("SUCCESS: Any ledger object functions (interface tested)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If we successfully cached an object, test the access functions
|
||||
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
|
||||
let mut cached_balance_buffer = [0u8; 8];
|
||||
let cached_balance_result = unsafe {
|
||||
host::get_ledger_obj_field(
|
||||
slot,
|
||||
sfield::Balance,
|
||||
cached_balance_buffer.as_mut_ptr(),
|
||||
cached_balance_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if cached_balance_result <= 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_ledger_obj_field(Balance) failed:",
|
||||
cached_balance_result as i64,
|
||||
);
|
||||
} else if cached_balance_result == 8 {
|
||||
let _ = trace_num(
|
||||
"Cached object balance length (XRP amount):",
|
||||
cached_balance_result as i64,
|
||||
);
|
||||
let _ = trace_data(
|
||||
"Cached object balance (serialized XRP amount):",
|
||||
&cached_balance_buffer,
|
||||
DataRepr::AsHex,
|
||||
);
|
||||
} else {
|
||||
let _ = trace_num(
|
||||
"Cached object balance length (non-XRP amount):",
|
||||
cached_balance_result as i64,
|
||||
);
|
||||
let _ = trace_data(
|
||||
"Cached object balance:",
|
||||
&cached_balance_buffer[..cached_balance_result as usize],
|
||||
DataRepr::AsHex,
|
||||
);
|
||||
}
|
||||
|
||||
// Test 4.3: get_ledger_obj_nested_field() - Nested field from cached object
|
||||
let locator = [0x01, 0x00];
|
||||
let mut cached_nested_buffer = [0u8; 32];
|
||||
let cached_nested_result = unsafe {
|
||||
host::get_ledger_obj_nested_field(
|
||||
slot,
|
||||
locator.as_ptr(),
|
||||
locator.len(),
|
||||
cached_nested_buffer.as_mut_ptr(),
|
||||
cached_nested_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if cached_nested_result < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_ledger_obj_nested_field not applicable:",
|
||||
cached_nested_result as i64,
|
||||
);
|
||||
} else {
|
||||
let _ = trace_num("Cached nested field length:", cached_nested_result as i64);
|
||||
let _ = trace_data(
|
||||
"Cached nested field:",
|
||||
&cached_nested_buffer[..cached_nested_result as usize],
|
||||
DataRepr::AsHex,
|
||||
);
|
||||
}
|
||||
|
||||
// Test 4.4: get_ledger_obj_array_len() - Array length from cached object
|
||||
let cached_array_len = unsafe { host::get_ledger_obj_array_len(slot, sfield::Signers) };
|
||||
let _ = trace_num(
|
||||
"Cached object Signers array length:",
|
||||
cached_array_len as i64,
|
||||
);
|
||||
|
||||
// Test 4.5: get_ledger_obj_nested_array_len() - Nested array length from cached object
|
||||
let cached_nested_array_len =
|
||||
unsafe { host::get_ledger_obj_nested_array_len(slot, locator.as_ptr(), locator.len()) };
|
||||
|
||||
if cached_nested_array_len < 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_ledger_obj_nested_array_len not applicable:",
|
||||
cached_nested_array_len as i64,
|
||||
);
|
||||
} else {
|
||||
let _ = trace_num(
|
||||
"Cached nested array length:",
|
||||
cached_nested_array_len as i64,
|
||||
);
|
||||
}
|
||||
|
||||
let _ = trace("SUCCESS: Any ledger object functions");
|
||||
0
|
||||
}
|
||||
|
||||
/// Test Category 5: Keylet Generation Functions (4 functions)
|
||||
/// Tests keylet generation functions for different ledger entry types
|
||||
fn test_keylet_generation_functions() -> i32 {
|
||||
let _ = trace("--- Category 5: Keylet Generation Functions ---");
|
||||
|
||||
let escrow_finish = EscrowFinish;
|
||||
let account_id = escrow_finish.get_account().unwrap();
|
||||
|
||||
// Test 5.1: account_keylet() - Generate keylet for account
|
||||
let mut account_keylet_buffer = [0u8; 32];
|
||||
let account_keylet_result = unsafe {
|
||||
host::account_keylet(
|
||||
account_id.0.as_ptr(),
|
||||
account_id.0.len(),
|
||||
account_keylet_buffer.as_mut_ptr(),
|
||||
account_keylet_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if account_keylet_result != 32 {
|
||||
let _ = trace_num(
|
||||
"ERROR: account_keylet failed:",
|
||||
account_keylet_result as i64,
|
||||
);
|
||||
return -501; // Account keylet generation failed
|
||||
}
|
||||
let _ = trace_data("Account keylet:", &account_keylet_buffer, DataRepr::AsHex);
|
||||
|
||||
// Test 5.2: credential_keylet() - Generate keylet for credential
|
||||
let mut credential_keylet_buffer = [0u8; 32];
|
||||
let credential_keylet_result = unsafe {
|
||||
host::credential_keylet(
|
||||
account_id.0.as_ptr(), // Subject
|
||||
account_id.0.len(),
|
||||
account_id.0.as_ptr(), // Issuer - same account for test
|
||||
account_id.0.len(),
|
||||
b"TestType".as_ptr(), // Credential type
|
||||
9usize, // Length of "TestType"
|
||||
credential_keylet_buffer.as_mut_ptr(),
|
||||
credential_keylet_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if credential_keylet_result <= 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: credential_keylet failed (expected - interface issue):",
|
||||
credential_keylet_result as i64,
|
||||
);
|
||||
// This is expected to fail due to unusual parameter types
|
||||
} else {
|
||||
let _ = trace_data(
|
||||
"Credential keylet:",
|
||||
&credential_keylet_buffer[..credential_keylet_result as usize],
|
||||
DataRepr::AsHex,
|
||||
);
|
||||
}
|
||||
|
||||
// Test 5.3: escrow_keylet() - Generate keylet for escrow
|
||||
let mut escrow_keylet_buffer = [0u8; 32];
|
||||
let escrow_keylet_result = unsafe {
|
||||
host::escrow_keylet(
|
||||
account_id.0.as_ptr(),
|
||||
account_id.0.len(),
|
||||
1000, // Sequence number
|
||||
escrow_keylet_buffer.as_mut_ptr(),
|
||||
escrow_keylet_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if escrow_keylet_result != 32 {
|
||||
let _ = trace_num("ERROR: escrow_keylet failed:", escrow_keylet_result as i64);
|
||||
return -503; // Escrow keylet generation failed
|
||||
}
|
||||
let _ = trace_data("Escrow keylet:", &escrow_keylet_buffer, DataRepr::AsHex);
|
||||
|
||||
// Test 5.4: oracle_keylet() - Generate keylet for oracle
|
||||
let mut oracle_keylet_buffer = [0u8; 32];
|
||||
let oracle_keylet_result = unsafe {
|
||||
host::oracle_keylet(
|
||||
account_id.0.as_ptr(),
|
||||
account_id.0.len(),
|
||||
42, // Document ID
|
||||
oracle_keylet_buffer.as_mut_ptr(),
|
||||
oracle_keylet_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if oracle_keylet_result != 32 {
|
||||
let _ = trace_num("ERROR: oracle_keylet failed:", oracle_keylet_result as i64);
|
||||
return -504; // Oracle keylet generation failed
|
||||
}
|
||||
let _ = trace_data("Oracle keylet:", &oracle_keylet_buffer, DataRepr::AsHex);
|
||||
|
||||
let _ = trace("SUCCESS: Keylet generation functions");
|
||||
0
|
||||
}
|
||||
|
||||
/// Test Category 6: Utility Functions (4 functions)
|
||||
/// Tests utility functions for hashing, NFT access, and tracing
|
||||
fn test_utility_functions() -> i32 {
|
||||
let _ = trace("--- Category 6: Utility Functions ---");
|
||||
|
||||
// Test 6.1: compute_sha512_half() - SHA512 hash computation (first 32 bytes)
|
||||
let test_data = b"Hello, XRPL WASM world!";
|
||||
let mut hash_output = [0u8; 32];
|
||||
let hash_result = unsafe {
|
||||
host::compute_sha512_half(
|
||||
test_data.as_ptr(),
|
||||
test_data.len(),
|
||||
hash_output.as_mut_ptr(),
|
||||
hash_output.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if hash_result != 32 {
|
||||
let _ = trace_num("ERROR: compute_sha512_half failed:", hash_result as i64);
|
||||
return -601; // SHA512 half computation failed
|
||||
}
|
||||
let _ = trace_data("Input data:", test_data, DataRepr::AsHex);
|
||||
let _ = trace_data("SHA512 half hash:", &hash_output, DataRepr::AsHex);
|
||||
|
||||
// Test 6.2: get_nft() - NFT data retrieval
|
||||
let escrow_finish = EscrowFinish;
|
||||
let account_id = escrow_finish.get_account().unwrap();
|
||||
let nft_id = [0u8; 32]; // Dummy NFT ID for testing
|
||||
let mut nft_buffer = [0u8; 256];
|
||||
let nft_result = unsafe {
|
||||
host::get_nft(
|
||||
account_id.0.as_ptr(),
|
||||
account_id.0.len(),
|
||||
nft_id.as_ptr(),
|
||||
nft_id.len(),
|
||||
nft_buffer.as_mut_ptr(),
|
||||
nft_buffer.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if nft_result <= 0 {
|
||||
let _ = trace_num(
|
||||
"INFO: get_nft failed (expected - no such NFT):",
|
||||
nft_result as i64,
|
||||
);
|
||||
// This is expected - test account likely doesn't own the dummy NFT
|
||||
} else {
|
||||
let _ = trace_num("NFT data length:", nft_result as i64);
|
||||
let _ = trace_data(
|
||||
"NFT data:",
|
||||
&nft_buffer[..nft_result as usize],
|
||||
DataRepr::AsHex,
|
||||
);
|
||||
}
|
||||
|
||||
// Test 6.3: trace() - Debug logging with data
|
||||
let trace_message = b"Test trace message";
|
||||
let trace_data_payload = b"payload";
|
||||
let trace_result = unsafe {
|
||||
host::trace(
|
||||
trace_message.as_ptr(),
|
||||
trace_message.len(),
|
||||
trace_data_payload.as_ptr(),
|
||||
trace_data_payload.len(),
|
||||
1, // as_hex = true
|
||||
)
|
||||
};
|
||||
|
||||
if trace_result < 0 {
|
||||
let _ = trace_num("ERROR: trace() failed:", trace_result as i64);
|
||||
return -603; // Trace function failed
|
||||
}
|
||||
let _ = trace_num("Trace function bytes written:", trace_result as i64);
|
||||
|
||||
// Test 6.4: trace_num() - Debug logging with number
|
||||
let test_number = 42i64;
|
||||
let trace_num_result = trace_num("Test number trace", test_number);
|
||||
|
||||
use xrpl_std::host::Result;
|
||||
match trace_num_result {
|
||||
Result::Ok(_) => {
|
||||
let _ = trace_num("Trace_num function succeeded", 0);
|
||||
}
|
||||
Result::Err(_) => {
|
||||
let _ = trace_num("ERROR: trace_num() failed:", -604);
|
||||
return -604; // Trace number function failed
|
||||
}
|
||||
}
|
||||
|
||||
let _ = trace("SUCCESS: Utility functions");
|
||||
0
|
||||
}
|
||||
|
||||
/// Test Category 7: Data Update Functions (1 function)
|
||||
/// Tests the function for modifying the current ledger entry
|
||||
fn test_data_update_functions() -> i32 {
|
||||
let _ = trace("--- Category 7: Data Update Functions ---");
|
||||
|
||||
// Test 7.1: update_data() - Update current ledger entry data
|
||||
let update_payload = b"Updated ledger entry data from WASM test";
|
||||
|
||||
let update_result = unsafe { host::update_data(update_payload.as_ptr(), update_payload.len()) };
|
||||
|
||||
if update_result != update_payload.len() as i32 {
|
||||
let _ = trace_num("ERROR: update_data failed:", update_result as i64);
|
||||
return -701; // Data update failed
|
||||
}
|
||||
|
||||
let _ = trace_data(
|
||||
"Successfully updated ledger entry with:",
|
||||
update_payload,
|
||||
DataRepr::AsHex,
|
||||
);
|
||||
let _ = trace("SUCCESS: Data update functions");
|
||||
0
|
||||
}
|
||||
171
src/test/app/wasm_fixtures/all_keylets/Cargo.lock
generated
Normal file
171
src/test/app/wasm_fixtures/all_keylets/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 = "all_keylets"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"xrpl-wasm-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-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 = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[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.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
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.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-address-macro"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"quote",
|
||||
"sha2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-stdlib"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e"
|
||||
dependencies = [
|
||||
"xrpl-address-macro",
|
||||
]
|
||||
21
src/test/app/wasm_fixtures/all_keylets/Cargo.toml
Normal file
21
src/test/app/wasm_fixtures/all_keylets/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
edition = "2024"
|
||||
name = "all_keylets"
|
||||
version = "0.0.1"
|
||||
|
||||
# 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" }
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
181
src/test/app/wasm_fixtures/all_keylets/src/lib.rs
Normal file
181
src/test/app/wasm_fixtures/all_keylets/src/lib.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
#![cfg_attr(target_arch = "wasm32", no_std)]
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
extern crate std;
|
||||
|
||||
use crate::host::{Error, Result, Result::Err, Result::Ok};
|
||||
use xrpl_std::core::ledger_objects::current_escrow::get_current_escrow;
|
||||
use xrpl_std::core::ledger_objects::current_escrow::CurrentEscrow;
|
||||
use xrpl_std::core::ledger_objects::ledger_object;
|
||||
use xrpl_std::core::ledger_objects::traits::CurrentEscrowFields;
|
||||
use xrpl_std::core::types::account_id::AccountID;
|
||||
use xrpl_std::core::types::currency::Currency;
|
||||
use xrpl_std::core::types::issue::{IouIssue, Issue, XrpIssue};
|
||||
use xrpl_std::core::types::keylets;
|
||||
use xrpl_std::core::types::mpt_id::MptId;
|
||||
use xrpl_std::core::types::uint::Hash256;
|
||||
use xrpl_std::host;
|
||||
use xrpl_std::host::trace::{trace, trace_account, trace_data, trace_num, DataRepr};
|
||||
use xrpl_std::sfield;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub fn object_exists(
|
||||
keylet_result: Result<keylets::KeyletBytes>,
|
||||
keylet_type: &str,
|
||||
field: i32,
|
||||
) -> Result<bool> {
|
||||
match keylet_result {
|
||||
Ok(keylet) => {
|
||||
let _ = trace_data(keylet_type, &keylet, DataRepr::AsHex);
|
||||
|
||||
let slot = unsafe { host::cache_ledger_obj(keylet.as_ptr(), keylet.len(), 0) };
|
||||
if slot <= 0 {
|
||||
let _ = trace_num("Error: ", slot.into());
|
||||
return Err(Error::from_code(slot));
|
||||
}
|
||||
if field == 0 {
|
||||
let new_field = sfield::PreviousTxnID;
|
||||
let _ = trace_num("Getting field: ", new_field.into());
|
||||
match ledger_object::get_field::<Hash256>(slot, new_field) {
|
||||
Ok(data) => {
|
||||
let _ = trace_data("Field data: ", &data.0, DataRepr::AsHex);
|
||||
}
|
||||
Err(result_code) => {
|
||||
let _ = trace_num("Error getting field: ", result_code.into());
|
||||
return Err(result_code);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let _ = trace_num("Getting field: ", field.into());
|
||||
match ledger_object::get_field::<AccountID>(slot, field) {
|
||||
Ok(data) => {
|
||||
let _ = trace_data("Field data: ", &data.0, DataRepr::AsHex);
|
||||
}
|
||||
Err(result_code) => {
|
||||
let _ = trace_num("Error getting field: ", result_code.into());
|
||||
return Err(result_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
Err(error) => {
|
||||
let _ = trace_num("Error getting keylet: ", error.into());
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn finish() -> i32 {
|
||||
let _ = trace("$$$$$ STARTING WASM EXECUTION $$$$$");
|
||||
|
||||
let escrow: CurrentEscrow = get_current_escrow();
|
||||
|
||||
let account = escrow.get_account().unwrap_or_panic();
|
||||
let _ = trace_account("Account:", &account);
|
||||
|
||||
let destination = escrow.get_destination().unwrap_or_panic();
|
||||
let _ = trace_account("Destination:", &destination);
|
||||
|
||||
let mut seq = 5;
|
||||
|
||||
macro_rules! check_object_exists {
|
||||
($keylet:expr, $type:expr, $field:expr) => {
|
||||
match object_exists($keylet, $type, $field) {
|
||||
Ok(_exists) => {
|
||||
// false isn't returned
|
||||
let _ = trace(concat!(
|
||||
$type,
|
||||
" object exists, proceeding with escrow finish."
|
||||
));
|
||||
}
|
||||
Err(error) => {
|
||||
let _ = trace_num("Current seq value:", seq.try_into().unwrap());
|
||||
return error.code();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let account_keylet = keylets::account_keylet(&account);
|
||||
check_object_exists!(account_keylet, "Account", sfield::Account);
|
||||
|
||||
let currency_code: &[u8; 3] = b"USD";
|
||||
let currency: Currency = Currency::from(*currency_code);
|
||||
let line_keylet = keylets::line_keylet(&account, &destination, ¤cy);
|
||||
check_object_exists!(line_keylet, "Trustline", sfield::Generic);
|
||||
seq += 1;
|
||||
|
||||
let asset1 = Issue::XRP(XrpIssue {});
|
||||
let asset2 = Issue::IOU(IouIssue::new(destination, currency));
|
||||
check_object_exists!(
|
||||
keylets::amm_keylet(&asset1, &asset2),
|
||||
"AMM",
|
||||
sfield::Account
|
||||
);
|
||||
|
||||
let check_keylet = keylets::check_keylet(&account, seq);
|
||||
check_object_exists!(check_keylet, "Check", sfield::Account);
|
||||
seq += 1;
|
||||
|
||||
let cred_type: &[u8] = b"termsandconditions";
|
||||
let credential_keylet = keylets::credential_keylet(&account, &account, cred_type);
|
||||
check_object_exists!(credential_keylet, "Credential", sfield::Subject);
|
||||
seq += 1;
|
||||
|
||||
let delegate_keylet = keylets::delegate_keylet(&account, &destination);
|
||||
check_object_exists!(delegate_keylet, "Delegate", sfield::Account);
|
||||
seq += 1;
|
||||
|
||||
let deposit_preauth_keylet = keylets::deposit_preauth_keylet(&account, &destination);
|
||||
check_object_exists!(deposit_preauth_keylet, "DepositPreauth", sfield::Account);
|
||||
seq += 1;
|
||||
|
||||
let did_keylet = keylets::did_keylet(&account);
|
||||
check_object_exists!(did_keylet, "DID", sfield::Account);
|
||||
seq += 1;
|
||||
|
||||
let escrow_keylet = keylets::escrow_keylet(&account, seq);
|
||||
check_object_exists!(escrow_keylet, "Escrow", sfield::Account);
|
||||
seq += 1;
|
||||
|
||||
let mpt_issuance_keylet = keylets::mpt_issuance_keylet(&account, seq);
|
||||
let mpt_id = MptId::new(seq.try_into().unwrap(), account);
|
||||
check_object_exists!(mpt_issuance_keylet, "MPTIssuance", sfield::Issuer);
|
||||
seq += 1;
|
||||
|
||||
let mptoken_keylet = keylets::mptoken_keylet(&mpt_id, &destination);
|
||||
check_object_exists!(mptoken_keylet, "MPToken", sfield::Account);
|
||||
|
||||
let nft_offer_keylet = keylets::nft_offer_keylet(&destination, 6);
|
||||
check_object_exists!(nft_offer_keylet, "NFTokenOffer", sfield::Owner);
|
||||
|
||||
let offer_keylet = keylets::offer_keylet(&account, seq);
|
||||
check_object_exists!(offer_keylet, "Offer", sfield::Account);
|
||||
seq += 1;
|
||||
|
||||
let paychan_keylet = keylets::paychan_keylet(&account, &destination, seq);
|
||||
check_object_exists!(paychan_keylet, "PayChannel", sfield::Account);
|
||||
seq += 1;
|
||||
|
||||
let pd_keylet = keylets::permissioned_domain_keylet(&account, seq);
|
||||
check_object_exists!(pd_keylet, "PermissionedDomain", sfield::Owner);
|
||||
seq += 1;
|
||||
|
||||
let signers_keylet = keylets::signers_keylet(&account);
|
||||
check_object_exists!(signers_keylet, "SignerList", sfield::Generic);
|
||||
seq += 1;
|
||||
|
||||
seq += 1; // ticket sequence number is one greater
|
||||
let ticket_keylet = keylets::ticket_keylet(&account, seq);
|
||||
check_object_exists!(ticket_keylet, "Ticket", sfield::Account);
|
||||
seq += 1;
|
||||
|
||||
let vault_keylet = keylets::vault_keylet(&account, seq);
|
||||
check_object_exists!(vault_keylet, "Vault", sfield::Account);
|
||||
// seq += 1;
|
||||
|
||||
1 // All keylets exist, finish the escrow.
|
||||
}
|
||||
72
src/test/app/wasm_fixtures/b58.c
Normal file
72
src/test/app/wasm_fixtures/b58.c
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <stdint.h>
|
||||
|
||||
static char const b58digits_ordered[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
|
||||
uint8_t e_data[32 * 1024];
|
||||
|
||||
void*
|
||||
allocate(int sz)
|
||||
{
|
||||
static int idx = 0;
|
||||
if (idx >= 32)
|
||||
return 0;
|
||||
if (sz > 1024)
|
||||
return 0;
|
||||
return &e_data[idx++ << 10];
|
||||
}
|
||||
|
||||
void
|
||||
deallocate(void* p)
|
||||
{
|
||||
}
|
||||
|
||||
extern int32_t
|
||||
b58enco(char* b58, int32_t b58sz, void const* data, int32_t binsz)
|
||||
{
|
||||
uint8_t const* bin = data;
|
||||
int32_t carry;
|
||||
int32_t i, j, high, zcount = 0;
|
||||
int32_t size;
|
||||
|
||||
while (zcount < binsz && !bin[zcount])
|
||||
++zcount;
|
||||
|
||||
size = (binsz - zcount) * 138 / 100 + 1;
|
||||
uint8_t* buf = allocate(size);
|
||||
if (!buf)
|
||||
return 0;
|
||||
// memset(buf, 0, size);
|
||||
for (i = 0; i < size; ++i)
|
||||
buf[i] = 0;
|
||||
|
||||
for (i = zcount, high = size - 1; i < binsz; ++i, high = j)
|
||||
{
|
||||
for (carry = bin[i], j = size - 1; (j > high) || carry; --j)
|
||||
{
|
||||
carry += 256 * buf[j];
|
||||
buf[j] = carry % 58;
|
||||
carry /= 58;
|
||||
if (!j)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 0; j < size && !buf[j]; ++j)
|
||||
;
|
||||
|
||||
if (b58sz <= zcount + size - j)
|
||||
return 0;
|
||||
|
||||
if (zcount)
|
||||
{
|
||||
// memset(b58, '1', zcount);
|
||||
for (i = 0; i < zcount; ++i)
|
||||
b58[i] = '1';
|
||||
}
|
||||
|
||||
for (i = zcount; j < size; ++i, ++j)
|
||||
b58[i] = b58digits_ordered[buf[j]];
|
||||
b58[i] = '\0';
|
||||
|
||||
return i + 1;
|
||||
}
|
||||
23
src/test/app/wasm_fixtures/bad_align.c
Normal file
23
src/test/app/wasm_fixtures/bad_align.c
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t
|
||||
float_from_uint(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
int32_t
|
||||
get_tx_nested_field(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
|
||||
uint8_t e_data[32 * 1024];
|
||||
|
||||
int32_t
|
||||
test()
|
||||
{
|
||||
e_data[1] = 0xFF;
|
||||
e_data[2] = 0xFF;
|
||||
e_data[3] = 0xFF;
|
||||
e_data[4] = 0xFF;
|
||||
e_data[5] = 0xFF;
|
||||
e_data[6] = 0xFF;
|
||||
e_data[7] = 0xFF;
|
||||
e_data[8] = 0xFF;
|
||||
float_from_uint(&e_data[1], 8, &e_data[35], 12, 0);
|
||||
return *((int32_t*)(&e_data[36]));
|
||||
}
|
||||
27
src/test/app/wasm_fixtures/bad_alloc.c
Normal file
27
src/test/app/wasm_fixtures/bad_alloc.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <stdint.h>
|
||||
|
||||
char buf[1024];
|
||||
|
||||
void*
|
||||
allocate(int sz)
|
||||
{
|
||||
if (!sz)
|
||||
return 0;
|
||||
|
||||
if (sz == 1)
|
||||
return ((void*)(8 * 1024 * 1024));
|
||||
if (sz == 2)
|
||||
return 0;
|
||||
if (sz == 3)
|
||||
return ((void*)(0xFFFFFFFF));
|
||||
|
||||
return &buf[sz];
|
||||
}
|
||||
|
||||
int32_t
|
||||
test(char* p, int32_t sz)
|
||||
{
|
||||
if (!sz)
|
||||
return 0;
|
||||
return p[0];
|
||||
}
|
||||
171
src/test/app/wasm_fixtures/codecov_tests/Cargo.lock
generated
Normal file
171
src/test/app/wasm_fixtures/codecov_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.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-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 = "codecov_tests"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"xrpl-wasm-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[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.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
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.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-address-macro"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"quote",
|
||||
"sha2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-stdlib"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e"
|
||||
dependencies = [
|
||||
"xrpl-address-macro",
|
||||
]
|
||||
18
src/test/app/wasm_fixtures/codecov_tests/Cargo.toml
Normal file
18
src/test/app/wasm_fixtures/codecov_tests/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
edition = "2024"
|
||||
name = "codecov_tests"
|
||||
version = "0.0.1"
|
||||
|
||||
# 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" }
|
||||
@@ -0,0 +1,47 @@
|
||||
//TODO add docs after discussing the interface
|
||||
//Note that Craft currently does not honor the rounding modes
|
||||
#[allow(unused)]
|
||||
pub const FLOAT_ROUNDING_MODES_TO_NEAREST: i32 = 0;
|
||||
#[allow(unused)]
|
||||
pub const FLOAT_ROUNDING_MODES_TOWARDS_ZERO: i32 = 1;
|
||||
#[allow(unused)]
|
||||
pub const FLOAT_ROUNDING_MODES_DOWNWARD: i32 = 2;
|
||||
#[allow(unused)]
|
||||
pub const FLOAT_ROUNDING_MODES_UPWARD: i32 = 3;
|
||||
|
||||
// pub enum RippledRoundingModes{
|
||||
// ToNearest = 0,
|
||||
// TowardsZero = 1,
|
||||
// DOWNWARD = 2,
|
||||
// UPWARD = 3
|
||||
// }
|
||||
|
||||
#[allow(unused)]
|
||||
#[link(wasm_import_module = "host_lib")]
|
||||
unsafe extern "C" {
|
||||
pub fn get_parent_ledger_hash(out_buff_ptr: i32, out_buff_len: i32) -> i32;
|
||||
|
||||
pub fn cache_ledger_obj(keylet_ptr: i32, keylet_len: i32, cache_num: i32) -> i32;
|
||||
|
||||
pub fn get_tx_nested_array_len(locator_ptr: i32, locator_len: i32) -> i32;
|
||||
|
||||
pub fn account_keylet(
|
||||
account_ptr: i32,
|
||||
account_len: i32,
|
||||
out_buff_ptr: *mut u8,
|
||||
out_buff_len: usize,
|
||||
) -> i32;
|
||||
|
||||
pub fn line_keylet(
|
||||
account1_ptr: *const u8,
|
||||
account1_len: usize,
|
||||
account2_ptr: *const u8,
|
||||
account2_len: usize,
|
||||
currency_ptr: i32,
|
||||
currency_len: i32,
|
||||
out_buff_ptr: *mut u8,
|
||||
out_buff_len: usize,
|
||||
) -> i32;
|
||||
|
||||
pub fn trace_num(msg_read_ptr: i32, msg_read_len: i32, number: i64) -> i32;
|
||||
}
|
||||
1521
src/test/app/wasm_fixtures/codecov_tests/src/lib.rs
Normal file
1521
src/test/app/wasm_fixtures/codecov_tests/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
134
src/test/app/wasm_fixtures/copyFixtures.py
Normal file
134
src/test/app/wasm_fixtures/copyFixtures.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
OPT = "-Oz"
|
||||
|
||||
|
||||
def update_fixture(project_name, wasm):
|
||||
fixture_name = (
|
||||
re.sub(r"_([a-z])", lambda m: m.group(1).upper(), project_name) + "WasmHex"
|
||||
)
|
||||
print(f"Updating fixture: {fixture_name}")
|
||||
|
||||
cpp_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "fixtures.cpp"))
|
||||
h_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "fixtures.h"))
|
||||
with open(cpp_path, "r", encoding="utf8") as f:
|
||||
cpp_content = f.read()
|
||||
|
||||
pattern = rf'extern std::string const {fixture_name} =[ \n]+"[^;]*;'
|
||||
if re.search(pattern, cpp_content, flags=re.MULTILINE):
|
||||
updated_cpp_content = re.sub(
|
||||
pattern,
|
||||
f'extern std::string const {fixture_name} = "{wasm}";',
|
||||
cpp_content,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
else:
|
||||
with open(h_path, "r", encoding="utf8") as f:
|
||||
h_content = f.read()
|
||||
updated_h_content = (
|
||||
h_content.rstrip() + f"\n\n extern std::string const {fixture_name};\n"
|
||||
)
|
||||
with open(h_path, "w", encoding="utf8") as f:
|
||||
f.write(updated_h_content)
|
||||
updated_cpp_content = (
|
||||
cpp_content.rstrip()
|
||||
+ f'\n\nextern std::string const {fixture_name} = "{wasm}";\n'
|
||||
)
|
||||
|
||||
with open(cpp_path, "w", encoding="utf8") as f:
|
||||
f.write(updated_cpp_content)
|
||||
|
||||
|
||||
def process_rust(project_name):
|
||||
project_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), project_name)
|
||||
)
|
||||
wasm_location = f"target/wasm32v1-none/release/{project_name}.wasm"
|
||||
build_cmd = (
|
||||
f"(cd {project_path} "
|
||||
f"&& cargo build --target wasm32v1-none --release "
|
||||
f"&& wasm-opt {wasm_location} {OPT} -o {wasm_location}"
|
||||
")"
|
||||
)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
build_cmd, shell=True, check=True, capture_output=True, text=True
|
||||
)
|
||||
print(f"stdout: {result.stdout}")
|
||||
if result.stderr:
|
||||
print(f"stderr: {result.stderr}")
|
||||
print(f"WASM file for {project_name} has been built and optimized.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"exec error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
src_path = os.path.abspath(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
f"{project_name}/target/wasm32v1-none/release/{project_name}.wasm",
|
||||
)
|
||||
)
|
||||
with open(src_path, "rb") as f:
|
||||
data = f.read()
|
||||
wasm = data.hex()
|
||||
update_fixture(project_name, wasm)
|
||||
|
||||
|
||||
def process_c(project_name):
|
||||
project_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), f"{project_name}.c")
|
||||
)
|
||||
wasm_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), f"{project_name}.wasm")
|
||||
)
|
||||
build_cmd = (
|
||||
f"$CC --sysroot=$SYSROOT "
|
||||
f"-O3 -ffast-math --target=wasm32 -fno-exceptions -fno-threadsafe-statics -fvisibility=default -Wl,--export-all -Wl,--no-entry -Wl,--allow-undefined -DNDEBUG --no-standard-libraries -fno-builtin-memset "
|
||||
f"-o {wasm_path} {project_path}"
|
||||
f"&& wasm-opt {wasm_path} {OPT} -o {wasm_path}"
|
||||
)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
build_cmd, shell=True, check=True, capture_output=True, text=True
|
||||
)
|
||||
print(f"stdout: {result.stdout}")
|
||||
if result.stderr:
|
||||
print(f"stderr: {result.stderr}")
|
||||
print(
|
||||
f"WASM file for {project_name} has been built with WASI support using clang."
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"exec error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
with open(wasm_path, "rb") as f:
|
||||
data = f.read()
|
||||
wasm = data.hex()
|
||||
update_fixture(project_name, wasm)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 2:
|
||||
print("Usage: python copyFixtures.py [<project_name>]")
|
||||
sys.exit(1)
|
||||
if len(sys.argv) == 2:
|
||||
if os.path.isdir(os.path.join(os.path.dirname(__file__), sys.argv[1])):
|
||||
process_rust(sys.argv[1])
|
||||
else:
|
||||
process_c(sys.argv[1])
|
||||
print("Fixture has been processed.")
|
||||
else:
|
||||
dirs = [
|
||||
d
|
||||
for d in os.listdir(os.path.dirname(__file__))
|
||||
if os.path.isdir(os.path.join(os.path.dirname(__file__), d))
|
||||
]
|
||||
c_files = [f for f in os.listdir(os.path.dirname(__file__)) if f.endswith(".c")]
|
||||
for d in dirs:
|
||||
process_rust(d)
|
||||
for c in c_files:
|
||||
process_c(c[:-2])
|
||||
print("All fixtures have been processed.")
|
||||
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 "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)))
|
||||
12
src/test/app/wasm_fixtures/fib.c
Normal file
12
src/test/app/wasm_fixtures/fib.c
Normal file
@@ -0,0 +1,12 @@
|
||||
// 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);
|
||||
}
|
||||
1393
src/test/app/wasm_fixtures/fixtures.cpp
Normal file
1393
src/test/app/wasm_fixtures/fixtures.cpp
Normal file
File diff suppressed because it is too large
Load Diff
87
src/test/app/wasm_fixtures/fixtures.h
Normal file
87
src/test/app/wasm_fixtures/fixtures.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
// TODO: consider moving these to separate files (and figure out the build)
|
||||
|
||||
#include <string>
|
||||
|
||||
extern std::string const ledgerSqnWasmHex;
|
||||
|
||||
extern std::string const allHostFunctionsWasmHex;
|
||||
|
||||
extern std::string const deepRecursionHex;
|
||||
|
||||
extern std::string const fibWasmHex;
|
||||
|
||||
extern std::string const b58WasmHex;
|
||||
|
||||
extern std::string const sha512PureWasmHex;
|
||||
|
||||
extern std::string const hfPerfTest;
|
||||
|
||||
extern std::string const allKeyletsWasmHex;
|
||||
|
||||
extern std::string const codecovTestsWasmHex;
|
||||
|
||||
extern std::string const floatTestsWasmHex;
|
||||
|
||||
extern std::string const float0Hex;
|
||||
|
||||
extern std::string const disabledFloatHex;
|
||||
|
||||
extern std::string const memoryPointerAtLimitHex;
|
||||
extern std::string const memoryPointerOverLimitHex;
|
||||
extern std::string const memoryOffsetOverLimitHex;
|
||||
extern std::string const memoryEndOfWordOverLimitHex;
|
||||
extern std::string const memoryGrow0To1PageHex;
|
||||
extern std::string const memoryGrow1To0PageHex;
|
||||
extern std::string const memoryLastByteOf8MBHex;
|
||||
extern std::string const memoryGrow1MoreThan8MBHex;
|
||||
extern std::string const memoryGrow0MoreThan8MBHex;
|
||||
extern std::string const memoryInit1MoreThan8MBHex;
|
||||
extern std::string const memoryNegativeAddressHex;
|
||||
|
||||
extern std::string const table64ElementsHex;
|
||||
extern std::string const table65ElementsHex;
|
||||
extern std::string const table2TablesHex;
|
||||
extern std::string const table0ElementsHex;
|
||||
extern std::string const tableUintMaxHex;
|
||||
|
||||
extern std::string const proposalMutableGlobalHex;
|
||||
extern std::string const proposalGcStructNewHex;
|
||||
extern std::string const proposalMultiValueHex;
|
||||
extern std::string const proposalSignExtHex;
|
||||
extern std::string const proposalFloatToIntHex;
|
||||
extern std::string const proposalBulkMemoryHex;
|
||||
extern std::string const proposalRefTypesHex;
|
||||
extern std::string const proposalTailCallHex;
|
||||
extern std::string const proposalExtendedConstHex;
|
||||
extern std::string const proposalMultiMemoryHex;
|
||||
extern std::string const proposalCustomPageSizesHex;
|
||||
extern std::string const proposalMemory64Hex;
|
||||
extern std::string const proposalWideArithmeticHex;
|
||||
|
||||
extern std::string const trapDivideBy0Hex;
|
||||
extern std::string const trapIntOverflowHex;
|
||||
extern std::string const trapUnreachableHex;
|
||||
extern std::string const trapNullCallHex;
|
||||
extern std::string const trapFuncSigMismatchHex;
|
||||
|
||||
extern std::string const wasiGetTimeHex;
|
||||
extern std::string const wasiPrintHex;
|
||||
|
||||
extern std::string const badMagicNumberHex;
|
||||
extern std::string const badVersionNumberHex;
|
||||
extern std::string const lyingHeaderHex;
|
||||
extern std::string const neverEndingNumberHex;
|
||||
extern std::string const vectorLieHex;
|
||||
extern std::string const sectionOrderingHex;
|
||||
extern std::string const ghostPayloadHex;
|
||||
extern std::string const junkAfterSectionHex;
|
||||
extern std::string const invalidSectionIdHex;
|
||||
extern std::string const localVariableBombHex;
|
||||
|
||||
extern std::string const infiniteLoopWasmHex;
|
||||
extern std::string const startLoopHex;
|
||||
|
||||
extern std::string const badAllocHex;
|
||||
extern std::string const badAlignHex;
|
||||
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.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-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 = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float_tests"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"xrpl-wasm-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[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.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
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.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-address-macro"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"quote",
|
||||
"sha2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-stdlib"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e"
|
||||
dependencies = [
|
||||
"xrpl-address-macro",
|
||||
]
|
||||
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" }
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
461
src/test/app/wasm_fixtures/float_tests/src/lib.rs
Normal file
461
src/test/app/wasm_fixtures/float_tests/src/lib.rs
Normal file
@@ -0,0 +1,461 @@
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_variables)]
|
||||
#![cfg_attr(target_arch = "wasm32", no_std)]
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
extern crate std;
|
||||
|
||||
use xrpl_std::core::locator::Locator;
|
||||
use xrpl_std::core::types::opaque_float::{FLOAT_NEGATIVE_ONE, FLOAT_ONE};
|
||||
use xrpl_std::decode_hex_32;
|
||||
use xrpl_std::host::trace::DataRepr::AsHex;
|
||||
use xrpl_std::host::trace::{trace, trace_data, trace_float, trace_num, DataRepr};
|
||||
use xrpl_std::host::{
|
||||
cache_ledger_obj, float_add, float_compare, float_divide, float_from_int, float_from_uint,
|
||||
float_log, float_multiply, float_pow, float_root, float_set, float_subtract,
|
||||
get_ledger_obj_array_len, get_ledger_obj_field, get_ledger_obj_nested_field,
|
||||
trace_opaque_float, FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
};
|
||||
use xrpl_std::sfield;
|
||||
use xrpl_std::sfield::{
|
||||
Account, AccountTxnID, Balance, Domain, EmailHash, Flags, LedgerEntryType, MessageKey,
|
||||
OwnerCount, PreviousTxnID, PreviousTxnLgrSeq, RegularKey, Sequence, TicketCount, TransferRate,
|
||||
};
|
||||
|
||||
fn test_float_from_wasm() {
|
||||
let _ = trace("\n$$$ test_float_from_wasm $$$");
|
||||
|
||||
let mut f: [u8; 8] = [0u8; 8];
|
||||
if 8 == unsafe { float_from_int(12300, f.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) } {
|
||||
let _ = trace_float(" float from i64 12300:", &f);
|
||||
let _ = trace_data(" float from i64 12300 as HEX:", &f, AsHex);
|
||||
} else {
|
||||
let _ = trace(" float from i64 12300: failed");
|
||||
}
|
||||
|
||||
let u64_value: u64 = 12300;
|
||||
if 8 == unsafe {
|
||||
float_from_uint(
|
||||
&u64_value as *const u64 as *const u8,
|
||||
8,
|
||||
f.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
} {
|
||||
let _ = trace_float(" float from u64 12300:", &f);
|
||||
} else {
|
||||
let _ = trace(" float from u64 12300: failed");
|
||||
}
|
||||
|
||||
if 8 == unsafe { float_set(2, 123, f.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) } {
|
||||
let _ = trace_float(" float from exp 2, mantissa 123:", &f);
|
||||
} else {
|
||||
let _ = trace(" float from exp 2, mantissa 3: failed");
|
||||
}
|
||||
|
||||
let _ = trace_float(" float from const 1:", &FLOAT_ONE);
|
||||
let _ = trace_float(" float from const -1:", &FLOAT_NEGATIVE_ONE);
|
||||
}
|
||||
|
||||
fn test_float_compare() {
|
||||
let _ = trace("\n$$$ test_float_compare $$$");
|
||||
|
||||
let mut f1: [u8; 8] = [0u8; 8];
|
||||
if 8 != unsafe { float_from_int(1, f1.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) } {
|
||||
let _ = trace(" float from 1: failed");
|
||||
} else {
|
||||
let _ = trace_float(" float from 1:", &f1);
|
||||
}
|
||||
|
||||
if 0 == unsafe { float_compare(f1.as_ptr(), 8, FLOAT_ONE.as_ptr(), 8) } {
|
||||
let _ = trace(" float from 1 == FLOAT_ONE");
|
||||
} else {
|
||||
let _ = trace(" float from 1 != FLOAT_ONE");
|
||||
}
|
||||
|
||||
if 1 == unsafe { float_compare(f1.as_ptr(), 8, FLOAT_NEGATIVE_ONE.as_ptr(), 8) } {
|
||||
let _ = trace(" float from 1 > FLOAT_NEGATIVE_ONE");
|
||||
} else {
|
||||
let _ = trace(" float from 1 !> FLOAT_NEGATIVE_ONE");
|
||||
}
|
||||
|
||||
if 2 == unsafe { float_compare(FLOAT_NEGATIVE_ONE.as_ptr(), 8, f1.as_ptr(), 8) } {
|
||||
let _ = trace(" FLOAT_NEGATIVE_ONE < float from 1");
|
||||
} else {
|
||||
let _ = trace(" FLOAT_NEGATIVE_ONE !< float from 1");
|
||||
}
|
||||
}
|
||||
|
||||
fn test_float_add_subtract() {
|
||||
let _ = trace("\n$$$ test_float_add_subtract $$$");
|
||||
|
||||
let mut f_compute: [u8; 8] = FLOAT_ONE;
|
||||
for i in 0..9 {
|
||||
unsafe {
|
||||
float_add(
|
||||
f_compute.as_ptr(),
|
||||
8,
|
||||
FLOAT_ONE.as_ptr(),
|
||||
8,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
// let _ = trace_float(" float:", &f_compute);
|
||||
}
|
||||
let mut f10: [u8; 8] = [0u8; 8];
|
||||
if 8 != unsafe { float_from_int(10, f10.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) } {
|
||||
// let _ = trace(" float from 10: failed");
|
||||
}
|
||||
if 0 == unsafe { float_compare(f10.as_ptr(), 8, f_compute.as_ptr(), 8) } {
|
||||
let _ = trace(" repeated add: good");
|
||||
} else {
|
||||
let _ = trace(" repeated add: bad");
|
||||
}
|
||||
|
||||
for i in 0..11 {
|
||||
unsafe {
|
||||
float_subtract(
|
||||
f_compute.as_ptr(),
|
||||
8,
|
||||
FLOAT_ONE.as_ptr(),
|
||||
8,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
}
|
||||
if 0 == unsafe { float_compare(f_compute.as_ptr(), 8, FLOAT_NEGATIVE_ONE.as_ptr(), 8) } {
|
||||
let _ = trace(" repeated subtract: good");
|
||||
} else {
|
||||
let _ = trace(" repeated subtract: bad");
|
||||
}
|
||||
}
|
||||
|
||||
fn test_float_multiply_divide() {
|
||||
let _ = trace("\n$$$ test_float_multiply_divide $$$");
|
||||
|
||||
let mut f10: [u8; 8] = [0u8; 8];
|
||||
unsafe { float_from_int(10, f10.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) };
|
||||
let mut f_compute: [u8; 8] = FLOAT_ONE;
|
||||
for i in 0..6 {
|
||||
unsafe {
|
||||
float_multiply(
|
||||
f_compute.as_ptr(),
|
||||
8,
|
||||
f10.as_ptr(),
|
||||
8,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
// let _ = trace_float(" float:", &f_compute);
|
||||
}
|
||||
let mut f1000000: [u8; 8] = [0u8; 8];
|
||||
unsafe {
|
||||
float_from_int(
|
||||
1000000,
|
||||
f1000000.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
|
||||
if 0 == unsafe { float_compare(f1000000.as_ptr(), 8, f_compute.as_ptr(), 8) } {
|
||||
let _ = trace(" repeated multiply: good");
|
||||
} else {
|
||||
let _ = trace(" repeated multiply: bad");
|
||||
}
|
||||
|
||||
for i in 0..7 {
|
||||
unsafe {
|
||||
float_divide(
|
||||
f_compute.as_ptr(),
|
||||
8,
|
||||
f10.as_ptr(),
|
||||
8,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
}
|
||||
let mut f01: [u8; 8] = [0u8; 8];
|
||||
unsafe { float_set(-1, 1, f01.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) };
|
||||
|
||||
if 0 == unsafe { float_compare(f_compute.as_ptr(), 8, f01.as_ptr(), 8) } {
|
||||
let _ = trace(" repeated divide: good");
|
||||
} else {
|
||||
let _ = trace(" repeated divide: bad");
|
||||
}
|
||||
}
|
||||
|
||||
fn test_float_pow() {
|
||||
let _ = trace("\n$$$ test_float_pow $$$");
|
||||
|
||||
let mut f_compute: [u8; 8] = [0u8; 8];
|
||||
unsafe {
|
||||
float_pow(
|
||||
FLOAT_ONE.as_ptr(),
|
||||
8,
|
||||
3,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_float(" float cube of 1:", &f_compute);
|
||||
|
||||
unsafe {
|
||||
float_pow(
|
||||
FLOAT_NEGATIVE_ONE.as_ptr(),
|
||||
8,
|
||||
6,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_float(" float 6th power of -1:", &f_compute);
|
||||
|
||||
let mut f9: [u8; 8] = [0u8; 8];
|
||||
unsafe { float_from_int(9, f9.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) };
|
||||
unsafe {
|
||||
float_pow(
|
||||
f9.as_ptr(),
|
||||
8,
|
||||
2,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_float(" float square of 9:", &f_compute);
|
||||
|
||||
unsafe {
|
||||
float_pow(
|
||||
f9.as_ptr(),
|
||||
8,
|
||||
0,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_float(" float 0th power of 9:", &f_compute);
|
||||
|
||||
let mut f0: [u8; 8] = [0u8; 8];
|
||||
unsafe { float_from_int(0, f0.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) };
|
||||
unsafe {
|
||||
float_pow(
|
||||
f0.as_ptr(),
|
||||
8,
|
||||
2,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_float(" float square of 0:", &f_compute);
|
||||
|
||||
let r = unsafe {
|
||||
float_pow(
|
||||
f0.as_ptr(),
|
||||
8,
|
||||
0,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_num(
|
||||
" float 0th power of 0 (expecting INVALID_PARAMS error):",
|
||||
r as i64,
|
||||
);
|
||||
}
|
||||
|
||||
fn test_float_root() {
|
||||
let _ = trace("\n$$$ test_float_root $$$");
|
||||
|
||||
let mut f9: [u8; 8] = [0u8; 8];
|
||||
unsafe { float_from_int(9, f9.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) };
|
||||
let mut f_compute: [u8; 8] = [0u8; 8];
|
||||
unsafe {
|
||||
float_root(
|
||||
f9.as_ptr(),
|
||||
8,
|
||||
2,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_float(" float sqrt of 9:", &f_compute);
|
||||
unsafe {
|
||||
float_root(
|
||||
f9.as_ptr(),
|
||||
8,
|
||||
3,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_float(" float cbrt of 9:", &f_compute);
|
||||
|
||||
let mut f1000000: [u8; 8] = [0u8; 8];
|
||||
unsafe {
|
||||
float_from_int(
|
||||
1000000,
|
||||
f1000000.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
unsafe {
|
||||
float_root(
|
||||
f1000000.as_ptr(),
|
||||
8,
|
||||
3,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_float(" float cbrt of 1000000:", &f_compute);
|
||||
unsafe {
|
||||
float_root(
|
||||
f1000000.as_ptr(),
|
||||
8,
|
||||
6,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_float(" float 6th root of 1000000:", &f_compute);
|
||||
}
|
||||
|
||||
fn test_float_log() {
|
||||
let _ = trace("\n$$$ test_float_log $$$");
|
||||
|
||||
let mut f1000000: [u8; 8] = [0u8; 8];
|
||||
unsafe {
|
||||
float_from_int(
|
||||
1000000,
|
||||
f1000000.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let mut f_compute: [u8; 8] = [0u8; 8];
|
||||
unsafe {
|
||||
float_log(
|
||||
f1000000.as_ptr(),
|
||||
8,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_float(" log_10 of 1000000:", &f_compute);
|
||||
}
|
||||
|
||||
fn test_float_negate() {
|
||||
let _ = trace("\n$$$ test_float_negate $$$");
|
||||
|
||||
let mut f_compute: [u8; 8] = [0u8; 8];
|
||||
unsafe {
|
||||
float_multiply(
|
||||
FLOAT_ONE.as_ptr(),
|
||||
8,
|
||||
FLOAT_NEGATIVE_ONE.as_ptr(),
|
||||
8,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
// let _ = trace_float(" float:", &f_compute);
|
||||
if 0 == unsafe { float_compare(FLOAT_NEGATIVE_ONE.as_ptr(), 8, f_compute.as_ptr(), 8) } {
|
||||
let _ = trace(" negate const 1: good");
|
||||
} else {
|
||||
let _ = trace(" negate const 1: bad");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
float_multiply(
|
||||
FLOAT_NEGATIVE_ONE.as_ptr(),
|
||||
8,
|
||||
FLOAT_NEGATIVE_ONE.as_ptr(),
|
||||
8,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
// let _ = trace_float(" float:", &f_compute);
|
||||
if 0 == unsafe { float_compare(FLOAT_ONE.as_ptr(), 8, f_compute.as_ptr(), 8) } {
|
||||
let _ = trace(" negate const -1: good");
|
||||
} else {
|
||||
let _ = trace(" negate const -1: bad");
|
||||
}
|
||||
}
|
||||
|
||||
fn test_float_invert() {
|
||||
let _ = trace("\n$$$ test_float_invert $$$");
|
||||
|
||||
let mut f_compute: [u8; 8] = [0u8; 8];
|
||||
let mut f10: [u8; 8] = [0u8; 8];
|
||||
unsafe { float_from_int(10, f10.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) };
|
||||
unsafe {
|
||||
float_divide(
|
||||
FLOAT_ONE.as_ptr(),
|
||||
8,
|
||||
f10.as_ptr(),
|
||||
8,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_float(" invert a float from 10:", &f_compute);
|
||||
unsafe {
|
||||
float_divide(
|
||||
FLOAT_ONE.as_ptr(),
|
||||
8,
|
||||
f_compute.as_ptr(),
|
||||
8,
|
||||
f_compute.as_mut_ptr(),
|
||||
8,
|
||||
FLOAT_ROUNDING_MODES_TO_NEAREST,
|
||||
)
|
||||
};
|
||||
let _ = trace_float(" invert again:", &f_compute);
|
||||
|
||||
// if f10's value is 7, then invert twice won't match the original value
|
||||
if 0 == unsafe { float_compare(f10.as_ptr(), 8, f_compute.as_ptr(), 8) } {
|
||||
let _ = trace(" invert twice: good");
|
||||
} else {
|
||||
let _ = trace(" invert twice: bad");
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn finish() -> i32 {
|
||||
test_float_from_wasm();
|
||||
test_float_compare();
|
||||
test_float_add_subtract();
|
||||
test_float_multiply_divide();
|
||||
test_float_pow();
|
||||
test_float_root();
|
||||
test_float_log();
|
||||
test_float_negate();
|
||||
test_float_invert();
|
||||
|
||||
1
|
||||
}
|
||||
8
src/test/app/wasm_fixtures/infiniteLoop.c
Normal file
8
src/test/app/wasm_fixtures/infiniteLoop.c
Normal file
@@ -0,0 +1,8 @@
|
||||
int
|
||||
loop()
|
||||
{
|
||||
int volatile x = 0;
|
||||
while (1)
|
||||
x++;
|
||||
return x;
|
||||
}
|
||||
27
src/test/app/wasm_fixtures/ledgerSqn.c
Normal file
27
src/test/app/wasm_fixtures/ledgerSqn.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t
|
||||
get_ledger_sqn();
|
||||
// int32_t trace(uint8_t const*, int32_t, uint8_t const*, int32_t, int32_t);
|
||||
// int32_t trace_num(uint8_t const*, int32_t, int64_t);
|
||||
|
||||
// uint8_t buf[1024];
|
||||
|
||||
// char const test_res[] = "sqn: ";
|
||||
// char const test_name[] = "TEST get_ledger_sqn";
|
||||
|
||||
int
|
||||
finish()
|
||||
{
|
||||
// trace((uint8_t const *)test_name, sizeof(test_name) - 1, 0, 0, 0);
|
||||
|
||||
// memset(buf, 0, sizeof(buf));
|
||||
// for(int i = 0; i < sizeof(buf); ++i) buf[i] = 0;
|
||||
|
||||
int x = get_ledger_sqn();
|
||||
// if (x >= 0)
|
||||
// x = *((int32_t*)buf);
|
||||
// trace_num((uint8_t const *)test`_res, sizeof(test_res) - 1, x);
|
||||
|
||||
return x < 0 ? x : (x >= 5 ? x : 0);
|
||||
}
|
||||
133
src/test/app/wasm_fixtures/sha512Pure.c
Normal file
133
src/test/app/wasm_fixtures/sha512Pure.c
Normal file
@@ -0,0 +1,133 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static uint64_t const K512[] = {
|
||||
0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538,
|
||||
0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe,
|
||||
0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235,
|
||||
0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
|
||||
0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab,
|
||||
0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725,
|
||||
0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed,
|
||||
0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
|
||||
0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218,
|
||||
0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53,
|
||||
0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373,
|
||||
0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
|
||||
0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c,
|
||||
0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6,
|
||||
0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc,
|
||||
0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817};
|
||||
|
||||
#define ROTATE(x, y) (((x) >> (y)) | ((x) << (64 - (y))))
|
||||
#define Sigma0(x) (ROTATE((x), 28) ^ ROTATE((x), 34) ^ ROTATE((x), 39))
|
||||
#define Sigma1(x) (ROTATE((x), 14) ^ ROTATE((x), 18) ^ ROTATE((x), 41))
|
||||
#define sigma0(x) (ROTATE((x), 1) ^ ROTATE((x), 8) ^ ((x) >> 7))
|
||||
#define sigma1(x) (ROTATE((x), 19) ^ ROTATE((x), 61) ^ ((x) >> 6))
|
||||
|
||||
#define Ch(x, y, z) (((x) & (y)) ^ ((~(x)) & (z)))
|
||||
#define Maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
|
||||
|
||||
static inline uint64_t
|
||||
B2U64(uint8_t val, uint8_t sh)
|
||||
{
|
||||
return ((uint64_t)val) << sh;
|
||||
}
|
||||
|
||||
void*
|
||||
allocate(int sz)
|
||||
{
|
||||
return malloc(sz);
|
||||
}
|
||||
void
|
||||
deallocate(void* p)
|
||||
{
|
||||
free(p);
|
||||
}
|
||||
|
||||
uint8_t e_data[32 * 1024];
|
||||
|
||||
uint8_t*
|
||||
sha512_process(uint8_t const* data, int32_t length)
|
||||
{
|
||||
static uint64_t state[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
uint64_t a, b, c, d, e, f, g, h, s0, s1, T1, T2;
|
||||
uint64_t X[16];
|
||||
|
||||
uint64_t blocks = length / 128;
|
||||
while (blocks--)
|
||||
{
|
||||
a = state[0];
|
||||
b = state[1];
|
||||
c = state[2];
|
||||
d = state[3];
|
||||
e = state[4];
|
||||
f = state[5];
|
||||
g = state[6];
|
||||
h = state[7];
|
||||
|
||||
unsigned i;
|
||||
for (i = 0; i < 16; i++)
|
||||
{
|
||||
X[i] = B2U64(data[0], 56) | B2U64(data[1], 48) | B2U64(data[2], 40) | B2U64(data[3], 32) |
|
||||
B2U64(data[4], 24) | B2U64(data[5], 16) | B2U64(data[6], 8) | B2U64(data[7], 0);
|
||||
data += 8;
|
||||
|
||||
T1 = h;
|
||||
T1 += Sigma1(e);
|
||||
T1 += Ch(e, f, g);
|
||||
T1 += K512[i];
|
||||
T1 += X[i];
|
||||
|
||||
T2 = Sigma0(a);
|
||||
T2 += Maj(a, b, c);
|
||||
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + T1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = T1 + T2;
|
||||
}
|
||||
|
||||
for (i = 16; i < 80; i++)
|
||||
{
|
||||
s0 = X[(i + 1) & 0x0f];
|
||||
s0 = sigma0(s0);
|
||||
s1 = X[(i + 14) & 0x0f];
|
||||
s1 = sigma1(s1);
|
||||
|
||||
T1 = X[i & 0xf] += s0 + s1 + X[(i + 9) & 0xf];
|
||||
T1 += h + Sigma1(e) + Ch(e, f, g) + K512[i];
|
||||
T2 = Sigma0(a) + Maj(a, b, c);
|
||||
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + T1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = T1 + T2;
|
||||
}
|
||||
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
state[4] += e;
|
||||
state[5] += f;
|
||||
state[6] += g;
|
||||
state[7] += h;
|
||||
}
|
||||
|
||||
return (uint8_t*)(state);
|
||||
}
|
||||
|
||||
// int main ()
|
||||
//{
|
||||
// return 0;
|
||||
// }
|
||||
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 kilobyte.
|
||||
;; Standard Wasm implies (pagesize 65536).
|
||||
(memory 1 (pagesize 1024))
|
||||
|
||||
(func $finish (result i32)
|
||||
;; If this module instantiates, the runtime accepted the custom page size.
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
(export "finish" (func $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 $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 $finish
|
||||
)
|
||||
|
||||
;; Export the only function we have
|
||||
(export "finish" (func $finish))
|
||||
)
|
||||
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 $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 "finish" (func $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 "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 "finish" (func $try_grow_beyond_limit))
|
||||
)
|
||||
26
src/test/app/wasm_fixtures/wat/memory_grow_0_to_1.wat
Normal file
26
src/test/app/wasm_fixtures/wat/memory_grow_0_to_1.wat
Normal file
@@ -0,0 +1,26 @@
|
||||
(module
|
||||
;; 1. Define Memory: Start with 0 pages
|
||||
(memory 0)
|
||||
|
||||
;; Export memory to host
|
||||
(export "memory" (memory 0))
|
||||
|
||||
(func $grow_from_zero (result i32)
|
||||
;; We have 0 pages. We want to add 1 page.
|
||||
;; Push delta (1) onto stack.
|
||||
i32.const 1
|
||||
|
||||
;; Grow the memory.
|
||||
;; If successful: memory becomes 64KB, returns old size (0).
|
||||
;; If failed: memory stays 0, returns -1.
|
||||
memory.grow
|
||||
|
||||
;; Drop the return value of memory.grow
|
||||
drop
|
||||
|
||||
;; Return 1 (as requested)
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
(export "finish" (func $grow_from_zero))
|
||||
)
|
||||
@@ -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 1 page
|
||||
i32.const 1
|
||||
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 "finish" (func $try_grow_beyond_limit))
|
||||
)
|
||||
33
src/test/app/wasm_fixtures/wat/memory_grow_1_to_0.wat
Normal file
33
src/test/app/wasm_fixtures/wat/memory_grow_1_to_0.wat
Normal file
@@ -0,0 +1,33 @@
|
||||
(module
|
||||
;; 1. Define Memory: Start with 1 page (64KB)
|
||||
(memory 1)
|
||||
|
||||
;; Export memory to host
|
||||
(export "memory" (memory 0))
|
||||
|
||||
(func $grow_negative (result i32)
|
||||
;; The user pushed -1. In Wasm, this is interpreted as unsigned MAX_UINT32.
|
||||
;; This is requesting to add 4,294,967,295 pages (approx 256 TB).
|
||||
;; A secure runtime MUST fail this request (return -1) without crashing.
|
||||
i32.const -1
|
||||
|
||||
;; Grow the memory.
|
||||
;; Returns: old_size if success, -1 if failure.
|
||||
memory.grow
|
||||
|
||||
;; Check if result == -1 (Failure)
|
||||
i32.const -1
|
||||
i32.eq
|
||||
if
|
||||
;; If memory.grow returned -1, we return -1 to signal "Correctly failed".
|
||||
i32.const -1
|
||||
return
|
||||
end
|
||||
|
||||
;; If we are here, memory.grow somehow SUCCEEDED (Vulnerability).
|
||||
;; We return 1 to signal "Unexpected Success".
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
(export "finish" (func $grow_negative))
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
(module
|
||||
;; Define memory: 129 pages (> 8MB limit) min, 129 pages max
|
||||
(memory 129 129)
|
||||
|
||||
;; Export memory so host can verify size
|
||||
(export "memory" (memory 0))
|
||||
|
||||
;; access last byte of 8MB limit
|
||||
(func $access_last_byte (result i32)
|
||||
;; Math: 128 pages * 64,536 bytes/page = 8,388,608 bytes
|
||||
;; Valid indices: 0 to 8,388,607
|
||||
|
||||
;; Push the address of the LAST valid byte
|
||||
i32.const 8388607
|
||||
|
||||
;; Load byte from that address
|
||||
i32.load8_u
|
||||
|
||||
;; Drop the value (we don't care what it is, just that we could read it)
|
||||
drop
|
||||
|
||||
;; Return 1 to indicate success
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
(export "finish" (func $access_last_byte))
|
||||
)
|
||||
26
src/test/app/wasm_fixtures/wat/memory_last_byte_of_8MB.wat
Normal file
26
src/test/app/wasm_fixtures/wat/memory_last_byte_of_8MB.wat
Normal file
@@ -0,0 +1,26 @@
|
||||
(module
|
||||
;; Define memory: 128 pages (8MB) min, 128 pages max
|
||||
(memory 128 128)
|
||||
|
||||
;; Export memory so host can verify size
|
||||
(export "memory" (memory 0))
|
||||
|
||||
(func $access_last_byte (result i32)
|
||||
;; Math: 128 pages * 64,536 bytes/page = 8,388,608 bytes
|
||||
;; Valid indices: 0 to 8,388,607
|
||||
|
||||
;; Push the address of the LAST valid byte
|
||||
i32.const 8388607
|
||||
|
||||
;; Load byte from that address
|
||||
i32.load8_u
|
||||
|
||||
;; Drop the value (we don't care what it is, just that we could read it)
|
||||
drop
|
||||
|
||||
;; Return 1 to indicate success
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
(export "finish" (func $access_last_byte))
|
||||
)
|
||||
23
src/test/app/wasm_fixtures/wat/memory_negative_address.wat
Normal file
23
src/test/app/wasm_fixtures/wat/memory_negative_address.wat
Normal file
@@ -0,0 +1,23 @@
|
||||
(module
|
||||
;; Define memory: 128 pages (8MB) min, 128 pages max
|
||||
(memory 128 128)
|
||||
|
||||
;; Export memory so host can verify size
|
||||
(export "memory" (memory 0))
|
||||
|
||||
(func $access_last_byte (result i32)
|
||||
;; Push a negative address
|
||||
i32.const -1
|
||||
|
||||
;; Load byte from that address
|
||||
i32.load8_u
|
||||
|
||||
;; Drop the value
|
||||
drop
|
||||
|
||||
;; Return 1 to indicate success
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
(export "finish" (func $access_last_byte))
|
||||
)
|
||||
27
src/test/app/wasm_fixtures/wat/memory_offset_over_limit.wat
Normal file
27
src/test/app/wasm_fixtures/wat/memory_offset_over_limit.wat
Normal file
@@ -0,0 +1,27 @@
|
||||
(module
|
||||
;; 1. Define Memory: 1 Page = 64KB
|
||||
(memory 1)
|
||||
|
||||
(export "memory" (memory 0))
|
||||
|
||||
(func $test_offset_overflow (result i32)
|
||||
;; 1. Push the base address onto the stack.
|
||||
;; We use '0', which is the safest, most valid address possible.
|
||||
i32.const 0
|
||||
|
||||
;; 2. Attempt to load using a static offset.
|
||||
;; syntax: i32.load offset=N align=N
|
||||
;; We set the offset to 65536 (the size of the memory).
|
||||
;; The effective address becomes 0 + 65536 = 65536.
|
||||
i32.load offset=65536
|
||||
|
||||
;; 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 "finish" (func $test_offset_overflow))
|
||||
)
|
||||
22
src/test/app/wasm_fixtures/wat/memory_pointer_at_limit.wat
Normal file
22
src/test/app/wasm_fixtures/wat/memory_pointer_at_limit.wat
Normal file
@@ -0,0 +1,22 @@
|
||||
(module
|
||||
;; Define 1 page of memory (64KB = 65,536 bytes)
|
||||
(memory 1)
|
||||
|
||||
(func $read_edge (result i32)
|
||||
;; Push the index of the LAST valid byte
|
||||
i32.const 65535
|
||||
|
||||
;; Load 1 byte (unsigned)
|
||||
i32.load8_u
|
||||
|
||||
;; 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 as "finish" as requested
|
||||
(export "finish" (func $read_edge))
|
||||
)
|
||||
23
src/test/app/wasm_fixtures/wat/memory_pointer_over_limit.wat
Normal file
23
src/test/app/wasm_fixtures/wat/memory_pointer_over_limit.wat
Normal file
@@ -0,0 +1,23 @@
|
||||
(module
|
||||
;; Define 1 page of memory (64KB = 65,536 bytes)
|
||||
(memory 1)
|
||||
|
||||
(func $read_overflow (result i32)
|
||||
;; Push the index of the FIRST invalid byte
|
||||
;; Memory is 0..65535, so 65536 is out of bounds.
|
||||
i32.const 65536
|
||||
|
||||
;; Load 1 byte (unsigned)
|
||||
i32.load8_u
|
||||
|
||||
;; 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 as "finish" as requested
|
||||
(export "finish" (func $read_overflow))
|
||||
)
|
||||
16
src/test/app/wasm_fixtures/wat/multi_memory.wat
Normal file
16
src/test/app/wasm_fixtures/wat/multi_memory.wat
Normal file
@@ -0,0 +1,16 @@
|
||||
(module
|
||||
;; Memory 0: Index 0 (Empty)
|
||||
(memory 0)
|
||||
|
||||
;; Memory 1: Index 1 (Size 1 page)
|
||||
;; If multi-memory is disabled, this line causes a validation error (max 1 memory).
|
||||
(memory 1)
|
||||
|
||||
(func $finish (result i32)
|
||||
;; Query size of Memory Index 1.
|
||||
;; Should return 1 (success).
|
||||
memory.size 1
|
||||
)
|
||||
|
||||
(export "finish" (func $finish))
|
||||
)
|
||||
25
src/test/app/wasm_fixtures/wat/proposal_bulk_memory.wat
Normal file
25
src/test/app/wasm_fixtures/wat/proposal_bulk_memory.wat
Normal file
@@ -0,0 +1,25 @@
|
||||
(module
|
||||
;; Define 1 page of memory
|
||||
(memory 1)
|
||||
(export "memory" (memory 0))
|
||||
|
||||
(func $test_bulk_ops (result i32)
|
||||
;; Setup: Write value 42 at index 0 so we have something to copy
|
||||
(i32.store8 (i32.const 0) (i32.const 42))
|
||||
|
||||
;; Test memory.copy (Opcode 0xFC 0x0A)
|
||||
;; Copy 1 byte from offset 0 to offset 100
|
||||
(memory.copy
|
||||
(i32.const 100) ;; Destination Offset
|
||||
(i32.const 0) ;; Source Offset
|
||||
(i32.const 1) ;; Size (bytes)
|
||||
)
|
||||
|
||||
;; Verify: Read byte at offset 100. Should be 42.
|
||||
(i32.load8_u (i32.const 100))
|
||||
(i32.const 42)
|
||||
i32.eq
|
||||
)
|
||||
|
||||
(export "finish" (func $test_bulk_ops))
|
||||
)
|
||||
15
src/test/app/wasm_fixtures/wat/proposal_extended_const.wat
Normal file
15
src/test/app/wasm_fixtures/wat/proposal_extended_const.wat
Normal file
@@ -0,0 +1,15 @@
|
||||
(module
|
||||
;; 1. Define a global using an EXTENDED constant expression.
|
||||
;; MVP only allows (i32.const X).
|
||||
;; This proposal allows (i32.add (i32.const X) (i32.const Y)).
|
||||
(global $g i32 (i32.add (i32.const 10) (i32.const 32)))
|
||||
|
||||
(func $finish (result i32)
|
||||
;; 2. verify the global equals 42
|
||||
global.get $g
|
||||
i32.const 42
|
||||
i32.eq
|
||||
)
|
||||
|
||||
(export "finish" (func $finish))
|
||||
)
|
||||
18
src/test/app/wasm_fixtures/wat/proposal_float_to_int.wat
Normal file
18
src/test/app/wasm_fixtures/wat/proposal_float_to_int.wat
Normal file
@@ -0,0 +1,18 @@
|
||||
(module
|
||||
(func $test_saturation (result i32)
|
||||
;; 1. Push a float that is too big for a 32-bit integer
|
||||
;; 1e10 (10 billion) > 2.14 billion (Max i32)
|
||||
f32.const 1.0e10
|
||||
|
||||
;; 2. Attempt saturating conversion (Opcode 0xFC 0x00)
|
||||
;; If supported: Clamps to MAX_I32.
|
||||
;; If disabled: Validation error (unknown instruction).
|
||||
i32.trunc_sat_f32_s
|
||||
|
||||
;; 3. Check if result is MAX_I32 (2147483647)
|
||||
i32.const 2147483647
|
||||
i32.eq
|
||||
)
|
||||
|
||||
(export "finish" (func $test_saturation))
|
||||
)
|
||||
12
src/test/app/wasm_fixtures/wat/proposal_gc_struct_new.wat
Normal file
12
src/test/app/wasm_fixtures/wat/proposal_gc_struct_new.wat
Normal file
@@ -0,0 +1,12 @@
|
||||
;; generated by wasm-tools print gc_test.wasm that has the following hex
|
||||
;; 0061736d01000000010b026000017f5f027f017f0103020100070a010666696e69736800000a0a010800fb01011a41010b
|
||||
(module
|
||||
(type (;0;) (func (result i32)))
|
||||
(type (;1;) (struct (field (mut i32)) (field (mut i32))))
|
||||
(export "finish" (func 0))
|
||||
(func (;0;) (type 0) (result i32)
|
||||
struct.new_default 1
|
||||
drop
|
||||
i32.const 1
|
||||
)
|
||||
)
|
||||
22
src/test/app/wasm_fixtures/wat/proposal_multi_value.wat
Normal file
22
src/test/app/wasm_fixtures/wat/proposal_multi_value.wat
Normal file
@@ -0,0 +1,22 @@
|
||||
(module
|
||||
;; 1. Function returning TWO values (Multi-Value feature)
|
||||
(func $get_numbers (result i32 i32)
|
||||
i32.const 10
|
||||
i32.const 20
|
||||
)
|
||||
|
||||
(func $finish (result i32)
|
||||
;; Call pushes [10, 20] onto the stack
|
||||
call $get_numbers
|
||||
|
||||
;; 2. Block taking TWO parameters (Multi-Value feature)
|
||||
;; It consumes the [10, 20] from the stack.
|
||||
block (param i32 i32) (result i32)
|
||||
i32.add ;; 10 + 20 = 30
|
||||
i32.const 30 ;; Expected result
|
||||
i32.eq ;; Compare: returns 1 if equal
|
||||
end
|
||||
)
|
||||
|
||||
(export "finish" (func $finish))
|
||||
)
|
||||
25
src/test/app/wasm_fixtures/wat/proposal_mutable_global.wat
Normal file
25
src/test/app/wasm_fixtures/wat/proposal_mutable_global.wat
Normal file
@@ -0,0 +1,25 @@
|
||||
(module
|
||||
;; Define a mutable global initialized to 0
|
||||
(global $counter (mut i32) (i32.const 0))
|
||||
|
||||
;; EXPORTING a mutable global is the key feature of this proposal.
|
||||
;; In strict MVP, exported globals had to be immutable (const).
|
||||
(export "counter" (global $counter))
|
||||
|
||||
(func $finish (result i32)
|
||||
;; 1. Get current value
|
||||
global.get $counter
|
||||
|
||||
;; 2. Add 1
|
||||
i32.const 1
|
||||
i32.add
|
||||
|
||||
;; 3. Set new value (Mutation)
|
||||
global.set $counter
|
||||
|
||||
;; 4. Return 1 for success
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
(export "finish" (func $finish))
|
||||
)
|
||||
18
src/test/app/wasm_fixtures/wat/proposal_ref_types.wat
Normal file
18
src/test/app/wasm_fixtures/wat/proposal_ref_types.wat
Normal file
@@ -0,0 +1,18 @@
|
||||
(module
|
||||
;; Import a table from the host that holds externrefs
|
||||
(import "env" "table" (table 1 externref))
|
||||
|
||||
(func $test_ref_types (result i32)
|
||||
;; Store a null externref into the table at index 0
|
||||
;; If reference_types is disabled, 'externref' and 'ref.null' will fail parsing.
|
||||
(table.set
|
||||
(i32.const 0) ;; Index
|
||||
(ref.null extern) ;; Value (Null External Reference)
|
||||
)
|
||||
|
||||
;; Return 1 (Success)
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
(export "finish" (func $test_ref_types))
|
||||
)
|
||||
18
src/test/app/wasm_fixtures/wat/proposal_sign_ext.wat
Normal file
18
src/test/app/wasm_fixtures/wat/proposal_sign_ext.wat
Normal file
@@ -0,0 +1,18 @@
|
||||
(module
|
||||
(func $test_sign_ext (result i32)
|
||||
;; Push 255 (0x000000FF) onto the stack
|
||||
i32.const 255
|
||||
|
||||
;; Sign-extend from 8-bit to 32-bit
|
||||
;; If 255 is treated as an i8, it is -1.
|
||||
;; Result should be -1 (0xFFFFFFFF).
|
||||
;; Without this proposal, this opcode (0xC0) causes a validation error.
|
||||
i32.extend8_s
|
||||
|
||||
;; Check if result is -1
|
||||
i32.const -1
|
||||
i32.eq
|
||||
)
|
||||
|
||||
(export "finish" (func $test_sign_ext))
|
||||
)
|
||||
1
src/test/app/wasm_fixtures/wat/proposal_stringref.wat
Normal file
1
src/test/app/wasm_fixtures/wat/proposal_stringref.wat
Normal file
@@ -0,0 +1 @@
|
||||
;;hard to generate
|
||||
15
src/test/app/wasm_fixtures/wat/proposal_tail_call.wat
Normal file
15
src/test/app/wasm_fixtures/wat/proposal_tail_call.wat
Normal file
@@ -0,0 +1,15 @@
|
||||
(module
|
||||
;; Define a simple function we can tail-call
|
||||
(func $target (result i32)
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
(func $finish (result i32)
|
||||
;; Try to use the 'return_call' instruction (Opcode 0x12)
|
||||
;; If Tail Call proposal is disabled, this fails to Compile/Validate.
|
||||
;; If enabled, it jumps to $target, which returns 1.
|
||||
return_call $target
|
||||
)
|
||||
|
||||
(export "finish" (func $finish))
|
||||
)
|
||||
22
src/test/app/wasm_fixtures/wat/start_loop.wat
Normal file
22
src/test/app/wasm_fixtures/wat/start_loop.wat
Normal file
@@ -0,0 +1,22 @@
|
||||
(module
|
||||
;; Function 1: The Infinite Loop
|
||||
(func $run_forever
|
||||
(loop $infinite
|
||||
br $infinite
|
||||
)
|
||||
)
|
||||
|
||||
;; Function 2: Finish
|
||||
(func $finish (result i32)
|
||||
i32.const 1
|
||||
)
|
||||
|
||||
;; 1. EXPORT the functions (optional, if you want to call them later)
|
||||
(export "start" (func $run_forever))
|
||||
(export "finish" (func $finish))
|
||||
|
||||
;; 2. The special start section
|
||||
;; This tells the VM: "Run function $run_forever immediately
|
||||
;; when this module is instantiated."
|
||||
(start $run_forever)
|
||||
)
|
||||
10
src/test/app/wasm_fixtures/wat/table_0_elements.wat
Normal file
10
src/test/app/wasm_fixtures/wat/table_0_elements.wat
Normal file
@@ -0,0 +1,10 @@
|
||||
(module
|
||||
;; Define a table with exactly 0 entries
|
||||
(table 0 funcref)
|
||||
|
||||
;; Standard finish function
|
||||
(func $finish (result i32)
|
||||
i32.const 1
|
||||
)
|
||||
(export "finish" (func $finish))
|
||||
)
|
||||
24
src/test/app/wasm_fixtures/wat/table_2_tables.wat
Normal file
24
src/test/app/wasm_fixtures/wat/table_2_tables.wat
Normal file
@@ -0,0 +1,24 @@
|
||||
(module
|
||||
;; Define a dummy function to put in the tables
|
||||
(func $dummy)
|
||||
|
||||
;; TABLE 0: The default table (allowed in MVP)
|
||||
;; Size: 1 initial, 1 max
|
||||
(table $t0 1 1 funcref)
|
||||
|
||||
;; Initialize Table 0 at index 0
|
||||
(elem (table $t0) (i32.const 0) $dummy)
|
||||
|
||||
;; TABLE 1: The second table (Requires Reference Types proposal)
|
||||
;; If strict MVP is enforced, the parser should error here.
|
||||
(table $t1 1 1 funcref)
|
||||
|
||||
;; Initialize Table 1 at index 0
|
||||
(elem (table $t1) (i32.const 0) $dummy)
|
||||
|
||||
(func $finish (result i32)
|
||||
;; If we successfully loaded a module with 2 tables, return 1.
|
||||
i32.const 1
|
||||
)
|
||||
(export "finish" (func $finish))
|
||||
)
|
||||
25
src/test/app/wasm_fixtures/wat/table_64_elements.wat
Normal file
25
src/test/app/wasm_fixtures/wat/table_64_elements.wat
Normal file
@@ -0,0 +1,25 @@
|
||||
(module
|
||||
;; Define a table with exactly 64 entries
|
||||
(table 64 funcref)
|
||||
|
||||
;; A dummy function to reference
|
||||
(func $dummy)
|
||||
|
||||
;; Initialize the table at offset 0 with 64 references to $dummy
|
||||
(elem (i32.const 0)
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 8
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 16
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 24
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 32
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 40
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 48
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 56
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 64
|
||||
)
|
||||
|
||||
;; Standard finish function
|
||||
(func $finish (result i32)
|
||||
i32.const 1
|
||||
)
|
||||
(export "finish" (func $finish))
|
||||
)
|
||||
25
src/test/app/wasm_fixtures/wat/table_65_elements.wat
Normal file
25
src/test/app/wasm_fixtures/wat/table_65_elements.wat
Normal file
@@ -0,0 +1,25 @@
|
||||
(module
|
||||
;; Define a table with exactly 65 entries
|
||||
(table 65 funcref)
|
||||
|
||||
;; A dummy function to reference
|
||||
(func $dummy)
|
||||
|
||||
;; Initialize the table at offset 0 with 65 references to $dummy
|
||||
(elem (i32.const 0)
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 8
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 16
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 24
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 32
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 40
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 48
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 56
|
||||
$dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 64
|
||||
$dummy ;; 65 (The one that breaks the camel's back)
|
||||
)
|
||||
|
||||
(func $finish (result i32)
|
||||
i32.const 1
|
||||
)
|
||||
(export "finish" (func $finish))
|
||||
)
|
||||
15
src/test/app/wasm_fixtures/wat/table_uint_max.wat
Normal file
15
src/test/app/wasm_fixtures/wat/table_uint_max.wat
Normal file
@@ -0,0 +1,15 @@
|
||||
(module
|
||||
;; Definition: (table <min> <optional_max> <type>)
|
||||
;; We use 0xFFFFFFFF (4,294,967,295), which is the unsigned equivalent of -1.
|
||||
;; This tests if the runtime handles the maximum possible u32 value
|
||||
;; without integer overflows or attempting a massive allocation.
|
||||
;;
|
||||
;; Note that using -1 as the table size cannot be parsed by wasm-tools or wat2wasm
|
||||
(table 0xFFFFFFFF funcref)
|
||||
|
||||
(func $finish (result i32)
|
||||
;; If the module loads despite the massive table, return 1.
|
||||
i32.const 1
|
||||
)
|
||||
(export "finish" (func $finish))
|
||||
)
|
||||
15
src/test/app/wasm_fixtures/wat/trap_divide_by_0.wat
Normal file
15
src/test/app/wasm_fixtures/wat/trap_divide_by_0.wat
Normal file
@@ -0,0 +1,15 @@
|
||||
(module
|
||||
(func $finish (export "finish") (result i32)
|
||||
;; Setup for Requirement 2: Divide an i32 by 0
|
||||
i32.const 42 ;; Push numerator
|
||||
i32.const 0 ;; Push denominator (0)
|
||||
i32.div_s ;; Perform signed division (42 / 0)
|
||||
|
||||
;; --- NOTE: Execution usually traps (crashes) at the line above ---
|
||||
|
||||
;; Logic to satisfy Requirement 1: Return i32 = 1
|
||||
;; If execution continued, we would drop the division result and return 1
|
||||
drop ;; Clear the stack
|
||||
i32.const 1 ;; Push the return value
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
(module
|
||||
;; Define a table with 1 slot
|
||||
(table 1 funcref)
|
||||
|
||||
;; Define Type A: Takes nothing, returns nothing
|
||||
(type $type_void (func))
|
||||
|
||||
;; Define Type B: Takes nothing, returns i32
|
||||
(type $type_i32 (func (result i32)))
|
||||
|
||||
;; Define a function of Type A
|
||||
(func $void_func (type $type_void)
|
||||
nop
|
||||
)
|
||||
|
||||
;; Put Type A function into Table[0]
|
||||
(elem (i32.const 0) $void_func)
|
||||
|
||||
(func $finish (result i32)
|
||||
;; Attempt to call Index 0, but CLAIM we expect Type B (result i32).
|
||||
;; The function at Index 0 matches Type A.
|
||||
;; TRAP: "indirect call type mismatch"
|
||||
|
||||
;; 1. Push the table index (0) onto the stack
|
||||
i32.const 0
|
||||
|
||||
;; 2. Call indirect using Type B signature.
|
||||
;; This pops the index (0) from the stack.
|
||||
call_indirect (type $type_i32)
|
||||
)
|
||||
|
||||
(export "finish" (func $finish))
|
||||
)
|
||||
18
src/test/app/wasm_fixtures/wat/trap_int_overflow.wat
Normal file
18
src/test/app/wasm_fixtures/wat/trap_int_overflow.wat
Normal file
@@ -0,0 +1,18 @@
|
||||
(module
|
||||
(func $test_int_overflow (result i32)
|
||||
;; 1. Push INT_MIN (-2147483648)
|
||||
;; In Hex: 0x80000000
|
||||
i32.const -2147483648
|
||||
|
||||
;; 2. Push -1
|
||||
i32.const -1
|
||||
|
||||
;; 3. Signed Division
|
||||
;; This specific case is the ONLY integer arithmetic operation
|
||||
;; (besides divide by zero) that traps in the spec.
|
||||
;; Result would be +2147483648, which is too big for signed i32.
|
||||
i32.div_s
|
||||
)
|
||||
|
||||
(export "finish" (func $test_int_overflow))
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user