mirror of
https://github.com/XRPLF/rippled.git
synced 2026-03-01 02:02:34 +00:00
Compare commits
207 Commits
pratik/std
...
ripple/se/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00eeab6d44 | ||
|
|
125df7a425 | ||
|
|
b08bcf5d21 | ||
|
|
dc413aef0c | ||
|
|
1a7f824b89 | ||
|
|
77dfd56ace | ||
|
|
b58c681189 | ||
|
|
953b9a3500 | ||
|
|
1d9ec84350 | ||
|
|
0392846a17 | ||
|
|
5148098079 | ||
|
|
1b4a564369 | ||
|
|
fd524c4be9 | ||
|
|
495dda7f58 | ||
|
|
9c3c0280b1 | ||
|
|
f73d8a6cf2 | ||
|
|
4fe508cb92 | ||
|
|
6728ab52b7 | ||
|
|
eb2d44d442 | ||
|
|
77673663ca | ||
|
|
c1381f8ddd | ||
|
|
bd16f7989d | ||
|
|
65f9cf80c0 | ||
|
|
cd46b5d999 | ||
|
|
de55a5ebfc | ||
|
|
2ec4a1114e | ||
|
|
13707dda05 | ||
|
|
ba03a8a9d2 | ||
|
|
7c8279ec83 | ||
|
|
f625fb993a | ||
|
|
0418ffb26a | ||
|
|
b2627039f6 | ||
|
|
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 |
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@320be44621ca2a080f05aeb15817c44b84518108
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@56de1bdf19639e009639a50b8d17c28ca954f267
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'
|
||||
|
||||
@@ -61,7 +61,15 @@ repos:
|
||||
hooks:
|
||||
- id: nix-fmt
|
||||
name: Format Nix files
|
||||
entry: nix --extra-experimental-features 'nix-command flakes' fmt
|
||||
entry: |
|
||||
bash -c '
|
||||
if command -v nix &> /dev/null || [ "$GITHUB_ACTIONS" = "true" ]; then
|
||||
nix --extra-experimental-features "nix-command flakes" fmt "$@"
|
||||
else
|
||||
echo "Skipping nix-fmt: nix not installed and not in GitHub Actions"
|
||||
exit 0
|
||||
fi
|
||||
' --
|
||||
language: system
|
||||
types:
|
||||
- nix
|
||||
|
||||
@@ -106,6 +106,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(
|
||||
|
||||
@@ -1272,6 +1272,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
|
||||
|
||||
@@ -47,6 +47,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.5",
|
||||
"secp256k1/0.7.1",
|
||||
"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",
|
||||
]
|
||||
|
||||
@@ -7,6 +7,8 @@ ignorePaths:
|
||||
- cmake/**
|
||||
- LICENSE.md
|
||||
- .clang-tidy
|
||||
- src/test/app/wasm_fixtures/**/*.wat
|
||||
- src/test/app/wasm_fixtures/*.c
|
||||
language: en
|
||||
allowCompoundWords: true # TODO (#6334)
|
||||
ignoreRandomStrings: true
|
||||
@@ -60,6 +62,7 @@ words:
|
||||
- Britto
|
||||
- Btrfs
|
||||
- canonicality
|
||||
- cdylib
|
||||
- changespq
|
||||
- checkme
|
||||
- choco
|
||||
@@ -291,6 +294,7 @@ words:
|
||||
- venv
|
||||
- vfalco
|
||||
- vinnie
|
||||
- wasmi
|
||||
- wextra
|
||||
- wptr
|
||||
- writeme
|
||||
|
||||
@@ -729,6 +729,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
|
||||
|
||||
@@ -4,6 +4,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
|
||||
@@ -11,9 +13,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;
|
||||
|
||||
|
||||
@@ -121,6 +121,8 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temARRAY_TOO_LARGE,
|
||||
temBAD_TRANSFER_FEE,
|
||||
temINVALID_INNER_BATCH,
|
||||
|
||||
temBAD_WASM,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SmartEscrow, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -33,9 +34,8 @@ XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo
|
||||
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Batch, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SingleAssetVault, 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
|
||||
|
||||
@@ -254,6 +254,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
|
||||
@@ -708,11 +711,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>
|
||||
|
||||
|
||||
@@ -1,732 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class ReadView;
|
||||
|
||||
#if GENERATING_DOCS
|
||||
/**
|
||||
* @brief Prototype for invariant check implementations.
|
||||
*
|
||||
* __THIS CLASS DOES NOT EXIST__ - or rather it exists in documentation only to
|
||||
* communicate the interface required of any invariant checker. Any invariant
|
||||
* check implementation should implement the public methods documented here.
|
||||
*
|
||||
*/
|
||||
class InvariantChecker_PROTOTYPE
|
||||
{
|
||||
public:
|
||||
explicit InvariantChecker_PROTOTYPE() = default;
|
||||
|
||||
/**
|
||||
* @brief called for each ledger entry in the current transaction.
|
||||
*
|
||||
* @param isDelete true if the SLE is being deleted
|
||||
* @param before ledger entry before modification by the transaction
|
||||
* @param after ledger entry after modification by the transaction
|
||||
*/
|
||||
void
|
||||
visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after);
|
||||
|
||||
/**
|
||||
* @brief called after all ledger entries have been visited to determine
|
||||
* the final status of the check
|
||||
*
|
||||
* @param tx the transaction being applied
|
||||
* @param tec the current TER result of the transaction
|
||||
* @param fee the fee actually charged for this transaction
|
||||
* @param view a ReadView of the ledger being modified
|
||||
* @param j journal for logging
|
||||
*
|
||||
* @return true if check passes, false if it fails
|
||||
*/
|
||||
bool
|
||||
finalize(
|
||||
STTx const& tx,
|
||||
TER const tec,
|
||||
XRPAmount const fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j);
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Invariant: We should never charge a transaction a negative fee or a
|
||||
* fee that is larger than what the transaction itself specifies.
|
||||
*
|
||||
* We can, in some circumstances, charge less.
|
||||
*/
|
||||
class TransactionFeeCheck
|
||||
{
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: A transaction must not create XRP and should only destroy
|
||||
* the XRP fee.
|
||||
*
|
||||
* We iterate through all account roots, payment channels and escrow entries
|
||||
* that were modified and calculate the net change in XRP caused by the
|
||||
* transactions.
|
||||
*/
|
||||
class XRPNotCreated
|
||||
{
|
||||
std::int64_t drops_ = 0;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: we cannot remove an account ledger entry
|
||||
*
|
||||
* We iterate all account roots that were modified, and ensure that any that
|
||||
* were present before the transaction was applied continue to be present
|
||||
* afterwards unless they were explicitly deleted by a successful
|
||||
* AccountDelete transaction.
|
||||
*/
|
||||
class AccountRootsNotDeleted
|
||||
{
|
||||
std::uint32_t accountsDeleted_ = 0;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: a deleted account must not have any objects left
|
||||
*
|
||||
* We iterate all deleted account roots, and ensure that there are no
|
||||
* objects left that are directly accessible with that account's ID.
|
||||
*
|
||||
* There should only be one deleted account, but that's checked by
|
||||
* AccountRootsNotDeleted. This invariant will handle multiple deleted account
|
||||
* roots without a problem.
|
||||
*/
|
||||
class AccountRootsDeletedClean
|
||||
{
|
||||
// Pair is <before, after>. Before is used for most of the checks, so that
|
||||
// if, for example, an object ID field is cleared, but the object is not
|
||||
// deleted, it can still be found. After is used specifically for any checks
|
||||
// that are expected as part of the deletion, such as zeroing out the
|
||||
// balance.
|
||||
std::vector<std::pair<std::shared_ptr<SLE const>, std::shared_ptr<SLE const>>> accountsDeleted_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: An account XRP balance must be in XRP and take a value
|
||||
* between 0 and INITIAL_XRP drops, inclusive.
|
||||
*
|
||||
* We iterate all account roots modified by the transaction and ensure that
|
||||
* their XRP balances are reasonable.
|
||||
*/
|
||||
class XRPBalanceChecks
|
||||
{
|
||||
bool bad_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: corresponding modified ledger entries should match in type
|
||||
* and added entries should be a valid type.
|
||||
*/
|
||||
class LedgerEntryTypesMatch
|
||||
{
|
||||
bool typeMismatch_ = false;
|
||||
bool invalidTypeAdded_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: Trust lines using XRP are not allowed.
|
||||
*
|
||||
* We iterate all the trust lines created by this transaction and ensure
|
||||
* that they are against a valid issuer.
|
||||
*/
|
||||
class NoXRPTrustLines
|
||||
{
|
||||
bool xrpTrustLine_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: Trust lines with deep freeze flag are not allowed if normal
|
||||
* freeze flag is not set.
|
||||
*
|
||||
* We iterate all the trust lines created by this transaction and ensure
|
||||
* that they don't have deep freeze flag set without normal freeze flag set.
|
||||
*/
|
||||
class NoDeepFreezeTrustLinesWithoutFreeze
|
||||
{
|
||||
bool deepFreezeWithoutFreeze_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: frozen trust line balance change is not allowed.
|
||||
*
|
||||
* We iterate all affected trust lines and ensure that they don't have
|
||||
* unexpected change of balance if they're frozen.
|
||||
*/
|
||||
class TransfersNotFrozen
|
||||
{
|
||||
struct BalanceChange
|
||||
{
|
||||
std::shared_ptr<SLE const> const line;
|
||||
int const balanceChangeSign;
|
||||
};
|
||||
|
||||
struct IssuerChanges
|
||||
{
|
||||
std::vector<BalanceChange> senders;
|
||||
std::vector<BalanceChange> receivers;
|
||||
};
|
||||
|
||||
using ByIssuer = std::map<Issue, IssuerChanges>;
|
||||
ByIssuer balanceChanges_;
|
||||
|
||||
std::map<AccountID, std::shared_ptr<SLE const> const> possibleIssuers_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
|
||||
private:
|
||||
bool
|
||||
isValidEntry(std::shared_ptr<SLE const> const& before, std::shared_ptr<SLE const> const& after);
|
||||
|
||||
STAmount
|
||||
calculateBalanceChange(
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after,
|
||||
bool isDelete);
|
||||
|
||||
void
|
||||
recordBalance(Issue const& issue, BalanceChange change);
|
||||
|
||||
void
|
||||
recordBalanceChanges(std::shared_ptr<SLE const> const& after, STAmount const& balanceChange);
|
||||
|
||||
std::shared_ptr<SLE const>
|
||||
findIssuer(AccountID const& issuerID, ReadView const& view);
|
||||
|
||||
bool
|
||||
validateIssuerChanges(
|
||||
std::shared_ptr<SLE const> const& issuer,
|
||||
IssuerChanges const& changes,
|
||||
STTx const& tx,
|
||||
beast::Journal const& j,
|
||||
bool enforce);
|
||||
|
||||
bool
|
||||
validateFrozenState(
|
||||
BalanceChange const& change,
|
||||
bool high,
|
||||
STTx const& tx,
|
||||
beast::Journal const& j,
|
||||
bool enforce,
|
||||
bool globalFreeze);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: offers should be for non-negative amounts and must not
|
||||
* be XRP to XRP.
|
||||
*
|
||||
* Examine all offers modified by the transaction and ensure that there are
|
||||
* no offers which contain negative amounts or which exchange XRP for XRP.
|
||||
*/
|
||||
class NoBadOffers
|
||||
{
|
||||
bool bad_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: an escrow entry must take a value between 0 and
|
||||
* INITIAL_XRP drops exclusive.
|
||||
*/
|
||||
class NoZeroEscrow
|
||||
{
|
||||
bool bad_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: a new account root must be the consequence of a payment,
|
||||
* must have the right starting sequence, and the payment
|
||||
* may not create more than one new account root.
|
||||
*/
|
||||
class ValidNewAccountRoot
|
||||
{
|
||||
std::uint32_t accountsCreated_ = 0;
|
||||
std::uint32_t accountSeq_ = 0;
|
||||
bool pseudoAccount_ = false;
|
||||
std::uint32_t flags_ = 0;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: Validates several invariants for NFToken pages.
|
||||
*
|
||||
* The following checks are made:
|
||||
* - The page is correctly associated with the owner.
|
||||
* - The page is correctly ordered between the next and previous links.
|
||||
* - The page contains at least one and no more than 32 NFTokens.
|
||||
* - The NFTokens on this page do not belong on a lower or higher page.
|
||||
* - The NFTokens are correctly sorted on the page.
|
||||
* - Each URI, if present, is not empty.
|
||||
*/
|
||||
class ValidNFTokenPage
|
||||
{
|
||||
bool badEntry_ = false;
|
||||
bool badLink_ = false;
|
||||
bool badSort_ = false;
|
||||
bool badURI_ = false;
|
||||
bool invalidSize_ = false;
|
||||
bool deletedFinalPage_ = false;
|
||||
bool deletedLink_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: Validates counts of NFTokens after all transaction types.
|
||||
*
|
||||
* The following checks are made:
|
||||
* - The number of minted or burned NFTokens can only be changed by
|
||||
* NFTokenMint or NFTokenBurn transactions.
|
||||
* - A successful NFTokenMint must increase the number of NFTokens.
|
||||
* - A failed NFTokenMint must not change the number of minted NFTokens.
|
||||
* - An NFTokenMint transaction cannot change the number of burned NFTokens.
|
||||
* - A successful NFTokenBurn must increase the number of burned NFTokens.
|
||||
* - A failed NFTokenBurn must not change the number of burned NFTokens.
|
||||
* - An NFTokenBurn transaction cannot change the number of minted NFTokens.
|
||||
*/
|
||||
class NFTokenCountTracking
|
||||
{
|
||||
std::uint32_t beforeMintedTotal = 0;
|
||||
std::uint32_t beforeBurnedTotal = 0;
|
||||
std::uint32_t afterMintedTotal = 0;
|
||||
std::uint32_t afterBurnedTotal = 0;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: Token holder's trustline balance cannot be negative after
|
||||
* Clawback.
|
||||
*
|
||||
* We iterate all the trust lines affected by this transaction and ensure
|
||||
* that no more than one trustline is modified, and also holder's balance is
|
||||
* non-negative.
|
||||
*/
|
||||
class ValidClawback
|
||||
{
|
||||
std::uint32_t trustlinesChanged = 0;
|
||||
std::uint32_t mptokensChanged = 0;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
class ValidMPTIssuance
|
||||
{
|
||||
std::uint32_t mptIssuancesCreated_ = 0;
|
||||
std::uint32_t mptIssuancesDeleted_ = 0;
|
||||
|
||||
std::uint32_t mptokensCreated_ = 0;
|
||||
std::uint32_t mptokensDeleted_ = 0;
|
||||
// non-MPT transactions may attempt to create
|
||||
// MPToken by an issuer
|
||||
bool mptCreatedByIssuer_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: Permissioned Domains must have some rules and
|
||||
* AcceptedCredentials must have length between 1 and 10 inclusive.
|
||||
*
|
||||
* Since only permissions constitute rules, an empty credentials list
|
||||
* means that there are no rules and the invariant is violated.
|
||||
*
|
||||
* Credentials must be sorted and no duplicates allowed
|
||||
*
|
||||
*/
|
||||
class ValidPermissionedDomain
|
||||
{
|
||||
struct SleStatus
|
||||
{
|
||||
std::size_t credentialsSize_{0};
|
||||
bool isSorted_ = false;
|
||||
bool isUnique_ = false;
|
||||
bool isDelete_ = false;
|
||||
};
|
||||
std::vector<SleStatus> sleStatus_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: Pseudo-accounts have valid and consistent properties
|
||||
*
|
||||
* Pseudo-accounts have certain properties, and some of those properties are
|
||||
* unique to pseudo-accounts. Check that all pseudo-accounts are following the
|
||||
* rules, and that only pseudo-accounts look like pseudo-accounts.
|
||||
*
|
||||
*/
|
||||
class ValidPseudoAccounts
|
||||
{
|
||||
std::vector<std::string> errors_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
class ValidPermissionedDEX
|
||||
{
|
||||
bool regularOffers_ = false;
|
||||
bool badHybrids_ = false;
|
||||
hash_set<uint256> domains_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
class ValidAMM
|
||||
{
|
||||
std::optional<AccountID> ammAccount_;
|
||||
std::optional<STAmount> lptAMMBalanceAfter_;
|
||||
std::optional<STAmount> lptAMMBalanceBefore_;
|
||||
bool ammPoolChanged_;
|
||||
|
||||
public:
|
||||
enum class ZeroAllowed : bool { No = false, Yes = true };
|
||||
|
||||
ValidAMM() : ammPoolChanged_{false}
|
||||
{
|
||||
}
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
|
||||
private:
|
||||
bool
|
||||
finalizeBid(bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeVote(bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeCreate(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeDelete(bool enforce, TER res, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeDeposit(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const;
|
||||
// Includes clawback
|
||||
bool
|
||||
finalizeWithdraw(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeDEX(bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
generalInvariant(STTx const&, ReadView const&, ZeroAllowed zeroAllowed, beast::Journal const&)
|
||||
const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: Some fields are unmodifiable
|
||||
*
|
||||
* Check that any fields specified as unmodifiable are not modified when the
|
||||
* object is modified. Creation and deletion are ignored.
|
||||
*
|
||||
*/
|
||||
class NoModifiedUnmodifiableFields
|
||||
{
|
||||
// Pair is <before, after>.
|
||||
std::set<std::pair<SLE::const_pointer, SLE::const_pointer>> changedEntries_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: Loan brokers are internally consistent
|
||||
*
|
||||
* 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one
|
||||
* node (the root), which will only hold entries for `RippleState` or
|
||||
* `MPToken` objects.
|
||||
*
|
||||
*/
|
||||
class ValidLoanBroker
|
||||
{
|
||||
// Not all of these elements will necessarily be populated. Remaining items
|
||||
// will be looked up as needed.
|
||||
struct BrokerInfo
|
||||
{
|
||||
SLE::const_pointer brokerBefore = nullptr;
|
||||
// After is used for most of the checks, except
|
||||
// those that check changed values.
|
||||
SLE::const_pointer brokerAfter = nullptr;
|
||||
};
|
||||
// Collect all the LoanBrokers found directly or indirectly through
|
||||
// pseudo-accounts. Key is the brokerID / index. It will be used to find the
|
||||
// LoanBroker object if brokerBefore and brokerAfter are nullptr
|
||||
std::map<uint256, BrokerInfo> brokers_;
|
||||
// Collect all the modified trust lines. Their high and low accounts will be
|
||||
// loaded to look for LoanBroker pseudo-accounts.
|
||||
std::vector<SLE::const_pointer> lines_;
|
||||
// Collect all the modified MPTokens. Their accounts will be loaded to look
|
||||
// for LoanBroker pseudo-accounts.
|
||||
std::vector<SLE::const_pointer> mpts_;
|
||||
|
||||
bool
|
||||
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j) const;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: Loans are internally consistent
|
||||
*
|
||||
* 1. If `Loan.PaymentRemaining = 0` then `Loan.PrincipalOutstanding = 0`
|
||||
*
|
||||
*/
|
||||
class ValidLoan
|
||||
{
|
||||
// Pair is <before, after>. After is used for most of the checks, except
|
||||
// those that check changed values.
|
||||
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> loans_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/*
|
||||
* @brief Invariants: Vault object and MPTokenIssuance for vault shares
|
||||
*
|
||||
* - vault deleted and vault created is empty
|
||||
* - vault created must be linked to pseudo-account for shares and assets
|
||||
* - vault must have MPTokenIssuance for shares
|
||||
* - vault without shares outstanding must have no shares
|
||||
* - loss unrealized does not exceed the difference between assets total and
|
||||
* assets available
|
||||
* - assets available do not exceed assets total
|
||||
* - vault deposit increases assets and share issuance, and adds to:
|
||||
* total assets, assets available, shares outstanding
|
||||
* - vault withdrawal and clawback reduce assets and share issuance, and
|
||||
* subtracts from: total assets, assets available, shares outstanding
|
||||
* - vault set must not alter the vault assets or shares balance
|
||||
* - no vault transaction can change loss unrealized (it's updated by loan
|
||||
* transactions)
|
||||
*
|
||||
*/
|
||||
class ValidVault
|
||||
{
|
||||
Number static constexpr zero{};
|
||||
|
||||
struct Vault final
|
||||
{
|
||||
uint256 key = beast::zero;
|
||||
Asset asset = {};
|
||||
AccountID pseudoId = {};
|
||||
AccountID owner = {};
|
||||
uint192 shareMPTID = beast::zero;
|
||||
Number assetsTotal = 0;
|
||||
Number assetsAvailable = 0;
|
||||
Number assetsMaximum = 0;
|
||||
Number lossUnrealized = 0;
|
||||
|
||||
Vault static make(SLE const&);
|
||||
};
|
||||
|
||||
struct Shares final
|
||||
{
|
||||
MPTIssue share = {};
|
||||
std::uint64_t sharesTotal = 0;
|
||||
std::uint64_t sharesMaximum = 0;
|
||||
|
||||
Shares static make(SLE const&);
|
||||
};
|
||||
|
||||
std::vector<Vault> afterVault_ = {};
|
||||
std::vector<Shares> afterMPTs_ = {};
|
||||
std::vector<Vault> beforeVault_ = {};
|
||||
std::vector<Shares> beforeMPTs_ = {};
|
||||
std::unordered_map<uint256, Number> deltas_ = {};
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
// additional invariant checks can be declared above and then added to this
|
||||
// tuple
|
||||
using InvariantChecks = std::tuple<
|
||||
TransactionFeeCheck,
|
||||
AccountRootsNotDeleted,
|
||||
AccountRootsDeletedClean,
|
||||
LedgerEntryTypesMatch,
|
||||
XRPBalanceChecks,
|
||||
XRPNotCreated,
|
||||
NoXRPTrustLines,
|
||||
NoDeepFreezeTrustLinesWithoutFreeze,
|
||||
TransfersNotFrozen,
|
||||
NoBadOffers,
|
||||
NoZeroEscrow,
|
||||
ValidNewAccountRoot,
|
||||
ValidNFTokenPage,
|
||||
NFTokenCountTracking,
|
||||
ValidClawback,
|
||||
ValidMPTIssuance,
|
||||
ValidPermissionedDomain,
|
||||
ValidPermissionedDEX,
|
||||
ValidAMM,
|
||||
NoModifiedUnmodifiableFields,
|
||||
ValidPseudoAccounts,
|
||||
ValidLoanBroker,
|
||||
ValidLoan,
|
||||
ValidVault>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
*
|
||||
* @return std::tuple of instances that implement the required invariant check
|
||||
* methods
|
||||
*
|
||||
* @see xrpl::InvariantChecker_PROTOTYPE
|
||||
*/
|
||||
inline InvariantChecks
|
||||
getInvariantChecks()
|
||||
{
|
||||
return InvariantChecks{};
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
53
include/xrpl/tx/invariants/AMMInvariant.h
Normal file
53
include/xrpl/tx/invariants/AMMInvariant.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class ValidAMM
|
||||
{
|
||||
std::optional<AccountID> ammAccount_;
|
||||
std::optional<STAmount> lptAMMBalanceAfter_;
|
||||
std::optional<STAmount> lptAMMBalanceBefore_;
|
||||
bool ammPoolChanged_;
|
||||
|
||||
public:
|
||||
enum class ZeroAllowed : bool { No = false, Yes = true };
|
||||
|
||||
ValidAMM() : ammPoolChanged_{false}
|
||||
{
|
||||
}
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
|
||||
private:
|
||||
bool
|
||||
finalizeBid(bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeVote(bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeCreate(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeDelete(bool enforce, TER res, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeDeposit(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const;
|
||||
// Includes clawback
|
||||
bool
|
||||
finalizeWithdraw(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
finalizeDEX(bool enforce, beast::Journal const&) const;
|
||||
bool
|
||||
generalInvariant(STTx const&, ReadView const&, ZeroAllowed zeroAllowed, beast::Journal const&)
|
||||
const;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
84
include/xrpl/tx/invariants/FreezeInvariant.h
Normal file
84
include/xrpl/tx/invariants/FreezeInvariant.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Invariant: frozen trust line balance change is not allowed.
|
||||
*
|
||||
* We iterate all affected trust lines and ensure that they don't have
|
||||
* unexpected change of balance if they're frozen.
|
||||
*/
|
||||
class TransfersNotFrozen
|
||||
{
|
||||
struct BalanceChange
|
||||
{
|
||||
std::shared_ptr<SLE const> const line;
|
||||
int const balanceChangeSign;
|
||||
};
|
||||
|
||||
struct IssuerChanges
|
||||
{
|
||||
std::vector<BalanceChange> senders;
|
||||
std::vector<BalanceChange> receivers;
|
||||
};
|
||||
|
||||
using ByIssuer = std::map<Issue, IssuerChanges>;
|
||||
ByIssuer balanceChanges_;
|
||||
|
||||
std::map<AccountID, std::shared_ptr<SLE const> const> possibleIssuers_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
|
||||
private:
|
||||
bool
|
||||
isValidEntry(std::shared_ptr<SLE const> const& before, std::shared_ptr<SLE const> const& after);
|
||||
|
||||
STAmount
|
||||
calculateBalanceChange(
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after,
|
||||
bool isDelete);
|
||||
|
||||
void
|
||||
recordBalance(Issue const& issue, BalanceChange change);
|
||||
|
||||
void
|
||||
recordBalanceChanges(std::shared_ptr<SLE const> const& after, STAmount const& balanceChange);
|
||||
|
||||
std::shared_ptr<SLE const>
|
||||
findIssuer(AccountID const& issuerID, ReadView const& view);
|
||||
|
||||
bool
|
||||
validateIssuerChanges(
|
||||
std::shared_ptr<SLE const> const& issuer,
|
||||
IssuerChanges const& changes,
|
||||
STTx const& tx,
|
||||
beast::Journal const& j,
|
||||
bool enforce);
|
||||
|
||||
bool
|
||||
validateFrozenState(
|
||||
BalanceChange const& change,
|
||||
bool high,
|
||||
STTx const& tx,
|
||||
beast::Journal const& j,
|
||||
bool enforce,
|
||||
bool globalFreeze);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
385
include/xrpl/tx/invariants/InvariantCheck.h
Normal file
385
include/xrpl/tx/invariants/InvariantCheck.h
Normal file
@@ -0,0 +1,385 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/tx/invariants/AMMInvariant.h>
|
||||
#include <xrpl/tx/invariants/FreezeInvariant.h>
|
||||
#include <xrpl/tx/invariants/LoanInvariant.h>
|
||||
#include <xrpl/tx/invariants/MPTInvariant.h>
|
||||
#include <xrpl/tx/invariants/NFTInvariant.h>
|
||||
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
|
||||
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
|
||||
#include <xrpl/tx/invariants/VaultInvariant.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
#if GENERATING_DOCS
|
||||
/**
|
||||
* @brief Prototype for invariant check implementations.
|
||||
*
|
||||
* __THIS CLASS DOES NOT EXIST__ - or rather it exists in documentation only to
|
||||
* communicate the interface required of any invariant checker. Any invariant
|
||||
* check implementation should implement the public methods documented here.
|
||||
*
|
||||
*/
|
||||
class InvariantChecker_PROTOTYPE
|
||||
{
|
||||
public:
|
||||
explicit InvariantChecker_PROTOTYPE() = default;
|
||||
|
||||
/**
|
||||
* @brief called for each ledger entry in the current transaction.
|
||||
*
|
||||
* @param isDelete true if the SLE is being deleted
|
||||
* @param before ledger entry before modification by the transaction
|
||||
* @param after ledger entry after modification by the transaction
|
||||
*/
|
||||
void
|
||||
visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after);
|
||||
|
||||
/**
|
||||
* @brief called after all ledger entries have been visited to determine
|
||||
* the final status of the check
|
||||
*
|
||||
* @param tx the transaction being applied
|
||||
* @param tec the current TER result of the transaction
|
||||
* @param fee the fee actually charged for this transaction
|
||||
* @param view a ReadView of the ledger being modified
|
||||
* @param j journal for logging
|
||||
*
|
||||
* @return true if check passes, false if it fails
|
||||
*/
|
||||
bool
|
||||
finalize(
|
||||
STTx const& tx,
|
||||
TER const tec,
|
||||
XRPAmount const fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j);
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Invariant: We should never charge a transaction a negative fee or a
|
||||
* fee that is larger than what the transaction itself specifies.
|
||||
*
|
||||
* We can, in some circumstances, charge less.
|
||||
*/
|
||||
class TransactionFeeCheck
|
||||
{
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: A transaction must not create XRP and should only destroy
|
||||
* the XRP fee.
|
||||
*
|
||||
* We iterate through all account roots, payment channels and escrow entries
|
||||
* that were modified and calculate the net change in XRP caused by the
|
||||
* transactions.
|
||||
*/
|
||||
class XRPNotCreated
|
||||
{
|
||||
std::int64_t drops_ = 0;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: we cannot remove an account ledger entry
|
||||
*
|
||||
* We iterate all account roots that were modified, and ensure that any that
|
||||
* were present before the transaction was applied continue to be present
|
||||
* afterwards unless they were explicitly deleted by a successful
|
||||
* AccountDelete transaction.
|
||||
*/
|
||||
class AccountRootsNotDeleted
|
||||
{
|
||||
std::uint32_t accountsDeleted_ = 0;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: a deleted account must not have any objects left
|
||||
*
|
||||
* We iterate all deleted account roots, and ensure that there are no
|
||||
* objects left that are directly accessible with that account's ID.
|
||||
*
|
||||
* There should only be one deleted account, but that's checked by
|
||||
* AccountRootsNotDeleted. This invariant will handle multiple deleted account
|
||||
* roots without a problem.
|
||||
*/
|
||||
class AccountRootsDeletedClean
|
||||
{
|
||||
// Pair is <before, after>. Before is used for most of the checks, so that
|
||||
// if, for example, an object ID field is cleared, but the object is not
|
||||
// deleted, it can still be found. After is used specifically for any checks
|
||||
// that are expected as part of the deletion, such as zeroing out the
|
||||
// balance.
|
||||
std::vector<std::pair<std::shared_ptr<SLE const>, std::shared_ptr<SLE const>>> accountsDeleted_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: An account XRP balance must be in XRP and take a value
|
||||
* between 0 and INITIAL_XRP drops, inclusive.
|
||||
*
|
||||
* We iterate all account roots modified by the transaction and ensure that
|
||||
* their XRP balances are reasonable.
|
||||
*/
|
||||
class XRPBalanceChecks
|
||||
{
|
||||
bool bad_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: corresponding modified ledger entries should match in type
|
||||
* and added entries should be a valid type.
|
||||
*/
|
||||
class LedgerEntryTypesMatch
|
||||
{
|
||||
bool typeMismatch_ = false;
|
||||
bool invalidTypeAdded_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: Trust lines using XRP are not allowed.
|
||||
*
|
||||
* We iterate all the trust lines created by this transaction and ensure
|
||||
* that they are against a valid issuer.
|
||||
*/
|
||||
class NoXRPTrustLines
|
||||
{
|
||||
bool xrpTrustLine_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: Trust lines with deep freeze flag are not allowed if normal
|
||||
* freeze flag is not set.
|
||||
*
|
||||
* We iterate all the trust lines created by this transaction and ensure
|
||||
* that they don't have deep freeze flag set without normal freeze flag set.
|
||||
*/
|
||||
class NoDeepFreezeTrustLinesWithoutFreeze
|
||||
{
|
||||
bool deepFreezeWithoutFreeze_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: offers should be for non-negative amounts and must not
|
||||
* be XRP to XRP.
|
||||
*
|
||||
* Examine all offers modified by the transaction and ensure that there are
|
||||
* no offers which contain negative amounts or which exchange XRP for XRP.
|
||||
*/
|
||||
class NoBadOffers
|
||||
{
|
||||
bool bad_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: an escrow entry must take a value between 0 and
|
||||
* INITIAL_XRP drops exclusive.
|
||||
*/
|
||||
class NoZeroEscrow
|
||||
{
|
||||
bool bad_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: a new account root must be the consequence of a payment,
|
||||
* must have the right starting sequence, and the payment
|
||||
* may not create more than one new account root.
|
||||
*/
|
||||
class ValidNewAccountRoot
|
||||
{
|
||||
std::uint32_t accountsCreated_ = 0;
|
||||
std::uint32_t accountSeq_ = 0;
|
||||
bool pseudoAccount_ = false;
|
||||
std::uint32_t flags_ = 0;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: Token holder's trustline balance cannot be negative after
|
||||
* Clawback.
|
||||
*
|
||||
* We iterate all the trust lines affected by this transaction and ensure
|
||||
* that no more than one trustline is modified, and also holder's balance is
|
||||
* non-negative.
|
||||
*/
|
||||
class ValidClawback
|
||||
{
|
||||
std::uint32_t trustlinesChanged = 0;
|
||||
std::uint32_t mptokensChanged = 0;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: Pseudo-accounts have valid and consistent properties
|
||||
*
|
||||
* Pseudo-accounts have certain properties, and some of those properties are
|
||||
* unique to pseudo-accounts. Check that all pseudo-accounts are following the
|
||||
* rules, and that only pseudo-accounts look like pseudo-accounts.
|
||||
*
|
||||
*/
|
||||
class ValidPseudoAccounts
|
||||
{
|
||||
std::vector<std::string> errors_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: Some fields are unmodifiable
|
||||
*
|
||||
* Check that any fields specified as unmodifiable are not modified when the
|
||||
* object is modified. Creation and deletion are ignored.
|
||||
*
|
||||
*/
|
||||
class NoModifiedUnmodifiableFields
|
||||
{
|
||||
// Pair is <before, after>.
|
||||
std::set<std::pair<SLE::const_pointer, SLE::const_pointer>> changedEntries_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
// additional invariant checks can be declared above and then added to this
|
||||
// tuple
|
||||
using InvariantChecks = std::tuple<
|
||||
TransactionFeeCheck,
|
||||
AccountRootsNotDeleted,
|
||||
AccountRootsDeletedClean,
|
||||
LedgerEntryTypesMatch,
|
||||
XRPBalanceChecks,
|
||||
XRPNotCreated,
|
||||
NoXRPTrustLines,
|
||||
NoDeepFreezeTrustLinesWithoutFreeze,
|
||||
TransfersNotFrozen,
|
||||
NoBadOffers,
|
||||
NoZeroEscrow,
|
||||
ValidNewAccountRoot,
|
||||
ValidNFTokenPage,
|
||||
NFTokenCountTracking,
|
||||
ValidClawback,
|
||||
ValidMPTIssuance,
|
||||
ValidPermissionedDomain,
|
||||
ValidPermissionedDEX,
|
||||
ValidAMM,
|
||||
NoModifiedUnmodifiableFields,
|
||||
ValidPseudoAccounts,
|
||||
ValidLoanBroker,
|
||||
ValidLoan,
|
||||
ValidVault>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
*
|
||||
* @return std::tuple of instances that implement the required invariant check
|
||||
* methods
|
||||
*
|
||||
* @see xrpl::InvariantChecker_PROTOTYPE
|
||||
*/
|
||||
inline InvariantChecks
|
||||
getInvariantChecks()
|
||||
{
|
||||
return InvariantChecks{};
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
60
include/xrpl/tx/invariants/InvariantCheckPrivilege.h
Normal file
60
include/xrpl/tx/invariants/InvariantCheckPrivilege.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/*
|
||||
assert(enforce)
|
||||
|
||||
There are several asserts (or XRPL_ASSERTs) in invariant check files that check
|
||||
a variable named `enforce` when an invariant fails. At first glance, those
|
||||
asserts may look incorrect, but they are not.
|
||||
|
||||
Those asserts take advantage of two facts:
|
||||
1. `asserts` are not (normally) executed in release builds.
|
||||
2. Invariants should *never* fail, except in tests that specifically modify
|
||||
the open ledger to break them.
|
||||
|
||||
This makes `assert(enforce)` sort of a second-layer of invariant enforcement
|
||||
aimed at _developers_. It's designed to fire if a developer writes code that
|
||||
violates an invariant, and runs it in unit tests or a develop build that _does
|
||||
not have the relevant amendments enabled_. It's intentionally a pain in the neck
|
||||
so that bad code gets caught and fixed as early as possible.
|
||||
*/
|
||||
|
||||
enum Privilege {
|
||||
noPriv = 0x0000, // The transaction can not do any of the enumerated operations
|
||||
createAcct = 0x0001, // The transaction can create a new ACCOUNT_ROOT object.
|
||||
createPseudoAcct = 0x0002, // The transaction can create a pseudo account,
|
||||
// which implies createAcct
|
||||
mustDeleteAcct = 0x0004, // The transaction must delete an ACCOUNT_ROOT object
|
||||
mayDeleteAcct = 0x0008, // The transaction may delete an ACCOUNT_ROOT
|
||||
// object, but does not have to
|
||||
overrideFreeze = 0x0010, // The transaction can override some freeze rules
|
||||
changeNFTCounts = 0x0020, // The transaction can mint or burn an NFT
|
||||
createMPTIssuance = 0x0040, // The transaction can create a new MPT issuance
|
||||
destroyMPTIssuance = 0x0080, // The transaction can destroy an MPT issuance
|
||||
mustAuthorizeMPT = 0x0100, // The transaction MUST create or delete an MPT
|
||||
// object (except by issuer)
|
||||
mayAuthorizeMPT = 0x0200, // The transaction MAY create or delete an MPT
|
||||
// object (except by issuer)
|
||||
mayDeleteMPT = 0x0400, // The transaction MAY delete an MPT object. May not create.
|
||||
mustModifyVault = 0x0800, // The transaction must modify, delete or create, a vault
|
||||
mayModifyVault = 0x1000, // The transaction MAY modify, delete or create, a vault
|
||||
};
|
||||
|
||||
constexpr Privilege
|
||||
operator|(Privilege lhs, Privilege rhs)
|
||||
{
|
||||
return safe_cast<Privilege>(
|
||||
safe_cast<std::underlying_type_t<Privilege>>(lhs) |
|
||||
safe_cast<std::underlying_type_t<Privilege>>(rhs));
|
||||
}
|
||||
|
||||
bool
|
||||
hasPrivilege(STTx const& tx, Privilege priv);
|
||||
|
||||
} // namespace xrpl
|
||||
75
include/xrpl/tx/invariants/LoanInvariant.h
Normal file
75
include/xrpl/tx/invariants/LoanInvariant.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Invariants: Loan brokers are internally consistent
|
||||
*
|
||||
* 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one
|
||||
* node (the root), which will only hold entries for `RippleState` or
|
||||
* `MPToken` objects.
|
||||
*
|
||||
*/
|
||||
class ValidLoanBroker
|
||||
{
|
||||
// Not all of these elements will necessarily be populated. Remaining items
|
||||
// will be looked up as needed.
|
||||
struct BrokerInfo
|
||||
{
|
||||
SLE::const_pointer brokerBefore = nullptr;
|
||||
// After is used for most of the checks, except
|
||||
// those that check changed values.
|
||||
SLE::const_pointer brokerAfter = nullptr;
|
||||
};
|
||||
// Collect all the LoanBrokers found directly or indirectly through
|
||||
// pseudo-accounts. Key is the brokerID / index. It will be used to find the
|
||||
// LoanBroker object if brokerBefore and brokerAfter are nullptr
|
||||
std::map<uint256, BrokerInfo> brokers_;
|
||||
// Collect all the modified trust lines. Their high and low accounts will be
|
||||
// loaded to look for LoanBroker pseudo-accounts.
|
||||
std::vector<SLE::const_pointer> lines_;
|
||||
// Collect all the modified MPTokens. Their accounts will be loaded to look
|
||||
// for LoanBroker pseudo-accounts.
|
||||
std::vector<SLE::const_pointer> mpts_;
|
||||
|
||||
bool
|
||||
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j) const;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: Loans are internally consistent
|
||||
*
|
||||
* 1. If `Loan.PaymentRemaining = 0` then `Loan.PrincipalOutstanding = 0`
|
||||
*
|
||||
*/
|
||||
class ValidLoan
|
||||
{
|
||||
// Pair is <before, after>. After is used for most of the checks, except
|
||||
// those that check changed values.
|
||||
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> loans_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
31
include/xrpl/tx/invariants/MPTInvariant.h
Normal file
31
include/xrpl/tx/invariants/MPTInvariant.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class ValidMPTIssuance
|
||||
{
|
||||
std::uint32_t mptIssuancesCreated_ = 0;
|
||||
std::uint32_t mptIssuancesDeleted_ = 0;
|
||||
|
||||
std::uint32_t mptokensCreated_ = 0;
|
||||
std::uint32_t mptokensDeleted_ = 0;
|
||||
// non-MPT transactions may attempt to create
|
||||
// MPToken by an issuer
|
||||
bool mptCreatedByIssuer_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
70
include/xrpl/tx/invariants/NFTInvariant.h
Normal file
70
include/xrpl/tx/invariants/NFTInvariant.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Invariant: Validates several invariants for NFToken pages.
|
||||
*
|
||||
* The following checks are made:
|
||||
* - The page is correctly associated with the owner.
|
||||
* - The page is correctly ordered between the next and previous links.
|
||||
* - The page contains at least one and no more than 32 NFTokens.
|
||||
* - The NFTokens on this page do not belong on a lower or higher page.
|
||||
* - The NFTokens are correctly sorted on the page.
|
||||
* - Each URI, if present, is not empty.
|
||||
*/
|
||||
class ValidNFTokenPage
|
||||
{
|
||||
bool badEntry_ = false;
|
||||
bool badLink_ = false;
|
||||
bool badSort_ = false;
|
||||
bool badURI_ = false;
|
||||
bool invalidSize_ = false;
|
||||
bool deletedFinalPage_ = false;
|
||||
bool deletedLink_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: Validates counts of NFTokens after all transaction types.
|
||||
*
|
||||
* The following checks are made:
|
||||
* - The number of minted or burned NFTokens can only be changed by
|
||||
* NFTokenMint or NFTokenBurn transactions.
|
||||
* - A successful NFTokenMint must increase the number of NFTokens.
|
||||
* - A failed NFTokenMint must not change the number of minted NFTokens.
|
||||
* - An NFTokenMint transaction cannot change the number of burned NFTokens.
|
||||
* - A successful NFTokenBurn must increase the number of burned NFTokens.
|
||||
* - A failed NFTokenBurn must not change the number of burned NFTokens.
|
||||
* - An NFTokenBurn transaction cannot change the number of minted NFTokens.
|
||||
*/
|
||||
class NFTokenCountTracking
|
||||
{
|
||||
std::uint32_t beforeMintedTotal = 0;
|
||||
std::uint32_t beforeBurnedTotal = 0;
|
||||
std::uint32_t afterMintedTotal = 0;
|
||||
std::uint32_t afterBurnedTotal = 0;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
25
include/xrpl/tx/invariants/PermissionedDEXInvariant.h
Normal file
25
include/xrpl/tx/invariants/PermissionedDEXInvariant.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class ValidPermissionedDEX
|
||||
{
|
||||
bool regularOffers_ = false;
|
||||
bool badHybrids_ = false;
|
||||
hash_set<uint256> domains_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
41
include/xrpl/tx/invariants/PermissionedDomainInvariant.h
Normal file
41
include/xrpl/tx/invariants/PermissionedDomainInvariant.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Invariants: Permissioned Domains must have some rules and
|
||||
* AcceptedCredentials must have length between 1 and 10 inclusive.
|
||||
*
|
||||
* Since only permissions constitute rules, an empty credentials list
|
||||
* means that there are no rules and the invariant is violated.
|
||||
*
|
||||
* Credentials must be sorted and no duplicates allowed
|
||||
*
|
||||
*/
|
||||
class ValidPermissionedDomain
|
||||
{
|
||||
struct SleStatus
|
||||
{
|
||||
std::size_t credentialsSize_{0};
|
||||
bool isSorted_ = false;
|
||||
bool isUnique_ = false;
|
||||
bool isDelete_ = false;
|
||||
};
|
||||
std::vector<SleStatus> sleStatus_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
77
include/xrpl/tx/invariants/VaultInvariant.h
Normal file
77
include/xrpl/tx/invariants/VaultInvariant.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/*
|
||||
* @brief Invariants: Vault object and MPTokenIssuance for vault shares
|
||||
*
|
||||
* - vault deleted and vault created is empty
|
||||
* - vault created must be linked to pseudo-account for shares and assets
|
||||
* - vault must have MPTokenIssuance for shares
|
||||
* - vault without shares outstanding must have no shares
|
||||
* - loss unrealized does not exceed the difference between assets total and
|
||||
* assets available
|
||||
* - assets available do not exceed assets total
|
||||
* - vault deposit increases assets and share issuance, and adds to:
|
||||
* total assets, assets available, shares outstanding
|
||||
* - vault withdrawal and clawback reduce assets and share issuance, and
|
||||
* subtracts from: total assets, assets available, shares outstanding
|
||||
* - vault set must not alter the vault assets or shares balance
|
||||
* - no vault transaction can change loss unrealized (it's updated by loan
|
||||
* transactions)
|
||||
*
|
||||
*/
|
||||
class ValidVault
|
||||
{
|
||||
Number static constexpr zero{};
|
||||
|
||||
struct Vault final
|
||||
{
|
||||
uint256 key = beast::zero;
|
||||
Asset asset = {};
|
||||
AccountID pseudoId = {};
|
||||
AccountID owner = {};
|
||||
uint192 shareMPTID = beast::zero;
|
||||
Number assetsTotal = 0;
|
||||
Number assetsAvailable = 0;
|
||||
Number assetsMaximum = 0;
|
||||
Number lossUnrealized = 0;
|
||||
|
||||
Vault static make(SLE const&);
|
||||
};
|
||||
|
||||
struct Shares final
|
||||
{
|
||||
MPTIssue share = {};
|
||||
std::uint64_t sharesTotal = 0;
|
||||
std::uint64_t sharesMaximum = 0;
|
||||
|
||||
Shares static make(SLE const&);
|
||||
};
|
||||
|
||||
std::vector<Vault> afterVault_ = {};
|
||||
std::vector<Shares> afterMPTs_ = {};
|
||||
std::vector<Vault> beforeVault_ = {};
|
||||
std::vector<Shares> beforeMPTs_ = {};
|
||||
std::unordered_map<uint256, Number> deltas_ = {};
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -943,6 +943,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
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/InvariantCheck.h>
|
||||
#include <xrpl/tx/invariants/InvariantCheck.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
305
src/libxrpl/tx/invariants/AMMInvariant.cpp
Normal file
305
src/libxrpl/tx/invariants/AMMInvariant.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
#include <xrpl/tx/invariants/AMMInvariant.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/tx/transactors/AMM/AMMHelpers.h>
|
||||
#include <xrpl/tx/transactors/AMM/AMMUtils.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
ValidAMM::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (isDelete)
|
||||
return;
|
||||
|
||||
if (after)
|
||||
{
|
||||
auto const type = after->getType();
|
||||
// AMM object changed
|
||||
if (type == ltAMM)
|
||||
{
|
||||
ammAccount_ = after->getAccountID(sfAccount);
|
||||
lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
|
||||
}
|
||||
// AMM pool changed
|
||||
else if (
|
||||
(type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) ||
|
||||
(type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)))
|
||||
{
|
||||
ammPoolChanged_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (before)
|
||||
{
|
||||
// AMM object changed
|
||||
if (before->getType() == ltAMM)
|
||||
{
|
||||
lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
validBalances(
|
||||
STAmount const& amount,
|
||||
STAmount const& amount2,
|
||||
STAmount const& lptAMMBalance,
|
||||
ValidAMM::ZeroAllowed zeroAllowed)
|
||||
{
|
||||
bool const positive =
|
||||
amount > beast::zero && amount2 > beast::zero && lptAMMBalance > beast::zero;
|
||||
if (zeroAllowed == ValidAMM::ZeroAllowed::Yes)
|
||||
return positive ||
|
||||
(amount == beast::zero && amount2 == beast::zero && lptAMMBalance == beast::zero);
|
||||
return positive;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
|
||||
{
|
||||
if (lptAMMBalanceAfter_ != lptAMMBalanceBefore_ || ammPoolChanged_)
|
||||
{
|
||||
// LPTokens and the pool can not change on vote
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMVote invariant failed: " << lptAMMBalanceBefore_.value_or(STAmount{})
|
||||
<< " " << lptAMMBalanceAfter_.value_or(STAmount{}) << " "
|
||||
<< ammPoolChanged_;
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
|
||||
{
|
||||
if (ammPoolChanged_)
|
||||
{
|
||||
// The pool can not change on bid
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMBid invariant failed: pool changed";
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
// LPTokens are burnt, therefore there should be fewer LPTokens
|
||||
else if (
|
||||
lptAMMBalanceBefore_ && lptAMMBalanceAfter_ &&
|
||||
(*lptAMMBalanceAfter_ > *lptAMMBalanceBefore_ || *lptAMMBalanceAfter_ <= beast::zero))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_ << " "
|
||||
<< *lptAMMBalanceAfter_;
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeCreate(
|
||||
STTx const& tx,
|
||||
ReadView const& view,
|
||||
bool enforce,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
if (!ammAccount_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMCreate invariant failed: AMM object is not created";
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const [amount, amount2] = ammPoolHolds(
|
||||
view,
|
||||
*ammAccount_,
|
||||
tx[sfAmount].get<Issue>(),
|
||||
tx[sfAmount2].get<Issue>(),
|
||||
fhIGNORE_FREEZE,
|
||||
j);
|
||||
// Create invariant:
|
||||
// sqrt(amount * amount2) == LPTokens
|
||||
// all balances are greater than zero
|
||||
if (!validBalances(amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
|
||||
ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) != *lptAMMBalanceAfter_)
|
||||
{
|
||||
JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " " << amount2 << " "
|
||||
<< *lptAMMBalanceAfter_;
|
||||
if (enforce)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const
|
||||
{
|
||||
if (ammAccount_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
std::string const msg = (res == tesSUCCESS) ? "AMM object is not deleted on tesSUCCESS"
|
||||
: "AMM object is changed on tecINCOMPLETE";
|
||||
JLOG(j.error()) << "AMMDelete invariant failed: " << msg;
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
|
||||
{
|
||||
if (ammAccount_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMM swap invariant failed: AMM object changed";
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::generalInvariant(
|
||||
xrpl::STTx const& tx,
|
||||
xrpl::ReadView const& view,
|
||||
ZeroAllowed zeroAllowed,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
auto const [amount, amount2] = ammPoolHolds(
|
||||
view,
|
||||
*ammAccount_,
|
||||
tx[sfAsset].get<Issue>(),
|
||||
tx[sfAsset2].get<Issue>(),
|
||||
fhIGNORE_FREEZE,
|
||||
j);
|
||||
// Deposit and Withdrawal invariant:
|
||||
// sqrt(amount * amount2) >= LPTokens
|
||||
// all balances are greater than zero
|
||||
// unless on last withdrawal
|
||||
auto const poolProductMean = root2(amount * amount2);
|
||||
bool const nonNegativeBalances =
|
||||
validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed);
|
||||
bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_;
|
||||
// Allow for a small relative error if strongInvariantCheck fails
|
||||
auto weakInvariantCheck = [&]() {
|
||||
return *lptAMMBalanceAfter_ != beast::zero &&
|
||||
withinRelativeDistance(poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11});
|
||||
};
|
||||
if (!nonNegativeBalances || (!strongInvariantCheck && !weakInvariantCheck()))
|
||||
{
|
||||
JLOG(j.error()) << "AMM " << tx.getTxnType()
|
||||
<< " invariant failed: " << tx.getHash(HashPrefix::transactionID) << " "
|
||||
<< ammPoolChanged_ << " " << amount << " " << amount2 << " "
|
||||
<< poolProductMean << " " << lptAMMBalanceAfter_->getText() << " "
|
||||
<< ((*lptAMMBalanceAfter_ == beast::zero)
|
||||
? Number{1}
|
||||
: ((*lptAMMBalanceAfter_ - poolProductMean) / poolProductMean));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeDeposit(
|
||||
xrpl::STTx const& tx,
|
||||
xrpl::ReadView const& view,
|
||||
bool enforce,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
if (!ammAccount_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted";
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalizeWithdraw(
|
||||
xrpl::STTx const& tx,
|
||||
xrpl::ReadView const& view,
|
||||
bool enforce,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
if (!ammAccount_)
|
||||
{
|
||||
// Last Withdraw or Clawback deleted AMM
|
||||
}
|
||||
else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j))
|
||||
{
|
||||
if (enforce)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidAMM::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Delete may return tecINCOMPLETE if there are too many
|
||||
// trustlines to delete.
|
||||
if (result != tesSUCCESS && result != tecINCOMPLETE)
|
||||
return true;
|
||||
|
||||
bool const enforce = view.rules().enabled(fixAMMv1_3);
|
||||
|
||||
switch (tx.getTxnType())
|
||||
{
|
||||
case ttAMM_CREATE:
|
||||
return finalizeCreate(tx, view, enforce, j);
|
||||
case ttAMM_DEPOSIT:
|
||||
return finalizeDeposit(tx, view, enforce, j);
|
||||
case ttAMM_CLAWBACK:
|
||||
case ttAMM_WITHDRAW:
|
||||
return finalizeWithdraw(tx, view, enforce, j);
|
||||
case ttAMM_BID:
|
||||
return finalizeBid(enforce, j);
|
||||
case ttAMM_VOTE:
|
||||
return finalizeVote(enforce, j);
|
||||
case ttAMM_DELETE:
|
||||
return finalizeDelete(enforce, result, j);
|
||||
case ttCHECK_CASH:
|
||||
case ttOFFER_CREATE:
|
||||
case ttPAYMENT:
|
||||
return finalizeDEX(enforce, j);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
278
src/libxrpl/tx/invariants/FreezeInvariant.cpp
Normal file
278
src/libxrpl/tx/invariants/FreezeInvariant.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
#include <xrpl/tx/invariants/FreezeInvariant.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
TransfersNotFrozen::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
/*
|
||||
* A trust line freeze state alone doesn't determine if a transfer is
|
||||
* frozen. The transfer must be examined "end-to-end" because both sides of
|
||||
* the transfer may have different freeze states and freeze impact depends
|
||||
* on the transfer direction. This is why first we need to track the
|
||||
* transfers using IssuerChanges senders/receivers.
|
||||
*
|
||||
* Only in validateIssuerChanges, after we collected all changes can we
|
||||
* determine if the transfer is valid.
|
||||
*/
|
||||
if (!isValidEntry(before, after))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto const balanceChange = calculateBalanceChange(before, after, isDelete);
|
||||
if (balanceChange.signum() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
recordBalanceChanges(after, balanceChange);
|
||||
}
|
||||
|
||||
bool
|
||||
TransfersNotFrozen::finalize(
|
||||
STTx const& tx,
|
||||
TER const ter,
|
||||
XRPAmount const fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
/*
|
||||
* We check this invariant regardless of deep freeze amendment status,
|
||||
* allowing for detection and logging of potential issues even when the
|
||||
* amendment is disabled.
|
||||
*
|
||||
* If an exploit that allows moving frozen assets is discovered,
|
||||
* we can alert operators who monitor fatal messages and trigger assert in
|
||||
* debug builds for an early warning.
|
||||
*
|
||||
* In an unlikely event that an exploit is found, this early detection
|
||||
* enables encouraging the UNL to expedite deep freeze amendment activation
|
||||
* or deploy hotfixes via new amendments. In case of a new amendment, we'd
|
||||
* only have to change this line setting 'enforce' variable.
|
||||
* enforce = view.rules().enabled(featureDeepFreeze) ||
|
||||
* view.rules().enabled(fixFreezeExploit);
|
||||
*/
|
||||
[[maybe_unused]] bool const enforce = view.rules().enabled(featureDeepFreeze);
|
||||
|
||||
for (auto const& [issue, changes] : balanceChanges_)
|
||||
{
|
||||
auto const issuerSle = findIssuer(issue.account, view);
|
||||
// It should be impossible for the issuer to not be found, but check
|
||||
// just in case so rippled doesn't crash in release.
|
||||
if (!issuerSle)
|
||||
{
|
||||
// The comment above starting with "assert(enforce)" explains this
|
||||
// assert.
|
||||
XRPL_ASSERT(
|
||||
enforce,
|
||||
"xrpl::TransfersNotFrozen::finalize : enforce "
|
||||
"invariant.");
|
||||
if (enforce)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TransfersNotFrozen::isValidEntry(
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
// `after` can never be null, even if the trust line is deleted.
|
||||
XRPL_ASSERT(after, "xrpl::TransfersNotFrozen::isValidEntry : valid after.");
|
||||
if (!after)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (after->getType() == ltACCOUNT_ROOT)
|
||||
{
|
||||
possibleIssuers_.emplace(after->at(sfAccount), after);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* While LedgerEntryTypesMatch invariant also checks types, all invariants
|
||||
* are processed regardless of previous failures.
|
||||
*
|
||||
* This type check is still necessary here because it prevents potential
|
||||
* issues in subsequent processing.
|
||||
*/
|
||||
return after->getType() == ltRIPPLE_STATE && (!before || before->getType() == ltRIPPLE_STATE);
|
||||
}
|
||||
|
||||
STAmount
|
||||
TransfersNotFrozen::calculateBalanceChange(
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after,
|
||||
bool isDelete)
|
||||
{
|
||||
auto const getBalance = [](auto const& line, auto const& other, bool zero) {
|
||||
STAmount amt = line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
|
||||
return zero ? amt.zeroed() : amt;
|
||||
};
|
||||
|
||||
/* Trust lines can be created dynamically by other transactions such as
|
||||
* Payment and OfferCreate that cross offers. Such trust line won't be
|
||||
* created frozen, but the sender might be, so the starting balance must be
|
||||
* treated as zero.
|
||||
*/
|
||||
auto const balanceBefore = getBalance(before, after, false);
|
||||
|
||||
/* Same as above, trust lines can be dynamically deleted, and for frozen
|
||||
* trust lines, payments not involving the issuer must be blocked. This is
|
||||
* achieved by treating the final balance as zero when isDelete=true to
|
||||
* ensure frozen line restrictions are enforced even during deletion.
|
||||
*/
|
||||
auto const balanceAfter = getBalance(after, before, isDelete);
|
||||
|
||||
return balanceAfter - balanceBefore;
|
||||
}
|
||||
|
||||
void
|
||||
TransfersNotFrozen::recordBalance(Issue const& issue, BalanceChange change)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
change.balanceChangeSign,
|
||||
"xrpl::TransfersNotFrozen::recordBalance : valid trustline "
|
||||
"balance sign.");
|
||||
auto& changes = balanceChanges_[issue];
|
||||
if (change.balanceChangeSign < 0)
|
||||
changes.senders.emplace_back(std::move(change));
|
||||
else
|
||||
changes.receivers.emplace_back(std::move(change));
|
||||
}
|
||||
|
||||
void
|
||||
TransfersNotFrozen::recordBalanceChanges(
|
||||
std::shared_ptr<SLE const> const& after,
|
||||
STAmount const& balanceChange)
|
||||
{
|
||||
auto const balanceChangeSign = balanceChange.signum();
|
||||
auto const currency = after->at(sfBalance).getCurrency();
|
||||
|
||||
// Change from low account's perspective, which is trust line default
|
||||
recordBalance({currency, after->at(sfHighLimit).getIssuer()}, {after, balanceChangeSign});
|
||||
|
||||
// Change from high account's perspective, which reverses the sign.
|
||||
recordBalance({currency, after->at(sfLowLimit).getIssuer()}, {after, -balanceChangeSign});
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE const>
|
||||
TransfersNotFrozen::findIssuer(AccountID const& issuerID, ReadView const& view)
|
||||
{
|
||||
if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return view.read(keylet::account(issuerID));
|
||||
}
|
||||
|
||||
bool
|
||||
TransfersNotFrozen::validateIssuerChanges(
|
||||
std::shared_ptr<SLE const> const& issuer,
|
||||
IssuerChanges const& changes,
|
||||
STTx const& tx,
|
||||
beast::Journal const& j,
|
||||
bool enforce)
|
||||
{
|
||||
if (!issuer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze);
|
||||
if (changes.receivers.empty() || changes.senders.empty())
|
||||
{
|
||||
/* If there are no receivers, then the holder(s) are returning
|
||||
* their tokens to the issuer. Likewise, if there are no
|
||||
* senders, then the issuer is issuing tokens to the holder(s).
|
||||
* This is allowed regardless of the issuer's freeze flags. (The
|
||||
* holder may have contradicting freeze flags, but that will be
|
||||
* checked when the holder is treated as issuer.)
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto const& actors : {changes.senders, changes.receivers})
|
||||
{
|
||||
for (auto const& change : actors)
|
||||
{
|
||||
bool const high = change.line->at(sfLowLimit).getIssuer() == issuer->at(sfAccount);
|
||||
|
||||
if (!validateFrozenState(change, high, tx, j, enforce, globalFreeze))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TransfersNotFrozen::validateFrozenState(
|
||||
BalanceChange const& change,
|
||||
bool high,
|
||||
STTx const& tx,
|
||||
beast::Journal const& j,
|
||||
bool enforce,
|
||||
bool globalFreeze)
|
||||
{
|
||||
bool const freeze =
|
||||
change.balanceChangeSign < 0 && change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze);
|
||||
bool const deepFreeze = change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze);
|
||||
bool const frozen = globalFreeze || deepFreeze || freeze;
|
||||
|
||||
bool const isAMMLine = change.line->isFlag(lsfAMMNode);
|
||||
|
||||
if (!frozen)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// AMMClawbacks are allowed to override some freeze rules
|
||||
if ((!isAMMLine || globalFreeze) && hasPrivilege(tx, overrideFreeze))
|
||||
{
|
||||
JLOG(j.debug()) << "Invariant check allowing funds to be moved "
|
||||
<< (change.balanceChangeSign > 0 ? "to" : "from")
|
||||
<< " a frozen trustline for AMMClawback " << tx.getTransactionID();
|
||||
return true;
|
||||
}
|
||||
|
||||
JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for "
|
||||
<< tx.getTransactionID();
|
||||
// The comment above starting with "assert(enforce)" explains this assert.
|
||||
XRPL_ASSERT(
|
||||
enforce,
|
||||
"xrpl::TransfersNotFrozen::validateFrozenState : enforce "
|
||||
"invariant.");
|
||||
|
||||
if (enforce)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
1009
src/libxrpl/tx/invariants/InvariantCheck.cpp
Normal file
1009
src/libxrpl/tx/invariants/InvariantCheck.cpp
Normal file
File diff suppressed because it is too large
Load Diff
278
src/libxrpl/tx/invariants/LoanInvariant.cpp
Normal file
278
src/libxrpl/tx/invariants/LoanInvariant.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
#include <xrpl/tx/invariants/LoanInvariant.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
ValidLoanBroker::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (after)
|
||||
{
|
||||
if (after->getType() == ltLOAN_BROKER)
|
||||
{
|
||||
auto& broker = brokers_[after->key()];
|
||||
broker.brokerBefore = before;
|
||||
broker.brokerAfter = after;
|
||||
}
|
||||
else if (after->getType() == ltACCOUNT_ROOT && after->isFieldPresent(sfLoanBrokerID))
|
||||
{
|
||||
auto const& loanBrokerID = after->at(sfLoanBrokerID);
|
||||
// create an entry if one doesn't already exist
|
||||
brokers_.emplace(loanBrokerID, BrokerInfo{});
|
||||
}
|
||||
else if (after->getType() == ltRIPPLE_STATE)
|
||||
{
|
||||
lines_.emplace_back(after);
|
||||
}
|
||||
else if (after->getType() == ltMPTOKEN)
|
||||
{
|
||||
mpts_.emplace_back(after);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ValidLoanBroker::goodZeroDirectory(
|
||||
ReadView const& view,
|
||||
SLE::const_ref dir,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
auto const next = dir->at(~sfIndexNext);
|
||||
auto const prev = dir->at(~sfIndexPrevious);
|
||||
if ((prev && *prev) || (next && *next))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
|
||||
"OwnerCount has multiple directory pages";
|
||||
return false;
|
||||
}
|
||||
auto indexes = dir->getFieldV256(sfIndexes);
|
||||
if (indexes.size() > 1)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
|
||||
"OwnerCount has multiple indexes in the Directory root";
|
||||
return false;
|
||||
}
|
||||
if (indexes.size() == 1)
|
||||
{
|
||||
auto const index = indexes.value().front();
|
||||
auto const sle = view.read(keylet::unchecked(index));
|
||||
if (!sle)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker directory corrupt";
|
||||
return false;
|
||||
}
|
||||
if (sle->getType() != ltRIPPLE_STATE && sle->getType() != ltMPTOKEN)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
|
||||
"OwnerCount has an unexpected entry in the directory";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidLoanBroker::finalize(
|
||||
STTx const& tx,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Loan Brokers will not exist on ledger if the Lending Protocol amendment
|
||||
// is not enabled, so there's no need to check it.
|
||||
|
||||
for (auto const& line : lines_)
|
||||
{
|
||||
for (auto const& field : {&sfLowLimit, &sfHighLimit})
|
||||
{
|
||||
auto const account = view.read(keylet::account(line->at(*field).getIssuer()));
|
||||
// This Invariant doesn't know about the rules for Trust Lines, so
|
||||
// if the account is missing, don't treat it as an error. This
|
||||
// loop is only concerned with finding Broker pseudo-accounts
|
||||
if (account && account->isFieldPresent(sfLoanBrokerID))
|
||||
{
|
||||
auto const& loanBrokerID = account->at(sfLoanBrokerID);
|
||||
// create an entry if one doesn't already exist
|
||||
brokers_.emplace(loanBrokerID, BrokerInfo{});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto const& mpt : mpts_)
|
||||
{
|
||||
auto const account = view.read(keylet::account(mpt->at(sfAccount)));
|
||||
// This Invariant doesn't know about the rules for MPTokens, so
|
||||
// if the account is missing, don't treat is as an error. This
|
||||
// loop is only concerned with finding Broker pseudo-accounts
|
||||
if (account && account->isFieldPresent(sfLoanBrokerID))
|
||||
{
|
||||
auto const& loanBrokerID = account->at(sfLoanBrokerID);
|
||||
// create an entry if one doesn't already exist
|
||||
brokers_.emplace(loanBrokerID, BrokerInfo{});
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& [brokerID, broker] : brokers_)
|
||||
{
|
||||
auto const& after =
|
||||
broker.brokerAfter ? broker.brokerAfter : view.read(keylet::loanbroker(brokerID));
|
||||
|
||||
if (!after)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker missing";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const& before = broker.brokerBefore;
|
||||
|
||||
// https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3123-invariants
|
||||
// If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most
|
||||
// one node (the root), which will only hold entries for `RippleState`
|
||||
// or `MPToken` objects.
|
||||
if (after->at(sfOwnerCount) == 0)
|
||||
{
|
||||
auto const dir = view.read(keylet::ownerDir(after->at(sfAccount)));
|
||||
if (dir)
|
||||
{
|
||||
if (!goodZeroDirectory(view, dir, j))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (before && before->at(sfLoanSequence) > after->at(sfLoanSequence))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker sequence number "
|
||||
"decreased";
|
||||
return false;
|
||||
}
|
||||
if (after->at(sfDebtTotal) < 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker debt total is negative";
|
||||
return false;
|
||||
}
|
||||
if (after->at(sfCoverAvailable) < 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available is negative";
|
||||
return false;
|
||||
}
|
||||
auto const vault = view.read(keylet::vault(after->at(sfVaultID)));
|
||||
if (!vault)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker vault ID is invalid";
|
||||
return false;
|
||||
}
|
||||
auto const& vaultAsset = vault->at(sfAsset);
|
||||
if (after->at(sfCoverAvailable) < accountHolds(
|
||||
view,
|
||||
after->at(sfAccount),
|
||||
vaultAsset,
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
j))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available "
|
||||
"is less than pseudo-account asset balance";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
ValidLoan::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (after && after->getType() == ltLOAN)
|
||||
{
|
||||
loans_.emplace_back(before, after);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ValidLoan::finalize(
|
||||
STTx const& tx,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Loans will not exist on ledger if the Lending Protocol amendment
|
||||
// is not enabled, so there's no need to check it.
|
||||
|
||||
for (auto const& [before, after] : loans_)
|
||||
{
|
||||
// https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3223-invariants
|
||||
// If `Loan.PaymentRemaining = 0` then the loan MUST be fully paid off
|
||||
if (after->at(sfPaymentRemaining) == 0 &&
|
||||
(after->at(sfTotalValueOutstanding) != beast::zero ||
|
||||
after->at(sfPrincipalOutstanding) != beast::zero ||
|
||||
after->at(sfManagementFeeOutstanding) != beast::zero))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan with zero payments "
|
||||
"remaining has not been paid off";
|
||||
return false;
|
||||
}
|
||||
// If `Loan.PaymentRemaining != 0` then the loan MUST NOT be fully paid
|
||||
// off
|
||||
if (after->at(sfPaymentRemaining) != 0 &&
|
||||
after->at(sfTotalValueOutstanding) == beast::zero &&
|
||||
after->at(sfPrincipalOutstanding) == beast::zero &&
|
||||
after->at(sfManagementFeeOutstanding) == beast::zero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan with zero payments "
|
||||
"remaining has not been paid off";
|
||||
return false;
|
||||
}
|
||||
if (before && (before->isFlag(lsfLoanOverpayment) != after->isFlag(lsfLoanOverpayment)))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Loan Overpayment flag changed";
|
||||
return false;
|
||||
}
|
||||
// Must not be negative - STNumber
|
||||
for (auto const field :
|
||||
{&sfLoanServiceFee,
|
||||
&sfLatePaymentFee,
|
||||
&sfClosePaymentFee,
|
||||
&sfPrincipalOutstanding,
|
||||
&sfTotalValueOutstanding,
|
||||
&sfManagementFeeOutstanding})
|
||||
{
|
||||
if (after->at(*field) < 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: " << field->getName() << " is negative ";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Must be positive - STNumber
|
||||
for (auto const field : {
|
||||
&sfPeriodicPayment,
|
||||
})
|
||||
{
|
||||
if (after->at(*field) <= 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: " << field->getName()
|
||||
<< " is zero or negative ";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
192
src/libxrpl/tx/invariants/MPTInvariant.cpp
Normal file
192
src/libxrpl/tx/invariants/MPTInvariant.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
#include <xrpl/tx/invariants/MPTInvariant.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
ValidMPTIssuance::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (after && after->getType() == ltMPTOKEN_ISSUANCE)
|
||||
{
|
||||
if (isDelete)
|
||||
mptIssuancesDeleted_++;
|
||||
else if (!before)
|
||||
mptIssuancesCreated_++;
|
||||
}
|
||||
|
||||
if (after && after->getType() == ltMPTOKEN)
|
||||
{
|
||||
if (isDelete)
|
||||
mptokensDeleted_++;
|
||||
else if (!before)
|
||||
{
|
||||
mptokensCreated_++;
|
||||
MPTIssue const mptIssue{after->at(sfMPTokenIssuanceID)};
|
||||
if (mptIssue.getIssuer() == after->at(sfAccount))
|
||||
mptCreatedByIssuer_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ValidMPTIssuance::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const _fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (result == tesSUCCESS)
|
||||
{
|
||||
auto const& rules = view.rules();
|
||||
[[maybe_unused]]
|
||||
bool enforceCreatedByIssuer =
|
||||
rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol);
|
||||
if (mptCreatedByIssuer_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPToken created for the MPT issuer";
|
||||
// The comment above starting with "assert(enforce)" explains this
|
||||
// assert.
|
||||
XRPL_ASSERT_PARTS(
|
||||
enforceCreatedByIssuer, "xrpl::ValidMPTIssuance::finalize", "no issuer MPToken");
|
||||
if (enforceCreatedByIssuer)
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const txnType = tx.getTxnType();
|
||||
if (hasPrivilege(tx, createMPTIssuance))
|
||||
{
|
||||
if (mptIssuancesCreated_ == 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: transaction "
|
||||
"succeeded without creating a MPT issuance";
|
||||
}
|
||||
else if (mptIssuancesDeleted_ != 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: transaction "
|
||||
"succeeded while removing MPT issuances";
|
||||
}
|
||||
else if (mptIssuancesCreated_ > 1)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: transaction "
|
||||
"succeeded but created multiple issuances";
|
||||
}
|
||||
|
||||
return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
|
||||
}
|
||||
|
||||
if (hasPrivilege(tx, destroyMPTIssuance))
|
||||
{
|
||||
if (mptIssuancesDeleted_ == 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
|
||||
"succeeded without removing a MPT issuance";
|
||||
}
|
||||
else if (mptIssuancesCreated_ > 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
|
||||
"succeeded while creating MPT issuances";
|
||||
}
|
||||
else if (mptIssuancesDeleted_ > 1)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
|
||||
"succeeded but deleted multiple issuances";
|
||||
}
|
||||
|
||||
return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
|
||||
}
|
||||
|
||||
bool const lendingProtocolEnabled = view.rules().enabled(featureLendingProtocol);
|
||||
// ttESCROW_FINISH may authorize an MPT, but it can't have the
|
||||
// mayAuthorizeMPT privilege, because that may cause
|
||||
// non-amendment-gated side effects.
|
||||
bool const enforceEscrowFinish = (txnType == ttESCROW_FINISH) &&
|
||||
(view.rules().enabled(featureSingleAssetVault) || lendingProtocolEnabled);
|
||||
if (hasPrivilege(tx, mustAuthorizeMPT | mayAuthorizeMPT) || enforceEscrowFinish)
|
||||
{
|
||||
bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
|
||||
|
||||
if (mptIssuancesCreated_ > 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize "
|
||||
"succeeded but created MPT issuances";
|
||||
return false;
|
||||
}
|
||||
else if (mptIssuancesDeleted_ > 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize "
|
||||
"succeeded but deleted issuances";
|
||||
return false;
|
||||
}
|
||||
else if (lendingProtocolEnabled && mptokensCreated_ + mptokensDeleted_ > 1)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize succeeded "
|
||||
"but created/deleted bad number mptokens";
|
||||
return false;
|
||||
}
|
||||
else if (submittedByIssuer && (mptokensCreated_ > 0 || mptokensDeleted_ > 0))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by issuer "
|
||||
"succeeded but created/deleted mptokens";
|
||||
return false;
|
||||
}
|
||||
else if (
|
||||
!submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) &&
|
||||
(mptokensCreated_ + mptokensDeleted_ != 1))
|
||||
{
|
||||
// if the holder submitted this tx, then a mptoken must be
|
||||
// either created or deleted.
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by holder "
|
||||
"succeeded but created/deleted bad number of mptokens";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
if (txnType == ttESCROW_FINISH)
|
||||
{
|
||||
// ttESCROW_FINISH may authorize an MPT, but it can't have the
|
||||
// mayAuthorizeMPT privilege, because that may cause
|
||||
// non-amendment-gated side effects.
|
||||
XRPL_ASSERT_PARTS(
|
||||
!enforceEscrowFinish, "xrpl::ValidMPTIssuance::finalize", "not escrow finish tx");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasPrivilege(tx, mayDeleteMPT) && mptokensDeleted_ == 1 && mptokensCreated_ == 0 &&
|
||||
mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mptIssuancesCreated_ != 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
|
||||
}
|
||||
else if (mptIssuancesDeleted_ != 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
|
||||
}
|
||||
else if (mptokensCreated_ != 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
|
||||
}
|
||||
else if (mptokensDeleted_ != 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
|
||||
}
|
||||
|
||||
return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 && mptokensCreated_ == 0 &&
|
||||
mptokensDeleted_ == 0;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
274
src/libxrpl/tx/invariants/NFTInvariant.cpp
Normal file
274
src/libxrpl/tx/invariants/NFTInvariant.cpp
Normal file
@@ -0,0 +1,274 @@
|
||||
#include <xrpl/tx/invariants/NFTInvariant.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/nftPageMask.h>
|
||||
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
|
||||
#include <xrpl/tx/transactors/NFT/NFTokenUtils.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
ValidNFTokenPage::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
static constexpr uint256 const& pageBits = nft::pageMask;
|
||||
static constexpr uint256 const accountBits = ~pageBits;
|
||||
|
||||
if ((before && before->getType() != ltNFTOKEN_PAGE) ||
|
||||
(after && after->getType() != ltNFTOKEN_PAGE))
|
||||
return;
|
||||
|
||||
auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
|
||||
uint256 const account = sle->key() & accountBits;
|
||||
uint256 const hiLimit = sle->key() & pageBits;
|
||||
std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
|
||||
|
||||
// Make sure that any page links...
|
||||
// 1. Are properly associated with the owning account and
|
||||
// 2. The page is correctly ordered between links.
|
||||
if (prev)
|
||||
{
|
||||
if (account != (*prev & accountBits))
|
||||
badLink_ = true;
|
||||
|
||||
if (hiLimit <= (*prev & pageBits))
|
||||
badLink_ = true;
|
||||
}
|
||||
|
||||
if (auto const next = (*sle)[~sfNextPageMin])
|
||||
{
|
||||
if (account != (*next & accountBits))
|
||||
badLink_ = true;
|
||||
|
||||
if (hiLimit >= (*next & pageBits))
|
||||
badLink_ = true;
|
||||
}
|
||||
|
||||
{
|
||||
auto const& nftokens = sle->getFieldArray(sfNFTokens);
|
||||
|
||||
// An NFTokenPage should never contain too many tokens or be empty.
|
||||
if (std::size_t const nftokenCount = nftokens.size();
|
||||
(!isDelete && nftokenCount == 0) || nftokenCount > dirMaxTokensPerPage)
|
||||
invalidSize_ = true;
|
||||
|
||||
// If prev is valid, use it to establish a lower bound for
|
||||
// page entries. If prev is not valid the lower bound is zero.
|
||||
uint256 const loLimit = prev ? *prev & pageBits : uint256(beast::zero);
|
||||
|
||||
// Also verify that all NFTokenIDs in the page are sorted.
|
||||
uint256 loCmp = loLimit;
|
||||
for (auto const& obj : nftokens)
|
||||
{
|
||||
uint256 const tokenID = obj[sfNFTokenID];
|
||||
if (!nft::compareTokens(loCmp, tokenID))
|
||||
badSort_ = true;
|
||||
loCmp = tokenID;
|
||||
|
||||
// None of the NFTs on this page should belong on lower or
|
||||
// higher pages.
|
||||
if (uint256 const tokenPageBits = tokenID & pageBits;
|
||||
tokenPageBits < loLimit || tokenPageBits >= hiLimit)
|
||||
badEntry_ = true;
|
||||
|
||||
if (auto uri = obj[~sfURI]; uri && uri->empty())
|
||||
badURI_ = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (before)
|
||||
{
|
||||
check(before);
|
||||
|
||||
// While an account's NFToken directory contains any NFTokens, the last
|
||||
// NFTokenPage (with 96 bits of 1 in the low part of the index) should
|
||||
// never be deleted.
|
||||
if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
|
||||
before->isFieldPresent(sfPreviousPageMin))
|
||||
{
|
||||
deletedFinalPage_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (after)
|
||||
check(after);
|
||||
|
||||
if (!isDelete && before && after)
|
||||
{
|
||||
// If the NFTokenPage
|
||||
// 1. Has a NextMinPage field in before, but loses it in after, and
|
||||
// 2. This is not the last page in the directory
|
||||
// Then we have identified a corruption in the links between the
|
||||
// NFToken pages in the NFToken directory.
|
||||
if ((before->key() & nft::pageMask) != nft::pageMask &&
|
||||
before->isFieldPresent(sfNextPageMin) && !after->isFieldPresent(sfNextPageMin))
|
||||
{
|
||||
deletedLink_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ValidNFTokenPage::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (badLink_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (badEntry_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (badSort_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (badURI_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (invalidSize_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (view.rules().enabled(fixNFTokenPageLinks))
|
||||
{
|
||||
if (deletedFinalPage_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
|
||||
"non-empty directory.";
|
||||
return false;
|
||||
}
|
||||
if (deletedLink_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void
|
||||
NFTokenCountTracking::visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (before && before->getType() == ltACCOUNT_ROOT)
|
||||
{
|
||||
beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
|
||||
beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
|
||||
}
|
||||
|
||||
if (after && after->getType() == ltACCOUNT_ROOT)
|
||||
{
|
||||
afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
|
||||
afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
NFTokenCountTracking::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (!hasPrivilege(tx, changeNFTCounts))
|
||||
{
|
||||
if (beforeMintedTotal != afterMintedTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
|
||||
"changed without a mint transaction!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (beforeBurnedTotal != afterBurnedTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
|
||||
"changed without a burn transaction!";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tx.getTxnType() == ttNFTOKEN_MINT)
|
||||
{
|
||||
if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: successful minting didn't increase "
|
||||
"the number of minted tokens.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
|
||||
"number of minted tokens.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (beforeBurnedTotal != afterBurnedTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: minting changed the number of "
|
||||
"burned tokens.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tx.getTxnType() == ttNFTOKEN_BURN)
|
||||
{
|
||||
if (result == tesSUCCESS)
|
||||
{
|
||||
if (beforeBurnedTotal >= afterBurnedTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: successful burning didn't increase "
|
||||
"the number of burned tokens.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
|
||||
"number of burned tokens.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (beforeMintedTotal != afterMintedTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: burning changed the number of "
|
||||
"minted tokens.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
93
src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp
Normal file
93
src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
ValidPermissionedDEX::visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (after && after->getType() == ltDIR_NODE)
|
||||
{
|
||||
if (after->isFieldPresent(sfDomainID))
|
||||
domains_.insert(after->getFieldH256(sfDomainID));
|
||||
}
|
||||
|
||||
if (after && after->getType() == ltOFFER)
|
||||
{
|
||||
if (after->isFieldPresent(sfDomainID))
|
||||
domains_.insert(after->getFieldH256(sfDomainID));
|
||||
else
|
||||
regularOffers_ = true;
|
||||
|
||||
// if a hybrid offer is missing domain or additional book, there's
|
||||
// something wrong
|
||||
if (after->isFlag(lsfHybrid) &&
|
||||
(!after->isFieldPresent(sfDomainID) || !after->isFieldPresent(sfAdditionalBooks) ||
|
||||
after->getFieldArray(sfAdditionalBooks).size() > 1))
|
||||
badHybrids_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ValidPermissionedDEX::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
auto const txType = tx.getTxnType();
|
||||
if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) || result != tesSUCCESS)
|
||||
return true;
|
||||
|
||||
// For each offercreate transaction, check if
|
||||
// permissioned offers are valid
|
||||
if (txType == ttOFFER_CREATE && badHybrids_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tx.isFieldPresent(sfDomainID))
|
||||
return true;
|
||||
|
||||
auto const domain = tx.getFieldH256(sfDomainID);
|
||||
|
||||
if (!view.exists(keylet::permissionedDomain(domain)))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: domain doesn't exist";
|
||||
return false;
|
||||
}
|
||||
|
||||
// for both payment and offercreate, there shouldn't be another domain
|
||||
// that's different from the domain specified
|
||||
for (auto const& d : domains_)
|
||||
{
|
||||
if (d != domain)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: transaction"
|
||||
" consumed wrong domains";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (regularOffers_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: domain transaction"
|
||||
" affected regular offers";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
162
src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp
Normal file
162
src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
|
||||
//
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
ValidPermissionedDomain::visitEntry(
|
||||
bool isDel,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (before && before->getType() != ltPERMISSIONED_DOMAIN)
|
||||
return;
|
||||
if (after && after->getType() != ltPERMISSIONED_DOMAIN)
|
||||
return;
|
||||
|
||||
auto check = [isDel](std::vector<SleStatus>& sleStatus, std::shared_ptr<SLE const> const& sle) {
|
||||
auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
|
||||
auto const sorted = credentials::makeSorted(credentials);
|
||||
|
||||
SleStatus ss{credentials.size(), false, !sorted.empty(), isDel};
|
||||
|
||||
// If array have duplicates then all the other checks are invalid
|
||||
if (ss.isUnique_)
|
||||
{
|
||||
unsigned i = 0;
|
||||
for (auto const& cred : sorted)
|
||||
{
|
||||
auto const& credTx = credentials[i++];
|
||||
ss.isSorted_ =
|
||||
(cred.first == credTx[sfIssuer]) && (cred.second == credTx[sfCredentialType]);
|
||||
if (!ss.isSorted_)
|
||||
break;
|
||||
}
|
||||
}
|
||||
sleStatus.emplace_back(std::move(ss));
|
||||
};
|
||||
|
||||
if (after)
|
||||
check(sleStatus_, after);
|
||||
}
|
||||
|
||||
bool
|
||||
ValidPermissionedDomain::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
|
||||
if (!sleStatus.credentialsSize_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
|
||||
"no rules.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sleStatus.credentialsSize_ > maxPermissionedDomainCredentialsArraySize)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
|
||||
"credentials size "
|
||||
<< sleStatus.credentialsSize_;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sleStatus.isUnique_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: permissioned domain credentials "
|
||||
"aren't unique";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sleStatus.isSorted_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: permissioned domain credentials "
|
||||
"aren't sorted";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if (view.rules().enabled(fixPermissionedDomainInvariant))
|
||||
{
|
||||
// No permissioned domains should be affected if the transaction failed
|
||||
if (result != tesSUCCESS)
|
||||
// If nothing changed, all is good. If there were changes, that's
|
||||
// bad.
|
||||
return sleStatus_.empty();
|
||||
|
||||
if (sleStatus_.size() > 1)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: transaction affected more "
|
||||
"than 1 permissioned domain entry.";
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (tx.getTxnType())
|
||||
{
|
||||
case ttPERMISSIONED_DOMAIN_SET: {
|
||||
if (sleStatus_.empty())
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: no domain objects affected by "
|
||||
"PermissionedDomainSet";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const& sleStatus = sleStatus_[0];
|
||||
if (sleStatus.isDelete_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: domain object "
|
||||
"deleted by PermissionedDomainSet";
|
||||
return false;
|
||||
}
|
||||
return check(sleStatus, j);
|
||||
}
|
||||
case ttPERMISSIONED_DOMAIN_DELETE: {
|
||||
if (sleStatus_.empty())
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: no domain objects affected by "
|
||||
"PermissionedDomainDelete";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sleStatus_[0].isDelete_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: domain object "
|
||||
"modified, but not deleted by "
|
||||
"PermissionedDomainDelete";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
if (!sleStatus_.empty())
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: " << sleStatus_.size()
|
||||
<< " domain object(s) affected by an "
|
||||
"unauthorized transaction. "
|
||||
<< tx.getTxnType();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS ||
|
||||
sleStatus_.empty())
|
||||
return true;
|
||||
return check(sleStatus_[0], j);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
926
src/libxrpl/tx/invariants/VaultInvariant.cpp
Normal file
926
src/libxrpl/tx/invariants/VaultInvariant.cpp
Normal file
@@ -0,0 +1,926 @@
|
||||
#include <xrpl/tx/invariants/VaultInvariant.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
ValidVault::Vault
|
||||
ValidVault::Vault::make(SLE const& from)
|
||||
{
|
||||
XRPL_ASSERT(from.getType() == ltVAULT, "ValidVault::Vault::make : from Vault object");
|
||||
|
||||
ValidVault::Vault self;
|
||||
self.key = from.key();
|
||||
self.asset = from.at(sfAsset);
|
||||
self.pseudoId = from.getAccountID(sfAccount);
|
||||
self.owner = from.at(sfOwner);
|
||||
self.shareMPTID = from.getFieldH192(sfShareMPTID);
|
||||
self.assetsTotal = from.at(sfAssetsTotal);
|
||||
self.assetsAvailable = from.at(sfAssetsAvailable);
|
||||
self.assetsMaximum = from.at(sfAssetsMaximum);
|
||||
self.lossUnrealized = from.at(sfLossUnrealized);
|
||||
return self;
|
||||
}
|
||||
|
||||
ValidVault::Shares
|
||||
ValidVault::Shares::make(SLE const& from)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
from.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"ValidVault::Shares::make : from MPTokenIssuance object");
|
||||
|
||||
ValidVault::Shares self;
|
||||
self.share = MPTIssue(makeMptID(from.getFieldU32(sfSequence), from.getAccountID(sfIssuer)));
|
||||
self.sharesTotal = from.at(sfOutstandingAmount);
|
||||
self.sharesMaximum = from[~sfMaximumAmount].value_or(maxMPTokenAmount);
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
ValidVault::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
// If `before` is empty, this means an object is being created, in which
|
||||
// case `isDelete` must be false. Otherwise `before` and `after` are set and
|
||||
// `isDelete` indicates whether an object is being deleted or modified.
|
||||
XRPL_ASSERT(
|
||||
after != nullptr && (before != nullptr || !isDelete),
|
||||
"xrpl::ValidVault::visitEntry : some object is available");
|
||||
|
||||
// Number balanceDelta will capture the difference (delta) between "before"
|
||||
// state (zero if created) and "after" state (zero if destroyed), so the
|
||||
// invariants can validate that the change in account balances matches the
|
||||
// change in vault balances, stored to deltas_ at the end of this function.
|
||||
Number balanceDelta{};
|
||||
|
||||
std::int8_t sign = 0;
|
||||
if (before)
|
||||
{
|
||||
switch (before->getType())
|
||||
{
|
||||
case ltVAULT:
|
||||
beforeVault_.push_back(Vault::make(*before));
|
||||
break;
|
||||
case ltMPTOKEN_ISSUANCE:
|
||||
// At this moment we have no way of telling if this object holds
|
||||
// vault shares or something else. Save it for finalize.
|
||||
beforeMPTs_.push_back(Shares::make(*before));
|
||||
balanceDelta = static_cast<std::int64_t>(before->getFieldU64(sfOutstandingAmount));
|
||||
sign = 1;
|
||||
break;
|
||||
case ltMPTOKEN:
|
||||
balanceDelta = static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
|
||||
sign = -1;
|
||||
break;
|
||||
case ltACCOUNT_ROOT:
|
||||
case ltRIPPLE_STATE:
|
||||
balanceDelta = before->getFieldAmount(sfBalance);
|
||||
sign = -1;
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDelete && after)
|
||||
{
|
||||
switch (after->getType())
|
||||
{
|
||||
case ltVAULT:
|
||||
afterVault_.push_back(Vault::make(*after));
|
||||
break;
|
||||
case ltMPTOKEN_ISSUANCE:
|
||||
// At this moment we have no way of telling if this object holds
|
||||
// vault shares or something else. Save it for finalize.
|
||||
afterMPTs_.push_back(Shares::make(*after));
|
||||
balanceDelta -=
|
||||
Number(static_cast<std::int64_t>(after->getFieldU64(sfOutstandingAmount)));
|
||||
sign = 1;
|
||||
break;
|
||||
case ltMPTOKEN:
|
||||
balanceDelta -= Number(static_cast<std::int64_t>(after->getFieldU64(sfMPTAmount)));
|
||||
sign = -1;
|
||||
break;
|
||||
case ltACCOUNT_ROOT:
|
||||
case ltRIPPLE_STATE:
|
||||
balanceDelta -= Number(after->getFieldAmount(sfBalance));
|
||||
sign = -1;
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
uint256 const key = (before ? before->key() : after->key());
|
||||
// Append to deltas if sign is non-zero, i.e. an object of an interesting
|
||||
// type has been updated. A transaction may update an object even when
|
||||
// its balance has not changed, e.g. transaction fee equals the amount
|
||||
// transferred to the account. We intentionally do not compare balanceDelta
|
||||
// against zero, to avoid missing such updates.
|
||||
if (sign != 0)
|
||||
deltas_[key] = balanceDelta * sign;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidVault::finalize(
|
||||
STTx const& tx,
|
||||
TER const ret,
|
||||
XRPAmount const fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
bool const enforce = view.rules().enabled(featureSingleAssetVault);
|
||||
|
||||
if (!isTesSuccess(ret))
|
||||
return true; // Do not perform checks
|
||||
|
||||
if (afterVault_.empty() && beforeVault_.empty())
|
||||
{
|
||||
if (hasPrivilege(tx, mustModifyVault))
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: vault operation succeeded without modifying "
|
||||
"a vault";
|
||||
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault noop invariant");
|
||||
return !enforce;
|
||||
}
|
||||
|
||||
return true; // Not a vault operation
|
||||
}
|
||||
else if (!(hasPrivilege(tx, mustModifyVault) || hasPrivilege(tx, mayModifyVault)))
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: vault updated by a wrong transaction type";
|
||||
XRPL_ASSERT(
|
||||
enforce,
|
||||
"xrpl::ValidVault::finalize : illegal vault transaction "
|
||||
"invariant");
|
||||
return !enforce; // Also not a vault operation
|
||||
}
|
||||
|
||||
if (beforeVault_.size() > 1 || afterVault_.size() > 1)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: vault operation updated more than single vault";
|
||||
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : single vault invariant");
|
||||
return !enforce; // That's all we can do here
|
||||
}
|
||||
|
||||
auto const txnType = tx.getTxnType();
|
||||
|
||||
// We do special handling for ttVAULT_DELETE first, because it's the only
|
||||
// vault-modifying transaction without an "after" state of the vault
|
||||
if (afterVault_.empty())
|
||||
{
|
||||
if (txnType != ttVAULT_DELETE)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: vault deleted by a wrong transaction type";
|
||||
XRPL_ASSERT(
|
||||
enforce,
|
||||
"xrpl::ValidVault::finalize : illegal vault deletion "
|
||||
"invariant");
|
||||
return !enforce; // That's all we can do here
|
||||
}
|
||||
|
||||
// Note, if afterVault_ is empty then we know that beforeVault_ is not
|
||||
// empty, as enforced at the top of this function
|
||||
auto const& beforeVault = beforeVault_[0];
|
||||
|
||||
// At this moment we only know a vault is being deleted and there
|
||||
// might be some MPTokenIssuance objects which are deleted in the
|
||||
// same transaction. Find the one matching this vault.
|
||||
auto const deletedShares = [&]() -> std::optional<Shares> {
|
||||
for (auto const& e : beforeMPTs_)
|
||||
{
|
||||
if (e.share.getMptID() == beforeVault.shareMPTID)
|
||||
return std::move(e);
|
||||
}
|
||||
return std::nullopt;
|
||||
}();
|
||||
|
||||
if (!deletedShares)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: deleted vault must also "
|
||||
"delete shares";
|
||||
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : shares deletion invariant");
|
||||
return !enforce; // That's all we can do here
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
if (deletedShares->sharesTotal != 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
|
||||
"shares outstanding";
|
||||
result = false;
|
||||
}
|
||||
if (beforeVault.assetsTotal != zero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
|
||||
"assets outstanding";
|
||||
result = false;
|
||||
}
|
||||
if (beforeVault.assetsAvailable != zero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
|
||||
"assets available";
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else if (txnType == ttVAULT_DELETE)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: vault deletion succeeded without "
|
||||
"deleting a vault";
|
||||
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault deletion invariant");
|
||||
return !enforce; // That's all we can do here
|
||||
}
|
||||
|
||||
// Note, `afterVault_.empty()` is handled above
|
||||
auto const& afterVault = afterVault_[0];
|
||||
XRPL_ASSERT(
|
||||
beforeVault_.empty() || beforeVault_[0].key == afterVault.key,
|
||||
"xrpl::ValidVault::finalize : single vault operation");
|
||||
|
||||
auto const updatedShares = [&]() -> std::optional<Shares> {
|
||||
// At this moment we only know that a vault is being updated and there
|
||||
// might be some MPTokenIssuance objects which are also updated in the
|
||||
// same transaction. Find the one matching the shares to this vault.
|
||||
// Note, we expect updatedMPTs collection to be extremely small. For
|
||||
// such collections linear search is faster than lookup.
|
||||
for (auto const& e : afterMPTs_)
|
||||
{
|
||||
if (e.share.getMptID() == afterVault.shareMPTID)
|
||||
return e;
|
||||
}
|
||||
|
||||
auto const sleShares = view.read(keylet::mptIssuance(afterVault.shareMPTID));
|
||||
|
||||
return sleShares ? std::optional<Shares>(Shares::make(*sleShares)) : std::nullopt;
|
||||
}();
|
||||
|
||||
bool result = true;
|
||||
|
||||
// Universal transaction checks
|
||||
if (!beforeVault_.empty())
|
||||
{
|
||||
auto const& beforeVault = beforeVault_[0];
|
||||
if (afterVault.asset != beforeVault.asset || afterVault.pseudoId != beforeVault.pseudoId ||
|
||||
afterVault.shareMPTID != beforeVault.shareMPTID)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: violation of vault immutable data";
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!updatedShares)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: updated vault must have shares";
|
||||
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault has shares invariant");
|
||||
return !enforce; // That's all we can do here
|
||||
}
|
||||
|
||||
if (updatedShares->sharesTotal == 0)
|
||||
{
|
||||
if (afterVault.assetsTotal != zero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: updated zero sized "
|
||||
"vault must have no assets outstanding";
|
||||
result = false;
|
||||
}
|
||||
if (afterVault.assetsAvailable != zero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: updated zero sized "
|
||||
"vault must have no assets available";
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
else if (updatedShares->sharesTotal > updatedShares->sharesMaximum)
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: updated shares must not exceed maximum "
|
||||
<< updatedShares->sharesMaximum;
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (afterVault.assetsAvailable < zero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: assets available must be positive";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (afterVault.assetsAvailable > afterVault.assetsTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: assets available must "
|
||||
"not be greater than assets outstanding";
|
||||
result = false;
|
||||
}
|
||||
else if (afterVault.lossUnrealized > afterVault.assetsTotal - afterVault.assetsAvailable)
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: loss unrealized must not exceed "
|
||||
"the difference between assets outstanding and available";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (afterVault.assetsTotal < zero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: assets outstanding must be positive";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (afterVault.assetsMaximum < zero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: assets maximum must be positive";
|
||||
result = false;
|
||||
}
|
||||
|
||||
// Thanks to this check we can simply do `assert(!beforeVault_.empty()` when
|
||||
// enforcing invariants on transaction types other than ttVAULT_CREATE
|
||||
if (beforeVault_.empty() && txnType != ttVAULT_CREATE)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: vault created by a wrong transaction type";
|
||||
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault creation invariant");
|
||||
return !enforce; // That's all we can do here
|
||||
}
|
||||
|
||||
if (!beforeVault_.empty() && afterVault.lossUnrealized != beforeVault_[0].lossUnrealized &&
|
||||
txnType != ttLOAN_MANAGE && txnType != ttLOAN_PAY)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: vault transaction must not change loss "
|
||||
"unrealized";
|
||||
result = false;
|
||||
}
|
||||
|
||||
auto const beforeShares = [&]() -> std::optional<Shares> {
|
||||
if (beforeVault_.empty())
|
||||
return std::nullopt;
|
||||
auto const& beforeVault = beforeVault_[0];
|
||||
|
||||
for (auto const& e : beforeMPTs_)
|
||||
{
|
||||
if (e.share.getMptID() == beforeVault.shareMPTID)
|
||||
return std::move(e);
|
||||
}
|
||||
return std::nullopt;
|
||||
}();
|
||||
|
||||
if (!beforeShares &&
|
||||
(tx.getTxnType() == ttVAULT_DEPOSIT || //
|
||||
tx.getTxnType() == ttVAULT_WITHDRAW || //
|
||||
tx.getTxnType() == ttVAULT_CLAWBACK))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: vault operation succeeded "
|
||||
"without updating shares";
|
||||
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : shares noop invariant");
|
||||
return !enforce; // That's all we can do here
|
||||
}
|
||||
|
||||
auto const& vaultAsset = afterVault.asset;
|
||||
auto const deltaAssets = [&](AccountID const& id) -> std::optional<Number> {
|
||||
auto const get = //
|
||||
[&](auto const& it, std::int8_t sign = 1) -> std::optional<Number> {
|
||||
if (it == deltas_.end())
|
||||
return std::nullopt;
|
||||
|
||||
return it->second * sign;
|
||||
};
|
||||
|
||||
return std::visit(
|
||||
[&]<typename TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
if (isXRP(issue))
|
||||
return get(deltas_.find(keylet::account(id).key));
|
||||
return get(
|
||||
deltas_.find(keylet::line(id, issue).key), id > issue.getIssuer() ? -1 : 1);
|
||||
}
|
||||
else if constexpr (std::is_same_v<TIss, MPTIssue>)
|
||||
{
|
||||
return get(deltas_.find(keylet::mptoken(issue.getMptID(), id).key));
|
||||
}
|
||||
},
|
||||
vaultAsset.value());
|
||||
};
|
||||
auto const deltaAssetsTxAccount = [&]() -> std::optional<Number> {
|
||||
auto ret = deltaAssets(tx[sfAccount]);
|
||||
// Nothing returned or not XRP transaction
|
||||
if (!ret.has_value() || !vaultAsset.native())
|
||||
return ret;
|
||||
|
||||
// Delegated transaction; no need to compensate for fees
|
||||
if (auto const delegate = tx[~sfDelegate];
|
||||
delegate.has_value() && *delegate != tx[sfAccount])
|
||||
return ret;
|
||||
|
||||
*ret += fee.drops();
|
||||
if (*ret == zero)
|
||||
return std::nullopt;
|
||||
|
||||
return ret;
|
||||
};
|
||||
auto const deltaShares = [&](AccountID const& id) -> std::optional<Number> {
|
||||
auto const it = [&]() {
|
||||
if (id == afterVault.pseudoId)
|
||||
return deltas_.find(keylet::mptIssuance(afterVault.shareMPTID).key);
|
||||
return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key);
|
||||
}();
|
||||
|
||||
return it != deltas_.end() ? std::optional<Number>(it->second) : std::nullopt;
|
||||
};
|
||||
|
||||
auto const vaultHoldsNoAssets = [&](Vault const& vault) {
|
||||
return vault.assetsAvailable == 0 && vault.assetsTotal == 0;
|
||||
};
|
||||
|
||||
// Technically this does not need to be a lambda, but it's more
|
||||
// convenient thanks to early "return false"; the not-so-nice
|
||||
// alternatives are several layers of nested if/else or more complex
|
||||
// (i.e. brittle) if statements.
|
||||
result &= [&]() {
|
||||
switch (txnType)
|
||||
{
|
||||
case ttVAULT_CREATE: {
|
||||
bool result = true;
|
||||
|
||||
if (!beforeVault_.empty())
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: create operation must not have "
|
||||
"updated a vault";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (afterVault.assetsAvailable != zero || afterVault.assetsTotal != zero ||
|
||||
afterVault.lossUnrealized != zero || updatedShares->sharesTotal != 0)
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: created vault must be empty";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (afterVault.pseudoId != updatedShares->share.getIssuer())
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: shares issuer and vault "
|
||||
"pseudo-account must be the same";
|
||||
result = false;
|
||||
}
|
||||
|
||||
auto const sleSharesIssuer =
|
||||
view.read(keylet::account(updatedShares->share.getIssuer()));
|
||||
if (!sleSharesIssuer)
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: shares issuer must exist";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isPseudoAccount(sleSharesIssuer))
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: shares issuer must be a "
|
||||
"pseudo-account";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
|
||||
!vaultId || *vaultId != afterVault.key)
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: shares issuer pseudo-account "
|
||||
"must point back to the vault";
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
case ttVAULT_SET: {
|
||||
bool result = true;
|
||||
|
||||
XRPL_ASSERT(
|
||||
!beforeVault_.empty(), "xrpl::ValidVault::finalize : set updated a vault");
|
||||
auto const& beforeVault = beforeVault_[0];
|
||||
|
||||
auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
|
||||
if (vaultDeltaAssets)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: set must not change vault balance";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (beforeVault.assetsTotal != afterVault.assetsTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: set must not change assets "
|
||||
"outstanding";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (afterVault.assetsMaximum > zero &&
|
||||
afterVault.assetsTotal > afterVault.assetsMaximum)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: set assets outstanding must not "
|
||||
"exceed assets maximum";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (beforeVault.assetsAvailable != afterVault.assetsAvailable)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: set must not change assets "
|
||||
"available";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (beforeShares && updatedShares &&
|
||||
beforeShares->sharesTotal != updatedShares->sharesTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: set must not change shares "
|
||||
"outstanding";
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
case ttVAULT_DEPOSIT: {
|
||||
bool result = true;
|
||||
|
||||
XRPL_ASSERT(
|
||||
!beforeVault_.empty(), "xrpl::ValidVault::finalize : deposit updated a vault");
|
||||
auto const& beforeVault = beforeVault_[0];
|
||||
|
||||
auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
|
||||
|
||||
if (!vaultDeltaAssets)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: deposit must change vault balance";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
|
||||
if (*vaultDeltaAssets > tx[sfAmount])
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: deposit must not change vault "
|
||||
"balance by more than deposited amount";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (*vaultDeltaAssets <= zero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: deposit must increase vault balance";
|
||||
result = false;
|
||||
}
|
||||
|
||||
// Any payments (including deposits) made by the issuer
|
||||
// do not change their balance, but create funds instead.
|
||||
bool const issuerDeposit = [&]() -> bool {
|
||||
if (vaultAsset.native())
|
||||
return false;
|
||||
return tx[sfAccount] == vaultAsset.getIssuer();
|
||||
}();
|
||||
|
||||
if (!issuerDeposit)
|
||||
{
|
||||
auto const accountDeltaAssets = deltaAssetsTxAccount();
|
||||
if (!accountDeltaAssets)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: deposit must change depositor "
|
||||
"balance";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*accountDeltaAssets >= zero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: deposit must decrease depositor "
|
||||
"balance";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (*accountDeltaAssets * -1 != *vaultDeltaAssets)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: deposit must change vault and "
|
||||
"depositor balance by equal amount";
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (afterVault.assetsMaximum > zero &&
|
||||
afterVault.assetsTotal > afterVault.assetsMaximum)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: deposit assets outstanding must not "
|
||||
"exceed assets maximum";
|
||||
result = false;
|
||||
}
|
||||
|
||||
auto const accountDeltaShares = deltaShares(tx[sfAccount]);
|
||||
if (!accountDeltaShares)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: deposit must change depositor "
|
||||
"shares";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
|
||||
if (*accountDeltaShares <= zero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: deposit must increase depositor "
|
||||
"shares";
|
||||
result = false;
|
||||
}
|
||||
|
||||
auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
|
||||
if (!vaultDeltaShares || *vaultDeltaShares == zero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: deposit must change vault shares";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
|
||||
if (*vaultDeltaShares * -1 != *accountDeltaShares)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: deposit must change depositor and "
|
||||
"vault shares by equal amount";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: deposit and assets "
|
||||
"outstanding must add up";
|
||||
result = false;
|
||||
}
|
||||
if (beforeVault.assetsAvailable + *vaultDeltaAssets != afterVault.assetsAvailable)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: deposit and assets "
|
||||
"available must add up";
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
case ttVAULT_WITHDRAW: {
|
||||
bool result = true;
|
||||
|
||||
XRPL_ASSERT(
|
||||
!beforeVault_.empty(),
|
||||
"xrpl::ValidVault::finalize : withdrawal updated a "
|
||||
"vault");
|
||||
auto const& beforeVault = beforeVault_[0];
|
||||
|
||||
auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
|
||||
|
||||
if (!vaultDeltaAssets)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: withdrawal must "
|
||||
"change vault balance";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
|
||||
if (*vaultDeltaAssets >= zero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: withdrawal must "
|
||||
"decrease vault balance";
|
||||
result = false;
|
||||
}
|
||||
|
||||
// Any payments (including withdrawal) going to the issuer
|
||||
// do not change their balance, but destroy funds instead.
|
||||
bool const issuerWithdrawal = [&]() -> bool {
|
||||
if (vaultAsset.native())
|
||||
return false;
|
||||
auto const destination = tx[~sfDestination].value_or(tx[sfAccount]);
|
||||
return destination == vaultAsset.getIssuer();
|
||||
}();
|
||||
|
||||
if (!issuerWithdrawal)
|
||||
{
|
||||
auto const accountDeltaAssets = deltaAssetsTxAccount();
|
||||
auto const otherAccountDelta = [&]() -> std::optional<Number> {
|
||||
if (auto const destination = tx[~sfDestination];
|
||||
destination && *destination != tx[sfAccount])
|
||||
return deltaAssets(*destination);
|
||||
return std::nullopt;
|
||||
}();
|
||||
|
||||
if (accountDeltaAssets.has_value() == otherAccountDelta.has_value())
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: withdrawal must change one "
|
||||
"destination balance";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const destinationDelta = //
|
||||
accountDeltaAssets ? *accountDeltaAssets : *otherAccountDelta;
|
||||
|
||||
if (destinationDelta <= zero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: withdrawal must increase "
|
||||
"destination balance";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (*vaultDeltaAssets * -1 != destinationDelta)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: withdrawal must change vault "
|
||||
"and destination balance by equal amount";
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto const accountDeltaShares = deltaShares(tx[sfAccount]);
|
||||
if (!accountDeltaShares)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: withdrawal must change depositor "
|
||||
"shares";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*accountDeltaShares >= zero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: withdrawal must decrease depositor "
|
||||
"shares";
|
||||
result = false;
|
||||
}
|
||||
|
||||
auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
|
||||
if (!vaultDeltaShares || *vaultDeltaShares == zero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: withdrawal must change vault shares";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
|
||||
if (*vaultDeltaShares * -1 != *accountDeltaShares)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: withdrawal must change depositor "
|
||||
"and vault shares by equal amount";
|
||||
result = false;
|
||||
}
|
||||
|
||||
// Note, vaultBalance is negative (see check above)
|
||||
if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: withdrawal and "
|
||||
"assets outstanding must add up";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (beforeVault.assetsAvailable + *vaultDeltaAssets != afterVault.assetsAvailable)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: withdrawal and "
|
||||
"assets available must add up";
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
case ttVAULT_CLAWBACK: {
|
||||
bool result = true;
|
||||
|
||||
XRPL_ASSERT(
|
||||
!beforeVault_.empty(), "xrpl::ValidVault::finalize : clawback updated a vault");
|
||||
auto const& beforeVault = beforeVault_[0];
|
||||
|
||||
if (vaultAsset.native() || vaultAsset.getIssuer() != tx[sfAccount])
|
||||
{
|
||||
// The owner can use clawback to force-burn shares when the
|
||||
// vault is empty but there are outstanding shares
|
||||
if (!(beforeShares && beforeShares->sharesTotal > 0 &&
|
||||
vaultHoldsNoAssets(beforeVault) && beforeVault.owner == tx[sfAccount]))
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback may only be performed "
|
||||
"by the asset issuer, or by the vault owner of an "
|
||||
"empty vault";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
}
|
||||
|
||||
auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
|
||||
if (vaultDeltaAssets)
|
||||
{
|
||||
if (*vaultDeltaAssets >= zero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback must decrease vault "
|
||||
"balance";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback and assets outstanding "
|
||||
"must add up";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
|
||||
afterVault.assetsAvailable)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback and assets available "
|
||||
"must add up";
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
else if (!vaultHoldsNoAssets(beforeVault))
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback must change vault balance";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
|
||||
auto const accountDeltaShares = deltaShares(tx[sfHolder]);
|
||||
if (!accountDeltaShares)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback must change holder shares";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
|
||||
if (*accountDeltaShares >= zero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback must decrease holder "
|
||||
"shares";
|
||||
result = false;
|
||||
}
|
||||
|
||||
auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
|
||||
if (!vaultDeltaShares || *vaultDeltaShares == zero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback must change vault shares";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
|
||||
if (*vaultDeltaShares * -1 != *accountDeltaShares)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback must change holder and "
|
||||
"vault shares by equal amount";
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case ttLOAN_SET:
|
||||
case ttLOAN_MANAGE:
|
||||
case ttLOAN_PAY: {
|
||||
// TBD
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::ValidVault::finalize : unknown transaction type");
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}();
|
||||
|
||||
if (!result)
|
||||
{
|
||||
// The comment at the top of this file starting with "assert(enforce)"
|
||||
// explains this assert.
|
||||
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault invariants");
|
||||
return !enforce;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -105,6 +105,20 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
|
||||
return temDISABLED;
|
||||
}
|
||||
if (ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
|
||||
!ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
|
||||
!ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temMALFORMED;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
|
||||
ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
|
||||
ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temDISABLED;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
case ttAMENDMENT:
|
||||
case ttUNL_MODIFY:
|
||||
@@ -264,6 +278,12 @@ Change::applyFee()
|
||||
set(feeObject, ctx_.tx, sfReserveBase);
|
||||
set(feeObject, ctx_.tx, sfReserveIncrement);
|
||||
}
|
||||
if (view().rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
set(feeObject, ctx_.tx, sfExtensionComputeLimit);
|
||||
set(feeObject, ctx_.tx, sfExtensionSizeLimit);
|
||||
set(feeObject, ctx_.tx, sfGasPrice);
|
||||
}
|
||||
|
||||
view().update(feeObject);
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#include <xrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.h>
|
||||
//
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/tx/apply.h>
|
||||
|
||||
#include <source_location>
|
||||
|
||||
namespace xrpl {
|
||||
namespace test {
|
||||
|
||||
@@ -24,10 +26,17 @@ struct FeeSettingsFields
|
||||
std::optional<XRPAmount> baseFeeDrops = std::nullopt;
|
||||
std::optional<XRPAmount> reserveBaseDrops = std::nullopt;
|
||||
std::optional<XRPAmount> reserveIncrementDrops = std::nullopt;
|
||||
std::optional<std::uint32_t> 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());
|
||||
@@ -55,6 +64,15 @@ createFeeTx(Rules const& rules, std::uint32_t seq, FeeSettingsFields const& fiel
|
||||
obj.setFieldU32(
|
||||
sfReferenceFeeUnits, fields.referenceFeeUnits ? *fields.referenceFeeUnits : 0);
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow) || forceAllFields)
|
||||
{
|
||||
obj.setFieldU32(
|
||||
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);
|
||||
}
|
||||
@@ -103,6 +121,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)
|
||||
@@ -110,11 +134,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
|
||||
@@ -165,6 +189,22 @@ verifyFeeObject(
|
||||
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;
|
||||
}
|
||||
@@ -187,6 +227,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
void
|
||||
testSetup()
|
||||
{
|
||||
testcase("FeeVote setup");
|
||||
FeeSetup const defaultSetup;
|
||||
{
|
||||
// defaults
|
||||
@@ -195,35 +236,65 @@ 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"});
|
||||
{"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"});
|
||||
{"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"});
|
||||
{"reference_fee = -50",
|
||||
"account_reserve = -1234567",
|
||||
"owner_reserve = -1234",
|
||||
"extension_compute_limit = -100",
|
||||
"extension_size_limit = -200",
|
||||
"gas_price = -300"});
|
||||
// Negative values wrap to large positive uint32_t values.
|
||||
// reference_fee is uint64_t and has bounds checking, so it keeps
|
||||
// the default.
|
||||
// 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(
|
||||
@@ -232,12 +303,18 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
config.append(
|
||||
{"reference_fee = " + big64,
|
||||
"account_reserve = " + big64,
|
||||
"owner_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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +325,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(),
|
||||
@@ -268,7 +345,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
|
||||
@@ -277,7 +354,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(),
|
||||
@@ -295,12 +372,69 @@ 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
|
||||
@@ -309,7 +443,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(),
|
||||
@@ -322,15 +456,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(),
|
||||
@@ -343,11 +477,32 @@ 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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +511,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());
|
||||
|
||||
@@ -382,7 +537,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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +546,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());
|
||||
|
||||
@@ -405,7 +560,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);
|
||||
}
|
||||
|
||||
@@ -422,7 +577,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);
|
||||
}
|
||||
|
||||
@@ -435,7 +590,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());
|
||||
|
||||
@@ -454,7 +609,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
|
||||
@@ -462,7 +617,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());
|
||||
|
||||
@@ -476,7 +631,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);
|
||||
}
|
||||
|
||||
@@ -491,7 +646,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);
|
||||
}
|
||||
|
||||
@@ -504,7 +659,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());
|
||||
|
||||
@@ -522,7 +677,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
|
||||
@@ -539,7 +694,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>(
|
||||
@@ -568,7 +723,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>(
|
||||
@@ -607,7 +762,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});
|
||||
@@ -678,6 +833,138 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
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
|
||||
{
|
||||
@@ -691,6 +978,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
testSingleInvalidTransaction();
|
||||
testDoValidation();
|
||||
testDoVoting();
|
||||
testDoVotingSmartEscrow();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
2850
src/test/app/HostFuncImpl_test.cpp
Normal file
2850
src/test/app/HostFuncImpl_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,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) {
|
||||
@@ -96,7 +102,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();
|
||||
}
|
||||
|
||||
537
src/test/app/TestHostFunctions.h
Normal file
537
src/test/app/TestHostFunctions.h
Normal file
@@ -0,0 +1,537 @@
|
||||
#include <test/app/wasm_fixtures/fixtures.h>
|
||||
#include <test/jtx.h>
|
||||
|
||||
#include <xrpld/app/wasm/HostFunc.h>
|
||||
#include <xrpld/app/wasm/WasmVM.h>
|
||||
|
||||
#include <xrpl/ledger/AmendmentTable.h>
|
||||
#include <xrpl/ledger/detail/ApplyViewBase.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/tx/transactors/NFT/NFTokenUtils.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace test {
|
||||
|
||||
struct TestLedgerDataProvider : public HostFunctions
|
||||
{
|
||||
jtx::Env& env_;
|
||||
void const* rt_ = nullptr;
|
||||
|
||||
public:
|
||||
TestLedgerDataProvider(jtx::Env& env) : HostFunctions(env.journal), env_(env)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void
|
||||
setRT(void const* rt) override
|
||||
{
|
||||
rt_ = rt;
|
||||
}
|
||||
|
||||
virtual void const*
|
||||
getRT() const override
|
||||
{
|
||||
return rt_;
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getLedgerSqn() const override
|
||||
{
|
||||
return env_.current()->seq();
|
||||
}
|
||||
};
|
||||
|
||||
struct TestHostFunctions : public HostFunctions
|
||||
{
|
||||
test::jtx::Env& env_;
|
||||
AccountID accountID_;
|
||||
Bytes data_;
|
||||
int clock_drift_ = 0;
|
||||
void const* rt_ = nullptr;
|
||||
|
||||
public:
|
||||
TestHostFunctions(test::jtx::Env& env, int cd = 0)
|
||||
: HostFunctions(env.journal), env_(env), clock_drift_(cd)
|
||||
{
|
||||
accountID_ = env_.master.id();
|
||||
std::string t = "10000";
|
||||
data_ = Bytes{t.begin(), t.end()};
|
||||
}
|
||||
|
||||
virtual void
|
||||
setRT(void const* rt) override
|
||||
{
|
||||
rt_ = rt;
|
||||
}
|
||||
|
||||
virtual void const*
|
||||
getRT() const override
|
||||
{
|
||||
return rt_;
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getLedgerSqn() const override
|
||||
{
|
||||
return 12345;
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getParentLedgerTime() const override
|
||||
{
|
||||
return 67890;
|
||||
}
|
||||
|
||||
Expected<Hash, HostFunctionError>
|
||||
getParentLedgerHash() const override
|
||||
{
|
||||
return env_.current()->header().parentHash;
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getBaseFee() const override
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(uint256 const& amendmentId) const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(std::string_view const& amendmentName) const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getTxField(SField const& fname) const override
|
||||
{
|
||||
if (fname == sfAccount)
|
||||
return Bytes(accountID_.begin(), accountID_.end());
|
||||
else if (fname == sfFee)
|
||||
{
|
||||
int64_t x = 235;
|
||||
uint8_t const* p = reinterpret_cast<uint8_t const*>(&x);
|
||||
return Bytes{p, p + sizeof(x)};
|
||||
}
|
||||
else if (fname == sfSequence)
|
||||
{
|
||||
auto const x = getLedgerSqn();
|
||||
if (!x)
|
||||
return Unexpected(x.error());
|
||||
std::uint32_t const data = x.value();
|
||||
auto const* b = reinterpret_cast<uint8_t const*>(&data);
|
||||
auto const* e = reinterpret_cast<uint8_t const*>(&data + 1);
|
||||
return Bytes{b, e};
|
||||
}
|
||||
return Bytes();
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjField(SField const& fname) const override
|
||||
{
|
||||
auto const& sn = fname.getName();
|
||||
if (sn == "Destination" || sn == "Account")
|
||||
return Bytes(accountID_.begin(), accountID_.end());
|
||||
else if (sn == "Data")
|
||||
return data_;
|
||||
else if (sn == "FinishAfter")
|
||||
{
|
||||
auto t = env_.current()->parentCloseTime().time_since_epoch().count();
|
||||
std::string s = std::to_string(t);
|
||||
return Bytes{s.begin(), s.end()};
|
||||
}
|
||||
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjField(int32_t cacheIdx, SField const& fname) const override
|
||||
{
|
||||
if (fname == sfBalance)
|
||||
{
|
||||
int64_t x = 10'000;
|
||||
uint8_t const* p = reinterpret_cast<uint8_t const*>(&x);
|
||||
return Bytes{p, p + sizeof(x)};
|
||||
}
|
||||
else if (fname == sfAccount)
|
||||
{
|
||||
return Bytes(accountID_.begin(), accountID_.end());
|
||||
}
|
||||
return data_;
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getTxNestedField(Slice const& locator) const override
|
||||
{
|
||||
if (locator.size() == 4)
|
||||
{
|
||||
int32_t const* l = reinterpret_cast<int32_t const*>(locator.data());
|
||||
int32_t const sfield = l[0];
|
||||
if (sfield == sfAccount.fieldCode)
|
||||
{
|
||||
return Bytes(accountID_.begin(), accountID_.end());
|
||||
}
|
||||
}
|
||||
uint8_t const a[] = {0x2b, 0x6a, 0x23, 0x2a, 0xa4, 0xc4, 0xbe, 0x41, 0xbf, 0x49, 0xd2,
|
||||
0x45, 0x9f, 0xa4, 0xa0, 0x34, 0x7e, 0x1b, 0x54, 0x3a, 0x4c, 0x92,
|
||||
0xfc, 0xee, 0x08, 0x21, 0xc0, 0x20, 0x1e, 0x2e, 0x9a, 0x00};
|
||||
return Bytes(&a[0], &a[sizeof(a)]);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjNestedField(Slice const& locator) const override
|
||||
{
|
||||
if (locator.size() == 4)
|
||||
{
|
||||
int32_t const* l = reinterpret_cast<int32_t const*>(locator.data());
|
||||
int32_t const sfield = l[0];
|
||||
if (sfield == sfAccount.fieldCode)
|
||||
{
|
||||
return Bytes(accountID_.begin(), accountID_.end());
|
||||
}
|
||||
}
|
||||
uint8_t const a[] = {0x2b, 0x6a, 0x23, 0x2a, 0xa4, 0xc4, 0xbe, 0x41, 0xbf, 0x49, 0xd2,
|
||||
0x45, 0x9f, 0xa4, 0xa0, 0x34, 0x7e, 0x1b, 0x54, 0x3a, 0x4c, 0x92,
|
||||
0xfc, 0xee, 0x08, 0x21, 0xc0, 0x20, 0x1e, 0x2e, 0x9a, 0x00};
|
||||
return Bytes(&a[0], &a[sizeof(a)]);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) const override
|
||||
{
|
||||
if (locator.size() == 4)
|
||||
{
|
||||
int32_t const* l = reinterpret_cast<int32_t const*>(locator.data());
|
||||
int32_t const sfield = l[0];
|
||||
if (sfield == sfAccount.fieldCode)
|
||||
{
|
||||
return Bytes(accountID_.begin(), accountID_.end());
|
||||
}
|
||||
}
|
||||
uint8_t const a[] = {0x2b, 0x6a, 0x23, 0x2a, 0xa4, 0xc4, 0xbe, 0x41, 0xbf, 0x49, 0xd2,
|
||||
0x45, 0x9f, 0xa4, 0xa0, 0x34, 0x7e, 0x1b, 0x54, 0x3a, 0x4c, 0x92,
|
||||
0xfc, 0xee, 0x08, 0x21, 0xc0, 0x20, 0x1e, 0x2e, 0x9a, 0x00};
|
||||
return Bytes(&a[0], &a[sizeof(a)]);
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getTxArrayLen(SField const& fname) const override
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjArrayLen(SField const& fname) const override
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const override
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getTxNestedArrayLen(Slice const& locator) const override
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjNestedArrayLen(Slice const& locator) const override
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) const override
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
updateData(Slice const& data) override
|
||||
{
|
||||
return data.size();
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
Expected<Hash, HostFunctionError>
|
||||
computeSha512HalfHash(Slice const& data) const override
|
||||
{
|
||||
return env_.current()->header().parentHash;
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
accountKeylet(AccountID const& account) const override
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::account(account);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
ammKeylet(Asset const& issue1, Asset const& issue2) const override
|
||||
{
|
||||
if (issue1 == issue2)
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
if (issue1.holds<MPTIssue>() || issue2.holds<MPTIssue>())
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
auto const keylet = keylet::amm(issue1, issue2);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
checkKeylet(AccountID const& account, std::uint32_t seq) const override
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::check(account, seq);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
|
||||
const override
|
||||
{
|
||||
if (!subject || !issuer || credentialType.empty() ||
|
||||
credentialType.size() > maxCredentialTypeLength)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::credential(subject, issuer, credentialType);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
escrowKeylet(AccountID const& account, std::uint32_t seq) const override
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::escrow(account, seq);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
oracleKeylet(AccountID const& account, std::uint32_t documentId) const override
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::oracle(account, documentId);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getNFT(AccountID const& account, uint256 const& nftId) const override
|
||||
{
|
||||
if (!account || !nftId)
|
||||
{
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
}
|
||||
|
||||
std::string s = "https://ripple.com";
|
||||
return Bytes(s.begin(), s.end());
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getNFTIssuer(uint256 const& nftId) const override
|
||||
{
|
||||
return Bytes(accountID_.begin(), accountID_.end());
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTTaxon(uint256 const& nftId) const override
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getNFTFlags(uint256 const& nftId) const override
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getNFTTransferFee(uint256 const& nftId) const override
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTSerial(uint256 const& nftId) const override
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void
|
||||
log(std::string_view const& msg, F&& dataFn) const
|
||||
{
|
||||
#ifdef DEBUG_OUTPUT
|
||||
auto& j = std::cerr;
|
||||
#else
|
||||
if (!getJournal().active(beast::severities::kTrace))
|
||||
return;
|
||||
auto j = getJournal().trace();
|
||||
#endif
|
||||
j << "WasmTrace: " << msg << " " << dataFn();
|
||||
|
||||
#ifdef DEBUG_OUTPUT
|
||||
j << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
trace(std::string_view const& msg, Slice const& data, bool asHex) const override
|
||||
{
|
||||
if (!asHex)
|
||||
{
|
||||
log(msg, [&data] {
|
||||
return std::string_view(reinterpret_cast<char const*>(data.data()), data.size());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
log(msg, [&data] {
|
||||
std::string hex;
|
||||
hex.reserve(data.size() * 2);
|
||||
boost::algorithm::hex(data.begin(), data.end(), std::back_inserter(hex));
|
||||
return hex;
|
||||
});
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceNum(std::string_view const& msg, int64_t data) const override
|
||||
{
|
||||
log(msg, [data] { return data; });
|
||||
return 0;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceAccount(std::string_view const& msg, AccountID const& account) const override
|
||||
{
|
||||
log(msg, [&account] { return toBase58(account); });
|
||||
return 0;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceFloat(std::string_view const& msg, Slice const& data) const override
|
||||
{
|
||||
log(msg, [&data] { return wasm_float::floatToString(data); });
|
||||
return 0;
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceAmount(std::string_view const& msg, STAmount const& amount) const override
|
||||
{
|
||||
log(msg, [&amount] { return amount.getFullText(); });
|
||||
return 0;
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromInt(int64_t x, int32_t mode) const override
|
||||
{
|
||||
return wasm_float::floatFromIntImpl(x, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromUint(uint64_t x, int32_t mode) const override
|
||||
{
|
||||
return wasm_float::floatFromUintImpl(x, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatSet(int64_t mantissa, int32_t exponent, int32_t mode) const override
|
||||
{
|
||||
return wasm_float::floatSetImpl(mantissa, exponent, mode);
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
floatCompare(Slice const& x, Slice const& y) const override
|
||||
{
|
||||
return wasm_float::floatCompareImpl(x, y);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatAdd(Slice const& x, Slice const& y, int32_t mode) const override
|
||||
{
|
||||
return wasm_float::floatAddImpl(x, y, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const override
|
||||
{
|
||||
return wasm_float::floatSubtractImpl(x, y, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const override
|
||||
{
|
||||
return wasm_float::floatMultiplyImpl(x, y, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatDivide(Slice const& x, Slice const& y, int32_t mode) const override
|
||||
{
|
||||
return wasm_float::floatDivideImpl(x, y, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatRoot(Slice const& x, int32_t n, int32_t mode) const override
|
||||
{
|
||||
return wasm_float::floatRootImpl(x, n, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatPower(Slice const& x, int32_t n, int32_t mode) const override
|
||||
{
|
||||
return wasm_float::floatPowerImpl(x, n, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatLog(Slice const& x, int32_t mode) const override
|
||||
{
|
||||
return wasm_float::floatLogImpl(x, mode);
|
||||
}
|
||||
};
|
||||
|
||||
struct TestHostFunctionsSink : public TestHostFunctions
|
||||
{
|
||||
test::StreamSink sink_;
|
||||
void const* rt_ = nullptr;
|
||||
|
||||
public:
|
||||
explicit TestHostFunctionsSink(test::jtx::Env& env, int cd = 0)
|
||||
: TestHostFunctions(env, cd), sink_(beast::severities::kDebug)
|
||||
{
|
||||
j_ = beast::Journal(sink_);
|
||||
}
|
||||
|
||||
test::StreamSink&
|
||||
getSink()
|
||||
{
|
||||
return sink_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace xrpl
|
||||
1411
src/test/app/Wasm_test.cpp
Normal file
1411
src/test/app/Wasm_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
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?branch=u32-buffer#1e5d096f46742ef7fcf1cb6f28a2526a72ed59d8"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"quote",
|
||||
"sha2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-stdlib"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=u32-buffer#1e5d096f46742ef7fcf1cb6f28a2526a72ed59d8"
|
||||
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", branch = "u32-buffer" }
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
783
src/test/app/wasm_fixtures/all_host_functions/src/lib.rs
Normal file
783
src/test/app/wasm_fixtures/all_host_functions/src/lib.rs
Normal file
@@ -0,0 +1,783 @@
|
||||
#![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 mut sqn_buffer = [0u8; 4];
|
||||
let sqn_result = unsafe { host::get_ledger_sqn(sqn_buffer.as_mut_ptr(), sqn_buffer.len()) };
|
||||
|
||||
if sqn_result <= 0 {
|
||||
let _ = trace_num("ERROR: get_ledger_sqn failed:", sqn_result as i64);
|
||||
return -101; // Ledger sequence number test failed
|
||||
}
|
||||
let ledger_sqn = u32::from_be_bytes(sqn_buffer);
|
||||
let _ = trace_num("Ledger sequence number:", ledger_sqn as i64);
|
||||
|
||||
// Test 1.2: get_parent_ledger_time() - should return parent ledger timestamp
|
||||
let mut time_buffer = [0u8; 4];
|
||||
let time_result =
|
||||
unsafe { host::get_parent_ledger_time(time_buffer.as_mut_ptr(), time_buffer.len()) };
|
||||
|
||||
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 parent_ledger_time = u32::from_be_bytes(time_buffer);
|
||||
let _ = trace_num("Parent ledger time:", parent_ledger_time 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 sequence_number: i32 = 1000;
|
||||
let sequence_number_bytes = sequence_number.to_be_bytes();
|
||||
let escrow_keylet_result = unsafe {
|
||||
host::escrow_keylet(
|
||||
account_id.0.as_ptr(),
|
||||
account_id.0.len(),
|
||||
sequence_number_bytes.as_ptr(),
|
||||
sequence_number_bytes.len(),
|
||||
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 document_id: i32 = 42;
|
||||
let document_id_bytes = document_id.to_be_bytes();
|
||||
let oracle_keylet_result = unsafe {
|
||||
host::oracle_keylet(
|
||||
account_id.0.as_ptr(),
|
||||
account_id.0.len(),
|
||||
document_id_bytes.as_ptr(),
|
||||
document_id_bytes.len(),
|
||||
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?branch=u32-buffer#1e5d096f46742ef7fcf1cb6f28a2526a72ed59d8"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"quote",
|
||||
"sha2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-stdlib"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=u32-buffer#1e5d096f46742ef7fcf1cb6f28a2526a72ed59d8"
|
||||
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", branch = "u32-buffer" }
|
||||
|
||||
[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.
|
||||
}
|
||||
43
src/test/app/wasm_fixtures/bad_align.c
Normal file
43
src/test/app/wasm_fixtures/bad_align.c
Normal file
@@ -0,0 +1,43 @@
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t float_from_uint(uint8_t const *, int32_t, uint8_t *, int32_t, int32_t);
|
||||
int32_t check_keylet(uint8_t const *, int32_t, uint8_t const *, int32_t,
|
||||
uint8_t *, int32_t);
|
||||
|
||||
uint8_t e_data1[32 * 1024];
|
||||
uint8_t e_data2[32 * 1024];
|
||||
|
||||
int32_t test1()
|
||||
{
|
||||
e_data1[1] = 0xFF;
|
||||
e_data1[2] = 0xFF;
|
||||
e_data1[3] = 0xFF;
|
||||
e_data1[4] = 0xFF;
|
||||
e_data1[5] = 0xFF;
|
||||
e_data1[6] = 0xFF;
|
||||
e_data1[7] = 0xFF;
|
||||
e_data1[8] = 0xFF;
|
||||
int32_t result = float_from_uint(&e_data1[1], 8, &e_data1[35], 12, 0);
|
||||
return result >= 0 ? *((int32_t *)(&e_data1[36])) : result;
|
||||
}
|
||||
|
||||
int32_t test2()
|
||||
{
|
||||
// Set up misaligned uint32 (seq) at offset 1
|
||||
e_data2[1] = 0xFF;
|
||||
e_data2[2] = 0xFF;
|
||||
e_data2[3] = 0xFF;
|
||||
e_data2[4] = 0xFF;
|
||||
// Set up valid non-zero AccountID (20 bytes) at offset 10
|
||||
for (int i = 0; i < 20; i++)
|
||||
e_data2[10 + i] = i + 1;
|
||||
// Call check_keylet with misaligned uint32 at &e_data2[1] to hit line 72 in
|
||||
// HostFuncWrapper.cpp
|
||||
int32_t result =
|
||||
check_keylet(&e_data2[10], 20, &e_data2[1], 4, &e_data2[35], 32);
|
||||
// Return the misaligned value directly to validate it was read correctly (-1
|
||||
// if all 0xFF)
|
||||
return result >= 0 ? *((int32_t *)(&e_data2[36])) : result;
|
||||
}
|
||||
|
||||
int32_t test() { return test1() + test2(); }
|
||||
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?branch=u32-buffer#1e5d096f46742ef7fcf1cb6f28a2526a72ed59d8"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"quote",
|
||||
"sha2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-stdlib"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=u32-buffer#1e5d096f46742ef7fcf1cb6f28a2526a72ed59d8"
|
||||
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", branch = "u32-buffer" }
|
||||
@@ -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;
|
||||
}
|
||||
1810
src/test/app/wasm_fixtures/codecov_tests/src/lib.rs
Normal file
1810
src/test/app/wasm_fixtures/codecov_tests/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
125
src/test/app/wasm_fixtures/copyFixtures.py
Normal file
125
src/test/app/wasm_fixtures/copyFixtures.py
Normal file
@@ -0,0 +1,125 @@
|
||||
# cspell: disable
|
||||
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:
|
||||
subprocess.run(build_cmd, shell=True, check=True)
|
||||
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:
|
||||
subprocess.run(build_cmd, shell=True, check=True)
|
||||
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)))
|
||||
11
src/test/app/wasm_fixtures/fib.c
Normal file
11
src/test/app/wasm_fixtures/fib.c
Normal file
@@ -0,0 +1,11 @@
|
||||
// typedef long long mint;
|
||||
typedef int mint;
|
||||
|
||||
mint fib(mint n)
|
||||
{
|
||||
if (!n)
|
||||
return 0;
|
||||
if (n <= 2)
|
||||
return 1;
|
||||
return fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
8980
src/test/app/wasm_fixtures/fixtures.cpp
Normal file
8980
src/test/app/wasm_fixtures/fixtures.cpp
Normal file
File diff suppressed because it is too large
Load Diff
81
src/test/app/wasm_fixtures/fixtures.h
Normal file
81
src/test/app/wasm_fixtures/fixtures.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#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 allKeyletsWasmHex;
|
||||
extern std::string const codecovTestsWasmHex;
|
||||
|
||||
extern std::string const fibWasmHex;
|
||||
|
||||
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 deepRecursionHex;
|
||||
extern std::string const infiniteLoopWasmHex;
|
||||
extern std::string const startLoopHex;
|
||||
|
||||
extern std::string const badAlignWasmHex;
|
||||
|
||||
extern std::string const thousandParamsHex;
|
||||
extern std::string const thousand1ParamsHex;
|
||||
extern std::string const locals10kHex;
|
||||
extern std::string const functions5kHex;
|
||||
|
||||
extern std::string const opcReservedHex;
|
||||
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?branch=u32-buffer#1e5d096f46742ef7fcf1cb6f28a2526a72ed59d8"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"quote",
|
||||
"sha2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-stdlib"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=u32-buffer#1e5d096f46742ef7fcf1cb6f28a2526a72ed59d8"
|
||||
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", branch = "u32-buffer" }
|
||||
|
||||
[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
|
||||
}
|
||||
7
src/test/app/wasm_fixtures/infiniteLoop.c
Normal file
7
src/test/app/wasm_fixtures/infiniteLoop.c
Normal file
@@ -0,0 +1,7 @@
|
||||
int loop()
|
||||
{
|
||||
int volatile x = 0;
|
||||
while (1)
|
||||
x++;
|
||||
return x;
|
||||
}
|
||||
14
src/test/app/wasm_fixtures/ledgerSqn.c
Normal file
14
src/test/app/wasm_fixtures/ledgerSqn.c
Normal file
@@ -0,0 +1,14 @@
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t get_ledger_sqn(uint8_t *, int32_t);
|
||||
|
||||
int finish()
|
||||
{
|
||||
uint32_t sqn;
|
||||
int32_t result = get_ledger_sqn((uint8_t *)&sqn, sizeof(sqn));
|
||||
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
return sqn >= 5 ? 5 : 0;
|
||||
}
|
||||
264
src/test/app/wasm_fixtures/thousand1_params.c
Normal file
264
src/test/app/wasm_fixtures/thousand1_params.c
Normal file
@@ -0,0 +1,264 @@
|
||||
// clang-format off
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t test(
|
||||
int32_t p0, int32_t p1, int32_t p2, int32_t p3, int32_t p4, int32_t p5, int32_t p6, int32_t p7
|
||||
, int32_t p8, int32_t p9, int32_t p10, int32_t p11, int32_t p12, int32_t p13, int32_t p14, int32_t p15
|
||||
, int32_t p16, int32_t p17, int32_t p18, int32_t p19, int32_t p20, int32_t p21, int32_t p22, int32_t p23
|
||||
, int32_t p24, int32_t p25, int32_t p26, int32_t p27, int32_t p28, int32_t p29, int32_t p30, int32_t p31
|
||||
, int32_t p32, int32_t p33, int32_t p34, int32_t p35, int32_t p36, int32_t p37, int32_t p38, int32_t p39
|
||||
, int32_t p40, int32_t p41, int32_t p42, int32_t p43, int32_t p44, int32_t p45, int32_t p46, int32_t p47
|
||||
, int32_t p48, int32_t p49, int32_t p50, int32_t p51, int32_t p52, int32_t p53, int32_t p54, int32_t p55
|
||||
, int32_t p56, int32_t p57, int32_t p58, int32_t p59, int32_t p60, int32_t p61, int32_t p62, int32_t p63
|
||||
, int32_t p64, int32_t p65, int32_t p66, int32_t p67, int32_t p68, int32_t p69, int32_t p70, int32_t p71
|
||||
, int32_t p72, int32_t p73, int32_t p74, int32_t p75, int32_t p76, int32_t p77, int32_t p78, int32_t p79
|
||||
, int32_t p80, int32_t p81, int32_t p82, int32_t p83, int32_t p84, int32_t p85, int32_t p86, int32_t p87
|
||||
, int32_t p88, int32_t p89, int32_t p90, int32_t p91, int32_t p92, int32_t p93, int32_t p94, int32_t p95
|
||||
, int32_t p96, int32_t p97, int32_t p98, int32_t p99, int32_t p100, int32_t p101, int32_t p102, int32_t p103
|
||||
, int32_t p104, int32_t p105, int32_t p106, int32_t p107, int32_t p108, int32_t p109, int32_t p110, int32_t p111
|
||||
, int32_t p112, int32_t p113, int32_t p114, int32_t p115, int32_t p116, int32_t p117, int32_t p118, int32_t p119
|
||||
, int32_t p120, int32_t p121, int32_t p122, int32_t p123, int32_t p124, int32_t p125, int32_t p126, int32_t p127
|
||||
, int32_t p128, int32_t p129, int32_t p130, int32_t p131, int32_t p132, int32_t p133, int32_t p134, int32_t p135
|
||||
, int32_t p136, int32_t p137, int32_t p138, int32_t p139, int32_t p140, int32_t p141, int32_t p142, int32_t p143
|
||||
, int32_t p144, int32_t p145, int32_t p146, int32_t p147, int32_t p148, int32_t p149, int32_t p150, int32_t p151
|
||||
, int32_t p152, int32_t p153, int32_t p154, int32_t p155, int32_t p156, int32_t p157, int32_t p158, int32_t p159
|
||||
, int32_t p160, int32_t p161, int32_t p162, int32_t p163, int32_t p164, int32_t p165, int32_t p166, int32_t p167
|
||||
, int32_t p168, int32_t p169, int32_t p170, int32_t p171, int32_t p172, int32_t p173, int32_t p174, int32_t p175
|
||||
, int32_t p176, int32_t p177, int32_t p178, int32_t p179, int32_t p180, int32_t p181, int32_t p182, int32_t p183
|
||||
, int32_t p184, int32_t p185, int32_t p186, int32_t p187, int32_t p188, int32_t p189, int32_t p190, int32_t p191
|
||||
, int32_t p192, int32_t p193, int32_t p194, int32_t p195, int32_t p196, int32_t p197, int32_t p198, int32_t p199
|
||||
, int32_t p200, int32_t p201, int32_t p202, int32_t p203, int32_t p204, int32_t p205, int32_t p206, int32_t p207
|
||||
, int32_t p208, int32_t p209, int32_t p210, int32_t p211, int32_t p212, int32_t p213, int32_t p214, int32_t p215
|
||||
, int32_t p216, int32_t p217, int32_t p218, int32_t p219, int32_t p220, int32_t p221, int32_t p222, int32_t p223
|
||||
, int32_t p224, int32_t p225, int32_t p226, int32_t p227, int32_t p228, int32_t p229, int32_t p230, int32_t p231
|
||||
, int32_t p232, int32_t p233, int32_t p234, int32_t p235, int32_t p236, int32_t p237, int32_t p238, int32_t p239
|
||||
, int32_t p240, int32_t p241, int32_t p242, int32_t p243, int32_t p244, int32_t p245, int32_t p246, int32_t p247
|
||||
, int32_t p248, int32_t p249, int32_t p250, int32_t p251, int32_t p252, int32_t p253, int32_t p254, int32_t p255
|
||||
, int32_t p256, int32_t p257, int32_t p258, int32_t p259, int32_t p260, int32_t p261, int32_t p262, int32_t p263
|
||||
, int32_t p264, int32_t p265, int32_t p266, int32_t p267, int32_t p268, int32_t p269, int32_t p270, int32_t p271
|
||||
, int32_t p272, int32_t p273, int32_t p274, int32_t p275, int32_t p276, int32_t p277, int32_t p278, int32_t p279
|
||||
, int32_t p280, int32_t p281, int32_t p282, int32_t p283, int32_t p284, int32_t p285, int32_t p286, int32_t p287
|
||||
, int32_t p288, int32_t p289, int32_t p290, int32_t p291, int32_t p292, int32_t p293, int32_t p294, int32_t p295
|
||||
, int32_t p296, int32_t p297, int32_t p298, int32_t p299, int32_t p300, int32_t p301, int32_t p302, int32_t p303
|
||||
, int32_t p304, int32_t p305, int32_t p306, int32_t p307, int32_t p308, int32_t p309, int32_t p310, int32_t p311
|
||||
, int32_t p312, int32_t p313, int32_t p314, int32_t p315, int32_t p316, int32_t p317, int32_t p318, int32_t p319
|
||||
, int32_t p320, int32_t p321, int32_t p322, int32_t p323, int32_t p324, int32_t p325, int32_t p326, int32_t p327
|
||||
, int32_t p328, int32_t p329, int32_t p330, int32_t p331, int32_t p332, int32_t p333, int32_t p334, int32_t p335
|
||||
, int32_t p336, int32_t p337, int32_t p338, int32_t p339, int32_t p340, int32_t p341, int32_t p342, int32_t p343
|
||||
, int32_t p344, int32_t p345, int32_t p346, int32_t p347, int32_t p348, int32_t p349, int32_t p350, int32_t p351
|
||||
, int32_t p352, int32_t p353, int32_t p354, int32_t p355, int32_t p356, int32_t p357, int32_t p358, int32_t p359
|
||||
, int32_t p360, int32_t p361, int32_t p362, int32_t p363, int32_t p364, int32_t p365, int32_t p366, int32_t p367
|
||||
, int32_t p368, int32_t p369, int32_t p370, int32_t p371, int32_t p372, int32_t p373, int32_t p374, int32_t p375
|
||||
, int32_t p376, int32_t p377, int32_t p378, int32_t p379, int32_t p380, int32_t p381, int32_t p382, int32_t p383
|
||||
, int32_t p384, int32_t p385, int32_t p386, int32_t p387, int32_t p388, int32_t p389, int32_t p390, int32_t p391
|
||||
, int32_t p392, int32_t p393, int32_t p394, int32_t p395, int32_t p396, int32_t p397, int32_t p398, int32_t p399
|
||||
, int32_t p400, int32_t p401, int32_t p402, int32_t p403, int32_t p404, int32_t p405, int32_t p406, int32_t p407
|
||||
, int32_t p408, int32_t p409, int32_t p410, int32_t p411, int32_t p412, int32_t p413, int32_t p414, int32_t p415
|
||||
, int32_t p416, int32_t p417, int32_t p418, int32_t p419, int32_t p420, int32_t p421, int32_t p422, int32_t p423
|
||||
, int32_t p424, int32_t p425, int32_t p426, int32_t p427, int32_t p428, int32_t p429, int32_t p430, int32_t p431
|
||||
, int32_t p432, int32_t p433, int32_t p434, int32_t p435, int32_t p436, int32_t p437, int32_t p438, int32_t p439
|
||||
, int32_t p440, int32_t p441, int32_t p442, int32_t p443, int32_t p444, int32_t p445, int32_t p446, int32_t p447
|
||||
, int32_t p448, int32_t p449, int32_t p450, int32_t p451, int32_t p452, int32_t p453, int32_t p454, int32_t p455
|
||||
, int32_t p456, int32_t p457, int32_t p458, int32_t p459, int32_t p460, int32_t p461, int32_t p462, int32_t p463
|
||||
, int32_t p464, int32_t p465, int32_t p466, int32_t p467, int32_t p468, int32_t p469, int32_t p470, int32_t p471
|
||||
, int32_t p472, int32_t p473, int32_t p474, int32_t p475, int32_t p476, int32_t p477, int32_t p478, int32_t p479
|
||||
, int32_t p480, int32_t p481, int32_t p482, int32_t p483, int32_t p484, int32_t p485, int32_t p486, int32_t p487
|
||||
, int32_t p488, int32_t p489, int32_t p490, int32_t p491, int32_t p492, int32_t p493, int32_t p494, int32_t p495
|
||||
, int32_t p496, int32_t p497, int32_t p498, int32_t p499, int32_t p500, int32_t p501, int32_t p502, int32_t p503
|
||||
, int32_t p504, int32_t p505, int32_t p506, int32_t p507, int32_t p508, int32_t p509, int32_t p510, int32_t p511
|
||||
, int32_t p512, int32_t p513, int32_t p514, int32_t p515, int32_t p516, int32_t p517, int32_t p518, int32_t p519
|
||||
, int32_t p520, int32_t p521, int32_t p522, int32_t p523, int32_t p524, int32_t p525, int32_t p526, int32_t p527
|
||||
, int32_t p528, int32_t p529, int32_t p530, int32_t p531, int32_t p532, int32_t p533, int32_t p534, int32_t p535
|
||||
, int32_t p536, int32_t p537, int32_t p538, int32_t p539, int32_t p540, int32_t p541, int32_t p542, int32_t p543
|
||||
, int32_t p544, int32_t p545, int32_t p546, int32_t p547, int32_t p548, int32_t p549, int32_t p550, int32_t p551
|
||||
, int32_t p552, int32_t p553, int32_t p554, int32_t p555, int32_t p556, int32_t p557, int32_t p558, int32_t p559
|
||||
, int32_t p560, int32_t p561, int32_t p562, int32_t p563, int32_t p564, int32_t p565, int32_t p566, int32_t p567
|
||||
, int32_t p568, int32_t p569, int32_t p570, int32_t p571, int32_t p572, int32_t p573, int32_t p574, int32_t p575
|
||||
, int32_t p576, int32_t p577, int32_t p578, int32_t p579, int32_t p580, int32_t p581, int32_t p582, int32_t p583
|
||||
, int32_t p584, int32_t p585, int32_t p586, int32_t p587, int32_t p588, int32_t p589, int32_t p590, int32_t p591
|
||||
, int32_t p592, int32_t p593, int32_t p594, int32_t p595, int32_t p596, int32_t p597, int32_t p598, int32_t p599
|
||||
, int32_t p600, int32_t p601, int32_t p602, int32_t p603, int32_t p604, int32_t p605, int32_t p606, int32_t p607
|
||||
, int32_t p608, int32_t p609, int32_t p610, int32_t p611, int32_t p612, int32_t p613, int32_t p614, int32_t p615
|
||||
, int32_t p616, int32_t p617, int32_t p618, int32_t p619, int32_t p620, int32_t p621, int32_t p622, int32_t p623
|
||||
, int32_t p624, int32_t p625, int32_t p626, int32_t p627, int32_t p628, int32_t p629, int32_t p630, int32_t p631
|
||||
, int32_t p632, int32_t p633, int32_t p634, int32_t p635, int32_t p636, int32_t p637, int32_t p638, int32_t p639
|
||||
, int32_t p640, int32_t p641, int32_t p642, int32_t p643, int32_t p644, int32_t p645, int32_t p646, int32_t p647
|
||||
, int32_t p648, int32_t p649, int32_t p650, int32_t p651, int32_t p652, int32_t p653, int32_t p654, int32_t p655
|
||||
, int32_t p656, int32_t p657, int32_t p658, int32_t p659, int32_t p660, int32_t p661, int32_t p662, int32_t p663
|
||||
, int32_t p664, int32_t p665, int32_t p666, int32_t p667, int32_t p668, int32_t p669, int32_t p670, int32_t p671
|
||||
, int32_t p672, int32_t p673, int32_t p674, int32_t p675, int32_t p676, int32_t p677, int32_t p678, int32_t p679
|
||||
, int32_t p680, int32_t p681, int32_t p682, int32_t p683, int32_t p684, int32_t p685, int32_t p686, int32_t p687
|
||||
, int32_t p688, int32_t p689, int32_t p690, int32_t p691, int32_t p692, int32_t p693, int32_t p694, int32_t p695
|
||||
, int32_t p696, int32_t p697, int32_t p698, int32_t p699, int32_t p700, int32_t p701, int32_t p702, int32_t p703
|
||||
, int32_t p704, int32_t p705, int32_t p706, int32_t p707, int32_t p708, int32_t p709, int32_t p710, int32_t p711
|
||||
, int32_t p712, int32_t p713, int32_t p714, int32_t p715, int32_t p716, int32_t p717, int32_t p718, int32_t p719
|
||||
, int32_t p720, int32_t p721, int32_t p722, int32_t p723, int32_t p724, int32_t p725, int32_t p726, int32_t p727
|
||||
, int32_t p728, int32_t p729, int32_t p730, int32_t p731, int32_t p732, int32_t p733, int32_t p734, int32_t p735
|
||||
, int32_t p736, int32_t p737, int32_t p738, int32_t p739, int32_t p740, int32_t p741, int32_t p742, int32_t p743
|
||||
, int32_t p744, int32_t p745, int32_t p746, int32_t p747, int32_t p748, int32_t p749, int32_t p750, int32_t p751
|
||||
, int32_t p752, int32_t p753, int32_t p754, int32_t p755, int32_t p756, int32_t p757, int32_t p758, int32_t p759
|
||||
, int32_t p760, int32_t p761, int32_t p762, int32_t p763, int32_t p764, int32_t p765, int32_t p766, int32_t p767
|
||||
, int32_t p768, int32_t p769, int32_t p770, int32_t p771, int32_t p772, int32_t p773, int32_t p774, int32_t p775
|
||||
, int32_t p776, int32_t p777, int32_t p778, int32_t p779, int32_t p780, int32_t p781, int32_t p782, int32_t p783
|
||||
, int32_t p784, int32_t p785, int32_t p786, int32_t p787, int32_t p788, int32_t p789, int32_t p790, int32_t p791
|
||||
, int32_t p792, int32_t p793, int32_t p794, int32_t p795, int32_t p796, int32_t p797, int32_t p798, int32_t p799
|
||||
, int32_t p800, int32_t p801, int32_t p802, int32_t p803, int32_t p804, int32_t p805, int32_t p806, int32_t p807
|
||||
, int32_t p808, int32_t p809, int32_t p810, int32_t p811, int32_t p812, int32_t p813, int32_t p814, int32_t p815
|
||||
, int32_t p816, int32_t p817, int32_t p818, int32_t p819, int32_t p820, int32_t p821, int32_t p822, int32_t p823
|
||||
, int32_t p824, int32_t p825, int32_t p826, int32_t p827, int32_t p828, int32_t p829, int32_t p830, int32_t p831
|
||||
, int32_t p832, int32_t p833, int32_t p834, int32_t p835, int32_t p836, int32_t p837, int32_t p838, int32_t p839
|
||||
, int32_t p840, int32_t p841, int32_t p842, int32_t p843, int32_t p844, int32_t p845, int32_t p846, int32_t p847
|
||||
, int32_t p848, int32_t p849, int32_t p850, int32_t p851, int32_t p852, int32_t p853, int32_t p854, int32_t p855
|
||||
, int32_t p856, int32_t p857, int32_t p858, int32_t p859, int32_t p860, int32_t p861, int32_t p862, int32_t p863
|
||||
, int32_t p864, int32_t p865, int32_t p866, int32_t p867, int32_t p868, int32_t p869, int32_t p870, int32_t p871
|
||||
, int32_t p872, int32_t p873, int32_t p874, int32_t p875, int32_t p876, int32_t p877, int32_t p878, int32_t p879
|
||||
, int32_t p880, int32_t p881, int32_t p882, int32_t p883, int32_t p884, int32_t p885, int32_t p886, int32_t p887
|
||||
, int32_t p888, int32_t p889, int32_t p890, int32_t p891, int32_t p892, int32_t p893, int32_t p894, int32_t p895
|
||||
, int32_t p896, int32_t p897, int32_t p898, int32_t p899, int32_t p900, int32_t p901, int32_t p902, int32_t p903
|
||||
, int32_t p904, int32_t p905, int32_t p906, int32_t p907, int32_t p908, int32_t p909, int32_t p910, int32_t p911
|
||||
, int32_t p912, int32_t p913, int32_t p914, int32_t p915, int32_t p916, int32_t p917, int32_t p918, int32_t p919
|
||||
, int32_t p920, int32_t p921, int32_t p922, int32_t p923, int32_t p924, int32_t p925, int32_t p926, int32_t p927
|
||||
, int32_t p928, int32_t p929, int32_t p930, int32_t p931, int32_t p932, int32_t p933, int32_t p934, int32_t p935
|
||||
, int32_t p936, int32_t p937, int32_t p938, int32_t p939, int32_t p940, int32_t p941, int32_t p942, int32_t p943
|
||||
, int32_t p944, int32_t p945, int32_t p946, int32_t p947, int32_t p948, int32_t p949, int32_t p950, int32_t p951
|
||||
, int32_t p952, int32_t p953, int32_t p954, int32_t p955, int32_t p956, int32_t p957, int32_t p958, int32_t p959
|
||||
, int32_t p960, int32_t p961, int32_t p962, int32_t p963, int32_t p964, int32_t p965, int32_t p966, int32_t p967
|
||||
, int32_t p968, int32_t p969, int32_t p970, int32_t p971, int32_t p972, int32_t p973, int32_t p974, int32_t p975
|
||||
, int32_t p976, int32_t p977, int32_t p978, int32_t p979, int32_t p980, int32_t p981, int32_t p982, int32_t p983
|
||||
, int32_t p984, int32_t p985, int32_t p986, int32_t p987, int32_t p988, int32_t p989, int32_t p990, int32_t p991
|
||||
, int32_t p992, int32_t p993, int32_t p994, int32_t p995, int32_t p996, int32_t p997, int32_t p998, int32_t p999
|
||||
, int32_t p1000
|
||||
)
|
||||
{
|
||||
int32_t x;
|
||||
x = p0 + p1 + p2 + p3 + p4 + p5 + p6 + p7
|
||||
+ p8 + p9 + p10 + p11 + p12 + p13 + p14 + p15
|
||||
+ p16 + p17 + p18 + p19 + p20 + p21 + p22 + p23
|
||||
+ p24 + p25 + p26 + p27 + p28 + p29 + p30 + p31
|
||||
+ p32 + p33 + p34 + p35 + p36 + p37 + p38 + p39
|
||||
+ p40 + p41 + p42 + p43 + p44 + p45 + p46 + p47
|
||||
+ p48 + p49 + p50 + p51 + p52 + p53 + p54 + p55
|
||||
+ p56 + p57 + p58 + p59 + p60 + p61 + p62 + p63
|
||||
+ p64 + p65 + p66 + p67 + p68 + p69 + p70 + p71
|
||||
+ p72 + p73 + p74 + p75 + p76 + p77 + p78 + p79
|
||||
+ p80 + p81 + p82 + p83 + p84 + p85 + p86 + p87
|
||||
+ p88 + p89 + p90 + p91 + p92 + p93 + p94 + p95
|
||||
+ p96 + p97 + p98 + p99 + p100 + p101 + p102 + p103
|
||||
+ p104 + p105 + p106 + p107 + p108 + p109 + p110 + p111
|
||||
+ p112 + p113 + p114 + p115 + p116 + p117 + p118 + p119
|
||||
+ p120 + p121 + p122 + p123 + p124 + p125 + p126 + p127
|
||||
+ p128 + p129 + p130 + p131 + p132 + p133 + p134 + p135
|
||||
+ p136 + p137 + p138 + p139 + p140 + p141 + p142 + p143
|
||||
+ p144 + p145 + p146 + p147 + p148 + p149 + p150 + p151
|
||||
+ p152 + p153 + p154 + p155 + p156 + p157 + p158 + p159
|
||||
+ p160 + p161 + p162 + p163 + p164 + p165 + p166 + p167
|
||||
+ p168 + p169 + p170 + p171 + p172 + p173 + p174 + p175
|
||||
+ p176 + p177 + p178 + p179 + p180 + p181 + p182 + p183
|
||||
+ p184 + p185 + p186 + p187 + p188 + p189 + p190 + p191
|
||||
+ p192 + p193 + p194 + p195 + p196 + p197 + p198 + p199
|
||||
+ p200 + p201 + p202 + p203 + p204 + p205 + p206 + p207
|
||||
+ p208 + p209 + p210 + p211 + p212 + p213 + p214 + p215
|
||||
+ p216 + p217 + p218 + p219 + p220 + p221 + p222 + p223
|
||||
+ p224 + p225 + p226 + p227 + p228 + p229 + p230 + p231
|
||||
+ p232 + p233 + p234 + p235 + p236 + p237 + p238 + p239
|
||||
+ p240 + p241 + p242 + p243 + p244 + p245 + p246 + p247
|
||||
+ p248 + p249 + p250 + p251 + p252 + p253 + p254 + p255
|
||||
+ p256 + p257 + p258 + p259 + p260 + p261 + p262 + p263
|
||||
+ p264 + p265 + p266 + p267 + p268 + p269 + p270 + p271
|
||||
+ p272 + p273 + p274 + p275 + p276 + p277 + p278 + p279
|
||||
+ p280 + p281 + p282 + p283 + p284 + p285 + p286 + p287
|
||||
+ p288 + p289 + p290 + p291 + p292 + p293 + p294 + p295
|
||||
+ p296 + p297 + p298 + p299 + p300 + p301 + p302 + p303
|
||||
+ p304 + p305 + p306 + p307 + p308 + p309 + p310 + p311
|
||||
+ p312 + p313 + p314 + p315 + p316 + p317 + p318 + p319
|
||||
+ p320 + p321 + p322 + p323 + p324 + p325 + p326 + p327
|
||||
+ p328 + p329 + p330 + p331 + p332 + p333 + p334 + p335
|
||||
+ p336 + p337 + p338 + p339 + p340 + p341 + p342 + p343
|
||||
+ p344 + p345 + p346 + p347 + p348 + p349 + p350 + p351
|
||||
+ p352 + p353 + p354 + p355 + p356 + p357 + p358 + p359
|
||||
+ p360 + p361 + p362 + p363 + p364 + p365 + p366 + p367
|
||||
+ p368 + p369 + p370 + p371 + p372 + p373 + p374 + p375
|
||||
+ p376 + p377 + p378 + p379 + p380 + p381 + p382 + p383
|
||||
+ p384 + p385 + p386 + p387 + p388 + p389 + p390 + p391
|
||||
+ p392 + p393 + p394 + p395 + p396 + p397 + p398 + p399
|
||||
+ p400 + p401 + p402 + p403 + p404 + p405 + p406 + p407
|
||||
+ p408 + p409 + p410 + p411 + p412 + p413 + p414 + p415
|
||||
+ p416 + p417 + p418 + p419 + p420 + p421 + p422 + p423
|
||||
+ p424 + p425 + p426 + p427 + p428 + p429 + p430 + p431
|
||||
+ p432 + p433 + p434 + p435 + p436 + p437 + p438 + p439
|
||||
+ p440 + p441 + p442 + p443 + p444 + p445 + p446 + p447
|
||||
+ p448 + p449 + p450 + p451 + p452 + p453 + p454 + p455
|
||||
+ p456 + p457 + p458 + p459 + p460 + p461 + p462 + p463
|
||||
+ p464 + p465 + p466 + p467 + p468 + p469 + p470 + p471
|
||||
+ p472 + p473 + p474 + p475 + p476 + p477 + p478 + p479
|
||||
+ p480 + p481 + p482 + p483 + p484 + p485 + p486 + p487
|
||||
+ p488 + p489 + p490 + p491 + p492 + p493 + p494 + p495
|
||||
+ p496 + p497 + p498 + p499 + p500 + p501 + p502 + p503
|
||||
+ p504 + p505 + p506 + p507 + p508 + p509 + p510 + p511
|
||||
+ p512 + p513 + p514 + p515 + p516 + p517 + p518 + p519
|
||||
+ p520 + p521 + p522 + p523 + p524 + p525 + p526 + p527
|
||||
+ p528 + p529 + p530 + p531 + p532 + p533 + p534 + p535
|
||||
+ p536 + p537 + p538 + p539 + p540 + p541 + p542 + p543
|
||||
+ p544 + p545 + p546 + p547 + p548 + p549 + p550 + p551
|
||||
+ p552 + p553 + p554 + p555 + p556 + p557 + p558 + p559
|
||||
+ p560 + p561 + p562 + p563 + p564 + p565 + p566 + p567
|
||||
+ p568 + p569 + p570 + p571 + p572 + p573 + p574 + p575
|
||||
+ p576 + p577 + p578 + p579 + p580 + p581 + p582 + p583
|
||||
+ p584 + p585 + p586 + p587 + p588 + p589 + p590 + p591
|
||||
+ p592 + p593 + p594 + p595 + p596 + p597 + p598 + p599
|
||||
+ p600 + p601 + p602 + p603 + p604 + p605 + p606 + p607
|
||||
+ p608 + p609 + p610 + p611 + p612 + p613 + p614 + p615
|
||||
+ p616 + p617 + p618 + p619 + p620 + p621 + p622 + p623
|
||||
+ p624 + p625 + p626 + p627 + p628 + p629 + p630 + p631
|
||||
+ p632 + p633 + p634 + p635 + p636 + p637 + p638 + p639
|
||||
+ p640 + p641 + p642 + p643 + p644 + p645 + p646 + p647
|
||||
+ p648 + p649 + p650 + p651 + p652 + p653 + p654 + p655
|
||||
+ p656 + p657 + p658 + p659 + p660 + p661 + p662 + p663
|
||||
+ p664 + p665 + p666 + p667 + p668 + p669 + p670 + p671
|
||||
+ p672 + p673 + p674 + p675 + p676 + p677 + p678 + p679
|
||||
+ p680 + p681 + p682 + p683 + p684 + p685 + p686 + p687
|
||||
+ p688 + p689 + p690 + p691 + p692 + p693 + p694 + p695
|
||||
+ p696 + p697 + p698 + p699 + p700 + p701 + p702 + p703
|
||||
+ p704 + p705 + p706 + p707 + p708 + p709 + p710 + p711
|
||||
+ p712 + p713 + p714 + p715 + p716 + p717 + p718 + p719
|
||||
+ p720 + p721 + p722 + p723 + p724 + p725 + p726 + p727
|
||||
+ p728 + p729 + p730 + p731 + p732 + p733 + p734 + p735
|
||||
+ p736 + p737 + p738 + p739 + p740 + p741 + p742 + p743
|
||||
+ p744 + p745 + p746 + p747 + p748 + p749 + p750 + p751
|
||||
+ p752 + p753 + p754 + p755 + p756 + p757 + p758 + p759
|
||||
+ p760 + p761 + p762 + p763 + p764 + p765 + p766 + p767
|
||||
+ p768 + p769 + p770 + p771 + p772 + p773 + p774 + p775
|
||||
+ p776 + p777 + p778 + p779 + p780 + p781 + p782 + p783
|
||||
+ p784 + p785 + p786 + p787 + p788 + p789 + p790 + p791
|
||||
+ p792 + p793 + p794 + p795 + p796 + p797 + p798 + p799
|
||||
+ p800 + p801 + p802 + p803 + p804 + p805 + p806 + p807
|
||||
+ p808 + p809 + p810 + p811 + p812 + p813 + p814 + p815
|
||||
+ p816 + p817 + p818 + p819 + p820 + p821 + p822 + p823
|
||||
+ p824 + p825 + p826 + p827 + p828 + p829 + p830 + p831
|
||||
+ p832 + p833 + p834 + p835 + p836 + p837 + p838 + p839
|
||||
+ p840 + p841 + p842 + p843 + p844 + p845 + p846 + p847
|
||||
+ p848 + p849 + p850 + p851 + p852 + p853 + p854 + p855
|
||||
+ p856 + p857 + p858 + p859 + p860 + p861 + p862 + p863
|
||||
+ p864 + p865 + p866 + p867 + p868 + p869 + p870 + p871
|
||||
+ p872 + p873 + p874 + p875 + p876 + p877 + p878 + p879
|
||||
+ p880 + p881 + p882 + p883 + p884 + p885 + p886 + p887
|
||||
+ p888 + p889 + p890 + p891 + p892 + p893 + p894 + p895
|
||||
+ p896 + p897 + p898 + p899 + p900 + p901 + p902 + p903
|
||||
+ p904 + p905 + p906 + p907 + p908 + p909 + p910 + p911
|
||||
+ p912 + p913 + p914 + p915 + p916 + p917 + p918 + p919
|
||||
+ p920 + p921 + p922 + p923 + p924 + p925 + p926 + p927
|
||||
+ p928 + p929 + p930 + p931 + p932 + p933 + p934 + p935
|
||||
+ p936 + p937 + p938 + p939 + p940 + p941 + p942 + p943
|
||||
+ p944 + p945 + p946 + p947 + p948 + p949 + p950 + p951
|
||||
+ p952 + p953 + p954 + p955 + p956 + p957 + p958 + p959
|
||||
+ p960 + p961 + p962 + p963 + p964 + p965 + p966 + p967
|
||||
+ p968 + p969 + p970 + p971 + p972 + p973 + p974 + p975
|
||||
+ p976 + p977 + p978 + p979 + p980 + p981 + p982 + p983
|
||||
+ p984 + p985 + p986 + p987 + p988 + p989 + p990 + p991
|
||||
+ p992 + p993 + p994 + p995 + p996 + p997 + p998 + p999
|
||||
+ p1000;
|
||||
return x;
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
262
src/test/app/wasm_fixtures/thousand_params.c
Normal file
262
src/test/app/wasm_fixtures/thousand_params.c
Normal file
@@ -0,0 +1,262 @@
|
||||
// clang-format off
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t test(
|
||||
int32_t p0, int32_t p1, int32_t p2, int32_t p3, int32_t p4, int32_t p5, int32_t p6, int32_t p7
|
||||
, int32_t p8, int32_t p9, int32_t p10, int32_t p11, int32_t p12, int32_t p13, int32_t p14, int32_t p15
|
||||
, int32_t p16, int32_t p17, int32_t p18, int32_t p19, int32_t p20, int32_t p21, int32_t p22, int32_t p23
|
||||
, int32_t p24, int32_t p25, int32_t p26, int32_t p27, int32_t p28, int32_t p29, int32_t p30, int32_t p31
|
||||
, int32_t p32, int32_t p33, int32_t p34, int32_t p35, int32_t p36, int32_t p37, int32_t p38, int32_t p39
|
||||
, int32_t p40, int32_t p41, int32_t p42, int32_t p43, int32_t p44, int32_t p45, int32_t p46, int32_t p47
|
||||
, int32_t p48, int32_t p49, int32_t p50, int32_t p51, int32_t p52, int32_t p53, int32_t p54, int32_t p55
|
||||
, int32_t p56, int32_t p57, int32_t p58, int32_t p59, int32_t p60, int32_t p61, int32_t p62, int32_t p63
|
||||
, int32_t p64, int32_t p65, int32_t p66, int32_t p67, int32_t p68, int32_t p69, int32_t p70, int32_t p71
|
||||
, int32_t p72, int32_t p73, int32_t p74, int32_t p75, int32_t p76, int32_t p77, int32_t p78, int32_t p79
|
||||
, int32_t p80, int32_t p81, int32_t p82, int32_t p83, int32_t p84, int32_t p85, int32_t p86, int32_t p87
|
||||
, int32_t p88, int32_t p89, int32_t p90, int32_t p91, int32_t p92, int32_t p93, int32_t p94, int32_t p95
|
||||
, int32_t p96, int32_t p97, int32_t p98, int32_t p99, int32_t p100, int32_t p101, int32_t p102, int32_t p103
|
||||
, int32_t p104, int32_t p105, int32_t p106, int32_t p107, int32_t p108, int32_t p109, int32_t p110, int32_t p111
|
||||
, int32_t p112, int32_t p113, int32_t p114, int32_t p115, int32_t p116, int32_t p117, int32_t p118, int32_t p119
|
||||
, int32_t p120, int32_t p121, int32_t p122, int32_t p123, int32_t p124, int32_t p125, int32_t p126, int32_t p127
|
||||
, int32_t p128, int32_t p129, int32_t p130, int32_t p131, int32_t p132, int32_t p133, int32_t p134, int32_t p135
|
||||
, int32_t p136, int32_t p137, int32_t p138, int32_t p139, int32_t p140, int32_t p141, int32_t p142, int32_t p143
|
||||
, int32_t p144, int32_t p145, int32_t p146, int32_t p147, int32_t p148, int32_t p149, int32_t p150, int32_t p151
|
||||
, int32_t p152, int32_t p153, int32_t p154, int32_t p155, int32_t p156, int32_t p157, int32_t p158, int32_t p159
|
||||
, int32_t p160, int32_t p161, int32_t p162, int32_t p163, int32_t p164, int32_t p165, int32_t p166, int32_t p167
|
||||
, int32_t p168, int32_t p169, int32_t p170, int32_t p171, int32_t p172, int32_t p173, int32_t p174, int32_t p175
|
||||
, int32_t p176, int32_t p177, int32_t p178, int32_t p179, int32_t p180, int32_t p181, int32_t p182, int32_t p183
|
||||
, int32_t p184, int32_t p185, int32_t p186, int32_t p187, int32_t p188, int32_t p189, int32_t p190, int32_t p191
|
||||
, int32_t p192, int32_t p193, int32_t p194, int32_t p195, int32_t p196, int32_t p197, int32_t p198, int32_t p199
|
||||
, int32_t p200, int32_t p201, int32_t p202, int32_t p203, int32_t p204, int32_t p205, int32_t p206, int32_t p207
|
||||
, int32_t p208, int32_t p209, int32_t p210, int32_t p211, int32_t p212, int32_t p213, int32_t p214, int32_t p215
|
||||
, int32_t p216, int32_t p217, int32_t p218, int32_t p219, int32_t p220, int32_t p221, int32_t p222, int32_t p223
|
||||
, int32_t p224, int32_t p225, int32_t p226, int32_t p227, int32_t p228, int32_t p229, int32_t p230, int32_t p231
|
||||
, int32_t p232, int32_t p233, int32_t p234, int32_t p235, int32_t p236, int32_t p237, int32_t p238, int32_t p239
|
||||
, int32_t p240, int32_t p241, int32_t p242, int32_t p243, int32_t p244, int32_t p245, int32_t p246, int32_t p247
|
||||
, int32_t p248, int32_t p249, int32_t p250, int32_t p251, int32_t p252, int32_t p253, int32_t p254, int32_t p255
|
||||
, int32_t p256, int32_t p257, int32_t p258, int32_t p259, int32_t p260, int32_t p261, int32_t p262, int32_t p263
|
||||
, int32_t p264, int32_t p265, int32_t p266, int32_t p267, int32_t p268, int32_t p269, int32_t p270, int32_t p271
|
||||
, int32_t p272, int32_t p273, int32_t p274, int32_t p275, int32_t p276, int32_t p277, int32_t p278, int32_t p279
|
||||
, int32_t p280, int32_t p281, int32_t p282, int32_t p283, int32_t p284, int32_t p285, int32_t p286, int32_t p287
|
||||
, int32_t p288, int32_t p289, int32_t p290, int32_t p291, int32_t p292, int32_t p293, int32_t p294, int32_t p295
|
||||
, int32_t p296, int32_t p297, int32_t p298, int32_t p299, int32_t p300, int32_t p301, int32_t p302, int32_t p303
|
||||
, int32_t p304, int32_t p305, int32_t p306, int32_t p307, int32_t p308, int32_t p309, int32_t p310, int32_t p311
|
||||
, int32_t p312, int32_t p313, int32_t p314, int32_t p315, int32_t p316, int32_t p317, int32_t p318, int32_t p319
|
||||
, int32_t p320, int32_t p321, int32_t p322, int32_t p323, int32_t p324, int32_t p325, int32_t p326, int32_t p327
|
||||
, int32_t p328, int32_t p329, int32_t p330, int32_t p331, int32_t p332, int32_t p333, int32_t p334, int32_t p335
|
||||
, int32_t p336, int32_t p337, int32_t p338, int32_t p339, int32_t p340, int32_t p341, int32_t p342, int32_t p343
|
||||
, int32_t p344, int32_t p345, int32_t p346, int32_t p347, int32_t p348, int32_t p349, int32_t p350, int32_t p351
|
||||
, int32_t p352, int32_t p353, int32_t p354, int32_t p355, int32_t p356, int32_t p357, int32_t p358, int32_t p359
|
||||
, int32_t p360, int32_t p361, int32_t p362, int32_t p363, int32_t p364, int32_t p365, int32_t p366, int32_t p367
|
||||
, int32_t p368, int32_t p369, int32_t p370, int32_t p371, int32_t p372, int32_t p373, int32_t p374, int32_t p375
|
||||
, int32_t p376, int32_t p377, int32_t p378, int32_t p379, int32_t p380, int32_t p381, int32_t p382, int32_t p383
|
||||
, int32_t p384, int32_t p385, int32_t p386, int32_t p387, int32_t p388, int32_t p389, int32_t p390, int32_t p391
|
||||
, int32_t p392, int32_t p393, int32_t p394, int32_t p395, int32_t p396, int32_t p397, int32_t p398, int32_t p399
|
||||
, int32_t p400, int32_t p401, int32_t p402, int32_t p403, int32_t p404, int32_t p405, int32_t p406, int32_t p407
|
||||
, int32_t p408, int32_t p409, int32_t p410, int32_t p411, int32_t p412, int32_t p413, int32_t p414, int32_t p415
|
||||
, int32_t p416, int32_t p417, int32_t p418, int32_t p419, int32_t p420, int32_t p421, int32_t p422, int32_t p423
|
||||
, int32_t p424, int32_t p425, int32_t p426, int32_t p427, int32_t p428, int32_t p429, int32_t p430, int32_t p431
|
||||
, int32_t p432, int32_t p433, int32_t p434, int32_t p435, int32_t p436, int32_t p437, int32_t p438, int32_t p439
|
||||
, int32_t p440, int32_t p441, int32_t p442, int32_t p443, int32_t p444, int32_t p445, int32_t p446, int32_t p447
|
||||
, int32_t p448, int32_t p449, int32_t p450, int32_t p451, int32_t p452, int32_t p453, int32_t p454, int32_t p455
|
||||
, int32_t p456, int32_t p457, int32_t p458, int32_t p459, int32_t p460, int32_t p461, int32_t p462, int32_t p463
|
||||
, int32_t p464, int32_t p465, int32_t p466, int32_t p467, int32_t p468, int32_t p469, int32_t p470, int32_t p471
|
||||
, int32_t p472, int32_t p473, int32_t p474, int32_t p475, int32_t p476, int32_t p477, int32_t p478, int32_t p479
|
||||
, int32_t p480, int32_t p481, int32_t p482, int32_t p483, int32_t p484, int32_t p485, int32_t p486, int32_t p487
|
||||
, int32_t p488, int32_t p489, int32_t p490, int32_t p491, int32_t p492, int32_t p493, int32_t p494, int32_t p495
|
||||
, int32_t p496, int32_t p497, int32_t p498, int32_t p499, int32_t p500, int32_t p501, int32_t p502, int32_t p503
|
||||
, int32_t p504, int32_t p505, int32_t p506, int32_t p507, int32_t p508, int32_t p509, int32_t p510, int32_t p511
|
||||
, int32_t p512, int32_t p513, int32_t p514, int32_t p515, int32_t p516, int32_t p517, int32_t p518, int32_t p519
|
||||
, int32_t p520, int32_t p521, int32_t p522, int32_t p523, int32_t p524, int32_t p525, int32_t p526, int32_t p527
|
||||
, int32_t p528, int32_t p529, int32_t p530, int32_t p531, int32_t p532, int32_t p533, int32_t p534, int32_t p535
|
||||
, int32_t p536, int32_t p537, int32_t p538, int32_t p539, int32_t p540, int32_t p541, int32_t p542, int32_t p543
|
||||
, int32_t p544, int32_t p545, int32_t p546, int32_t p547, int32_t p548, int32_t p549, int32_t p550, int32_t p551
|
||||
, int32_t p552, int32_t p553, int32_t p554, int32_t p555, int32_t p556, int32_t p557, int32_t p558, int32_t p559
|
||||
, int32_t p560, int32_t p561, int32_t p562, int32_t p563, int32_t p564, int32_t p565, int32_t p566, int32_t p567
|
||||
, int32_t p568, int32_t p569, int32_t p570, int32_t p571, int32_t p572, int32_t p573, int32_t p574, int32_t p575
|
||||
, int32_t p576, int32_t p577, int32_t p578, int32_t p579, int32_t p580, int32_t p581, int32_t p582, int32_t p583
|
||||
, int32_t p584, int32_t p585, int32_t p586, int32_t p587, int32_t p588, int32_t p589, int32_t p590, int32_t p591
|
||||
, int32_t p592, int32_t p593, int32_t p594, int32_t p595, int32_t p596, int32_t p597, int32_t p598, int32_t p599
|
||||
, int32_t p600, int32_t p601, int32_t p602, int32_t p603, int32_t p604, int32_t p605, int32_t p606, int32_t p607
|
||||
, int32_t p608, int32_t p609, int32_t p610, int32_t p611, int32_t p612, int32_t p613, int32_t p614, int32_t p615
|
||||
, int32_t p616, int32_t p617, int32_t p618, int32_t p619, int32_t p620, int32_t p621, int32_t p622, int32_t p623
|
||||
, int32_t p624, int32_t p625, int32_t p626, int32_t p627, int32_t p628, int32_t p629, int32_t p630, int32_t p631
|
||||
, int32_t p632, int32_t p633, int32_t p634, int32_t p635, int32_t p636, int32_t p637, int32_t p638, int32_t p639
|
||||
, int32_t p640, int32_t p641, int32_t p642, int32_t p643, int32_t p644, int32_t p645, int32_t p646, int32_t p647
|
||||
, int32_t p648, int32_t p649, int32_t p650, int32_t p651, int32_t p652, int32_t p653, int32_t p654, int32_t p655
|
||||
, int32_t p656, int32_t p657, int32_t p658, int32_t p659, int32_t p660, int32_t p661, int32_t p662, int32_t p663
|
||||
, int32_t p664, int32_t p665, int32_t p666, int32_t p667, int32_t p668, int32_t p669, int32_t p670, int32_t p671
|
||||
, int32_t p672, int32_t p673, int32_t p674, int32_t p675, int32_t p676, int32_t p677, int32_t p678, int32_t p679
|
||||
, int32_t p680, int32_t p681, int32_t p682, int32_t p683, int32_t p684, int32_t p685, int32_t p686, int32_t p687
|
||||
, int32_t p688, int32_t p689, int32_t p690, int32_t p691, int32_t p692, int32_t p693, int32_t p694, int32_t p695
|
||||
, int32_t p696, int32_t p697, int32_t p698, int32_t p699, int32_t p700, int32_t p701, int32_t p702, int32_t p703
|
||||
, int32_t p704, int32_t p705, int32_t p706, int32_t p707, int32_t p708, int32_t p709, int32_t p710, int32_t p711
|
||||
, int32_t p712, int32_t p713, int32_t p714, int32_t p715, int32_t p716, int32_t p717, int32_t p718, int32_t p719
|
||||
, int32_t p720, int32_t p721, int32_t p722, int32_t p723, int32_t p724, int32_t p725, int32_t p726, int32_t p727
|
||||
, int32_t p728, int32_t p729, int32_t p730, int32_t p731, int32_t p732, int32_t p733, int32_t p734, int32_t p735
|
||||
, int32_t p736, int32_t p737, int32_t p738, int32_t p739, int32_t p740, int32_t p741, int32_t p742, int32_t p743
|
||||
, int32_t p744, int32_t p745, int32_t p746, int32_t p747, int32_t p748, int32_t p749, int32_t p750, int32_t p751
|
||||
, int32_t p752, int32_t p753, int32_t p754, int32_t p755, int32_t p756, int32_t p757, int32_t p758, int32_t p759
|
||||
, int32_t p760, int32_t p761, int32_t p762, int32_t p763, int32_t p764, int32_t p765, int32_t p766, int32_t p767
|
||||
, int32_t p768, int32_t p769, int32_t p770, int32_t p771, int32_t p772, int32_t p773, int32_t p774, int32_t p775
|
||||
, int32_t p776, int32_t p777, int32_t p778, int32_t p779, int32_t p780, int32_t p781, int32_t p782, int32_t p783
|
||||
, int32_t p784, int32_t p785, int32_t p786, int32_t p787, int32_t p788, int32_t p789, int32_t p790, int32_t p791
|
||||
, int32_t p792, int32_t p793, int32_t p794, int32_t p795, int32_t p796, int32_t p797, int32_t p798, int32_t p799
|
||||
, int32_t p800, int32_t p801, int32_t p802, int32_t p803, int32_t p804, int32_t p805, int32_t p806, int32_t p807
|
||||
, int32_t p808, int32_t p809, int32_t p810, int32_t p811, int32_t p812, int32_t p813, int32_t p814, int32_t p815
|
||||
, int32_t p816, int32_t p817, int32_t p818, int32_t p819, int32_t p820, int32_t p821, int32_t p822, int32_t p823
|
||||
, int32_t p824, int32_t p825, int32_t p826, int32_t p827, int32_t p828, int32_t p829, int32_t p830, int32_t p831
|
||||
, int32_t p832, int32_t p833, int32_t p834, int32_t p835, int32_t p836, int32_t p837, int32_t p838, int32_t p839
|
||||
, int32_t p840, int32_t p841, int32_t p842, int32_t p843, int32_t p844, int32_t p845, int32_t p846, int32_t p847
|
||||
, int32_t p848, int32_t p849, int32_t p850, int32_t p851, int32_t p852, int32_t p853, int32_t p854, int32_t p855
|
||||
, int32_t p856, int32_t p857, int32_t p858, int32_t p859, int32_t p860, int32_t p861, int32_t p862, int32_t p863
|
||||
, int32_t p864, int32_t p865, int32_t p866, int32_t p867, int32_t p868, int32_t p869, int32_t p870, int32_t p871
|
||||
, int32_t p872, int32_t p873, int32_t p874, int32_t p875, int32_t p876, int32_t p877, int32_t p878, int32_t p879
|
||||
, int32_t p880, int32_t p881, int32_t p882, int32_t p883, int32_t p884, int32_t p885, int32_t p886, int32_t p887
|
||||
, int32_t p888, int32_t p889, int32_t p890, int32_t p891, int32_t p892, int32_t p893, int32_t p894, int32_t p895
|
||||
, int32_t p896, int32_t p897, int32_t p898, int32_t p899, int32_t p900, int32_t p901, int32_t p902, int32_t p903
|
||||
, int32_t p904, int32_t p905, int32_t p906, int32_t p907, int32_t p908, int32_t p909, int32_t p910, int32_t p911
|
||||
, int32_t p912, int32_t p913, int32_t p914, int32_t p915, int32_t p916, int32_t p917, int32_t p918, int32_t p919
|
||||
, int32_t p920, int32_t p921, int32_t p922, int32_t p923, int32_t p924, int32_t p925, int32_t p926, int32_t p927
|
||||
, int32_t p928, int32_t p929, int32_t p930, int32_t p931, int32_t p932, int32_t p933, int32_t p934, int32_t p935
|
||||
, int32_t p936, int32_t p937, int32_t p938, int32_t p939, int32_t p940, int32_t p941, int32_t p942, int32_t p943
|
||||
, int32_t p944, int32_t p945, int32_t p946, int32_t p947, int32_t p948, int32_t p949, int32_t p950, int32_t p951
|
||||
, int32_t p952, int32_t p953, int32_t p954, int32_t p955, int32_t p956, int32_t p957, int32_t p958, int32_t p959
|
||||
, int32_t p960, int32_t p961, int32_t p962, int32_t p963, int32_t p964, int32_t p965, int32_t p966, int32_t p967
|
||||
, int32_t p968, int32_t p969, int32_t p970, int32_t p971, int32_t p972, int32_t p973, int32_t p974, int32_t p975
|
||||
, int32_t p976, int32_t p977, int32_t p978, int32_t p979, int32_t p980, int32_t p981, int32_t p982, int32_t p983
|
||||
, int32_t p984, int32_t p985, int32_t p986, int32_t p987, int32_t p988, int32_t p989, int32_t p990, int32_t p991
|
||||
, int32_t p992, int32_t p993, int32_t p994, int32_t p995, int32_t p996, int32_t p997, int32_t p998, int32_t p999
|
||||
)
|
||||
{
|
||||
int32_t x;
|
||||
x = p0 + p1 + p2 + p3 + p4 + p5 + p6 + p7
|
||||
+ p8 + p9 + p10 + p11 + p12 + p13 + p14 + p15
|
||||
+ p16 + p17 + p18 + p19 + p20 + p21 + p22 + p23
|
||||
+ p24 + p25 + p26 + p27 + p28 + p29 + p30 + p31
|
||||
+ p32 + p33 + p34 + p35 + p36 + p37 + p38 + p39
|
||||
+ p40 + p41 + p42 + p43 + p44 + p45 + p46 + p47
|
||||
+ p48 + p49 + p50 + p51 + p52 + p53 + p54 + p55
|
||||
+ p56 + p57 + p58 + p59 + p60 + p61 + p62 + p63
|
||||
+ p64 + p65 + p66 + p67 + p68 + p69 + p70 + p71
|
||||
+ p72 + p73 + p74 + p75 + p76 + p77 + p78 + p79
|
||||
+ p80 + p81 + p82 + p83 + p84 + p85 + p86 + p87
|
||||
+ p88 + p89 + p90 + p91 + p92 + p93 + p94 + p95
|
||||
+ p96 + p97 + p98 + p99 + p100 + p101 + p102 + p103
|
||||
+ p104 + p105 + p106 + p107 + p108 + p109 + p110 + p111
|
||||
+ p112 + p113 + p114 + p115 + p116 + p117 + p118 + p119
|
||||
+ p120 + p121 + p122 + p123 + p124 + p125 + p126 + p127
|
||||
+ p128 + p129 + p130 + p131 + p132 + p133 + p134 + p135
|
||||
+ p136 + p137 + p138 + p139 + p140 + p141 + p142 + p143
|
||||
+ p144 + p145 + p146 + p147 + p148 + p149 + p150 + p151
|
||||
+ p152 + p153 + p154 + p155 + p156 + p157 + p158 + p159
|
||||
+ p160 + p161 + p162 + p163 + p164 + p165 + p166 + p167
|
||||
+ p168 + p169 + p170 + p171 + p172 + p173 + p174 + p175
|
||||
+ p176 + p177 + p178 + p179 + p180 + p181 + p182 + p183
|
||||
+ p184 + p185 + p186 + p187 + p188 + p189 + p190 + p191
|
||||
+ p192 + p193 + p194 + p195 + p196 + p197 + p198 + p199
|
||||
+ p200 + p201 + p202 + p203 + p204 + p205 + p206 + p207
|
||||
+ p208 + p209 + p210 + p211 + p212 + p213 + p214 + p215
|
||||
+ p216 + p217 + p218 + p219 + p220 + p221 + p222 + p223
|
||||
+ p224 + p225 + p226 + p227 + p228 + p229 + p230 + p231
|
||||
+ p232 + p233 + p234 + p235 + p236 + p237 + p238 + p239
|
||||
+ p240 + p241 + p242 + p243 + p244 + p245 + p246 + p247
|
||||
+ p248 + p249 + p250 + p251 + p252 + p253 + p254 + p255
|
||||
+ p256 + p257 + p258 + p259 + p260 + p261 + p262 + p263
|
||||
+ p264 + p265 + p266 + p267 + p268 + p269 + p270 + p271
|
||||
+ p272 + p273 + p274 + p275 + p276 + p277 + p278 + p279
|
||||
+ p280 + p281 + p282 + p283 + p284 + p285 + p286 + p287
|
||||
+ p288 + p289 + p290 + p291 + p292 + p293 + p294 + p295
|
||||
+ p296 + p297 + p298 + p299 + p300 + p301 + p302 + p303
|
||||
+ p304 + p305 + p306 + p307 + p308 + p309 + p310 + p311
|
||||
+ p312 + p313 + p314 + p315 + p316 + p317 + p318 + p319
|
||||
+ p320 + p321 + p322 + p323 + p324 + p325 + p326 + p327
|
||||
+ p328 + p329 + p330 + p331 + p332 + p333 + p334 + p335
|
||||
+ p336 + p337 + p338 + p339 + p340 + p341 + p342 + p343
|
||||
+ p344 + p345 + p346 + p347 + p348 + p349 + p350 + p351
|
||||
+ p352 + p353 + p354 + p355 + p356 + p357 + p358 + p359
|
||||
+ p360 + p361 + p362 + p363 + p364 + p365 + p366 + p367
|
||||
+ p368 + p369 + p370 + p371 + p372 + p373 + p374 + p375
|
||||
+ p376 + p377 + p378 + p379 + p380 + p381 + p382 + p383
|
||||
+ p384 + p385 + p386 + p387 + p388 + p389 + p390 + p391
|
||||
+ p392 + p393 + p394 + p395 + p396 + p397 + p398 + p399
|
||||
+ p400 + p401 + p402 + p403 + p404 + p405 + p406 + p407
|
||||
+ p408 + p409 + p410 + p411 + p412 + p413 + p414 + p415
|
||||
+ p416 + p417 + p418 + p419 + p420 + p421 + p422 + p423
|
||||
+ p424 + p425 + p426 + p427 + p428 + p429 + p430 + p431
|
||||
+ p432 + p433 + p434 + p435 + p436 + p437 + p438 + p439
|
||||
+ p440 + p441 + p442 + p443 + p444 + p445 + p446 + p447
|
||||
+ p448 + p449 + p450 + p451 + p452 + p453 + p454 + p455
|
||||
+ p456 + p457 + p458 + p459 + p460 + p461 + p462 + p463
|
||||
+ p464 + p465 + p466 + p467 + p468 + p469 + p470 + p471
|
||||
+ p472 + p473 + p474 + p475 + p476 + p477 + p478 + p479
|
||||
+ p480 + p481 + p482 + p483 + p484 + p485 + p486 + p487
|
||||
+ p488 + p489 + p490 + p491 + p492 + p493 + p494 + p495
|
||||
+ p496 + p497 + p498 + p499 + p500 + p501 + p502 + p503
|
||||
+ p504 + p505 + p506 + p507 + p508 + p509 + p510 + p511
|
||||
+ p512 + p513 + p514 + p515 + p516 + p517 + p518 + p519
|
||||
+ p520 + p521 + p522 + p523 + p524 + p525 + p526 + p527
|
||||
+ p528 + p529 + p530 + p531 + p532 + p533 + p534 + p535
|
||||
+ p536 + p537 + p538 + p539 + p540 + p541 + p542 + p543
|
||||
+ p544 + p545 + p546 + p547 + p548 + p549 + p550 + p551
|
||||
+ p552 + p553 + p554 + p555 + p556 + p557 + p558 + p559
|
||||
+ p560 + p561 + p562 + p563 + p564 + p565 + p566 + p567
|
||||
+ p568 + p569 + p570 + p571 + p572 + p573 + p574 + p575
|
||||
+ p576 + p577 + p578 + p579 + p580 + p581 + p582 + p583
|
||||
+ p584 + p585 + p586 + p587 + p588 + p589 + p590 + p591
|
||||
+ p592 + p593 + p594 + p595 + p596 + p597 + p598 + p599
|
||||
+ p600 + p601 + p602 + p603 + p604 + p605 + p606 + p607
|
||||
+ p608 + p609 + p610 + p611 + p612 + p613 + p614 + p615
|
||||
+ p616 + p617 + p618 + p619 + p620 + p621 + p622 + p623
|
||||
+ p624 + p625 + p626 + p627 + p628 + p629 + p630 + p631
|
||||
+ p632 + p633 + p634 + p635 + p636 + p637 + p638 + p639
|
||||
+ p640 + p641 + p642 + p643 + p644 + p645 + p646 + p647
|
||||
+ p648 + p649 + p650 + p651 + p652 + p653 + p654 + p655
|
||||
+ p656 + p657 + p658 + p659 + p660 + p661 + p662 + p663
|
||||
+ p664 + p665 + p666 + p667 + p668 + p669 + p670 + p671
|
||||
+ p672 + p673 + p674 + p675 + p676 + p677 + p678 + p679
|
||||
+ p680 + p681 + p682 + p683 + p684 + p685 + p686 + p687
|
||||
+ p688 + p689 + p690 + p691 + p692 + p693 + p694 + p695
|
||||
+ p696 + p697 + p698 + p699 + p700 + p701 + p702 + p703
|
||||
+ p704 + p705 + p706 + p707 + p708 + p709 + p710 + p711
|
||||
+ p712 + p713 + p714 + p715 + p716 + p717 + p718 + p719
|
||||
+ p720 + p721 + p722 + p723 + p724 + p725 + p726 + p727
|
||||
+ p728 + p729 + p730 + p731 + p732 + p733 + p734 + p735
|
||||
+ p736 + p737 + p738 + p739 + p740 + p741 + p742 + p743
|
||||
+ p744 + p745 + p746 + p747 + p748 + p749 + p750 + p751
|
||||
+ p752 + p753 + p754 + p755 + p756 + p757 + p758 + p759
|
||||
+ p760 + p761 + p762 + p763 + p764 + p765 + p766 + p767
|
||||
+ p768 + p769 + p770 + p771 + p772 + p773 + p774 + p775
|
||||
+ p776 + p777 + p778 + p779 + p780 + p781 + p782 + p783
|
||||
+ p784 + p785 + p786 + p787 + p788 + p789 + p790 + p791
|
||||
+ p792 + p793 + p794 + p795 + p796 + p797 + p798 + p799
|
||||
+ p800 + p801 + p802 + p803 + p804 + p805 + p806 + p807
|
||||
+ p808 + p809 + p810 + p811 + p812 + p813 + p814 + p815
|
||||
+ p816 + p817 + p818 + p819 + p820 + p821 + p822 + p823
|
||||
+ p824 + p825 + p826 + p827 + p828 + p829 + p830 + p831
|
||||
+ p832 + p833 + p834 + p835 + p836 + p837 + p838 + p839
|
||||
+ p840 + p841 + p842 + p843 + p844 + p845 + p846 + p847
|
||||
+ p848 + p849 + p850 + p851 + p852 + p853 + p854 + p855
|
||||
+ p856 + p857 + p858 + p859 + p860 + p861 + p862 + p863
|
||||
+ p864 + p865 + p866 + p867 + p868 + p869 + p870 + p871
|
||||
+ p872 + p873 + p874 + p875 + p876 + p877 + p878 + p879
|
||||
+ p880 + p881 + p882 + p883 + p884 + p885 + p886 + p887
|
||||
+ p888 + p889 + p890 + p891 + p892 + p893 + p894 + p895
|
||||
+ p896 + p897 + p898 + p899 + p900 + p901 + p902 + p903
|
||||
+ p904 + p905 + p906 + p907 + p908 + p909 + p910 + p911
|
||||
+ p912 + p913 + p914 + p915 + p916 + p917 + p918 + p919
|
||||
+ p920 + p921 + p922 + p923 + p924 + p925 + p926 + p927
|
||||
+ p928 + p929 + p930 + p931 + p932 + p933 + p934 + p935
|
||||
+ p936 + p937 + p938 + p939 + p940 + p941 + p942 + p943
|
||||
+ p944 + p945 + p946 + p947 + p948 + p949 + p950 + p951
|
||||
+ p952 + p953 + p954 + p955 + p956 + p957 + p958 + p959
|
||||
+ p960 + p961 + p962 + p963 + p964 + p965 + p966 + p967
|
||||
+ p968 + p969 + p970 + p971 + p972 + p973 + p974 + p975
|
||||
+ p976 + p977 + p978 + p979 + p980 + p981 + p982 + p983
|
||||
+ p984 + p985 + p986 + p987 + p988 + p989 + p990 + p991
|
||||
+ p992 + p993 + p994 + p995 + p996 + p997 + p998 + p999;
|
||||
return x;
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
13
src/test/app/wasm_fixtures/wat/custom_page_sizes.wat
Normal file
13
src/test/app/wasm_fixtures/wat/custom_page_sizes.wat
Normal file
@@ -0,0 +1,13 @@
|
||||
(module
|
||||
;; Define a memory with 1 initial page.
|
||||
;; CRITICAL: We explicitly set the page size to 1 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))
|
||||
)
|
||||
45002
src/test/app/wasm_fixtures/wat/functions_5k.wat
Normal file
45002
src/test/app/wasm_fixtures/wat/functions_5k.wat
Normal file
File diff suppressed because it is too large
Load Diff
50000
src/test/app/wasm_fixtures/wat/locals_10k.wat
Normal file
50000
src/test/app/wasm_fixtures/wat/locals_10k.wat
Normal file
File diff suppressed because it is too large
Load Diff
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))
|
||||
)
|
||||
98
src/test/app/wasm_fixtures/wat/opc_reserved.wat
Normal file
98
src/test/app/wasm_fixtures/wat/opc_reserved.wat
Normal file
@@ -0,0 +1,98 @@
|
||||
(module
|
||||
|
||||
;; Type for call_indirect
|
||||
(type (func (result i32)))
|
||||
|
||||
;; Memory and table declarations
|
||||
(memory 1)
|
||||
(table 1 funcref)
|
||||
(data (i32.const 0) "test")
|
||||
(elem (i32.const 0) $test_func)
|
||||
|
||||
;; Global declarations
|
||||
(global $g0 (mut i32) (i32.const 0))
|
||||
(global $g1 (mut i64) (i64.const 0))
|
||||
|
||||
;; Test function for call/call_indirect
|
||||
(func $test_func (result i32)
|
||||
i32.const 42
|
||||
)
|
||||
|
||||
|
||||
;; Main function with all instructions in hex order
|
||||
(func $all_instructions (export "all_instructions") (result i32)
|
||||
(local $l0 i32)
|
||||
(local $l1 i64)
|
||||
|
||||
;; 0x01: nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
i32.const 11
|
||||
)
|
||||
)
|
||||
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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user