From 6da92d8922c07c2075cfc81828df35157627ff8d Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 23 Dec 2025 18:34:59 -0500 Subject: [PATCH] feature --- .github/CODEOWNERS | 6 - .github/scripts/strategy-matrix/linux.json | 56 +- CMakeLists.txt | 1 + cfg/rippled-example.cfg | 33 + cmake/XrplCore.cmake | 1 + conan.lock | 1 + conanfile.py | 2 + include/xrpl/basics/Number.h | 4 + include/xrpl/ledger/ApplyViewImpl.h | 14 + include/xrpl/ledger/OpenViewSandbox.h | 105 + include/xrpl/ledger/View.h | 35 + include/xrpl/ledger/detail/ApplyStateTable.h | 2 + include/xrpl/protocol/Emitable.h | 100 + include/xrpl/protocol/Fees.h | 6 + include/xrpl/protocol/IOUAmount.h | 7 + include/xrpl/protocol/Indexes.h | 25 + include/xrpl/protocol/LedgerFormats.h | 8 - include/xrpl/protocol/Permissions.h | 1 + include/xrpl/protocol/Protocol.h | 7 + include/xrpl/protocol/SField.h | 9 + include/xrpl/protocol/STData.h | 308 ++ include/xrpl/protocol/STDataType.h | 110 + include/xrpl/protocol/STJson.h | 214 ++ include/xrpl/protocol/STObject.h | 12 + include/xrpl/protocol/TER.h | 6 + include/xrpl/protocol/TxFlags.h | 14 + include/xrpl/protocol/TxMeta.h | 38 +- include/xrpl/protocol/detail/emitable.macro | 38 + include/xrpl/protocol/detail/features.macro | 3 +- .../xrpl/protocol/detail/ledger_entries.macro | 50 +- include/xrpl/protocol/detail/sfields.macro | 81 +- .../xrpl/protocol/detail/transactions.macro | 210 +- include/xrpl/protocol/jss.h | 14 +- src/libxrpl/basics/Number.cpp | 42 + src/libxrpl/ledger/ApplyStateTable.cpp | 4 + src/libxrpl/ledger/ApplyViewImpl.cpp | 11 +- src/libxrpl/ledger/View.cpp | 320 +- src/libxrpl/protocol/Emitable.cpp | 188 ++ src/libxrpl/protocol/IOUAmount.cpp | 10 +- src/libxrpl/protocol/Indexes.cpp | 31 +- src/libxrpl/protocol/InnerObjectFormats.cpp | 29 + src/libxrpl/protocol/STData.cpp | 1019 ++++++ src/libxrpl/protocol/STDataType.cpp | 273 ++ src/libxrpl/protocol/STJson.cpp | 799 +++++ src/libxrpl/protocol/STObject.cpp | 95 + src/libxrpl/protocol/STParsedJSON.cpp | 49 + src/libxrpl/protocol/STValidation.cpp | 4 + src/libxrpl/protocol/STVar.cpp | 12 + src/libxrpl/protocol/TER.cpp | 5 + src/libxrpl/protocol/TxFormats.cpp | 4 +- src/libxrpl/protocol/TxMeta.cpp | 10 +- src/test/app/AMM_test.cpp | 2 +- src/test/app/ContractHostFuncImpl_test.cpp | 1466 ++++++++ src/test/app/Contract_test.cpp | 2141 ++++++++++++ src/test/app/EscrowSmart_test.cpp | 1012 ++++++ src/test/app/EscrowToken_test.cpp | 70 + src/test/app/Escrow_test.cpp | 4 +- src/test/app/FeeVote_test.cpp | 384 ++- src/test/app/HostFuncImpl_test.cpp | 2967 +++++++++++++++++ src/test/app/NetworkOPs_test.cpp | 6 +- src/test/app/PseudoTx_test.cpp | 8 + src/test/app/TestHostFunctions.h | 1329 ++++++++ src/test/app/Wasm_test.cpp | 746 +++++ src/test/app/wasm_fixtures/.gitignore | 3 + .../all_host_functions/Cargo.lock | 171 + .../all_host_functions/Cargo.toml | 21 + .../all_host_functions/src/lib.rs | 772 +++++ .../app/wasm_fixtures/all_keylets/Cargo.lock | 171 + .../app/wasm_fixtures/all_keylets/Cargo.toml | 21 + .../app/wasm_fixtures/all_keylets/src/lib.rs | 181 + src/test/app/wasm_fixtures/b58.c | 73 + .../wasm_fixtures/codecov_tests/Cargo.lock | 171 + .../wasm_fixtures/codecov_tests/Cargo.toml | 18 + .../codecov_tests/src/host_bindings_loose.rs | 47 + .../wasm_fixtures/codecov_tests/src/lib.rs | 1547 +++++++++ .../wasm_fixtures/contract_data/Cargo.lock | 15 + .../wasm_fixtures/contract_data/Cargo.toml | 21 + .../wasm_fixtures/contract_data/src/lib.rs | 532 +++ src/test/app/wasm_fixtures/copyFixtures.py | 134 + src/test/app/wasm_fixtures/disableFloat.wat | 34 + .../app/wasm_fixtures/emit_txn/Cargo.toml | 21 + .../app/wasm_fixtures/emit_txn/src/lib.rs | 218 ++ src/test/app/wasm_fixtures/events/Cargo.toml | 21 + src/test/app/wasm_fixtures/events/src/lib.rs | 99 + src/test/app/wasm_fixtures/fib.c | 12 + src/test/app/wasm_fixtures/fixtures.cpp | 1986 +++++++++++ src/test/app/wasm_fixtures/fixtures.h | 39 + .../app/wasm_fixtures/float_tests/Cargo.lock | 171 + .../app/wasm_fixtures/float_tests/Cargo.toml | 21 + .../app/wasm_fixtures/float_tests/src/lib.rs | 461 +++ src/test/app/wasm_fixtures/ledgerSqn.c | 27 + .../app/wasm_fixtures/parameters/Cargo.lock | 15 + .../app/wasm_fixtures/parameters/Cargo.toml | 21 + .../app/wasm_fixtures/parameters/src/lib.rs | 717 ++++ src/test/app/wasm_fixtures/sha512Pure.c | 145 + src/test/app/wasm_fixtures/updateData.c | 13 + .../app/wasm_fixtures/wat/deep_recursion.wat | 29 + src/test/jtx.h | 1 + src/test/jtx/contract.h | 198 ++ src/test/jtx/escrow.h | 70 + src/test/jtx/impl/contract.cpp | 160 + src/test/jtx/impl/envconfig.cpp | 5 + src/test/protocol/Hooks_test.cpp | 179 - src/test/protocol/STDataType_test.cpp | 664 ++++ src/test/protocol/STData_test.cpp | 1324 ++++++++ src/test/protocol/STJson_test.cpp | 887 +++++ src/test/rpc/Subscribe_test.cpp | 19 +- src/xrpld/app/ledger/Ledger.cpp | 41 + src/xrpld/app/misc/ContractUtils.cpp | 678 ++++ src/xrpld/app/misc/ContractUtils.h | 113 + src/xrpld/app/misc/DeleteUtils.cpp | 415 +++ src/xrpld/app/misc/DeleteUtils.h | 64 + src/xrpld/app/misc/FeeVoteImpl.cpp | 85 +- src/xrpld/app/misc/NetworkOPs.cpp | 101 +- src/xrpld/app/misc/NetworkOPs.h | 3 + src/xrpld/app/tx/apply.h | 9 + src/xrpld/app/tx/detail/ApplyContext.cpp | 21 +- src/xrpld/app/tx/detail/ApplyContext.h | 47 +- src/xrpld/app/tx/detail/Change.cpp | 20 + src/xrpld/app/tx/detail/ContractCall.cpp | 360 ++ src/xrpld/app/tx/detail/ContractCall.h | 51 + src/xrpld/app/tx/detail/ContractClawback.cpp | 53 + src/xrpld/app/tx/detail/ContractClawback.h | 48 + src/xrpld/app/tx/detail/ContractCreate.cpp | 326 ++ src/xrpld/app/tx/detail/ContractCreate.h | 51 + src/xrpld/app/tx/detail/ContractDelete.cpp | 173 + src/xrpld/app/tx/detail/ContractDelete.h | 56 + src/xrpld/app/tx/detail/ContractModify.cpp | 411 +++ src/xrpld/app/tx/detail/ContractModify.h | 51 + .../app/tx/detail/ContractUserDelete.cpp | 53 + src/xrpld/app/tx/detail/ContractUserDelete.h | 48 + src/xrpld/app/tx/detail/DeleteAccount.cpp | 163 +- src/xrpld/app/tx/detail/Escrow.cpp | 280 +- src/xrpld/app/tx/detail/Escrow.h | 6 + src/xrpld/app/tx/detail/InvariantCheck.cpp | 10 +- .../app/tx/detail/NFTokenAcceptOffer.cpp | 58 +- src/xrpld/app/tx/detail/NFTokenAcceptOffer.h | 6 - .../app/tx/detail/NFTokenCreateOffer.cpp | 4 + src/xrpld/app/tx/detail/NFTokenUtils.cpp | 55 + src/xrpld/app/tx/detail/NFTokenUtils.h | 7 + src/xrpld/app/tx/detail/Transactor.cpp | 183 +- src/xrpld/app/tx/detail/Transactor.h | 4 + src/xrpld/app/tx/detail/VaultCreate.cpp | 2 + src/xrpld/app/wasm/ContractContext.h | 108 + src/xrpld/app/wasm/ContractHostFuncImpl.h | 123 + src/xrpld/app/wasm/HostFunc.h | 641 ++++ src/xrpld/app/wasm/HostFuncImpl.h | 274 ++ src/xrpld/app/wasm/HostFuncWrapper.h | 684 ++++ src/xrpld/app/wasm/ParamsHelper.h | 251 ++ src/xrpld/app/wasm/WasmVM.h | 107 + src/xrpld/app/wasm/WasmiVM.h | 348 ++ src/xrpld/app/wasm/detail/ContractContext.cpp | 53 + .../app/wasm/detail/ContractHostFuncImpl.cpp | 1116 +++++++ src/xrpld/app/wasm/detail/HostFuncImpl.cpp | 1305 ++++++++ src/xrpld/app/wasm/detail/HostFuncWrapper.cpp | 2831 ++++++++++++++++ src/xrpld/app/wasm/detail/WasmVM.cpp | 294 ++ src/xrpld/app/wasm/detail/WasmiVM.cpp | 966 ++++++ src/xrpld/core/Config.h | 9 + src/xrpld/core/detail/Config.cpp | 6 + src/xrpld/rpc/InfoSub.h | 5 + src/xrpld/rpc/detail/Handler.cpp | 1 + src/xrpld/rpc/detail/InfoSub.cpp | 1 + src/xrpld/rpc/detail/RPCLedgerHelpers.cpp | 37 + src/xrpld/rpc/detail/RPCLedgerHelpers.h | 3 + src/xrpld/rpc/handlers/AccountInfo.cpp | 39 +- src/xrpld/rpc/handlers/ContractInfo.cpp | 185 + src/xrpld/rpc/handlers/Handlers.h | 2 + src/xrpld/rpc/handlers/LedgerEntry.cpp | 65 + src/xrpld/rpc/handlers/ServerDefinitions.cpp | 1 + src/xrpld/rpc/handlers/Subscribe.cpp | 4 + src/xrpld/rpc/handlers/Unsubscribe.cpp | 4 + 171 files changed, 38849 insertions(+), 686 deletions(-) create mode 100644 include/xrpl/ledger/OpenViewSandbox.h create mode 100644 include/xrpl/protocol/Emitable.h create mode 100644 include/xrpl/protocol/STData.h create mode 100644 include/xrpl/protocol/STDataType.h create mode 100644 include/xrpl/protocol/STJson.h create mode 100644 include/xrpl/protocol/detail/emitable.macro create mode 100644 src/libxrpl/protocol/Emitable.cpp create mode 100644 src/libxrpl/protocol/STData.cpp create mode 100644 src/libxrpl/protocol/STDataType.cpp create mode 100644 src/libxrpl/protocol/STJson.cpp create mode 100644 src/test/app/ContractHostFuncImpl_test.cpp create mode 100644 src/test/app/Contract_test.cpp create mode 100644 src/test/app/EscrowSmart_test.cpp create mode 100644 src/test/app/HostFuncImpl_test.cpp create mode 100644 src/test/app/TestHostFunctions.h create mode 100644 src/test/app/Wasm_test.cpp create mode 100644 src/test/app/wasm_fixtures/.gitignore create mode 100644 src/test/app/wasm_fixtures/all_host_functions/Cargo.lock create mode 100644 src/test/app/wasm_fixtures/all_host_functions/Cargo.toml create mode 100644 src/test/app/wasm_fixtures/all_host_functions/src/lib.rs create mode 100644 src/test/app/wasm_fixtures/all_keylets/Cargo.lock create mode 100644 src/test/app/wasm_fixtures/all_keylets/Cargo.toml create mode 100644 src/test/app/wasm_fixtures/all_keylets/src/lib.rs create mode 100644 src/test/app/wasm_fixtures/b58.c create mode 100644 src/test/app/wasm_fixtures/codecov_tests/Cargo.lock create mode 100644 src/test/app/wasm_fixtures/codecov_tests/Cargo.toml create mode 100644 src/test/app/wasm_fixtures/codecov_tests/src/host_bindings_loose.rs create mode 100644 src/test/app/wasm_fixtures/codecov_tests/src/lib.rs create mode 100644 src/test/app/wasm_fixtures/contract_data/Cargo.lock create mode 100644 src/test/app/wasm_fixtures/contract_data/Cargo.toml create mode 100644 src/test/app/wasm_fixtures/contract_data/src/lib.rs create mode 100644 src/test/app/wasm_fixtures/copyFixtures.py create mode 100644 src/test/app/wasm_fixtures/disableFloat.wat create mode 100644 src/test/app/wasm_fixtures/emit_txn/Cargo.toml create mode 100644 src/test/app/wasm_fixtures/emit_txn/src/lib.rs create mode 100644 src/test/app/wasm_fixtures/events/Cargo.toml create mode 100644 src/test/app/wasm_fixtures/events/src/lib.rs create mode 100644 src/test/app/wasm_fixtures/fib.c create mode 100644 src/test/app/wasm_fixtures/fixtures.cpp create mode 100644 src/test/app/wasm_fixtures/fixtures.h create mode 100644 src/test/app/wasm_fixtures/float_tests/Cargo.lock create mode 100644 src/test/app/wasm_fixtures/float_tests/Cargo.toml create mode 100644 src/test/app/wasm_fixtures/float_tests/src/lib.rs create mode 100644 src/test/app/wasm_fixtures/ledgerSqn.c create mode 100644 src/test/app/wasm_fixtures/parameters/Cargo.lock create mode 100644 src/test/app/wasm_fixtures/parameters/Cargo.toml create mode 100644 src/test/app/wasm_fixtures/parameters/src/lib.rs create mode 100644 src/test/app/wasm_fixtures/sha512Pure.c create mode 100644 src/test/app/wasm_fixtures/updateData.c create mode 100644 src/test/app/wasm_fixtures/wat/deep_recursion.wat create mode 100644 src/test/jtx/contract.h create mode 100644 src/test/jtx/impl/contract.cpp delete mode 100644 src/test/protocol/Hooks_test.cpp create mode 100644 src/test/protocol/STDataType_test.cpp create mode 100644 src/test/protocol/STData_test.cpp create mode 100644 src/test/protocol/STJson_test.cpp create mode 100644 src/xrpld/app/misc/ContractUtils.cpp create mode 100644 src/xrpld/app/misc/ContractUtils.h create mode 100644 src/xrpld/app/misc/DeleteUtils.cpp create mode 100644 src/xrpld/app/misc/DeleteUtils.h create mode 100644 src/xrpld/app/tx/detail/ContractCall.cpp create mode 100644 src/xrpld/app/tx/detail/ContractCall.h create mode 100644 src/xrpld/app/tx/detail/ContractClawback.cpp create mode 100644 src/xrpld/app/tx/detail/ContractClawback.h create mode 100644 src/xrpld/app/tx/detail/ContractCreate.cpp create mode 100644 src/xrpld/app/tx/detail/ContractCreate.h create mode 100644 src/xrpld/app/tx/detail/ContractDelete.cpp create mode 100644 src/xrpld/app/tx/detail/ContractDelete.h create mode 100644 src/xrpld/app/tx/detail/ContractModify.cpp create mode 100644 src/xrpld/app/tx/detail/ContractModify.h create mode 100644 src/xrpld/app/tx/detail/ContractUserDelete.cpp create mode 100644 src/xrpld/app/tx/detail/ContractUserDelete.h create mode 100644 src/xrpld/app/wasm/ContractContext.h create mode 100644 src/xrpld/app/wasm/ContractHostFuncImpl.h create mode 100644 src/xrpld/app/wasm/HostFunc.h create mode 100644 src/xrpld/app/wasm/HostFuncImpl.h create mode 100644 src/xrpld/app/wasm/HostFuncWrapper.h create mode 100644 src/xrpld/app/wasm/ParamsHelper.h create mode 100644 src/xrpld/app/wasm/WasmVM.h create mode 100644 src/xrpld/app/wasm/WasmiVM.h create mode 100644 src/xrpld/app/wasm/detail/ContractContext.cpp create mode 100644 src/xrpld/app/wasm/detail/ContractHostFuncImpl.cpp create mode 100644 src/xrpld/app/wasm/detail/HostFuncImpl.cpp create mode 100644 src/xrpld/app/wasm/detail/HostFuncWrapper.cpp create mode 100644 src/xrpld/app/wasm/detail/WasmVM.cpp create mode 100644 src/xrpld/app/wasm/detail/WasmiVM.cpp create mode 100644 src/xrpld/rpc/handlers/ContractInfo.cpp diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bc4fe2febd..24620f8d7c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,8 +1,2 @@ # Allow anyone to review any change by default. * - -# Require the rpc-reviewers team to review changes to the rpc code. -include/xrpl/protocol/ @xrplf/rpc-reviewers -src/libxrpl/protocol/ @xrplf/rpc-reviewers -src/xrpld/rpc/ @xrplf/rpc-reviewers -src/xrpld/app/misc/ @xrplf/rpc-reviewers diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 748ee031c9..331f4e33f6 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -15,196 +15,196 @@ "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "clang", "compiler_version": "21", - "image_sha": "0525eae" + "image_sha": "ca4517d" }, { "distro_name": "rhel", "distro_version": "8", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "rhel", "distro_version": "8", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "rhel", "distro_version": "10", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "rhel", "distro_version": "10", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "ubuntu", "distro_version": "jammy", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "e1782cd" + "image_sha": "ca4517d" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "e1782cd" + "image_sha": "ca4517d" } ], "build_type": ["Debug", "Release"], diff --git a/CMakeLists.txt b/CMakeLists.txt index ade9c2f995..1a96436556 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,6 +113,7 @@ find_package(date REQUIRED) find_package(ed25519 REQUIRED) find_package(nudb REQUIRED) find_package(secp256k1 REQUIRED) +find_package(wasmi REQUIRED) find_package(xxHash REQUIRED) target_link_libraries(xrpl_libs INTERFACE diff --git a/cfg/rippled-example.cfg b/cfg/rippled-example.cfg index 5db008431d..95b97f7a3c 100644 --- a/cfg/rippled-example.cfg +++ b/cfg/rippled-example.cfg @@ -1290,6 +1290,39 @@ # Example: # owner_reserve = 2000000 # 2 XRP # +# extension_compute_limit = +# +# 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, rippled will use an internal +# default. Don't change this without understanding the consequences. +# +# Example: +# extension_compute_limit = 1000000 # 1 million gas +# +# extension_size_limit = +# +# 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, rippled will use an internal +# default. Don't change this without understanding the consequences. +# +# Example: +# extension_size_limit = 100000 # 100 kb +# +# gas_price = +# +# The gas price is the conversion between WASM gas and its price in drops. +# +# If this parameter is unspecified, rippled will use an internal +# default. Don't change this without understanding the consequences. +# +# Example: +# gas_price = 1000000 # 1 drop per gas #------------------------------------------------------------------------------- # # 9. Misc Settings diff --git a/cmake/XrplCore.cmake b/cmake/XrplCore.cmake index 12ba58d499..c0ad676658 100644 --- a/cmake/XrplCore.cmake +++ b/cmake/XrplCore.cmake @@ -63,6 +63,7 @@ target_link_libraries(xrpl.imports.main Xrpl::opts Xrpl::syslibs secp256k1::secp256k1 + wasmi::wasmi xrpl.libpb xxHash::xxhash $<$:antithesis-sdk-cpp> diff --git a/conan.lock b/conan.lock index 1385ad05bd..3215456b63 100644 --- a/conan.lock +++ b/conan.lock @@ -3,6 +3,7 @@ "requires": [ "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1756234269.497", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1756234289.683", + "wasmi/0.42.1#2a96357d4e6bf40dfe201106d849c24f%1764802092.014", "sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1756234266.869", "soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1756234262.318", "snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1756234314.246", diff --git a/conanfile.py b/conanfile.py index 8dc09da0af..16c4a7380f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -35,6 +35,7 @@ class Xrpl(ConanFile): "openssl/3.5.4", "secp256k1/0.7.0", "soci/4.0.3", + "wasmi/0.42.1", "zlib/1.3.1", ] @@ -212,6 +213,7 @@ class Xrpl(ConanFile): "soci::soci", "secp256k1::secp256k1", "sqlite3::sqlite", + "wasmi::wasmi", "xxhash::xxhash", "zlib::zlib", ] diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index 4420530239..fe0db38f16 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -339,6 +339,10 @@ abs(Number x) noexcept Number power(Number const& f, unsigned n); +// logarithm with base 10 +Number +lg(Number const& value); + // 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 diff --git a/include/xrpl/ledger/ApplyViewImpl.h b/include/xrpl/ledger/ApplyViewImpl.h index 0c11726135..c0e665596e 100644 --- a/include/xrpl/ledger/ApplyViewImpl.h +++ b/include/xrpl/ledger/ApplyViewImpl.h @@ -55,6 +55,18 @@ public: deliver_ = amount; } + void + setGasUsed(std::optional const gasUsed) + { + gasUsed_ = gasUsed; + } + + void + setWasmReturnCode(std::int32_t const wasmReturnCode) + { + wasmReturnCode_ = wasmReturnCode; + } + /** Get the number of modified entries */ std::size_t @@ -73,6 +85,8 @@ public: private: std::optional deliver_; + std::optional gasUsed_; + std::optional wasmReturnCode_; }; } // namespace xrpl diff --git a/include/xrpl/ledger/OpenViewSandbox.h b/include/xrpl/ledger/OpenViewSandbox.h new file mode 100644 index 0000000000..7b41e43aaa --- /dev/null +++ b/include/xrpl/ledger/OpenViewSandbox.h @@ -0,0 +1,105 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_LEDGER_OPENVIEWSANDBOX_H_INCLUDED +#define RIPPLE_LEDGER_OPENVIEWSANDBOX_H_INCLUDED + +#include + +#include + +namespace xrpl { + +class OpenViewSandbox +{ +private: + OpenView& parent_; + std::unique_ptr sandbox_; + +public: + using key_type = ReadView::key_type; + + OpenViewSandbox(OpenView& parent) + : parent_(parent) + , sandbox_(std::make_unique(batch_view, parent)) + { + } + + void + rawErase(std::shared_ptr const& sle) + { + sandbox_->rawErase(sle); + } + + void + rawInsert(std::shared_ptr const& sle) + { + sandbox_->rawInsert(sle); + } + + void + rawReplace(std::shared_ptr const& sle) + { + sandbox_->rawReplace(sle); + } + + void + rawDestroyXRP(XRPAmount const& fee) + { + sandbox_->rawDestroyXRP(fee); + } + + void + rawTxInsert( + key_type const& key, + std::shared_ptr const& txn, + std::shared_ptr const& metaData) + { + sandbox_->rawTxInsert(key, txn, metaData); + } + + void + commit() + { + sandbox_->apply(parent_); + sandbox_ = std::make_unique(batch_view, parent_); + } + + void + discard() + { + sandbox_ = std::make_unique(batch_view, parent_); + } + + OpenView const& + view() const + { + return *sandbox_; + } + + OpenView& + view() + { + return *sandbox_; + } +}; + +} // namespace xrpl + +#endif diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 767622596b..08ed989d19 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -1083,6 +1083,41 @@ enforceMPTokenAuthorization( XRPAmount const& priorBalance, beast::Journal j); +enum class SendIssuerHandling { + ihSENDER_NOT_ALLOWED, + ihRECEIVER_NOT_ALLOWED, + ihIGNORE +}; +enum class SendEscrowHandling { ehIGNORE, ehCHECK }; +enum class SendAuthHandling { + ahCHECK_SENDER, + ahCHECK_RECEIVER, + ahBOTH, + ahNEITHER +}; +enum class SendFreezeHandling { + fhCHECK_SENDER, + fhCHECK_RECEIVER, + fhBOTH, + fhNEITHER +}; +enum class SendTransferHandling { thIGNORE, thCHECK }; +enum class SendBalanceHandling { bhIGNORE, bhCHECK }; + +TER +canTransferFT( + ReadView const& view, + AccountID const& sender, + AccountID const& receiver, + STAmount const& amount, + beast::Journal j, + SendIssuerHandling issuerHandling, + SendEscrowHandling escrowHandling, + SendAuthHandling authHandling, + SendFreezeHandling freezeHandling, + SendTransferHandling transferHandling, + SendBalanceHandling balanceHandling); + /** Check if the destination account is allowed * to receive MPT. Return tecNO_AUTH if it doesn't * and tesSUCCESS otherwise. diff --git a/include/xrpl/ledger/detail/ApplyStateTable.h b/include/xrpl/ledger/detail/ApplyStateTable.h index f5ec0c9f51..592ddfca25 100644 --- a/include/xrpl/ledger/detail/ApplyStateTable.h +++ b/include/xrpl/ledger/detail/ApplyStateTable.h @@ -53,6 +53,8 @@ public: TER ter, std::optional const& deliver, std::optional const& parentBatchId, + std::optional const& gasUsed, + std::optional const& wasmReturnCode, bool isDryRun, beast::Journal j); diff --git a/include/xrpl/protocol/Emitable.h b/include/xrpl/protocol/Emitable.h new file mode 100644 index 0000000000..7ae4a599ad --- /dev/null +++ b/include/xrpl/protocol/Emitable.h @@ -0,0 +1,100 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Emitable to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this emitable notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_EMITABLE_H_INCLUDED +#define RIPPLE_PROTOCOL_EMITABLE_H_INCLUDED + +#include +#include +#include + +#include +#include +#include + +namespace xrpl { +/** + * We have both transaction type emitables and granular type emitables. + * Since we will reuse the TransactionFormats to parse the Transaction + * Emitables, only the GranularEmitableType is defined here. To prevent + * conflicts with TxType, the GranularEmitableType is always set to a value + * greater than the maximum value of uint16. + */ +enum GranularEmitableType : std::uint32_t { +#pragma push_macro("EMITABLE") +#undef EMITABLE + +#define EMITABLE(type, txType, value) type = value, + +#include + +#undef EMITABLE +#pragma pop_macro("EMITABLE") +}; + +enum Emittance { emitable, notEmitable }; + +class Emitable +{ +private: + Emitable(); + + std::unordered_map emitableTx_; + + std::unordered_map granularEmitableMap_; + + std::unordered_map granularNameMap_; + + std::unordered_map granularTxTypeMap_; + +public: + static Emitable const& + getInstance(); + + Emitable(Emitable const&) = delete; + Emitable& + operator=(Emitable const&) = delete; + + std::optional + getEmitableName(std::uint32_t const value) const; + + std::optional + getGranularValue(std::string const& name) const; + + std::optional + getGranularName(GranularEmitableType const& value) const; + + std::optional + getGranularTxType(GranularEmitableType const& gpType) const; + + bool + isEmitable(std::uint32_t const& emitableValue) const; + + // for tx level emitable, emitable value is equal to tx type plus one + uint32_t + txToEmitableType(TxType const& type) const; + + // tx type value is emitable value minus one + TxType + emitableToTxType(uint32_t const& value) const; +}; + +} // namespace xrpl + +#endif diff --git a/include/xrpl/protocol/Fees.h b/include/xrpl/protocol/Fees.h index 8c7cb51777..2ca5c387a9 100644 --- a/include/xrpl/protocol/Fees.h +++ b/include/xrpl/protocol/Fees.h @@ -5,6 +5,8 @@ namespace xrpl { +constexpr std::uint32_t MICRO_DROPS_PER_DROP{1'000'000}; + /** Reflects the fee settings for a particular ledger. The fees are always the same for any transactions applied @@ -15,6 +17,10 @@ struct Fees 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; diff --git a/include/xrpl/protocol/IOUAmount.h b/include/xrpl/protocol/IOUAmount.h index 60a61a5825..bb1b1eae95 100644 --- a/include/xrpl/protocol/IOUAmount.h +++ b/include/xrpl/protocol/IOUAmount.h @@ -39,6 +39,13 @@ private: normalize(); public: + /* The range for the mantissa when normalized */ + static std::int64_t constexpr minMantissa = 1000000000000000ull; + static std::int64_t constexpr maxMantissa = 9999999999999999ull; + /* The range for the exponent when normalized */ + static int constexpr minExponent = -96; + static int constexpr maxExponent = 80; + IOUAmount() = default; explicit IOUAmount(Number const& other); IOUAmount(beast::Zero); diff --git a/include/xrpl/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h index 1cb22b4d6c..d3d550bf38 100644 --- a/include/xrpl/protocol/Indexes.h +++ b/include/xrpl/protocol/Indexes.h @@ -212,6 +212,12 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept Keylet escrow(AccountID const& src, std::uint32_t seq) noexcept; +inline Keylet +escrow(uint256 const& key) noexcept +{ + return {ltESCROW, key}; +} + /** A PaymentChannel */ Keylet payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept; @@ -350,6 +356,25 @@ permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept; Keylet permissionedDomain(uint256 const& domainID) noexcept; + +Keylet +contractSource(uint256 const& contractHash) noexcept; + +Keylet +contract( + uint256 const& contractHash, + AccountID const& owner, + std::uint32_t seq) noexcept; + +inline Keylet +contract(uint256 const& contractID) +{ + return {ltCONTRACT, contractID}; +} + +Keylet +contractData(AccountID const& owner, AccountID const& contractAccount) noexcept; + } // namespace keylet // Everything below is deprecated and should be removed in favor of keylets: diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index b52a25af17..f7a2622153 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -80,14 +80,6 @@ enum LedgerEntryType : std::uint16_t */ ltNICKNAME [[deprecated("This object type is not supported and should not be used.")]] = 0x006e, - /** A legacy, deprecated type. - - \deprecated **This object type is not supported and should not be used.** - Support for this type of object was never implemented. - No objects of this type were ever created. - */ - ltCONTRACT [[deprecated("This object type is not supported and should not be used.")]] = 0x0063, - /** A legacy, deprecated type. \deprecated **This object type is not supported and should not be used.** diff --git a/include/xrpl/protocol/Permissions.h b/include/xrpl/protocol/Permissions.h index 252605e641..2e14c2b469 100644 --- a/include/xrpl/protocol/Permissions.h +++ b/include/xrpl/protocol/Permissions.h @@ -1,6 +1,7 @@ #ifndef XRPL_PROTOCOL_PERMISSION_H_INCLUDED #define XRPL_PROTOCOL_PERMISSION_H_INCLUDED +#include #include #include #include diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index fb315eace4..48284b9fc5 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -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; diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index b1d353196d..db8ce6d267 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -34,6 +34,9 @@ class STNumber; class STXChainBridge; class STVector256; class STCurrency; +class STData; +class STDataType; +class STJson; #pragma push_macro("XMACRO") #undef XMACRO @@ -72,6 +75,9 @@ class STCurrency; STYPE(STI_ISSUE, 24) \ STYPE(STI_XCHAIN_BRIDGE, 25) \ STYPE(STI_CURRENCY, 26) \ + STYPE(STI_DATA, 27) \ + STYPE(STI_DATATYPE, 28) \ + STYPE(STI_JSON, 29) \ \ /* high-level types */ \ /* cannot be serialized inside other types */ \ @@ -350,6 +356,9 @@ using SF_NUMBER = TypedField; using SF_VL = TypedField; using SF_VECTOR256 = TypedField; using SF_XCHAIN_BRIDGE = TypedField; +using SF_DATA = TypedField; +using SF_DATATYPE = TypedField; +using SF_JSON = TypedField; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/STData.h b/include/xrpl/protocol/STData.h new file mode 100644 index 0000000000..7e0a538eaa --- /dev/null +++ b/include/xrpl/protocol/STData.h @@ -0,0 +1,308 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_STDATA_H_INCLUDED +#define RIPPLE_PROTOCOL_STDATA_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl { + +class STData final : public STBase +{ +private: + using data_type = detail::STVar; + std::uint16_t inner_type_; + data_type data_; + bool default_{true}; + +public: + using value_type = STData; // Although not directly holding a single value + + STData(SField const& n); + STData(SField const& n, unsigned char); + STData(SField const& n, std::uint16_t); + STData(SField const& n, std::uint32_t); + STData(SField const& n, std::uint64_t); + STData(SField const& n, uint128 const&); + STData(SField const& n, uint160 const&); + STData(SField const& n, uint192 const&); + STData(SField const& n, uint256 const&); + STData(SField const& n, Blob const&); + STData(SField const& n, Slice const&); + STData(SField const& n, AccountID const&); + STData(SField const& n, STAmount const&); + STData(SField const& n, STIssue const&); + STData(SField const& n, STCurrency const&); + STData(SField const& n, STNumber const&); + + STData(SerialIter& sit, SField const& name); + + std::size_t + size() const; + + SerializedTypeID + getSType() const override; + + std::string + getInnerTypeString() const; + + std::string + getText() const override; + + Json::Value getJson(JsonOptions) const override; + + void + add(Serializer& s) const override; + + bool + isEquivalent(STBase const& t) const override; + + bool + isDefault() const override; + + SerializedTypeID + getInnerSType() const noexcept; + + STBase* + makeFieldPresent(); + + void + setFieldU8(unsigned char); + void setFieldU16(std::uint16_t); + void setFieldU32(std::uint32_t); + void setFieldU64(std::uint64_t); + void + setFieldH128(uint128 const&); + void + setFieldH160(uint160 const&); + void + setFieldH192(uint192 const&); + void + setFieldH256(uint256 const&); + void + setFieldVL(Blob const&); + void + setFieldVL(Slice const&); + void + setAccountID(AccountID const&); + void + setFieldAmount(STAmount const&); + void + setIssue(STIssue const&); + void + setCurrency(STCurrency const&); + void + setFieldNumber(STNumber const&); + + unsigned char + getFieldU8() const; + std::uint16_t + getFieldU16() const; + std::uint32_t + getFieldU32() const; + std::uint64_t + getFieldU64() const; + uint128 + getFieldH128() const; + uint160 + getFieldH160() const; + uint192 + getFieldH192() const; + uint256 + getFieldH256() const; + Blob + getFieldVL() const; + AccountID + getAccountID() const; + STAmount const& + getFieldAmount() const; + STIssue + getFieldIssue() const; + STCurrency + getFieldCurrency() const; + STNumber + getFieldNumber() const; + +private: + STBase* + copy(std::size_t n, void* buf) const override; + STBase* + move(std::size_t n, void* buf) override; + + friend class detail::STVar; + + // Implementation for getting (most) fields that return by value. + // + // The remove_cv and remove_reference are necessitated by the STBitString + // types. Their value() returns by const ref. We return those types + // by value. + template < + typename T, + typename V = typename std::remove_cv().value())>::type>::type> + V + getFieldByValue() const; + + // Implementations for getting (most) fields that return by const reference. + // + // If an absent optional field is deserialized we don't have anything + // obvious to return. So we insist on having the call provide an + // 'empty' value we return in that circumstance. + template + V const& + getFieldByConstRef(V const& empty) const; + + // Implementation for setting most fields with a setValue() method. + template + void + setFieldUsingSetValue(V value); + + // Implementation for setting fields using assignment + template + void + setFieldUsingAssignment(T const& value); +}; + +//------------------------------------------------------------------------------ +// Implementation +//------------------------------------------------------------------------------ + +inline SerializedTypeID +STData::getInnerSType() const noexcept +{ + return static_cast(inner_type_); +} + +template +V +STData::getFieldByValue() const +{ + STBase const* rf = &data_.get(); + + // if (!rf) + // throwFieldNotFound(getFName()); + + SerializedTypeID id = rf->getSType(); + + if (id == STI_NOTPRESENT) + Throw("Field not present"); + + T const* cf = dynamic_cast(rf); + + if (!cf) + Throw("Wrong field type"); + + return cf->value(); +} + +// Implementations for getting (most) fields that return by const reference. +// +// If an absent optional field is deserialized we don't have anything +// obvious to return. So we insist on having the call provide an +// 'empty' value we return in that circumstance. +template +V const& +STData::getFieldByConstRef(V const& empty) const +{ + STBase const* rf = &data_.get(); + + // if (!rf) + // throwFieldNotFound(field); + + SerializedTypeID id = rf->getSType(); + + if (id == STI_NOTPRESENT) + return empty; // optional field not present + + T const* cf = dynamic_cast(rf); + + if (!cf) + Throw("Wrong field type"); + + return *cf; +} + +// Implementation for setting most fields with a setValue() method. +template +void +STData::setFieldUsingSetValue(V value) +{ + static_assert(!std::is_lvalue_reference::value, ""); + + STBase* rf = &data_.get(); + + // if (!rf) + // throwFieldNotFound(field); + + if (rf->getSType() == STI_NOTPRESENT) + rf = makeFieldPresent(); + + T* cf = dynamic_cast(rf); + + if (!cf) + Throw("Wrong field type"); + + cf->setValue(std::move(value)); +} + +// Implementation for setting fields using assignment +template +void +STData::setFieldUsingAssignment(T const& value) +{ + STBase* rf = &data_.get(); + + // if (!rf) + // throwFieldNotFound(field); + + // if (rf->getSType() == STI_NOTPRESENT) + // rf = makeFieldPresent(field); + + T* cf = dynamic_cast(rf); + + if (!cf) + Throw("Wrong field type"); + + (*cf) = value; +} + +//------------------------------------------------------------------------------ +// +// Creation +// +//------------------------------------------------------------------------------ + +STData +dataFromJson(SField const& field, Json::Value const& value); + +} // namespace xrpl + +#endif diff --git a/include/xrpl/protocol/STDataType.h b/include/xrpl/protocol/STDataType.h new file mode 100644 index 0000000000..2d47c46a18 --- /dev/null +++ b/include/xrpl/protocol/STDataType.h @@ -0,0 +1,110 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_STDATATYPE_H_INCLUDED +#define RIPPLE_PROTOCOL_STDATATYPE_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl { + +class STDataType final : public STBase +{ +private: + std::uint16_t inner_type_; + bool default_{true}; + +public: + using value_type = + STDataType; // Although not directly holding a single value + + STDataType(SField const& n); + STDataType(SField const& n, SerializedTypeID); + + STDataType(SerialIter& sit, SField const& name); + + SerializedTypeID + getSType() const override; + + std::string + getInnerTypeString() const; + + std::string + getText() const override; + + Json::Value getJson(JsonOptions) const override; + + void + add(Serializer& s) const override; + + bool + isEquivalent(STBase const& t) const override; + + bool + isDefault() const override; + + void setInnerSType(SerializedTypeID); + + SerializedTypeID + getInnerSType() const noexcept; + + STBase* + makeFieldPresent(); + + STBase* + copy(std::size_t n, void* buf) const override; + STBase* + move(std::size_t n, void* buf) override; + + friend class detail::STVar; +}; + +//------------------------------------------------------------------------------ +// Implementation +//------------------------------------------------------------------------------ + +inline SerializedTypeID +STDataType::getInnerSType() const noexcept +{ + return static_cast(inner_type_); +} + +//------------------------------------------------------------------------------ +// +// Creation +// +//------------------------------------------------------------------------------ + +STDataType +dataTypeFromJson(SField const& field, Json::Value const& value); + +} // namespace xrpl + +#endif diff --git a/include/xrpl/protocol/STJson.h b/include/xrpl/protocol/STJson.h new file mode 100644 index 0000000000..6149cc544a --- /dev/null +++ b/include/xrpl/protocol/STJson.h @@ -0,0 +1,214 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_STJSON_H_INCLUDED +#define RIPPLE_PROTOCOL_STJSON_H_INCLUDED + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace xrpl { + +/** + * STJson: Serialized Type for JSON-like structures (objects or arrays). + * + * Supports two modes: + * - Object: Key-value pairs where keys are VL-encoded strings + * - Array: Ordered list of values + * + * Values are [SType marker][VL-encoded SType serialization]. + * Values can be any SType, including nested STJson. + * + * Serialization format: [type_byte][VL_length][data...] + * - type_byte: 0x00 = Object, 0x01 = Array + */ +class STJson : public STBase +{ +public: + enum class JsonType : uint8_t { Object = 0x00, Array = 0x01 }; + + using Key = std::string; + using Value = std::shared_ptr; + using Map = std::map; + using Array = std::vector; + + STJson() = default; + + explicit STJson(Map&& map); + explicit STJson(Array&& array); + explicit STJson(SField const& name); + explicit STJson(SerialIter& sit, SField const& name); + + SerializedTypeID + getSType() const override; + + // Type checking + bool + isArray() const; + + bool + isObject() const; + + JsonType + getType() const; + + // Depth checking (0 = no nesting, 1 = one level of nesting) + int + getDepth() const; + + // Parse from binary blob + static std::shared_ptr + fromBlob(void const* data, std::size_t size); + + // Parse from SerialIter + static std::shared_ptr + fromSerialIter(SerialIter& sit); + + // Serialize to binary + void + add(Serializer& s) const override; + + // JSON representation + Json::Value + getJson(JsonOptions options) const override; + + bool + isEquivalent(STBase const& t) const override; + + bool + isDefault() const override; + + // Blob representation + Blob + toBlob() const; + + // STJson size + std::size_t + size() const; + + // Object accessors (only valid when isObject() == true) + Map const& + getMap() const; + + void + setObjectField(Key const& key, Value const& value); + + std::optional + getObjectField(Key const& key) const; + + void + setNestedObjectField( + Key const& key, + Key const& nestedKey, + Value const& value); + + std::optional + getNestedObjectField(Key const& key, Key const& nestedKey) const; + + // Array accessors (only valid when isArray() == true) + Array const& + getArray() const; + + void + pushArrayElement(Value const& value); + + std::optional + getArrayElement(size_t index) const; + + void + setArrayElement(size_t index, Value const& value); + + void + setArrayElementField(size_t index, Key const& key, Value const& value); + + std::optional + getArrayElementField(size_t index, Key const& key) const; + + size_t + arraySize() const; + + // Nested array accessors (for arrays stored in object fields) + void + setNestedArrayElement(Key const& key, size_t index, Value const& value); + + void + setNestedArrayElementField( + Key const& key, + size_t index, + Key const& nestedKey, + Value const& value); + + std::optional + getNestedArrayElement(Key const& key, size_t index) const; + + std::optional + getNestedArrayElementField( + Key const& key, + size_t index, + Key const& nestedKey) const; + + // Factory for SType value from blob (with SType marker) + static Value + makeValueFromVLWithType(SerialIter& sit); + + void + setValue(STJson const& v); + +private: + std::variant data_{Map{}}; + bool default_{false}; + + // Helper: validate nesting depth (max 1 level) + void + validateDepth(Value const& value, int currentDepth) const; + + // Helper: parse a single key-value pair from SerialIter + static std::pair + parsePair(SerialIter& sit); + + // Helper: parse array elements from SerialIter + static Array + parseArray(SerialIter& sit, int length); + + // Helper: encode a key as VL + static void + addVLKey(Serializer& s, std::string const& str); + + // Helper: encode a value as [SType marker][VL] + static void + addVLValue(Serializer& s, std::shared_ptr const& value); + + STBase* + copy(std::size_t n, void* buf) const override; + STBase* + move(std::size_t n, void* buf) override; + + friend class detail::STVar; +}; + +} // namespace xrpl + +#endif diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index c6ca133e89..ce8c53e9d7 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -216,6 +217,10 @@ public: getFieldI32(SField const& field) const; AccountID getAccountID(SField const& field) const; + STData + getFieldData(SField const& field) const; + STDataType + getFieldDataType(SField const& field) const; Blob getFieldVL(SField const& field) const; @@ -234,6 +239,8 @@ public: getFieldCurrency(SField const& field) const; STNumber const& getFieldNumber(SField const& field) const; + STJson const& + getFieldJson(SField const& field) const; /** Get the value of a field. @param A TypedField built from an SField value representing the desired @@ -338,6 +345,9 @@ public: void set(STBase&& v); + void + addFieldFromSlice(SField const& sfield, Slice const& data); + void setFieldU8(SField const& field, unsigned char); void @@ -376,6 +386,8 @@ public: setFieldArray(SField const& field, STArray const& v); void setFieldObject(SField const& field, STObject const& v); + void + setFieldJson(SField const& field, STJson const& v); template void diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index de2f596127..8ad5215f0d 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -122,6 +122,8 @@ enum TEMcodes : TERUnderlyingType { temARRAY_TOO_LARGE, temBAD_TRANSFER_FEE, temINVALID_INNER_BATCH, + + temBAD_WASM, }; //------------------------------------------------------------------------------ @@ -166,6 +168,8 @@ enum TEFcodes : TERUnderlyingType { tefNO_TICKET, tefNFTOKEN_IS_NOT_TRANSFERABLE, tefINVALID_LEDGER_FIX_TYPE, + tefNO_WASM, + tefWASM_FIELD_NOT_INCLUDED, }; //------------------------------------------------------------------------------ @@ -347,6 +351,8 @@ enum TECcodes : TERUnderlyingType { // backward compatibility with historical data on non-prod networks, can be // reclaimed after those networks reset. tecNO_DELEGATE_PERMISSION = 198, + tecWASM_REJECTED = 199, + tecINVALID_PARAMETERS = 200, }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 194c4c6af1..17af2a8414 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -292,6 +292,20 @@ constexpr std::uint32_t const tfLoanImpair = 0x00020000; constexpr std::uint32_t const tfLoanUnimpair = 0x00040000; constexpr std::uint32_t const tfLoanManageMask = ~(tfUniversal | tfLoanDefault | tfLoanImpair | tfLoanUnimpair); +// Contract flags: +constexpr std::uint32_t tfImmutable = 0x00010000; +constexpr std::uint32_t tfCodeImmutable = 0x00020000; +constexpr std::uint32_t tfABIImmutable = 0x00040000; +constexpr std::uint32_t tfUndeletable = 0x00080000; +constexpr std::uint32_t tfContractMask = + ~(tfUniversal | tfImmutable | tfCodeImmutable | tfABIImmutable | tfUndeletable); + +constexpr std::uint32_t tfSendAmount = 0x00010000; +constexpr std::uint32_t tfSendNFToken = 0x00020000; +constexpr std::uint32_t tfAuthorizeToken = 0x00040000; +constexpr std::uint32_t tfContractParameterMask = + ~(tfSendAmount | tfSendNFToken | tfAuthorizeToken); + // clang-format on } // namespace xrpl diff --git a/include/xrpl/protocol/TxMeta.h b/include/xrpl/protocol/TxMeta.h index 5b6716e380..2e9930b350 100644 --- a/include/xrpl/protocol/TxMeta.h +++ b/include/xrpl/protocol/TxMeta.h @@ -84,7 +84,13 @@ public: deliveredAmount_ = obj.getFieldAmount(sfDeliveredAmount); if (obj.isFieldPresent(sfParentBatchID)) - parentBatchID_ = obj.getFieldH256(sfParentBatchID); + parentBatchId_ = obj.getFieldH256(sfParentBatchID); + + if (obj.isFieldPresent(sfGasUsed)) + gasUsed_ = obj.getFieldU32(sfGasUsed); + + if (obj.isFieldPresent(sfWasmReturnCode)) + wasmReturnCode_ = obj.getFieldI32(sfWasmReturnCode); } std::optional const& @@ -102,7 +108,31 @@ public: void setParentBatchID(std::optional const& id) { - parentBatchID_ = id; + parentBatchId_ = id; + } + + void + setGasUsed(std::optional const gasUsed) + { + gasUsed_ = gasUsed; + } + + std::optional const& + getGasUsed() const + { + return gasUsed_; + } + + void + setWasmReturnCode(std::optional const wasmReturnCode) + { + wasmReturnCode_ = wasmReturnCode; + } + + std::optional const& + getWasmReturnCode() const + { + return wasmReturnCode_; } private: @@ -112,7 +142,9 @@ private: int result_; std::optional deliveredAmount_; - std::optional parentBatchID_; + std::optional parentBatchId_; + std::optional gasUsed_; + std::optional wasmReturnCode_; STArray nodes_; }; diff --git a/include/xrpl/protocol/detail/emitable.macro b/include/xrpl/protocol/detail/emitable.macro new file mode 100644 index 0000000000..e355a888f5 --- /dev/null +++ b/include/xrpl/protocol/detail/emitable.macro @@ -0,0 +1,38 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#if !defined(EMITABLE) +#error "undefined macro: EMITABLE" +#endif + +/** + * EMITABLE(name, type, txType, value) + * + * This macro defines a permission: + * name: the name of the permission. + * type: the GranularPermissionType enum. + * txType: the corresponding TxType for this permission. + * value: the uint32 numeric value for the enum type. + */ + +/** This removes the contract account the ability to set or remove deposit auth. */ +EMITABLE(AccountDepositAuth, ttACCOUNT_SET, 65537) + +// ** This removes the contract account the ability to set or remove disable master key. */ +EMITABLE(AccountDisableMaster, ttACCOUNT_SET, 65538) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 5c8d2aa198..43b24500d8 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -16,6 +16,8 @@ // Add new amendments to the top of this list. // Keep it sorted in reverse chronological order. +XRPL_FEATURE(SmartContract, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(SmartEscrow, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(LendingProtocol, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo) @@ -32,7 +34,6 @@ XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(SingleAssetVault, Supported::no, 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) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 1034c35895..afed612471 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -150,6 +150,7 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({ {sfAMMID, soeOPTIONAL}, // pseudo-account designator {sfVaultID, soeOPTIONAL}, // pseudo-account designator {sfLoanBrokerID, soeOPTIONAL}, // pseudo-account designator + {sfContractID, soeOPTIONAL}, // pseudo-account designator })) /** A ledger object which contains a list of object identifiers. @@ -302,6 +303,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}, })) @@ -332,6 +338,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({ {sfCondition, soeOPTIONAL}, {sfCancelAfter, soeOPTIONAL}, {sfFinishAfter, soeOPTIONAL}, + {sfFinishFunction, soeOPTIONAL}, + {sfData, soeOPTIONAL}, {sfSourceTag, soeOPTIONAL}, {sfDestinationTag, soeOPTIONAL}, {sfOwnerNode, soeREQUIRED}, @@ -578,7 +586,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({ // The unrounded true total value of the loan. // // - TrueTotalPrincialOutstanding can be computed using the algorithm - // in the ripple::detail::loanPrincipalFromPeriodicPayment function. + // in the xrpl::detail::loanPrincipalFromPeriodicPayment function. // // - TrueTotalInterestOutstanding = TrueTotalLoanValue - // TrueTotalPrincipalOutstanding @@ -603,5 +611,45 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({ {sfLoanScale, soeDEFAULT}, })) +/** A ledger object representing a contract source. + \sa keylet::contractSource + */ +LEDGER_ENTRY(ltCONTRACT_SOURCE, 0x0085, ContractSource, contract_source, ({ + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, + {sfContractHash, soeREQUIRED}, + {sfContractCode, soeREQUIRED}, + {sfFunctions, soeREQUIRED}, + {sfInstanceParameters, soeOPTIONAL}, + {sfReferenceCount, soeREQUIRED}, +})) + +/** A ledger object representing a contract. + \sa keylet::contract + */ +LEDGER_ENTRY(ltCONTRACT, 0x0086, Contract, contract, ({ + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, + {sfSequence, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, + {sfOwner, soeREQUIRED}, + {sfContractAccount, soeREQUIRED}, + {sfContractHash, soeREQUIRED}, + {sfInstanceParameterValues, soeOPTIONAL}, + {sfURI, soeOPTIONAL}, +})) + +/** A ledger object representing a contract data. + \sa keylet::contractData + */ +LEDGER_ENTRY(ltCONTRACT_DATA, 0x0087, ContractData, contract_data, ({ + {sfPreviousTxnID, soeREQUIRED}, + {sfPreviousTxnLgrSeq, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, + {sfOwner, soeREQUIRED}, + {sfContractAccount, soeREQUIRED}, + {sfContractJson, soeREQUIRED}, +})) + #undef EXPAND #undef LEDGER_ENTRY_DUPLICATE diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index d5c5d9447f..84e313e4b6 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -23,9 +23,10 @@ TYPED_SFIELD(sfAssetScale, UINT8, 5) // 8-bit integers (uncommon) TYPED_SFIELD(sfTickSize, UINT8, 16) TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17) -TYPED_SFIELD(sfHookResult, UINT8, 18) +// 18 unused TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19) TYPED_SFIELD(sfWithdrawalPolicy, UINT8, 20) +TYPED_SFIELD(sfContractResult, UINT8, 21) // 16-bit integers (common) TYPED_SFIELD(sfLedgerEntryType, UINT16, 1, SField::sMD_Never) @@ -37,10 +38,7 @@ TYPED_SFIELD(sfDiscountedFee, UINT16, 6) // 16-bit integers (uncommon) TYPED_SFIELD(sfVersion, UINT16, 16) -TYPED_SFIELD(sfHookStateChangeCount, UINT16, 17) -TYPED_SFIELD(sfHookEmitCount, UINT16, 18) -TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19) -TYPED_SFIELD(sfHookApiVersion, UINT16, 20) +// 17 to 20 unused TYPED_SFIELD(sfLedgerFixType, UINT16, 21) TYPED_SFIELD(sfManagementFeeRate, UINT16, 22) // 1/10 basis points (bips) @@ -91,9 +89,7 @@ TYPED_SFIELD(sfTicketSequence, UINT32, 41) TYPED_SFIELD(sfNFTokenTaxon, UINT32, 42) TYPED_SFIELD(sfMintedNFTokens, UINT32, 43) TYPED_SFIELD(sfBurnedNFTokens, UINT32, 44) -TYPED_SFIELD(sfHookStateCount, UINT32, 45) -TYPED_SFIELD(sfEmitGeneration, UINT32, 46) -// 47 reserved for Hooks +// 45 to 47 unused TYPED_SFIELD(sfVoteWeight, UINT32, 48) TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50) TYPED_SFIELD(sfOracleDocumentID, UINT32, 51) @@ -114,6 +110,12 @@ 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) +TYPED_SFIELD(sfComputationAllowance, UINT32, 72) +TYPED_SFIELD(sfGasUsed, UINT32, 73) +TYPED_SFIELD(sfParameterFlag, UINT32, 74) // 64-bit integers (common) TYPED_SFIELD(sfIndexNext, UINT64, 1) @@ -131,9 +133,7 @@ TYPED_SFIELD(sfNFTokenOfferNode, UINT64, 12) TYPED_SFIELD(sfEmitBurden, UINT64, 13) // 64-bit integers (uncommon) -TYPED_SFIELD(sfHookOn, UINT64, 16) -TYPED_SFIELD(sfHookInstructionCount, UINT64, 17) -TYPED_SFIELD(sfHookReturnCode, UINT64, 18) +// 16 to 18 unused TYPED_SFIELD(sfReferenceCount, UINT64, 19) TYPED_SFIELD(sfXChainClaimID, UINT64, 20) TYPED_SFIELD(sfXChainAccountCreateCount, UINT64, 21) @@ -193,10 +193,7 @@ TYPED_SFIELD(sfPreviousPageMin, UINT256, 26) TYPED_SFIELD(sfNextPageMin, UINT256, 27) TYPED_SFIELD(sfNFTokenBuyOffer, UINT256, 28) TYPED_SFIELD(sfNFTokenSellOffer, UINT256, 29) -TYPED_SFIELD(sfHookStateKey, UINT256, 30) -TYPED_SFIELD(sfHookHash, UINT256, 31) -TYPED_SFIELD(sfHookNamespace, UINT256, 32) -TYPED_SFIELD(sfHookSetTxnID, UINT256, 33) +// 30 to 33 unused TYPED_SFIELD(sfDomainID, UINT256, 34) TYPED_SFIELD(sfVaultID, UINT256, 35, SField::sMD_PseudoAccount | SField::sMD_Default) @@ -204,6 +201,9 @@ TYPED_SFIELD(sfParentBatchID, UINT256, 36) TYPED_SFIELD(sfLoanBrokerID, UINT256, 37, SField::sMD_PseudoAccount | SField::sMD_Default) TYPED_SFIELD(sfLoanID, UINT256, 38) +TYPED_SFIELD(sfContractHash, UINT256, 39) +TYPED_SFIELD(sfContractID, UINT256, 40, + SField::sMD_PseudoAccount | SField::sMD_Default) // number (common) TYPED_SFIELD(sfNumber, NUMBER, 1) @@ -224,8 +224,9 @@ TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15) TYPED_SFIELD(sfPeriodicPayment, NUMBER, 16) TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17) -// int32 +// 32-bit signed (common) TYPED_SFIELD(sfLoanScale, INT32, 1) +TYPED_SFIELD(sfWasmReturnCode, INT32, 2) // currency amount (common) TYPED_SFIELD(sfAmount, AMOUNT, 1) @@ -247,15 +248,13 @@ TYPED_SFIELD(sfMinimumOffer, AMOUNT, 16) TYPED_SFIELD(sfRippleEscrow, AMOUNT, 17) TYPED_SFIELD(sfDeliveredAmount, AMOUNT, 18) TYPED_SFIELD(sfNFTokenBrokerFee, AMOUNT, 19) - -// Reserve 20 & 21 for Hooks. - +// 20 to 21 unused // currency amount (fees) TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22) TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23) TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24) -// currency amount (AMM) +// currency amount (more) TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25) TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26) TYPED_SFIELD(sfEPrice, AMOUNT, 27) @@ -287,16 +286,16 @@ TYPED_SFIELD(sfMasterSignature, VL, 18, SField::sMD_Default, SFi TYPED_SFIELD(sfUNLModifyValidator, VL, 19) TYPED_SFIELD(sfValidatorToDisable, VL, 20) TYPED_SFIELD(sfValidatorToReEnable, VL, 21) -TYPED_SFIELD(sfHookStateData, VL, 22) -TYPED_SFIELD(sfHookReturnString, VL, 23) -TYPED_SFIELD(sfHookParameterName, VL, 24) -TYPED_SFIELD(sfHookParameterValue, VL, 25) +// 22 to 25 unused TYPED_SFIELD(sfDIDDocument, VL, 26) TYPED_SFIELD(sfData, VL, 27) TYPED_SFIELD(sfAssetClass, VL, 28) TYPED_SFIELD(sfProvider, VL, 29) TYPED_SFIELD(sfMPTokenMetadata, VL, 30) TYPED_SFIELD(sfCredentialType, VL, 31) +TYPED_SFIELD(sfFinishFunction, VL, 32) +TYPED_SFIELD(sfContractCode, VL, 33) +TYPED_SFIELD(sfFunctionName, VL, 34) // account (common) TYPED_SFIELD(sfAccount, ACCOUNT, 1) @@ -313,7 +312,7 @@ TYPED_SFIELD(sfHolder, ACCOUNT, 11) TYPED_SFIELD(sfDelegate, ACCOUNT, 12) // account (uncommon) -TYPED_SFIELD(sfHookAccount, ACCOUNT, 16) +// 16 unused TYPED_SFIELD(sfOtherChainSource, ACCOUNT, 18) TYPED_SFIELD(sfOtherChainDestination, ACCOUNT, 19) TYPED_SFIELD(sfAttestationSignerAccount, ACCOUNT, 20) @@ -323,6 +322,7 @@ TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23) TYPED_SFIELD(sfSubject, ACCOUNT, 24) TYPED_SFIELD(sfBorrower, ACCOUNT, 25) TYPED_SFIELD(sfCounterparty, ACCOUNT, 26) +TYPED_SFIELD(sfContractAccount, ACCOUNT, 27) // vector of 256-bit TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::sMD_Never) @@ -361,7 +361,7 @@ UNTYPED_SFIELD(sfMemo, OBJECT, 10) UNTYPED_SFIELD(sfSignerEntry, OBJECT, 11) UNTYPED_SFIELD(sfNFToken, OBJECT, 12) UNTYPED_SFIELD(sfEmitDetails, OBJECT, 13) -UNTYPED_SFIELD(sfHook, OBJECT, 14) +// 14 unused UNTYPED_SFIELD(sfPermission, OBJECT, 15) // inner object (uncommon) @@ -369,11 +369,7 @@ UNTYPED_SFIELD(sfSigner, OBJECT, 16) // 17 unused UNTYPED_SFIELD(sfMajority, OBJECT, 18) UNTYPED_SFIELD(sfDisabledValidator, OBJECT, 19) -UNTYPED_SFIELD(sfEmittedTxn, OBJECT, 20) -UNTYPED_SFIELD(sfHookExecution, OBJECT, 21) -UNTYPED_SFIELD(sfHookDefinition, OBJECT, 22) -UNTYPED_SFIELD(sfHookParameter, OBJECT, 23) -UNTYPED_SFIELD(sfHookGrant, OBJECT, 24) +// 20 to 24 unused UNTYPED_SFIELD(sfVoteEntry, OBJECT, 25) UNTYPED_SFIELD(sfAuctionSlot, OBJECT, 26) UNTYPED_SFIELD(sfAuthAccount, OBJECT, 27) @@ -387,6 +383,10 @@ UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34) UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35) UNTYPED_SFIELD(sfBook, OBJECT, 36) UNTYPED_SFIELD(sfCounterpartySignature, OBJECT, 37, SField::sMD_Default, SField::notSigning) +UNTYPED_SFIELD(sfFunction, OBJECT, 38) +UNTYPED_SFIELD(sfInstanceParameter, OBJECT, 39) +UNTYPED_SFIELD(sfInstanceParameterValue, OBJECT, 40) +UNTYPED_SFIELD(sfParameter, OBJECT, 41) // array of objects (common) // ARRAY/1 is reserved for end of array @@ -400,16 +400,14 @@ UNTYPED_SFIELD(sfSufficient, ARRAY, 7) UNTYPED_SFIELD(sfAffectedNodes, ARRAY, 8) UNTYPED_SFIELD(sfMemos, ARRAY, 9) UNTYPED_SFIELD(sfNFTokens, ARRAY, 10) -UNTYPED_SFIELD(sfHooks, ARRAY, 11) +// 11 unused UNTYPED_SFIELD(sfVoteSlots, ARRAY, 12) UNTYPED_SFIELD(sfAdditionalBooks, ARRAY, 13) // array of objects (uncommon) UNTYPED_SFIELD(sfMajorities, ARRAY, 16) UNTYPED_SFIELD(sfDisabledValidators, ARRAY, 17) -UNTYPED_SFIELD(sfHookExecutions, ARRAY, 18) -UNTYPED_SFIELD(sfHookParameters, ARRAY, 19) -UNTYPED_SFIELD(sfHookGrants, ARRAY, 20) +// 18 to 20 unused UNTYPED_SFIELD(sfXChainClaimAttestations, ARRAY, 21) UNTYPED_SFIELD(sfXChainCreateAccountAttestations, ARRAY, 22) // 23 unused @@ -421,5 +419,16 @@ UNTYPED_SFIELD(sfAcceptedCredentials, ARRAY, 28) UNTYPED_SFIELD(sfPermissions, ARRAY, 29) UNTYPED_SFIELD(sfRawTransactions, ARRAY, 30) UNTYPED_SFIELD(sfBatchSigners, ARRAY, 31, SField::sMD_Default, SField::notSigning) +UNTYPED_SFIELD(sfFunctions, ARRAY, 32) +UNTYPED_SFIELD(sfInstanceParameters, ARRAY, 33) +UNTYPED_SFIELD(sfInstanceParameterValues, ARRAY, 34) +UNTYPED_SFIELD(sfParameters, ARRAY, 35) -// clang-format on +// data +TYPED_SFIELD(sfParameterValue, DATA, 1, SField::sMD_Default) + +// data type +TYPED_SFIELD(sfParameterType, DATATYPE, 1) + +// json +TYPED_SFIELD(sfContractJson, JSON, 1) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 6d2d833440..8f9fab0a92 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -3,7 +3,7 @@ #endif /** - * TRANSACTION(tag, value, name, delegatable, amendments, privileges, fields) + * TRANSACTION(tag, value, name, delegatable, amendments, privileges, emitable, fields) * * To ease maintenance, you may replace any unneeded values with "..." * e.g. #define TRANSACTION(tag, value, name, ...) @@ -28,6 +28,7 @@ TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegatable, uint256{}, createAcct, + Emittance::emitable, ({ {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, @@ -48,13 +49,16 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfDestination, soeREQUIRED}, + {sfDestinationTag, soeOPTIONAL}, {sfAmount, soeREQUIRED, soeMPTSupported}, {sfCondition, soeOPTIONAL}, {sfCancelAfter, soeOPTIONAL}, {sfFinishAfter, soeOPTIONAL}, - {sfDestinationTag, soeOPTIONAL}, + {sfFinishFunction, soeOPTIONAL}, + {sfData, soeOPTIONAL}, })) /** This transaction type completes an existing escrow. */ @@ -62,12 +66,14 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfOwner, soeREQUIRED}, {sfOfferSequence, soeREQUIRED}, {sfFulfillment, soeOPTIONAL}, {sfCondition, soeOPTIONAL}, {sfCredentialIDs, soeOPTIONAL}, + {sfComputationAllowance, soeOPTIONAL}, })) @@ -79,6 +85,7 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, Delegation::notDelegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfEmailHash, soeOPTIONAL}, {sfWalletLocator, soeOPTIONAL}, @@ -100,6 +107,7 @@ TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfOwner, soeREQUIRED}, {sfOfferSequence, soeREQUIRED}, @@ -113,6 +121,7 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, Delegation::notDelegatable, uint256{}, noPriv, + Emittance::notEmitable, ({ {sfRegularKey, soeOPTIONAL}, })) @@ -127,6 +136,7 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfTakerPays, soeREQUIRED}, {sfTakerGets, soeREQUIRED}, @@ -143,6 +153,7 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfOfferSequence, soeREQUIRED}, })) @@ -157,6 +168,7 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfTicketCount, soeREQUIRED}, })) @@ -173,6 +185,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, Delegation::notDelegatable, uint256{}, noPriv, + Emittance::notEmitable, ({ {sfSignerQuorum, soeREQUIRED}, {sfSignerEntries, soeOPTIONAL}, @@ -186,6 +199,7 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED}, @@ -200,6 +214,7 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfChannel, soeREQUIRED}, {sfAmount, soeREQUIRED}, @@ -211,6 +226,7 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfChannel, soeREQUIRED}, {sfAmount, soeOPTIONAL}, @@ -228,6 +244,7 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfDestination, soeREQUIRED}, {sfSendMax, soeREQUIRED}, @@ -244,6 +261,7 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfCheckID, soeREQUIRED}, {sfAmount, soeOPTIONAL}, @@ -258,6 +276,7 @@ TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfCheckID, soeREQUIRED}, })) @@ -270,6 +289,7 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, Delegation::delegatable, uint256{}, noPriv, + Emittance::notEmitable, ({ {sfAuthorize, soeOPTIONAL}, {sfUnauthorize, soeOPTIONAL}, @@ -285,6 +305,7 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfLimitAmount, soeOPTIONAL}, {sfQualityIn, soeOPTIONAL}, @@ -299,6 +320,7 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, Delegation::notDelegatable, uint256{}, mustDeleteAcct, + Emittance::notEmitable, ({ {sfDestination, soeREQUIRED}, {sfDestinationTag, soeOPTIONAL}, @@ -315,6 +337,7 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, Delegation::delegatable, uint256{}, changeNFTCounts, + Emittance::emitable, ({ {sfNFTokenTaxon, soeREQUIRED}, {sfTransferFee, soeOPTIONAL}, @@ -333,6 +356,7 @@ TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, Delegation::delegatable, uint256{}, changeNFTCounts, + Emittance::emitable, ({ {sfNFTokenID, soeREQUIRED}, {sfOwner, soeOPTIONAL}, @@ -346,6 +370,7 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfNFTokenID, soeREQUIRED}, {sfAmount, soeREQUIRED}, @@ -362,6 +387,7 @@ TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfNFTokenOffers, soeREQUIRED}, })) @@ -374,6 +400,7 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, Delegation::delegatable, uint256{}, noPriv, + Emittance::emitable, ({ {sfNFTokenBuyOffer, soeOPTIONAL}, {sfNFTokenSellOffer, soeOPTIONAL}, @@ -388,6 +415,7 @@ TRANSACTION(ttCLAWBACK, 30, Clawback, Delegation::delegatable, featureClawback, noPriv, + Emittance::emitable, ({ {sfAmount, soeREQUIRED, soeMPTSupported}, {sfHolder, soeOPTIONAL}, @@ -401,6 +429,7 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, Delegation::delegatable, featureAMMClawback, mayDeleteAcct | overrideFreeze, + Emittance::emitable, ({ {sfHolder, soeREQUIRED}, {sfAsset, soeREQUIRED}, @@ -416,6 +445,7 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate, Delegation::delegatable, featureAMM, createPseudoAcct, + Emittance::emitable, ({ {sfAmount, soeREQUIRED}, {sfAmount2, soeREQUIRED}, @@ -430,6 +460,7 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, Delegation::delegatable, featureAMM, noPriv, + Emittance::emitable, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, @@ -448,6 +479,7 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, Delegation::delegatable, featureAMM, mayDeleteAcct, + Emittance::emitable, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, @@ -465,6 +497,7 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote, Delegation::delegatable, featureAMM, noPriv, + Emittance::emitable, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, @@ -479,6 +512,7 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, Delegation::delegatable, featureAMM, noPriv, + Emittance::emitable, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, @@ -495,6 +529,7 @@ TRANSACTION(ttAMM_DELETE, 40, AMMDelete, Delegation::delegatable, featureAMM, mustDeleteAcct, + Emittance::emitable, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, @@ -508,6 +543,7 @@ TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, Delegation::delegatable, featureXChainBridge, noPriv, + Emittance::emitable, ({ {sfXChainBridge, soeREQUIRED}, {sfSignatureReward, soeREQUIRED}, @@ -519,6 +555,7 @@ TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, Delegation::delegatable, featureXChainBridge, noPriv, + Emittance::emitable, ({ {sfXChainBridge, soeREQUIRED}, {sfXChainClaimID, soeREQUIRED}, @@ -531,6 +568,7 @@ TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, Delegation::delegatable, featureXChainBridge, noPriv, + Emittance::emitable, ({ {sfXChainBridge, soeREQUIRED}, {sfXChainClaimID, soeREQUIRED}, @@ -544,6 +582,7 @@ TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, Delegation::delegatable, featureXChainBridge, noPriv, + Emittance::emitable, ({ {sfXChainBridge, soeREQUIRED}, {sfDestination, soeREQUIRED}, @@ -556,6 +595,7 @@ TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, Delegation::delegatable, featureXChainBridge, createAcct, + Emittance::emitable, ({ {sfXChainBridge, soeREQUIRED}, @@ -577,6 +617,7 @@ TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, Delegation::delegatable, featureXChainBridge, createAcct, + Emittance::emitable, ({ {sfXChainBridge, soeREQUIRED}, @@ -598,6 +639,7 @@ TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, Delegation::delegatable, featureXChainBridge, noPriv, + Emittance::emitable, ({ {sfXChainBridge, soeREQUIRED}, {sfSignatureReward, soeOPTIONAL}, @@ -609,6 +651,7 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, Delegation::delegatable, featureXChainBridge, noPriv, + Emittance::emitable, ({ {sfXChainBridge, soeREQUIRED}, {sfSignatureReward, soeREQUIRED}, @@ -623,6 +666,7 @@ TRANSACTION(ttDID_SET, 49, DIDSet, Delegation::delegatable, featureDID, noPriv, + Emittance::emitable, ({ {sfDIDDocument, soeOPTIONAL}, {sfURI, soeOPTIONAL}, @@ -634,6 +678,7 @@ TRANSACTION(ttDID_DELETE, 50, DIDDelete, Delegation::delegatable, featureDID, noPriv, + Emittance::emitable, ({})) /** This transaction type creates an Oracle instance */ @@ -644,6 +689,7 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet, Delegation::delegatable, featurePriceOracle, noPriv, + Emittance::emitable, ({ {sfOracleDocumentID, soeREQUIRED}, {sfProvider, soeOPTIONAL}, @@ -661,6 +707,7 @@ TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, Delegation::delegatable, featurePriceOracle, noPriv, + Emittance::emitable, ({ {sfOracleDocumentID, soeREQUIRED}, })) @@ -673,6 +720,7 @@ TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, Delegation::delegatable, fixNFTokenPageLinks, noPriv, + Emittance::emitable, ({ {sfLedgerFixType, soeREQUIRED}, {sfOwner, soeOPTIONAL}, @@ -686,6 +734,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::delegatable, featureMPTokensV1, createMPTIssuance, + Emittance::emitable, ({ {sfAssetScale, soeOPTIONAL}, {sfTransferFee, soeOPTIONAL}, @@ -703,6 +752,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, Delegation::delegatable, featureMPTokensV1, destroyMPTIssuance, + Emittance::emitable, ({ {sfMPTokenIssuanceID, soeREQUIRED}, })) @@ -715,6 +765,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::delegatable, featureMPTokensV1, noPriv, + Emittance::emitable, ({ {sfMPTokenIssuanceID, soeREQUIRED}, {sfHolder, soeOPTIONAL}, @@ -732,6 +783,7 @@ TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, Delegation::delegatable, featureMPTokensV1, mustAuthorizeMPT, + Emittance::emitable, ({ {sfMPTokenIssuanceID, soeREQUIRED}, {sfHolder, soeOPTIONAL}, @@ -745,6 +797,7 @@ TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, Delegation::delegatable, featureCredentials, noPriv, + Emittance::emitable, ({ {sfSubject, soeREQUIRED}, {sfCredentialType, soeREQUIRED}, @@ -757,6 +810,7 @@ TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, Delegation::delegatable, featureCredentials, noPriv, + Emittance::emitable, ({ {sfIssuer, soeREQUIRED}, {sfCredentialType, soeREQUIRED}, @@ -767,6 +821,7 @@ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, Delegation::delegatable, featureCredentials, noPriv, + Emittance::emitable, ({ {sfSubject, soeOPTIONAL}, {sfIssuer, soeOPTIONAL}, @@ -781,6 +836,7 @@ TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, Delegation::delegatable, featureDynamicNFT, noPriv, + Emittance::emitable, ({ {sfNFTokenID, soeREQUIRED}, {sfOwner, soeOPTIONAL}, @@ -795,6 +851,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, Delegation::delegatable, featurePermissionedDomains, noPriv, + Emittance::emitable, ({ {sfDomainID, soeOPTIONAL}, {sfAcceptedCredentials, soeREQUIRED}, @@ -808,6 +865,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, Delegation::delegatable, featurePermissionedDomains, noPriv, + Emittance::emitable, ({ {sfDomainID, soeREQUIRED}, })) @@ -820,6 +878,7 @@ TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, Delegation::notDelegatable, featurePermissionDelegationV1_1, noPriv, + Emittance::notEmitable, ({ {sfAuthorize, soeREQUIRED}, {sfPermissions, soeREQUIRED}, @@ -833,6 +892,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, featureSingleAssetVault, createPseudoAcct | createMPTIssuance | mustModifyVault, + Emittance::emitable, ({ {sfAsset, soeREQUIRED, soeMPTSupported}, {sfAssetsMaximum, soeOPTIONAL}, @@ -851,6 +911,7 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegatable, featureSingleAssetVault, mustModifyVault, + Emittance::emitable, ({ {sfVaultID, soeREQUIRED}, {sfAssetsMaximum, soeOPTIONAL}, @@ -866,6 +927,7 @@ TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, Delegation::delegatable, featureSingleAssetVault, mustDeleteAcct | destroyMPTIssuance | mustModifyVault, + Emittance::emitable, ({ {sfVaultID, soeREQUIRED}, })) @@ -878,6 +940,7 @@ TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, Delegation::delegatable, featureSingleAssetVault, mayAuthorizeMPT | mustModifyVault, + Emittance::emitable, ({ {sfVaultID, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, @@ -891,6 +954,7 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, Delegation::delegatable, featureSingleAssetVault, mayDeleteMPT | mayAuthorizeMPT | mustModifyVault, + Emittance::emitable, ({ {sfVaultID, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, @@ -906,6 +970,7 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, Delegation::delegatable, featureSingleAssetVault, mayDeleteMPT | mustModifyVault, + Emittance::emitable, ({ {sfVaultID, soeREQUIRED}, {sfHolder, soeREQUIRED}, @@ -920,6 +985,7 @@ TRANSACTION(ttBATCH, 71, Batch, Delegation::notDelegatable, featureBatch, noPriv, + Emittance::notEmitable, ({ {sfRawTransactions, soeREQUIRED}, {sfBatchSigners, soeOPTIONAL}, @@ -934,7 +1000,9 @@ TRANSACTION(ttBATCH, 71, Batch, TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet, Delegation::delegatable, featureLendingProtocol, - createPseudoAcct | mayAuthorizeMPT, ({ + createPseudoAcct | mayAuthorizeMPT, + Emittance::emitable, + ({ {sfVaultID, soeREQUIRED}, {sfLoanBrokerID, soeOPTIONAL}, {sfData, soeOPTIONAL}, @@ -951,7 +1019,9 @@ TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet, TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete, Delegation::delegatable, featureLendingProtocol, - mustDeleteAcct | mayAuthorizeMPT, ({ + mustDeleteAcct | mayAuthorizeMPT, + Emittance::emitable, + ({ {sfLoanBrokerID, soeREQUIRED}, })) @@ -962,7 +1032,9 @@ TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete, TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit, Delegation::delegatable, featureLendingProtocol, - noPriv, ({ + noPriv, + Emittance::emitable, + ({ {sfLoanBrokerID, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, })) @@ -974,7 +1046,9 @@ TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit, TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw, Delegation::delegatable, featureLendingProtocol, - mayAuthorizeMPT, ({ + mayAuthorizeMPT, + Emittance::emitable, + ({ {sfLoanBrokerID, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, {sfDestination, soeOPTIONAL}, @@ -989,7 +1063,9 @@ TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw, TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback, Delegation::delegatable, featureLendingProtocol, - noPriv, ({ + noPriv, + Emittance::emitable, + ({ {sfLoanBrokerID, soeOPTIONAL}, {sfAmount, soeOPTIONAL, soeMPTSupported}, })) @@ -1001,7 +1077,9 @@ TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback, TRANSACTION(ttLOAN_SET, 80, LoanSet, Delegation::delegatable, featureLendingProtocol, - mayAuthorizeMPT | mustModifyVault, ({ + mayAuthorizeMPT | mustModifyVault, + Emittance::emitable, + ({ {sfLoanBrokerID, soeREQUIRED}, {sfData, soeOPTIONAL}, {sfCounterparty, soeOPTIONAL}, @@ -1028,7 +1106,9 @@ TRANSACTION(ttLOAN_SET, 80, LoanSet, TRANSACTION(ttLOAN_DELETE, 81, LoanDelete, Delegation::delegatable, featureLendingProtocol, - noPriv, ({ + noPriv, + Emittance::emitable, +({ {sfLoanID, soeREQUIRED}, })) @@ -1042,7 +1122,9 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage, // All of the LoanManage options will modify the vault, but the // transaction can succeed without options, essentially making it // a noop. - mayModifyVault, ({ + mayModifyVault, + Emittance::emitable, + ({ {sfLoanID, soeREQUIRED}, })) @@ -1053,11 +1135,110 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage, TRANSACTION(ttLOAN_PAY, 84, LoanPay, Delegation::delegatable, featureLendingProtocol, - mayAuthorizeMPT | mustModifyVault, ({ + mayAuthorizeMPT | mustModifyVault, + Emittance::emitable, + ({ {sfLoanID, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, })) +/** This transaction type creates the smart contract. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCONTRACT_CREATE, 85, ContractCreate, + Delegation::delegatable, + featureSmartContract, + createPseudoAcct, + Emittance::emitable, + ({ + {sfContractCode, soeOPTIONAL}, + {sfContractHash, soeOPTIONAL}, + {sfFunctions, soeOPTIONAL}, + {sfInstanceParameters, soeOPTIONAL}, + {sfInstanceParameterValues, soeOPTIONAL}, + {sfURI, soeOPTIONAL}, +})) + +/** This transaction type modifies the smart contract. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCONTRACT_MODIFY, 86, ContractModify, + Delegation::delegatable, + featureSmartContract, + noPriv, + Emittance::emitable, + ({ + {sfContractAccount, soeOPTIONAL}, + {sfOwner, soeOPTIONAL}, + {sfContractCode, soeOPTIONAL}, + {sfContractHash, soeOPTIONAL}, + {sfFunctions, soeOPTIONAL}, + {sfInstanceParameters, soeOPTIONAL}, + {sfInstanceParameterValues, soeOPTIONAL}, + {sfURI, soeOPTIONAL}, +})) + +/** This transaction type deletes the smart contract. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCONTRACT_DELETE, 87, ContractDelete, + Delegation::delegatable, + featureSmartContract, + mustDeleteAcct, + Emittance::emitable, + ({ + {sfContractAccount, soeREQUIRED}, +})) + +/** This transaction type claws back funds from the contract. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCONTRACT_CLAWBACK, 88, ContractClawback, + Delegation::delegatable, + featureSmartContract, + noPriv, + Emittance::emitable, + ({ + {sfContractAccount, soeOPTIONAL}, + {sfAmount, soeREQUIRED, soeMPTSupported}, +})) + +/** This transaction type deletes user data. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCONTRACT_USER_DELETE, 89, ContractUserDelete, + Delegation::delegatable, + featureSmartContract, + noPriv, + Emittance::notEmitable, + ({ + {sfContractAccount, soeREQUIRED}, + {sfFunctionName, soeREQUIRED}, + {sfParameters, soeOPTIONAL}, + {sfComputationAllowance, soeREQUIRED}, +})) + +/** This transaction type calls the smart contract. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCONTRACT_CALL, 90, ContractCall, + Delegation::delegatable, + featureSmartContract, + noPriv, + Emittance::notEmitable, + ({ + {sfContractAccount, soeREQUIRED}, + {sfFunctionName, soeREQUIRED}, + {sfParameters, soeOPTIONAL}, + {sfComputationAllowance, soeREQUIRED}, +})) + /** This system-generated transaction type is used to update the status of the various amendments. For details, see: https://xrpl.org/amendments.html @@ -1069,6 +1250,7 @@ TRANSACTION(ttAMENDMENT, 100, EnableAmendment, Delegation::notDelegatable, uint256{}, noPriv, + Emittance::notEmitable, ({ {sfLedgerSequence, soeREQUIRED}, {sfAmendment, soeREQUIRED}, @@ -1081,6 +1263,7 @@ TRANSACTION(ttFEE, 101, SetFee, Delegation::notDelegatable, uint256{}, noPriv, + Emittance::notEmitable, ({ {sfLedgerSequence, soeOPTIONAL}, // Old version uses raw numbers @@ -1092,6 +1275,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 @@ -1102,6 +1289,7 @@ TRANSACTION(ttUNL_MODIFY, 102, UNLModify, Delegation::notDelegatable, uint256{}, noPriv, + Emittance::notEmitable, ({ {sfUNLModifyDisabling, soeREQUIRED}, {sfLedgerSequence, soeREQUIRED}, diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index d86ca9bf1f..194e676228 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -191,6 +191,7 @@ JSS(command); // in: RPCHandler JSS(complete); // out: NetworkOPs, InboundLedger JSS(complete_ledgers); // out: NetworkOPs, PeerImp JSS(consensus); // out: NetworkOPs, LedgerConsensus +JSS(contract_account); // out: ContractInfo JSS(converge_time); // out: NetworkOPs JSS(converge_time_s); // out: NetworkOPs JSS(cookie); // out: NetworkOPs @@ -255,6 +256,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 @@ -275,6 +279,8 @@ JSS(flags); // out: AccountOffers, JSS(forward); // in: AccountTx JSS(freeze); // out: AccountLines JSS(freeze_peer); // out: AccountLines +JSS(function); // in: ContractInfo +JSS(functions); // out: ContractInfo JSS(deep_freeze); // out: AccountLines JSS(deep_freeze_peer); // out: AccountLines JSS(frozen_balances); // out: GatewayBalances @@ -565,6 +571,7 @@ JSS(size); // out: get_aggregate_price JSS(snapshot); // in: Subscribe JSS(source_account); // in: PathRequest, RipplePathFind JSS(source_amount); // in: PathRequest, RipplePathFind +JSS(source_code_uri); // out: ContractInfo JSS(source_currencies); // in: PathRequest, RipplePathFind JSS(source_tag); // out: AccountChannels JSS(stand_alone); // out: NetworkOPs @@ -662,6 +669,7 @@ JSS(url_password); // in: Subscribe JSS(url_username); // in: Subscribe JSS(urlgravatar); // JSS(username); // in: Subscribe +JSS(user_data); // out: ContractInfo JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx* // Tx JSS(validator_list_expires); // out: NetworkOps, ValidatorList @@ -709,11 +717,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 diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 96c13c9db8..d68a5071cf 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -631,6 +631,48 @@ power(Number const& f, unsigned n) return r; } +// Continued fraction approximation of ln(x) +static Number +ln(Number const& x, unsigned iterations = 50) +{ + if (x <= 0) + throw std::runtime_error("Not positive value"); + + Number const z = (x - 1) / (x + 1); + Number const zz = z * z; + Number denom = Number(1, -10); + + // Construct the fraction from the bottom up + for (int i = iterations; i > 0; --i) + { + Number k(2 * i - 1); + denom = k - (i * i * zz / denom); + } + + auto const r = 2 * z / denom; + return r; +} + +Number +lg(Number const& x) +{ + static Number const ln10 = ln(Number(10)); + + if (x <= Number(10)) + { + auto const r = ln(x) / ln10; + return r; + } + + // ln(x) = ln(normX * 10^norm) = ln(normX) + norm * ln(10) + int diffExp = 15 + x.exponent(); + Number const normalX = x / Number(1, diffExp); // (1 <= normalX < 10) + auto const lnX = ln(normalX) + diffExp * ln10; + + auto const r = lnX / ln10; + return r; +} + // 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 diff --git a/src/libxrpl/ledger/ApplyStateTable.cpp b/src/libxrpl/ledger/ApplyStateTable.cpp index 94fd9d7273..c59b2762d8 100644 --- a/src/libxrpl/ledger/ApplyStateTable.cpp +++ b/src/libxrpl/ledger/ApplyStateTable.cpp @@ -97,6 +97,8 @@ ApplyStateTable::apply( TER ter, std::optional const& deliver, std::optional const& parentBatchId, + std::optional const& gasUsed, + std::optional const& wasmReturnCode, bool isDryRun, beast::Journal j) { @@ -111,6 +113,8 @@ ApplyStateTable::apply( meta.setDeliveredAmount(deliver); meta.setParentBatchID(parentBatchId); + meta.setGasUsed(gasUsed); + meta.setWasmReturnCode(wasmReturnCode); Mods newMod; for (auto& item : items_) diff --git a/src/libxrpl/ledger/ApplyViewImpl.cpp b/src/libxrpl/ledger/ApplyViewImpl.cpp index b0aeb70228..5fdf00b234 100644 --- a/src/libxrpl/ledger/ApplyViewImpl.cpp +++ b/src/libxrpl/ledger/ApplyViewImpl.cpp @@ -16,7 +16,16 @@ ApplyViewImpl::apply( bool isDryRun, beast::Journal j) { - return items_.apply(to, tx, ter, deliver_, parentBatchId, isDryRun, j); + return items_.apply( + to, + tx, + ter, + deliver_, + parentBatchId, + gasUsed_, + wasmReturnCode_, + isDryRun, + j); } std::size_t diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index c817a85b65..f6bb3c9567 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -1454,7 +1454,6 @@ addEmptyHolding( auto const& srcId = issuerId; auto const& dstId = accountID; - auto const high = srcId > dstId; auto const index = keylet::line(srcId, dstId, currency); auto const sleSrc = view.peek(keylet::account(srcId)); auto const sleDst = view.peek(keylet::account(dstId)); @@ -1471,22 +1470,7 @@ addEmptyHolding( if (priorBalance < view.fees().accountReserve(ownerCount + 1)) return tecNO_LINE_INSUF_RESERVE; - return trustCreate( - view, - high, - srcId, - dstId, - index.key, - sleDst, - /*auth=*/false, - /*noRipple=*/true, - /*freeze=*/false, - /*deepFreeze*/ false, - /*balance=*/STAmount{Issue{currency, noAccount()}}, - /*limit=*/STAmount{Issue{currency, dstId}}, - /*qualityIn=*/0, - /*qualityOut=*/0, - journal); + return tesSUCCESS; } [[nodiscard]] TER @@ -3341,6 +3325,308 @@ canTransfer( return tesSUCCESS; } +// TER +// requireNoRipple( +// ReadView const& view, +// Issue const& issue, +// AccountID const& account) +// { +// if (isXRP(issue) || issue.account == account) +// return tesSUCCESS; + +// auto const trustLine = +// view.read(keylet::line(account, issue.account, issue.currency)); +// if (!trustLine) +// return tesSUCCESS; + +// bool accountHigh = account > issue.account; +// auto const flagIssuerNoRipple{ +// accountHigh ? lsfLowNoRipple : lsfHighNoRipple}; +// uint32_t flags = trustLine->getFieldU32(sfFlags); +// if (flags & flagIssuerNoRipple) +// return tecPATH_DRY; + +// return tesSUCCESS; +// } + +static TER +canTransferIOU( + ReadView const& view, + AccountID const& sender, + AccountID const& receiver, + STAmount const& amount, + beast::Journal j, + SendIssuerHandling issuerHandling, + SendEscrowHandling escrowHandling, + SendAuthHandling authHandling, + SendFreezeHandling freezeHandling, + SendTransferHandling transferHandling, + SendBalanceHandling balanceHandling) +{ + AccountID issuer = amount.getIssuer(); + // If the issuer is the same as the sender + if (issuerHandling == SendIssuerHandling::ihSENDER_NOT_ALLOWED && + issuer == sender) + return tecNO_PERMISSION; + + // If the issuer is the same as the receiver + if (issuerHandling == SendIssuerHandling::ihRECEIVER_NOT_ALLOWED && + issuer == receiver) + return tecNO_PERMISSION; + + // If the lsfAllowTrustLineLocking is not enabled + auto const sleIssuer = view.read(keylet::account(issuer)); + if (!sleIssuer) + return tecNO_ISSUER; + + if (issuerHandling != SendIssuerHandling::ihSENDER_NOT_ALLOWED && + issuerHandling != SendIssuerHandling::ihRECEIVER_NOT_ALLOWED && + !sleIssuer->isFlag(lsfDefaultRipple)) + return terNO_RIPPLE; + + if (escrowHandling == SendEscrowHandling::ehCHECK && + !sleIssuer->isFlag(lsfAllowTrustLineLocking)) + return tecNO_PERMISSION; + + // If the sender does not have a trustline to the issuer + auto const sleRippleState = + view.read(keylet::line(sender, issuer, amount.getCurrency())); + + if (!sleRippleState) + return tecNO_LINE; + + STAmount const balance = (*sleRippleState)[sfBalance]; + + // If balance is positive, issuer must have higher address than sender + if (balance > beast::zero && issuer < sender) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + // If balance is negative, issuer must have lower address than sender + if (balance < beast::zero && issuer > sender) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + // // If the account trustline has no-ripple set for the issuer + // if (auto const ter = requireNoRipple(ctx.view, amount.issue(), account); + // ter != tesSUCCESS) + // return ter; + + // // If the dest trustline has no-ripple set for the issuer + // if (auto const ter = requireNoRipple(ctx.view, amount.issue(), dest); + // ter != tesSUCCESS) + // return ter; + + // If the issuer has requireAuth set, check if the sender is authorized + if (authHandling == SendAuthHandling::ahCHECK_SENDER || + authHandling == SendAuthHandling::ahBOTH) + { + if (auto const ter = requireAuth(view, amount.issue(), sender); + ter != tesSUCCESS) + return ter; + } + + // If the issuer has requireAuth set, check if the receiver is authorized + if (authHandling == SendAuthHandling::ahCHECK_RECEIVER || + authHandling == SendAuthHandling::ahBOTH) + { + if (auto const ter = requireAuth(view, amount.issue(), receiver); + ter != tesSUCCESS) + return ter; + } + + // If the issuer has frozen the sender + if ((freezeHandling == SendFreezeHandling::fhCHECK_SENDER || + freezeHandling == SendFreezeHandling::fhBOTH) && + isFrozen(view, sender, amount.issue())) + return tecFROZEN; + + // If the issuer has frozen the receiver + if ((freezeHandling == SendFreezeHandling::fhCHECK_RECEIVER || + freezeHandling == SendFreezeHandling::fhBOTH) && + isFrozen(view, receiver, amount.issue())) + return tecFROZEN; + + if (balanceHandling == SendBalanceHandling::bhIGNORE) + return tesSUCCESS; + + STAmount const spendableAmount = accountHolds( + view, + sender, + amount.get(), + fhIGNORE_FREEZE, // already checked freeze above + ahIGNORE_AUTH, // already checked auth above + j); + + // If the balance is less than or equal to 0 + if (spendableAmount <= beast::zero) + return tecINSUFFICIENT_FUNDS; + + // If the spendable amount is less than the amount + if (spendableAmount < amount) + return tecINSUFFICIENT_FUNDS; + + // If the amount is not addable to the balance + if (!canAdd(spendableAmount, amount)) + return tecPRECISION_LOSS; + + return tesSUCCESS; +} + +static TER +canTransferMPT( + ReadView const& view, + AccountID const& sender, + AccountID const& receiver, + STAmount const& amount, + beast::Journal j, + SendIssuerHandling issuerHandling, + SendEscrowHandling escrowHandling, + SendAuthHandling authHandling, + SendFreezeHandling freezeHandling, + SendTransferHandling transferHandling, + SendBalanceHandling balanceHandling) +{ + AccountID issuer = amount.getIssuer(); + // If the issuer is the same as the sender + if (issuerHandling == SendIssuerHandling::ihSENDER_NOT_ALLOWED && + issuer == sender) + return tecNO_PERMISSION; + + // If the issuer is the same as the receiver + if (issuerHandling == SendIssuerHandling::ihRECEIVER_NOT_ALLOWED && + issuer == receiver) + return tecNO_PERMISSION; + + // If the mpt does not exist + auto const issuanceKey = + keylet::mptIssuance(amount.get().getMptID()); + auto const sleIssuance = view.read(issuanceKey); + if (!sleIssuance) + return tecOBJECT_NOT_FOUND; + + // If the lsfMPTCanEscrow is not enabled + if (escrowHandling == SendEscrowHandling::ehCHECK && + !sleIssuance->isFlag(lsfMPTCanEscrow)) + return tecNO_PERMISSION; + + // If the issuer is not the same as the issuer of the mpt + if (sleIssuance->getAccountID(sfIssuer) != issuer) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + // If the sender does not have the mpt + if (!view.exists(keylet::mptoken(issuanceKey.key, sender))) + return tecOBJECT_NOT_FOUND; + + auto const& mptIssue = amount.get(); + + // If the issuer has requireAuth set, check if the sender is authorized + if (authHandling == SendAuthHandling::ahCHECK_SENDER || + authHandling == SendAuthHandling::ahBOTH) + { + if (auto const ter = + requireAuth(view, mptIssue, sender, AuthType::WeakAuth); + ter != tesSUCCESS) + return ter; + } + + // If the issuer has requireAuth set, check if the receiver is authorized + if (authHandling == SendAuthHandling::ahCHECK_RECEIVER || + authHandling == SendAuthHandling::ahBOTH) + { + if (auto const ter = + requireAuth(view, mptIssue, receiver, AuthType::WeakAuth); + ter != tesSUCCESS) + return ter; + } + + // If the issuer has frozen the sender, return tecLOCKED + if ((freezeHandling == SendFreezeHandling::fhCHECK_SENDER || + freezeHandling == SendFreezeHandling::fhBOTH) && + isFrozen(view, sender, mptIssue)) + return tecLOCKED; + + // If the issuer has frozen the receiver, return tecLOCKED + if ((freezeHandling == SendFreezeHandling::fhCHECK_RECEIVER || + freezeHandling == SendFreezeHandling::fhBOTH) && + isFrozen(view, receiver, mptIssue)) + return tecLOCKED; + + // If the mpt cannot be transferred, return tecNO_AUTH + if (transferHandling == SendTransferHandling::thCHECK) + { + if (auto const ter = canTransfer(view, mptIssue, sender, receiver); + ter != tesSUCCESS) + return ter; + } + + if (balanceHandling == SendBalanceHandling::bhIGNORE) + return tesSUCCESS; + + STAmount const spendableAmount = accountHolds( + view, + sender, + amount.get(), + fhIGNORE_FREEZE, // already checked freeze above + ahIGNORE_AUTH, // already checked auth above + j); + + // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS + if (spendableAmount <= beast::zero) + return tecINSUFFICIENT_FUNDS; + + // If the spendable amount is less than the amount, return + // tecINSUFFICIENT_FUNDS + if (spendableAmount < amount) + return tecINSUFFICIENT_FUNDS; + + return tesSUCCESS; +} + +TER +canTransferFT( + ReadView const& view, + AccountID const& sender, + AccountID const& receiver, + STAmount const& amount, + beast::Journal j, + SendIssuerHandling issuerHandling = SendIssuerHandling::ihIGNORE, + SendEscrowHandling escrowHandling = SendEscrowHandling::ehIGNORE, + SendAuthHandling authHandling = SendAuthHandling::ahBOTH, + SendFreezeHandling freezeHandling = SendFreezeHandling::fhBOTH, + SendTransferHandling transferHandling = SendTransferHandling::thIGNORE, + SendBalanceHandling balanceHandling = SendBalanceHandling::bhCHECK) +{ + return std::visit( + [&](TIss const& issue) -> TER { + if constexpr (std::is_same_v) + return canTransferIOU( + view, + sender, + receiver, + amount, + j, + issuerHandling, + escrowHandling, + authHandling, + freezeHandling, + transferHandling, + balanceHandling); + else + return canTransferMPT( + view, + sender, + receiver, + amount, + j, + issuerHandling, + escrowHandling, + authHandling, + freezeHandling, + transferHandling, + balanceHandling); + }, + amount.asset().value()); +} + [[nodiscard]] TER canTransfer( ReadView const& view, diff --git a/src/libxrpl/protocol/Emitable.cpp b/src/libxrpl/protocol/Emitable.cpp new file mode 100644 index 0000000000..c8aa4d869d --- /dev/null +++ b/src/libxrpl/protocol/Emitable.cpp @@ -0,0 +1,188 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Emitable to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this emitable notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include + +namespace xrpl { + +Emitable::Emitable() +{ + emitableTx_ = { +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +#define TRANSACTION( \ + tag, value, name, delegatable, amendment, permissions, emitable, fields) \ + {value, emitable}, +#include + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") + }; + + granularEmitableMap_ = { +#pragma push_macro("EMITABLE") +#undef EMITABLE + +#define EMITABLE(type, txType, value) {#type, type}, + +#include + +#undef EMITABLE +#pragma pop_macro("EMITABLE") + }; + + granularNameMap_ = { +#pragma push_macro("EMITABLE") +#undef EMITABLE + +#define EMITABLE(type, txType, value) {type, #type}, + +#include + +#undef EMITABLE +#pragma pop_macro("EMITABLE") + }; + + granularTxTypeMap_ = { +#pragma push_macro("EMITABLE") +#undef EMITABLE + +#define EMITABLE(type, txType, value) {type, txType}, + +#include + +#undef EMITABLE +#pragma pop_macro("EMITABLE") + }; + + for ([[maybe_unused]] auto const& emitable : granularEmitableMap_) + XRPL_ASSERT( + emitable.second > UINT16_MAX, + "xrpl::Emitable::granularEmitableMap_ : granular emitable " + "value must not exceed the maximum uint16_t value."); +} + +Emitable const& +Emitable::getInstance() +{ + static Emitable const instance; + return instance; +} + +std::optional +Emitable::getEmitableName(std::uint32_t const value) const +{ + auto const emitableValue = static_cast(value); + if (auto const granular = getGranularName(emitableValue)) + return *granular; + + // not a granular emitable, check if it maps to a transaction type + auto const txType = emitableToTxType(value); + if (auto const* item = TxFormats::getInstance().findByType(txType); + item != nullptr) + return item->getName(); + + return std::nullopt; +} + +std::optional +Emitable::getGranularValue(std::string const& name) const +{ + auto const it = granularEmitableMap_.find(name); + if (it != granularEmitableMap_.end()) + return static_cast(it->second); + + return std::nullopt; +} + +std::optional +Emitable::getGranularName(GranularEmitableType const& value) const +{ + auto const it = granularNameMap_.find(value); + if (it != granularNameMap_.end()) + return it->second; + + return std::nullopt; +} + +std::optional +Emitable::getGranularTxType(GranularEmitableType const& gpType) const +{ + auto const it = granularTxTypeMap_.find(gpType); + if (it != granularTxTypeMap_.end()) + return it->second; + + return std::nullopt; +} + +bool +Emitable::isEmitable(std::uint32_t const& emitableValue) const +{ + auto const granularEmitable = + getGranularName(static_cast(emitableValue)); + if (granularEmitable) + // granular emitables are always allowed to be delegated + return true; + + auto const txType = emitableToTxType(emitableValue); + auto const it = emitableTx_.find(txType); + + // if (rules.enabled(fixDelegateV1_1)) + // { + // if (it == delegatableTx_.end()) + // return false; + + // auto const txFeaturesIt = txFeatureMap_.find(txType); + // XRPL_ASSERT( + // txFeaturesIt != txFeatureMap_.end(), + // "xrpl::Emitables::isDelegatable : tx exists in txFeatureMap_"); + + // // fixDelegateV1_1: Delegation is only allowed if the required + // amendment + // // for the transaction is enabled. For transactions that do not + // require + // // an amendment, delegation is always allowed. + // if (txFeaturesIt->second != uint256{} && + // !rules.enabled(txFeaturesIt->second)) + // return false; + // } + + if (it != emitableTx_.end() && it->second == Emittance::notEmitable) + return false; + + return true; +} + +uint32_t +Emitable::txToEmitableType(TxType const& type) const +{ + return static_cast(type) + 1; +} + +TxType +Emitable::emitableToTxType(uint32_t const& value) const +{ + return static_cast(value - 1); +} + +} // namespace xrpl diff --git a/src/libxrpl/protocol/IOUAmount.cpp b/src/libxrpl/protocol/IOUAmount.cpp index 01283886e1..a62bd2853b 100644 --- a/src/libxrpl/protocol/IOUAmount.cpp +++ b/src/libxrpl/protocol/IOUAmount.cpp @@ -39,13 +39,6 @@ setSTNumberSwitchover(bool v) *getStaticSTNumberSwitchover() = v; } -/* The range for the mantissa when normalized */ -static std::int64_t constexpr minMantissa = 1000000000000000ull; -static std::int64_t constexpr maxMantissa = 9999999999999999ull; -/* The range for the exponent when normalized */ -static int constexpr minExponent = -96; -static int constexpr maxExponent = 80; - IOUAmount IOUAmount::minPositiveAmount() { @@ -293,7 +286,8 @@ mulRatio( { if (!result) { - return IOUAmount(-minMantissa, minExponent); + return IOUAmount( + -IOUAmount::minMantissa, IOUAmount::minExponent); } // This subtraction cannot underflow because `result` is not zero return IOUAmount(result.mantissa() - 1, result.exponent()); diff --git a/src/libxrpl/protocol/Indexes.cpp b/src/libxrpl/protocol/Indexes.cpp index 77fcd44a3e..1d627683c1 100644 --- a/src/libxrpl/protocol/Indexes.cpp +++ b/src/libxrpl/protocol/Indexes.cpp @@ -79,10 +79,12 @@ enum class LedgerNameSpace : std::uint16_t { VAULT = 'V', LOAN_BROKER = 'l', // lower-case L LOAN = 'L', + CONTRACT_SOURCE = 'Z', + CONTRACT = 'z', + CONTRACT_DATA = 'b', // No longer used or supported. Left here to reserve the space // to avoid accidental reuse. - CONTRACT [[deprecated]] = 'c', GENERATOR [[deprecated]] = 'g', NICKNAME [[deprecated]] = 'n', }; @@ -574,6 +576,33 @@ permissionedDomain(uint256 const& domainID) noexcept return {ltPERMISSIONED_DOMAIN, domainID}; } +Keylet +contractSource(uint256 const& contractHash) noexcept +{ + return { + ltCONTRACT_SOURCE, + indexHash(LedgerNameSpace::CONTRACT_SOURCE, contractHash)}; +} + +Keylet +contract( + uint256 const& contractHash, + AccountID const& owner, + std::uint32_t seq) noexcept +{ + return { + ltCONTRACT, + indexHash(LedgerNameSpace::CONTRACT, contractHash, owner, seq)}; +} + +Keylet +contractData(AccountID const& owner, AccountID const& contractAccount) noexcept +{ + return { + ltCONTRACT_DATA, + indexHash(LedgerNameSpace::CONTRACT_DATA, owner, contractAccount)}; +} + } // namespace keylet } // namespace xrpl diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 3eb73e7c9b..1a4b7cdcbb 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -161,6 +161,35 @@ InnerObjectFormats::InnerObjectFormats() {sfTxnSignature, soeOPTIONAL}, {sfSigners, soeOPTIONAL}, }); + + add(sfFunction.jsonName.c_str(), + sfFunction.getCode(), + { + {sfFunctionName, soeREQUIRED}, + {sfParameters, soeOPTIONAL}, + }); + + add(sfInstanceParameter.jsonName, + sfInstanceParameter.getCode(), + { + {sfParameterFlag, soeREQUIRED}, + {sfParameterType, soeREQUIRED}, + }); + + add(sfInstanceParameterValue.jsonName, + sfInstanceParameterValue.getCode(), + { + {sfParameterFlag, soeREQUIRED}, + {sfParameterValue, soeREQUIRED}, + }); + + add(sfParameter.jsonName, + sfParameter.getCode(), + { + {sfParameterFlag, soeOPTIONAL}, + {sfParameterType, soeOPTIONAL}, + {sfParameterValue, soeOPTIONAL}, + }); } InnerObjectFormats const& diff --git a/src/libxrpl/protocol/STData.cpp b/src/libxrpl/protocol/STData.cpp new file mode 100644 index 0000000000..7411cb03e2 --- /dev/null +++ b/src/libxrpl/protocol/STData.cpp @@ -0,0 +1,1019 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +template +constexpr std:: + enable_if_t::value && std::is_signed::value, U> + to_unsigned(S value) +{ + if (value < 0 || std::numeric_limits::max() < value) + Throw("Value out of range"); + return static_cast(value); +} + +template +constexpr std:: + enable_if_t::value && std::is_unsigned::value, U1> + to_unsigned(U2 value) +{ + if (std::numeric_limits::max() < value) + Throw("Value out of range"); + return static_cast(value); +} + +// TODO +STData::STData(SField const& n) + : STBase(n), inner_type_(STI_NOTPRESENT), data_(STBase{}) +{ +} + +STData::STData(SField const& n, unsigned char v) + : STBase(n) + , inner_type_(STI_UINT8) + , data_(detail::STVar(detail::defaultObject, sfCloseResolution)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, std::uint16_t v) + : STBase(n) + , inner_type_(STI_UINT16) + , data_(detail::STVar(detail::defaultObject, sfSignerWeight)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, std::uint32_t v) + : STBase(n) + , inner_type_(STI_UINT32) + , data_(detail::STVar(detail::defaultObject, sfNetworkID)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, std::uint64_t v) + : STBase(n) + , inner_type_(STI_UINT64) + , data_(detail::STVar(detail::defaultObject, sfIndexNext)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, uint128 const& v) + : STBase(n) + , inner_type_(STI_UINT128) + , data_(detail::STVar(detail::defaultObject, sfEmailHash)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, uint160 const& v) + : STBase(n) + , inner_type_(STI_UINT160) + , data_(detail::STVar(detail::defaultObject, sfTakerPaysCurrency)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, uint192 const& v) + : STBase(n) + , inner_type_(STI_UINT192) + , data_(detail::STVar(detail::defaultObject, sfMPTokenIssuanceID)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, uint256 const& v) + : STBase(n) + , inner_type_(STI_UINT256) + , data_(detail::STVar(detail::defaultObject, sfLedgerHash)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, Blob const& v) + : STBase(n) + , inner_type_(STI_VL) + , data_(detail::STVar(detail::defaultObject, sfURI)) +{ + setFieldUsingSetValue(Buffer(v.data(), v.size())); +} + +STData::STData(SField const& n, Slice const& v) + : STBase(n) + , inner_type_(STI_VL) + , data_(detail::STVar(detail::defaultObject, sfURI)) +{ + setFieldUsingSetValue(Buffer(v.data(), v.size())); +} + +STData::STData(SField const& n, STAmount const& v) + : STBase(n) + , inner_type_(STI_AMOUNT) + , data_(detail::STVar(detail::defaultObject, sfAmount)) +{ + setFieldUsingAssignment(v); +} + +STData::STData(SField const& n, AccountID const& v) + : STBase(n) + , inner_type_(STI_ACCOUNT) + , data_(detail::STVar(detail::defaultObject, sfAccount)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, STIssue const& v) + : STBase(n) + , inner_type_(STI_ISSUE) + , data_(detail::STVar(detail::defaultObject, sfAsset)) +{ + setFieldUsingAssignment(v); +} + +STData::STData(SField const& n, STCurrency const& v) + : STBase(n) + , inner_type_(STI_CURRENCY) + , data_(detail::STVar(detail::defaultObject, sfBaseAsset)) +{ + setFieldUsingAssignment(v); +} + +STData::STData(SField const& n, STNumber const& v) + : STBase(n) + , inner_type_(STI_NUMBER) + , data_(detail::STVar(detail::defaultObject, sfNumber)) +{ + setFieldUsingAssignment(v); +} + +STData::STData(SerialIter& sit, SField const& name) + : STBase(name), data_(STBase{}) +{ + std::uint16_t stype = SerializedTypeID(sit.get16()); + inner_type_ = stype; + SerializedTypeID s = static_cast(stype); + switch (s) + { + case STI_UINT8: { + data_ = detail::STVar(sit, sfCloseResolution); + break; + } + case STI_UINT16: { + data_ = detail::STVar(sit, sfSignerWeight); + break; + } + case STI_UINT32: { + data_ = detail::STVar(sit, sfNetworkID); + break; + } + case STI_UINT64: { + data_ = detail::STVar(sit, sfIndexNext); + break; + } + case STI_UINT128: { + data_ = detail::STVar(sit, sfEmailHash); + break; + } + case STI_UINT160: { + data_ = detail::STVar(sit, sfTakerPaysCurrency); + break; + } + case STI_UINT192: { + data_ = detail::STVar(sit, sfMPTokenIssuanceID); + break; + } + case STI_UINT256: { + data_ = detail::STVar(sit, sfLedgerHash); + break; + } + case STI_VL: { + data_ = detail::STVar(sit, sfURI); + break; + } + case STI_AMOUNT: { + data_ = detail::STVar(sit, sfAmount); + break; + } + case STI_ACCOUNT: { + data_ = detail::STVar(sit, sfAccount); + break; + } + case STI_ISSUE: { + data_ = detail::STVar(sit, sfAsset); + break; + } + case STI_CURRENCY: { + data_ = detail::STVar(sit, sfBaseAsset); + break; + } + case STI_NUMBER: { + data_ = detail::STVar(sit, sfNumber); + break; + } + default: + Throw("STData: unknown type"); + } +} + +STBase* +STData::copy(std::size_t n, void* buf) const +{ + return emplace(n, buf, *this); +} + +STBase* +STData::move(std::size_t n, void* buf) +{ + return emplace(n, buf, std::move(*this)); +} + +std::size_t +STData::size() const +{ + switch (static_cast(inner_type_)) + { + case STI_UINT8: { + return sizeof(uint8_t); + } + case STI_UINT16: { + return sizeof(uint16_t); + } + case STI_UINT32: { + return sizeof(uint32_t); + } + case STI_UINT64: { + return sizeof(uint64_t); + } + case STI_UINT128: { + return uint128::size(); + } + case STI_UINT160: { + return uint160::size(); + } + case STI_UINT192: { + return uint192::size(); + } + case STI_UINT256: { + return uint256::size(); + } + case STI_VL: { + STBlob const& st_blob = data_.get().downcast(); + return st_blob.size(); + } + case STI_AMOUNT: { + // TODO: STAmount::size() + STAmount const& st_amt = data_.get().downcast(); + return st_amt.native() ? 8 : 48; + } + case STI_ACCOUNT: { + return uint160::size(); + } + case STI_ISSUE: { + // const STIssue& st_issue = data_.get().downcast(); + return 40; // 20 bytes for currency + 20 bytes for account + } + case STI_CURRENCY: { + // const STCurrency& st_currency = + // data_.get().downcast(); + return 20; // 20 bytes for currency + } + case STI_NUMBER: { + return sizeof(double); + } + default: + Throw("STData: unknown type"); + } +} + +SerializedTypeID +STData::getSType() const +{ + return STI_DATA; +} + +void +STData::add(Serializer& s) const +{ + s.add16(inner_type_); + + switch (static_cast(inner_type_)) + { + case STI_UINT8: { + STUInt8 const& st_uint8 = data_.get().downcast(); + st_uint8.add(s); + break; + } + case STI_UINT16: { + STUInt16 const& st_uint16 = data_.get().downcast(); + st_uint16.add(s); + break; + } + case STI_UINT32: { + STUInt32 const& st_uint32 = data_.get().downcast(); + st_uint32.add(s); + break; + } + case STI_UINT64: { + STUInt64 const& st_uint64 = data_.get().downcast(); + st_uint64.add(s); + break; + } + case STI_UINT128: { + STUInt128 const& st_uint128 = data_.get().downcast(); + st_uint128.add(s); + break; + } + case STI_UINT160: { + STUInt160 const& st_uint160 = data_.get().downcast(); + st_uint160.add(s); + break; + } + case STI_UINT192: { + STUInt192 const& st_uint192 = data_.get().downcast(); + st_uint192.add(s); + break; + } + case STI_UINT256: { + STUInt256 const& st_uint256 = data_.get().downcast(); + st_uint256.add(s); + break; + } + case STI_VL: { + STBlob const& st_blob = data_.get().downcast(); + st_blob.add(s); + break; + } + case STI_AMOUNT: { + STAmount const& st_amt = data_.get().downcast(); + st_amt.add(s); + break; + } + case STI_ACCOUNT: { + STAccount const& st_acc = data_.get().downcast(); + st_acc.add(s); + break; + } + case STI_ISSUE: { + STIssue const& st_issue = data_.get().downcast(); + st_issue.add(s); + break; + } + case STI_CURRENCY: { + STCurrency const& st_currency = data_.get().downcast(); + st_currency.add(s); + break; + } + case STI_NUMBER: { + STNumber const& st_number = data_.get().downcast(); + st_number.add(s); + break; + } + default: + Throw("STData: unknown type"); + } +} + +bool +STData::isEquivalent(STBase const& t) const +{ + auto const* const tPtr = dynamic_cast(&t); + return tPtr && (default_ == tPtr->default_) && + (inner_type_ == tPtr->inner_type_) && (data_ == tPtr->data_); +} + +bool +STData::isDefault() const +{ + return default_; +} + +std::string +STData::getInnerTypeString() const +{ + std::string inner_type_str = "Unknown"; + switch (static_cast(inner_type_)) + { + case STI_UINT8: + inner_type_str = "UINT8"; + break; + case STI_UINT16: + inner_type_str = "UINT16"; + break; + case STI_UINT32: + inner_type_str = "UINT32"; + break; + case STI_UINT64: + inner_type_str = "UINT64"; + break; + case STI_UINT128: + inner_type_str = "UINT128"; + break; + case STI_UINT160: + inner_type_str = "UINT160"; + break; + case STI_UINT192: + inner_type_str = "UINT192"; + break; + case STI_UINT256: + inner_type_str = "UINT256"; + break; + case STI_VL: + inner_type_str = "VL"; + break; + case STI_AMOUNT: + inner_type_str = "AMOUNT"; + break; + case STI_ACCOUNT: + inner_type_str = "ACCOUNT"; + break; + case STI_ISSUE: + inner_type_str = "ISSUE"; + break; + case STI_CURRENCY: + inner_type_str = "CURRENCY"; + break; + case STI_NUMBER: + inner_type_str = "NUMBER"; + break; + // Add other known types as needed + default: + inner_type_str = std::to_string(inner_type_); + } + + return inner_type_str; +} + +std::string +STData::getText() const +{ + std::string inner_type_str = getInnerTypeString(); + return "STData{InnerType: " + inner_type_str + + ", Data: " + data_.get().getText() + "}"; +} + +Json::Value +STData::getJson(JsonOptions options) const +{ + Json::Value ret(Json::objectValue); + ret[jss::type] = getInnerTypeString(); + ret[jss::value] = data_.get().getJson(options); + return ret; +} + +STBase* +STData::makeFieldPresent() +{ + STBase* f = &data_.get(); // getPIndex(index); + + if (f->getSType() != STI_NOTPRESENT) + return f; + + data_ = detail::STVar(detail::nonPresentObject, f->getFName()); + return &data_.get(); +} + +void +STData::setFieldU8(unsigned char v) +{ + inner_type_ = STI_UINT8; + data_ = detail::STVar(detail::defaultObject, sfCloseResolution); + setFieldUsingSetValue(v); +} + +void +STData::setFieldU16(std::uint16_t v) +{ + inner_type_ = STI_UINT16; + data_ = detail::STVar(detail::defaultObject, sfSignerWeight); + setFieldUsingSetValue(v); +} + +void +STData::setFieldU32(std::uint32_t v) +{ + inner_type_ = STI_UINT32; + data_ = detail::STVar(detail::defaultObject, sfNetworkID); + setFieldUsingSetValue(v); +} + +void +STData::setFieldU64(std::uint64_t v) +{ + inner_type_ = STI_UINT64; + data_ = detail::STVar(detail::defaultObject, sfIndexNext); + setFieldUsingSetValue(v); +} + +void +STData::setFieldH128(uint128 const& v) +{ + inner_type_ = STI_UINT128; + data_ = detail::STVar(detail::defaultObject, sfEmailHash); + setFieldUsingSetValue(v); +} + +void +STData::setFieldH160(uint160 const& v) +{ + inner_type_ = STI_UINT160; + data_ = detail::STVar(detail::defaultObject, sfTakerPaysCurrency); + setFieldUsingSetValue(v); +} + +void +STData::setFieldH192(uint192 const& v) +{ + inner_type_ = STI_UINT192; + data_ = detail::STVar(detail::defaultObject, sfMPTokenIssuanceID); + setFieldUsingSetValue(v); +} + +void +STData::setFieldH256(uint256 const& v) +{ + inner_type_ = STI_UINT256; + data_ = detail::STVar(detail::defaultObject, sfLedgerHash); + setFieldUsingSetValue(v); +} + +void +STData::setFieldVL(Blob const& v) +{ + inner_type_ = STI_VL; + data_ = detail::STVar(detail::defaultObject, sfData); + setFieldUsingSetValue(Buffer(v.data(), v.size())); +} + +void +STData::setFieldVL(Slice const& s) +{ + inner_type_ = STI_VL; + data_ = detail::STVar(detail::defaultObject, sfData); + setFieldUsingSetValue(Buffer(s.data(), s.size())); +} + +void +STData::setAccountID(AccountID const& v) +{ + inner_type_ = STI_ACCOUNT; + data_ = detail::STVar(detail::defaultObject, sfAccount); + setFieldUsingSetValue(v); +} + +void +STData::setFieldAmount(STAmount const& v) +{ + inner_type_ = STI_AMOUNT; + data_ = detail::STVar(detail::defaultObject, sfAmount); + setFieldUsingAssignment(v); +} + +void +STData::setIssue(STIssue const& v) +{ + inner_type_ = STI_ISSUE; + data_ = detail::STVar(detail::defaultObject, sfAsset); + setFieldUsingAssignment(v); +} + +void +STData::setCurrency(STCurrency const& v) +{ + inner_type_ = STI_CURRENCY; + data_ = detail::STVar(detail::defaultObject, sfBaseAsset); + setFieldUsingAssignment(v); +} + +void +STData::setFieldNumber(STNumber const& v) +{ + inner_type_ = STI_NUMBER; + data_ = detail::STVar(detail::defaultObject, sfNumber); + setFieldUsingAssignment(v); +} + +unsigned char +STData::getFieldU8() const +{ + return getFieldByValue(); +} + +std::uint16_t +STData::getFieldU16() const +{ + return getFieldByValue(); +} + +std::uint32_t +STData::getFieldU32() const +{ + return getFieldByValue(); +} + +std::uint64_t +STData::getFieldU64() const +{ + return getFieldByValue(); +} + +uint128 +STData::getFieldH128() const +{ + return getFieldByValue(); +} + +uint160 +STData::getFieldH160() const +{ + return getFieldByValue(); +} + +uint192 +STData::getFieldH192() const +{ + return getFieldByValue(); +} + +uint256 +STData::getFieldH256() const +{ + return getFieldByValue(); +} + +Blob +STData::getFieldVL() const +{ + STBlob empty; + STBlob const& b = getFieldByConstRef(empty); + return Blob(b.data(), b.data() + b.size()); +} + +AccountID +STData::getAccountID() const +{ + return getFieldByValue(); +} + +STAmount const& +STData::getFieldAmount() const +{ + static STAmount const empty{}; + return getFieldByConstRef(empty); +} + +STIssue +STData::getFieldIssue() const +{ + static STIssue const empty{}; + return getFieldByConstRef(empty); +} + +STCurrency +STData::getFieldCurrency() const +{ + static STCurrency const empty{}; + return getFieldByConstRef(empty); +} + +STNumber +STData::getFieldNumber() const +{ + static STNumber const empty{}; + return getFieldByConstRef(empty); +} + +STData +dataFromJson(SField const& field, Json::Value const& v) +{ + Json::Value type; + Json::Value value; + + if (!v.isObject()) + Throw("STData: expected object"); + + type = v[jss::type]; + value = v[jss::value]; + + if (type.isNull()) + Throw("STData: type is null"); + if (value.isNull()) + Throw("STData: value is null"); + + auto typeStr = type.asString(); + + if (typeStr == "UINT8") + { + STData data(field, static_cast(value.asUInt())); + return data; + } + else if (typeStr == "UINT16") + { + STData data(field, static_cast(value.asUInt())); + return data; + } + else if (typeStr == "UINT32") + { + try + { + if (value.isString()) + { + STData data( + field, + beast::lexicalCastThrow(value.asString())); + return data; + } + else if (value.isInt()) + { + STData data(field, to_unsigned(value.asInt())); + return data; + } + else if (value.isUInt()) + { + STData data(field, safe_cast(value.asUInt())); + return data; + } + else + { + Throw("bad type for UINT32"); + } + } + catch (std::exception const&) + { + Throw("invalid data for UINT32"); + } + } + else if (typeStr == "UINT64") + { + try + { + if (value.isString()) + { + auto const str = value.asString(); + + std::uint64_t val; + + bool const useBase10 = field.shouldMeta(SField::sMD_BaseTen); + + // if the field is amount, serialize as base 10 + auto [p, ec] = std::from_chars( + str.data(), + str.data() + str.size(), + val, + useBase10 ? 10 : 16); + + if (ec != std::errc() || (p != str.data() + str.size())) + Throw("STData: invalid UINT64 data"); + + STData data(field, val); + return data; + } + else if (value.isInt()) + { + STData data(field, to_unsigned(value.asInt())); + return data; + } + else if (value.isUInt()) + { + STData data(field, safe_cast(value.asUInt())); + return data; + } + else + { + Throw("STData: bad type for UINT64"); + } + } + catch (std::exception const&) + { + Throw("STData: invalid data for UINT64"); + } + } + else if (typeStr == "UINT128") + { + if (!value.isString()) + { + Throw("STData: expected string for UINT128"); + } + + uint128 num; + + if (auto const s = value.asString(); !num.parseHex(s)) + { + if (!s.empty()) + { + Throw("STData: invalid UINT128 data"); + } + + num.zero(); + } + + STData data(field, num); + return data; + } + else if (typeStr == "UINT192") + { + if (!value.isString()) + { + Throw("STData: expected string for UINT192"); + } + + uint192 num; + + if (auto const s = value.asString(); !num.parseHex(s)) + { + if (!s.empty()) + { + Throw("STData: invalid UINT192 data"); + } + + num.zero(); + } + + STData data(field, num); + return data; + } + else if (typeStr == "UINT160") + { + if (!value.isString()) + { + Throw("STData: expected string for UINT160"); + } + + uint160 num; + + if (auto const s = value.asString(); !num.parseHex(s)) + { + if (!s.empty()) + { + Throw("STData: invalid UINT160 data"); + } + + num.zero(); + } + + STData data(field, num); + return data; + } + else if (typeStr == "UINT256") + { + if (!value.isString()) + { + Throw("STData: expected string for UINT256"); + } + + uint256 num; + + if (auto const s = value.asString(); !num.parseHex(s)) + { + if (!s.empty()) + { + Throw("STData: invalid UINT256 data"); + } + + num.zero(); + } + STData data(field, num); + return data; + } + else if (typeStr == "VL") + { + if (!value.isString()) + { + Throw("STData: expected string for VL"); + } + + try + { + if (auto vBlob = strUnHex(value.asString())) + { + STData data(field, *vBlob); + return data; + } + else + { + Throw("invalid data"); + } + } + catch (std::exception const&) + { + Throw("STData: invalid data"); + } + } + else if (typeStr == "AMOUNT") + { + try + { + STData data(field, amountFromJson(field, value)); + return data; + } + catch (std::exception const&) + { + Throw("STData: invalid data for AMOUNT"); + } + } + else if (typeStr == "ACCOUNT") + { + if (!value.isString()) + { + Throw("STData: expected string for ACCOUNT"); + } + + std::string const strValue = value.asString(); + + try + { + if (AccountID account; account.parseHex(strValue)) + { + STData data(field, account); + return data; + } + + if (auto result = parseBase58(strValue)) + { + STData data(field, *result); + return data; + } + + Throw("STData: invalid data for ACCOUNT"); + } + catch (std::exception const&) + { + Throw("STData: invalid data for ACCOUNT"); + } + } + else if (typeStr == "ISSUE") + { + try + { + STData data(field, issueFromJson(field, value)); + return data; + } + catch (std::exception const&) + { + Throw("STData: invalid data for ISSUE"); + } + } + else if (typeStr == "CURRENCY") + { + try + { + STData data(field, currencyFromJson(field, value)); + return data; + } + catch (std::exception const&) + { + Throw("STData: invalid data for CURRENCY"); + } + } + else if (typeStr == "NUMBER") + { + if (!value.isString()) + { + Throw("STData: expected string for NUMBER"); + } + + STNumber number = numberFromJson(field, value); + STData data(field, number); + return data; + } + + // Handle unknown or unsupported type + Throw("STData: unsupported type string: " + typeStr); +} + +} // namespace xrpl diff --git a/src/libxrpl/protocol/STDataType.cpp b/src/libxrpl/protocol/STDataType.cpp new file mode 100644 index 0000000000..e5ece7d4f5 --- /dev/null +++ b/src/libxrpl/protocol/STDataType.cpp @@ -0,0 +1,273 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +// TODO +STDataType::STDataType(SField const& n) : STBase(n), inner_type_(STI_NOTPRESENT) +{ +} + +STDataType::STDataType(SField const& n, SerializedTypeID v) + : STBase(n), inner_type_(v), default_(false) +{ +} + +STDataType::STDataType(SerialIter& sit, SField const& name) + : STBase(name), inner_type_(STI_DATA), default_(false) +{ + std::uint16_t stype = SerializedTypeID(sit.get16()); + inner_type_ = stype; +} + +STBase* +STDataType::copy(std::size_t n, void* buf) const +{ + return emplace(n, buf, *this); +} + +STBase* +STDataType::move(std::size_t n, void* buf) +{ + return emplace(n, buf, std::move(*this)); +} + +SerializedTypeID +STDataType::getSType() const +{ + return STI_DATATYPE; +} + +void +STDataType::setInnerSType(SerializedTypeID v) +{ + inner_type_ = v; +} + +void +STDataType::add(Serializer& s) const +{ + s.add16(inner_type_); +} + +bool +STDataType::isEquivalent(STBase const& t) const +{ + auto const* const tPtr = dynamic_cast(&t); + return tPtr && (default_ == tPtr->default_) && + (inner_type_ == tPtr->inner_type_); +} + +bool +STDataType::isDefault() const +{ + return default_; +} + +std::string +STDataType::getInnerTypeString() const +{ + std::string inner_type_str = "Unknown"; + // Optionally, convert inner_type_ to its string representation if mappings + // exist + switch (static_cast(inner_type_)) + { + case STI_UINT8: + inner_type_str = "UINT8"; + break; + case STI_UINT16: + inner_type_str = "UINT16"; + break; + case STI_UINT32: + inner_type_str = "UINT32"; + break; + case STI_UINT64: + inner_type_str = "UINT64"; + break; + case STI_UINT128: + inner_type_str = "UINT128"; + break; + case STI_UINT160: + inner_type_str = "UINT160"; + break; + case STI_UINT192: + inner_type_str = "UINT192"; + break; + case STI_UINT256: + inner_type_str = "UINT256"; + break; + case STI_VL: + inner_type_str = "VL"; + break; + case STI_ACCOUNT: + inner_type_str = "ACCOUNT"; + break; + case STI_AMOUNT: + inner_type_str = "AMOUNT"; + break; + case STI_ISSUE: + inner_type_str = "ISSUE"; + break; + case STI_CURRENCY: + inner_type_str = "CURRENCY"; + break; + case STI_NUMBER: + inner_type_str = "NUMBER"; + break; + // Add other known types as needed + default: + inner_type_str = std::to_string(inner_type_); + } + + return inner_type_str; +} + +std::string +STDataType::getText() const +{ + std::string inner_type_str = getInnerTypeString(); + return "STDataType{InnerType: " + inner_type_str + "}"; +} + +Json::Value +STDataType::getJson(JsonOptions) const +{ + Json::Value ret(Json::objectValue); + ret[jss::type] = getInnerTypeString(); + return ret; +} + +STDataType +dataTypeFromJson(SField const& field, Json::Value const& v) +{ + SerializedTypeID typeId = STI_NOTPRESENT; + Json::Value type; + Json::Value value; + + if (!v.isObject()) + { + Throw("STData: expected object"); + } + + type = v[jss::type]; + auto typeStr = type.asString(); + + if (typeStr == "UINT8") + { + typeId = STI_UINT8; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT16") + { + typeId = STI_UINT16; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT32") + { + typeId = STI_UINT32; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT64") + { + typeId = STI_UINT64; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT128") + { + typeId = STI_UINT128; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT160") + { + typeId = STI_UINT160; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT192") + { + typeId = STI_UINT192; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT256") + { + typeId = STI_UINT256; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "VL") + { + typeId = STI_VL; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "ACCOUNT") + { + typeId = STI_ACCOUNT; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "AMOUNT") + { + typeId = STI_AMOUNT; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "ISSUE") + { + typeId = STI_ISSUE; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "CURRENCY") + { + typeId = STI_CURRENCY; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "NUMBER") + { + typeId = STI_NUMBER; + STDataType data(field, typeId); + return data; + } + + // Handle unknown or unsupported type + Throw("STData: unsupported type string: " + typeStr); +} + +} // namespace xrpl diff --git a/src/libxrpl/protocol/STJson.cpp b/src/libxrpl/protocol/STJson.cpp new file mode 100644 index 0000000000..4fc78b0d5b --- /dev/null +++ b/src/libxrpl/protocol/STJson.cpp @@ -0,0 +1,799 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +STJson::STJson(SField const& name) : STBase{name}, data_{Map{}} +{ +} + +STJson::STJson(SerialIter& sit, SField const& name) : STBase{name} +{ + if (sit.empty()) + { + data_ = Map{}; + return; + } + + int length = sit.getVLDataLength(); + if (length < 0) + Throw("Invalid STJson length"); + + if (length == 0) + { + data_ = Map{}; + return; + } + + // Read type byte + auto typeByte = sit.get8(); + JsonType type = static_cast(typeByte); + length--; // Account for type byte + + int initialBytesLeft = sit.getBytesLeft(); + + if (type == JsonType::Array) + { + Array array; + while (sit.getBytesLeft() > 0 && + (initialBytesLeft - sit.getBytesLeft()) < length) + { + auto valueVL = sit.getVL(); + if (!valueVL.empty()) + { + SerialIter valueSit(valueVL.data(), valueVL.size()); + auto value = makeValueFromVLWithType(valueSit); + array.push_back(std::move(value)); + } + else + { + array.push_back(nullptr); + } + } + data_ = std::move(array); + } + else // JsonType::Object + { + Map map; + while (sit.getBytesLeft() > 0 && + (initialBytesLeft - sit.getBytesLeft()) < length) + { + auto [key, value] = parsePair(sit); + map.emplace(std::move(key), std::move(value)); + } + data_ = std::move(map); + } + + int consumedBytes = initialBytesLeft - sit.getBytesLeft(); + if (consumedBytes != length) + Throw("STJson length mismatch"); +} + +STJson::STJson(Map&& map) : data_(std::move(map)) +{ +} + +STJson::STJson(Array&& array) : data_(std::move(array)) +{ +} + +SerializedTypeID +STJson::getSType() const +{ + return STI_JSON; +} + +bool +STJson::isArray() const +{ + return std::holds_alternative(data_); +} + +bool +STJson::isObject() const +{ + return std::holds_alternative(data_); +} + +STJson::JsonType +STJson::getType() const +{ + return isArray() ? JsonType::Array : JsonType::Object; +} + +int +STJson::getDepth() const +{ + if (isArray()) + { + auto const& array = std::get(data_); + for (auto const& value : array) + { + if (value) + { + auto nested = std::dynamic_pointer_cast(value); + if (nested) + return 1 + nested->getDepth(); + } + } + return 0; + } + else // isObject() + { + auto const& map = std::get(data_); + for (auto const& [key, value] : map) + { + if (value) + { + auto nested = std::dynamic_pointer_cast(value); + if (nested) + return 1 + nested->getDepth(); + } + } + return 0; + } +} + +void +STJson::validateDepth(Value const& value, int currentDepth) const +{ + if (!value) + return; + + auto nested = std::dynamic_pointer_cast(value); + if (!nested) + return; + + int valueDepth = nested->getDepth(); + if (currentDepth + valueDepth > 1) + Throw("STJson nesting depth exceeds maximum of 1"); +} + +void +STJson::setObjectField(Key const& key, Value const& value) +{ + if (!isObject()) + Throw( + "STJson::setObjectField called on non-object"); + validateDepth(value, 0); + std::get(data_)[key] = value; +} + +std::shared_ptr +STJson::fromBlob(void const* data, std::size_t size) +{ + SerialIter sit(static_cast(data), size); + return fromSerialIter(sit); +} + +std::shared_ptr +STJson::fromSerialIter(SerialIter& sit) +{ + if (sit.empty()) + return nullptr; + + int length = sit.getVLDataLength(); + if (length < 0) + Throw("Invalid STJson length"); + + if (length == 0) + return std::make_shared(Map{}); + + // Read type byte + auto typeByte = sit.get8(); + JsonType type = static_cast(typeByte); + length--; // Account for type byte + + int initialBytesLeft = sit.getBytesLeft(); + + if (type == JsonType::Array) + { + Array array; + while (sit.getBytesLeft() > 0 && + (initialBytesLeft - sit.getBytesLeft()) < length) + { + auto valueVL = sit.getVL(); + if (!valueVL.empty()) + { + SerialIter valueSit(valueVL.data(), valueVL.size()); + auto value = makeValueFromVLWithType(valueSit); + array.push_back(std::move(value)); + } + else + { + array.push_back(nullptr); + } + } + + int consumedBytes = initialBytesLeft - sit.getBytesLeft(); + if (consumedBytes != length) + Throw("STJson length mismatch"); + + return std::make_shared(std::move(array)); + } + else // JsonType::Object + { + Map map; + while (sit.getBytesLeft() > 0 && + (initialBytesLeft - sit.getBytesLeft()) < length) + { + auto [key, value] = parsePair(sit); + map.emplace(std::move(key), std::move(value)); + } + + int consumedBytes = initialBytesLeft - sit.getBytesLeft(); + if (consumedBytes != length) + Throw("STJson length mismatch"); + + return std::make_shared(std::move(map)); + } +} + +std::pair +STJson::parsePair(SerialIter& sit) +{ + auto keyBlob = sit.getVL(); + std::string key( + reinterpret_cast(keyBlob.data()), keyBlob.size()); + auto valueVL = sit.getVL(); + if (valueVL.empty()) + return {std::move(key), nullptr}; + + SerialIter valueSit(valueVL.data(), valueVL.size()); + auto value = makeValueFromVLWithType(valueSit); + + return {std::move(key), std::move(value)}; +} + +STJson::Array +STJson::parseArray(SerialIter& sit, int length) +{ + Array array; + int initialBytesLeft = sit.getBytesLeft(); + + while (sit.getBytesLeft() > 0 && + (initialBytesLeft - sit.getBytesLeft()) < length) + { + auto valueVL = sit.getVL(); + if (!valueVL.empty()) + { + SerialIter valueSit(valueVL.data(), valueVL.size()); + auto value = makeValueFromVLWithType(valueSit); + array.push_back(std::move(value)); + } + else + { + array.push_back(nullptr); + } + } + + return array; +} + +STJson::Value +STJson::makeValueFromVLWithType(SerialIter& sit) +{ + if (sit.getBytesLeft() == 0) + return nullptr; + + // Read SType marker (1 byte) + auto typeCode = sit.get8(); + SerializedTypeID stype = static_cast(typeCode); + + // Dispatch to correct SType + switch (stype) + { + case STI_UINT8: + return std::make_shared(sfCloseResolution, sit.get8()); + case STI_UINT16: + return std::make_shared(sfSignerWeight, sit.get16()); + case STI_UINT32: + return std::make_shared(sfNetworkID, sit.get32()); + case STI_UINT64: + return std::make_shared(sfIndexNext, sit.get64()); + case STI_UINT128: + return std::make_shared(sfEmailHash, sit.get128()); + case STI_UINT160: + return std::make_shared( + sfTakerPaysCurrency, sit.get160()); + case STI_UINT192: + return std::make_shared( + sfMPTokenIssuanceID, sit.get192()); + case STI_UINT256: + return std::make_shared(sfLedgerHash, sit.get256()); + case STI_VL: { + auto blob = sit.getVL(); + return std::make_shared(sfData, blob.data(), blob.size()); + } + case STI_ACCOUNT: + return std::make_shared(sit, sfAccount); + case STI_AMOUNT: + return std::make_shared(sit, sfAmount); + // case STI_NUMBER: + // return std::make_shared(sit, sfNumber); + case STI_ISSUE: + return std::make_shared(sit, sfAsset); + case STI_CURRENCY: + return std::make_shared(sit, sfBaseAsset); + case STI_JSON: + return std::make_shared(sit, sfContractJson); + case STI_OBJECT: + case STI_ARRAY: + case STI_PATHSET: + case STI_VECTOR256: + default: + // Unknown type, treat as blob + { + auto blob = sit.getSlice(sit.getBytesLeft()); + return std::make_shared( + sfData, blob.data(), blob.size()); + } + } +} + +std::optional +STJson::getObjectField(Key const& key) const +{ + if (!isObject()) + return std::nullopt; + + auto const& map = std::get(data_); + auto it = map.find(key); + if (it == map.end() || !it->second) + return std::nullopt; + return it->second; +} + +void +STJson::setNestedObjectField( + Key const& key, + Key const& nestedKey, + Value const& value) +{ + if (!isObject()) + Throw( + "STJson::setNestedObjectField called on non-object"); + + validateDepth(value, 1); // We're at depth 1 (nested) + + auto& map = std::get(data_); + auto it = map.find(key); + std::shared_ptr nested; + if (it == map.end() || !it->second) + { + // Create new nested STJson + nested = std::make_shared(); + map[key] = nested; + } + else + { + nested = std::dynamic_pointer_cast(it->second); + if (!nested) + { + // Overwrite with new STJson if not an STJson + nested = std::make_shared(); + map[key] = nested; + } + } + nested->setObjectField(nestedKey, value); +} + +std::optional +STJson::getNestedObjectField(Key const& key, Key const& nestedKey) const +{ + if (!isObject()) + return std::nullopt; + + auto const& map = std::get(data_); + auto it = map.find(key); + if (it == map.end() || !it->second) + return std::nullopt; + auto nested = std::dynamic_pointer_cast(it->second); + if (!nested) + return std::nullopt; + return nested->getObjectField(nestedKey); +} + +STJson::Map const& +STJson::getMap() const +{ + if (!isObject()) + Throw("STJson::getMap called on non-object"); + return std::get(data_); +} + +STJson::Array const& +STJson::getArray() const +{ + if (!isArray()) + Throw("STJson::getArray called on non-array"); + return std::get(data_); +} + +void +STJson::pushArrayElement(Value const& value) +{ + if (!isArray()) + Throw( + "STJson::pushArrayElement called on non-array"); + validateDepth(value, 0); + std::get(data_).push_back(value); +} + +std::optional +STJson::getArrayElement(size_t index) const +{ + if (!isArray()) + return std::nullopt; + + auto const& array = std::get(data_); + if (index >= array.size()) + return std::nullopt; + + return array[index]; +} + +void +STJson::setArrayElement(size_t index, Value const& value) +{ + if (!isArray()) + Throw( + "STJson::setArrayElement called on non-array"); + validateDepth(value, 0); + + auto& array = std::get(data_); + // Auto-resize with nulls if needed + if (index >= array.size()) + array.resize(index + 1, nullptr); + + array[index] = value; +} + +void +STJson::setArrayElementField(size_t index, Key const& key, Value const& value) +{ + if (!isArray()) + Throw( + "STJson::setArrayElementField called on non-array"); + + validateDepth(value, 1); // We're at depth 1 (inside array element) + + auto& array = std::get(data_); + // Auto-resize with nulls if needed + if (index >= array.size()) + array.resize(index + 1, nullptr); + + // Get or create STJson object at index + std::shared_ptr element; + if (!array[index]) + { + element = std::make_shared(Map{}); + array[index] = element; + } + else + { + element = std::dynamic_pointer_cast(array[index]); + if (!element) + { + // Replace with new STJson if not an STJson + element = std::make_shared(Map{}); + array[index] = element; + } + } + + element->setObjectField(key, value); +} + +std::optional +STJson::getArrayElementField(size_t index, Key const& key) const +{ + if (!isArray()) + return std::nullopt; + + auto const& array = std::get(data_); + if (index >= array.size()) + return std::nullopt; + + auto element = std::dynamic_pointer_cast(array[index]); + if (!element) + return std::nullopt; + + return element->getObjectField(key); +} + +size_t +STJson::arraySize() const +{ + if (!isArray()) + return 0; + return std::get(data_).size(); +} + +void +STJson::setNestedArrayElement(Key const& key, size_t index, Value const& value) +{ + if (!isObject()) + Throw( + "STJson::setNestedArrayElement called on non-object"); + + validateDepth(value, 1); // We're at depth 1 (nested array) + + auto& map = std::get(data_); + auto it = map.find(key); + std::shared_ptr arrayJson; + + if (it == map.end() || !it->second) + { + // Create new nested STJson array + arrayJson = std::make_shared(Array{}); + map[key] = arrayJson; + } + else + { + arrayJson = std::dynamic_pointer_cast(it->second); + if (!arrayJson) + { + // Replace with new STJson array if not an STJson + arrayJson = std::make_shared(Array{}); + map[key] = arrayJson; + } + else if (!arrayJson->isArray()) + { + // Replace with array if not an array + arrayJson = std::make_shared(Array{}); + map[key] = arrayJson; + } + } + + arrayJson->setArrayElement(index, value); +} + +void +STJson::setNestedArrayElementField( + Key const& key, + size_t index, + Key const& nestedKey, + Value const& value) +{ + if (!isObject()) + Throw( + "STJson::setNestedArrayElementField called on non-object"); + + validateDepth(value, 1); // We're at depth 1 (nested array element field - + // still counts as depth 1) + + auto& map = std::get(data_); + auto it = map.find(key); + std::shared_ptr arrayJson; + + if (it == map.end() || !it->second) + { + // Create new nested STJson array + arrayJson = std::make_shared(Array{}); + map[key] = arrayJson; + } + else + { + arrayJson = std::dynamic_pointer_cast(it->second); + if (!arrayJson) + { + // Replace with new STJson array if not an STJson + arrayJson = std::make_shared(Array{}); + map[key] = arrayJson; + } + else if (!arrayJson->isArray()) + { + // Replace with array if not an array + arrayJson = std::make_shared(Array{}); + map[key] = arrayJson; + } + } + + arrayJson->setArrayElementField(index, nestedKey, value); +} + +std::optional +STJson::getNestedArrayElement(Key const& key, size_t index) const +{ + if (!isObject()) + return std::nullopt; + + auto const& map = std::get(data_); + auto it = map.find(key); + if (it == map.end() || !it->second) + return std::nullopt; + + auto arrayJson = std::dynamic_pointer_cast(it->second); + if (!arrayJson || !arrayJson->isArray()) + return std::nullopt; + + return arrayJson->getArrayElement(index); +} + +std::optional +STJson::getNestedArrayElementField( + Key const& key, + size_t index, + Key const& nestedKey) const +{ + if (!isObject()) + return std::nullopt; + + auto const& map = std::get(data_); + auto it = map.find(key); + if (it == map.end() || !it->second) + return std::nullopt; + + auto arrayJson = std::dynamic_pointer_cast(it->second); + if (!arrayJson || !arrayJson->isArray()) + return std::nullopt; + + return arrayJson->getArrayElementField(index, nestedKey); +} + +void +STJson::addVLKey(Serializer& s, std::string const& str) +{ + s.addVL(str.data(), str.size()); +} + +void +STJson::addVLValue(Serializer& s, std::shared_ptr const& value) +{ + if (!value) + { + s.addVL(nullptr, 0); + return; + } + Serializer tmp; + tmp.add8(static_cast(value->getSType())); + value->add(tmp); + s.addVL(tmp.peekData().data(), tmp.peekData().size()); +} + +void +STJson::add(Serializer& s) const +{ + Serializer inner; + + // Add type byte + inner.add8(static_cast(getType())); + + if (isArray()) + { + auto const& array = std::get(data_); + for (auto const& value : array) + { + addVLValue(inner, value); + } + } + else // isObject() + { + auto const& map = std::get(data_); + for (auto const& [key, value] : map) + { + addVLKey(inner, key); + addVLValue(inner, value); + } + } + + s.addVL(inner.peekData().data(), inner.peekData().size()); +} + +Json::Value +STJson::getJson(JsonOptions options) const +{ + if (isArray()) + { + Json::Value arr(Json::arrayValue); + auto const& array = std::get(data_); + for (auto const& value : array) + { + if (value) + arr.append(value->getJson(options)); + else + arr.append(Json::nullValue); + } + return arr; + } + else // isObject() + { + Json::Value obj(Json::objectValue); + auto const& map = std::get(data_); + for (auto const& [key, value] : map) + { + if (value) + obj[key] = value->getJson(options); + else + obj[key] = Json::nullValue; + } + return obj; + } +} + +bool +STJson::isEquivalent(STBase const& t) const +{ + auto const* const tPtr = dynamic_cast(&t); + return tPtr && (data_ == tPtr->data_); +} + +bool +STJson::isDefault() const +{ + return default_; +} + +Blob +STJson::toBlob() const +{ + Serializer s; + add(s); + return s.peekData(); +} + +std::size_t +STJson::size() const +{ + Serializer s; + add(s); + return s.size(); +} + +void +STJson::setValue(STJson const& v) +{ + data_ = v.data_; +} + +STBase* +STJson::copy(std::size_t n, void* buf) const +{ + return emplace(n, buf, *this); +} + +STBase* +STJson::move(std::size_t n, void* buf) +{ + return emplace(n, buf, std::move(*this)); +} + +} // namespace xrpl diff --git a/src/libxrpl/protocol/STObject.cpp b/src/libxrpl/protocol/STObject.cpp index 6007753ed1..015dd19681 100644 --- a/src/libxrpl/protocol/STObject.cpp +++ b/src/libxrpl/protocol/STObject.cpp @@ -19,8 +19,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -250,6 +253,8 @@ STObject::set(SerialIter& sit, int depth) { JLOG(debugLog().error()) << "Unknown field: field_type=" << type << ", field_name=" << field; + std::cout << "Unknown field: field_type=" << type + << ", field_name=" << field << std::endl; Throw("Unknown field"); } @@ -640,6 +645,20 @@ STObject::getAccountID(SField const& field) const return getFieldByValue(field); } +STData +STObject::getFieldData(SField const& field) const +{ + static STData const empty{field}; + return getFieldByConstRef(field, empty); +} + +STDataType +STObject::getFieldDataType(SField const& field) const +{ + static STDataType const empty{field}; + return getFieldByConstRef(field, empty); +} + Blob STObject::getFieldVL(SField const& field) const { @@ -700,6 +719,13 @@ STObject::getFieldNumber(SField const& field) const return getFieldByConstRef(field, empty); } +STJson const& +STObject::getFieldJson(SField const& field) const +{ + static STJson const empty{field}; + return getFieldByConstRef(field, empty); +} + void STObject::set(std::unique_ptr v) { @@ -722,6 +748,69 @@ STObject::set(STBase&& v) } } +void +STObject::addFieldFromSlice(SField const& sfield, Slice const& data) +{ + SerialIter sit(data.data(), data.size()); + std::unique_ptr element; + + switch (sfield.fieldType) + { + case STI_AMOUNT: + element = std::make_unique(sit, sfield); + break; + case STI_ACCOUNT: + element = std::make_unique(sit, sfield); + break; + case STI_UINT8: + element = std::make_unique(sit, sfield); + break; + case STI_UINT16: + element = std::make_unique(sit, sfield); + break; + case STI_UINT32: + element = std::make_unique(sit, sfield); + break; + case STI_UINT64: + element = std::make_unique(sit, sfield); + break; + case STI_UINT128: + element = std::make_unique(sit, sfield); + break; + case STI_UINT160: + element = std::make_unique(sit, sfield); + break; + case STI_UINT256: + element = std::make_unique(sit, sfield); + break; + case STI_VECTOR256: + element = std::make_unique(sit, sfield); + break; + case STI_VL: + element = std::make_unique(sit, sfield); + break; + case STI_CURRENCY: + element = std::make_unique(sit, sfield); + break; + case STI_ISSUE: + element = std::make_unique(sit, sfield); + break; + case STI_PATHSET: + element = std::make_unique(sit, sfield); + break; + case STI_ARRAY: + element = std::make_unique(sit, sfield); + break; + case STI_OBJECT: + element = std::make_unique(sit, sfield, 0); + break; + default: + throw std::runtime_error("Unsupported SField type"); + } + + this->set(std::move(element)); +} + void STObject::setFieldU8(SField const& field, unsigned char v) { @@ -830,6 +919,12 @@ STObject::setFieldObject(SField const& field, STObject const& v) setFieldUsingAssignment(field, v); } +void +STObject::setFieldJson(SField const& field, STJson const& v) +{ + setFieldUsingAssignment(field, v); +} + Json::Value STObject::getJson(JsonOptions options) const { diff --git a/src/libxrpl/protocol/STParsedJSON.cpp b/src/libxrpl/protocol/STParsedJSON.cpp index 34c8b70e45..9ef6615b4d 100644 --- a/src/libxrpl/protocol/STParsedJSON.cpp +++ b/src/libxrpl/protocol/STParsedJSON.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -953,6 +955,53 @@ parseLeaf( } break; + case STI_DATA: { + try + { + ret = detail::make_stvar(dataFromJson(field, value)); + } + catch (std::exception const&) + { + std::cout << "STI_DATA failed for field: " << fieldName + << " in object: " << json_name << "\n"; + error = invalid_data(json_name, fieldName); + return ret; + } + + break; + } + case STI_DATATYPE: { + try + { + ret = detail::make_stvar( + dataTypeFromJson(field, value)); + } + catch (std::exception const&) + { + std::cout << "STI_DATATYPE failed for field: " << fieldName + << " in object: " << json_name << "\n"; + error = invalid_data(json_name, fieldName); + return ret; + } + break; + } + + case STI_JSON: + Throw("STI_JSON is not supported"); + // try + // { + // ret = detail::make_stvar( + // dataTypeFromJson(field, value)); + // } + // catch (std::exception const&) + // { + // std::cout << "STI_DATATYPE failed for field: " << fieldName + // << " in object: " << json_name << "\n"; + // error = invalid_data(json_name, fieldName); + // return ret; + // } + break; + default: error = bad_type(json_name, fieldName); return ret; diff --git a/src/libxrpl/protocol/STValidation.cpp b/src/libxrpl/protocol/STValidation.cpp index f6f89d43e9..7362a45d84 100644 --- a/src/libxrpl/protocol/STValidation.cpp +++ b/src/libxrpl/protocol/STValidation.cpp @@ -58,6 +58,10 @@ STValidation::validationFormat() {sfBaseFeeDrops, soeOPTIONAL}, {sfReserveBaseDrops, soeOPTIONAL}, {sfReserveIncrementDrops, soeOPTIONAL}, + // featureSmartEscrow + {sfExtensionComputeLimit, soeOPTIONAL}, + {sfExtensionSizeLimit, soeOPTIONAL}, + {sfGasPrice, soeOPTIONAL}, }; // clang-format on diff --git a/src/libxrpl/protocol/STVar.cpp b/src/libxrpl/protocol/STVar.cpp index e0df5d51a9..46be7e90f5 100644 --- a/src/libxrpl/protocol/STVar.cpp +++ b/src/libxrpl/protocol/STVar.cpp @@ -8,8 +8,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -219,6 +222,15 @@ STVar::constructST(SerializedTypeID id, int depth, Args&&... args) case STI_CURRENCY: construct(std::forward(args)...); return; + case STI_DATA: + construct(std::forward(args)...); + return; + case STI_DATATYPE: + construct(std::forward(args)...); + return; + case STI_JSON: + construct(std::forward(args)...); + return; default: Throw("Unknown object type"); } diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index 6871cf5700..0289f6306f 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -108,6 +108,8 @@ transResults() MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."), MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."), MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."), + MAKE_ERROR(tecWASM_REJECTED, "The custom WASM code that was run rejected your transaction."), + MAKE_ERROR(tecINVALID_PARAMETERS, "Contract parameters do not match the expected ABI."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), @@ -131,6 +133,8 @@ transResults() MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."), MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."), MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."), + MAKE_ERROR(tefNO_WASM, "There is no WASM code to run, but a WASM-specific field was included."), + MAKE_ERROR(tefWASM_FIELD_NOT_INCLUDED, "WASM code requires a field to be included that was not included."), MAKE_ERROR(telLOCAL_ERROR, "Local failure."), MAKE_ERROR(telBAD_DOMAIN, "Domain too long."), @@ -200,6 +204,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."), diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index 12f92615cd..f61f48f578 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -36,8 +36,8 @@ TxFormats::TxFormats() #undef TRANSACTION #define UNWRAP(...) __VA_ARGS__ -#define TRANSACTION( \ - tag, value, name, delegatable, amendment, privileges, fields) \ +#define TRANSACTION( \ + tag, value, name, delegatable, amendment, privileges, emitable, fields) \ add(jss::name, tag, UNWRAP fields, commonFields); #include diff --git a/src/libxrpl/protocol/TxMeta.cpp b/src/libxrpl/protocol/TxMeta.cpp index f0077c7f92..b4d58aa872 100644 --- a/src/libxrpl/protocol/TxMeta.cpp +++ b/src/libxrpl/protocol/TxMeta.cpp @@ -207,8 +207,14 @@ TxMeta::getAsObject() const if (deliveredAmount_.has_value()) metaData.setFieldAmount(sfDeliveredAmount, *deliveredAmount_); - if (parentBatchID_.has_value()) - metaData.setFieldH256(sfParentBatchID, *parentBatchID_); + if (parentBatchId_.has_value()) + metaData.setFieldH256(sfParentBatchID, *parentBatchId_); + + if (gasUsed_.has_value()) + metaData.setFieldU32(sfGasUsed, *gasUsed_); + + if (wasmReturnCode_.has_value()) + metaData.setFieldI32(sfWasmReturnCode, *wasmReturnCode_); return metaData; } diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 8d64dfed2a..3838162aa3 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -7466,7 +7466,7 @@ private: using namespace test::jtx; auto const testCase = [&](std::string suffix, FeatureBitset features) { - testcase("Fail pseudo-account allocation " + suffix); + testcase("Pseudo-account allocation failure " + suffix); std::string logs; Env env{*this, features, std::make_unique(&logs)}; env.fund(XRP(30'000), gw, alice); diff --git a/src/test/app/ContractHostFuncImpl_test.cpp b/src/test/app/ContractHostFuncImpl_test.cpp new file mode 100644 index 0000000000..6c788ccef9 --- /dev/null +++ b/src/test/app/ContractHostFuncImpl_test.cpp @@ -0,0 +1,1466 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { +namespace test { + +static ApplyContext +createApplyContext( + test::jtx::Env& env, + OpenView& ov, + STTx const& tx = STTx(ttCONTRACT_CALL, [](STObject&) {})) +{ + ApplyContext ac{ + env.app(), + ov, + tx, + tesSUCCESS, + env.current()->fees().base, + tapNONE, + env.journal}; + return ac; +} + +struct ContractHostFuncImpl_test : public beast::unit_test::suite +{ + ContractContext + createContractContext( + ApplyContext& ac, + jtx::Account const& contract, + jtx::Account const& otxn, + uint256 const& contractHash = uint256{1}) + { + using namespace jtx; + xrpl::ContractDataMap dataMap; + xrpl::ContractEventMap eventMap; + std::vector instanceParameters; + std::vector functionParameters; + + uint256 const& txId = uint256{2}; + + auto const nextSequence = ac.view() + .read(keylet::account(contract.id())) + ->getFieldU32(sfSequence); + + auto const k = keylet::contract(contractHash, otxn, 0); + return ContractContext{ + .applyCtx = ac, + .instanceParameters = instanceParameters, + .functionParameters = functionParameters, + .built_txns = {}, + .expected_etxn_count = 0, + .generation = 0, + .burden = 0, + .result = + { + .contractHash = contractHash, + .contractKeylet = k, + .contractSourceKeylet = k, + .contractAccountKeylet = k, + .contractAccount = contract.id(), + .nextSequence = nextSequence, + .otxnAccount = otxn.id(), + .otxnId = txId, + .exitCode = -1, + .dataMap = dataMap, + .eventMap = eventMap, + .changedDataCount = 0, + }, + }; + } + + // Helper function to create STJson::Value from different types + STJson::Value + createJsonValue( + SerializedTypeID type, + std::function addData) + { + Serializer s; + s.add8(static_cast(type)); + addData(s); + + SerialIter sit(s.peekData().data(), s.peekData().size()); + return STJson::makeValueFromVLWithType(sit); + } + + void + testInstanceParam() + { + testcase("instanceParam"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + // Add test instance parameters for all supported types + + // UINT8 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint8_t{0xFF})}); + + // UINT16 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint16_t{0xFFFF})}); + + // UINT32 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint32_t{0xFFFFFFFF})}); + + // UINT64 + contractCtx.instanceParameters.push_back(ParameterValueVec{ + STData(sfParameterValue, uint64_t{0x8000000000000000})}); + + // UINT128 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint128{1})}); + + // UINT160 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint160{1})}); + + // UINT192 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint192{1})}); + + // UINT256 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint256{1})}); + + // VL (Variable Length) + contractCtx.instanceParameters.push_back(ParameterValueVec{ + STData(sfParameterValue, Blob{0x01, 0x02, 0x03, 0x04, 0x05})}); + + // ACCOUNT + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, alice.id())}); + + // AMOUNT (XRP) + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, STAmount{100000})}); + + // AMOUNT (IOU) + contractCtx.instanceParameters.push_back(ParameterValueVec{STData( + sfParameterValue, + STAmount{Issue{Currency{1}, AccountID{2}}, 1000})}); + + // AMOUNT (MPT) + MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}}; + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, STAmount{mpt, 1000})}); + + // NUMBER + contractCtx.instanceParameters.push_back(ParameterValueVec{ + STData(sfParameterValue, numberFromJson(sfNumber, "42.5"))}); + + // ISSUE + // STIssue{sfAsset2, MPTIssue{mptId} + auto const iouAsset = env.master["USD"]; + contractCtx.instanceParameters.push_back(ParameterValueVec{ + STData(sfParameterValue, STIssue{sfAsset, iouAsset.issue()})}); + + // CURRENCY + contractCtx.instanceParameters.push_back(ParameterValueVec{STData( + sfParameterValue, + STCurrency{sfBaseAsset, iouAsset.issue().currency})}); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Test UINT8 + { + auto result = cfs.instanceParam(0, STI_UINT8); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 1); + BEAST_EXPECT(bytes[0] == 0xFF); + } + } + + // Test UINT16 + { + auto result = cfs.instanceParam(1, STI_UINT16); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 2); + BEAST_EXPECT(bytes[0] == 0xFF); + BEAST_EXPECT(bytes[1] == 0xFF); + } + } + + // Test UINT32 + { + auto result = cfs.instanceParam(2, STI_UINT32); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 4); + BEAST_EXPECT(bytes[0] == 0xFF); + BEAST_EXPECT(bytes[1] == 0xFF); + BEAST_EXPECT(bytes[2] == 0xFF); + BEAST_EXPECT(bytes[3] == 0xFF); + } + } + + // Test UINT64 + { + auto result = cfs.instanceParam(3, STI_UINT64); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 8); + BEAST_EXPECT(bytes[0] == 0x00); + BEAST_EXPECT(bytes[7] == 0x80); + } + } + + // Test UINT128 + { + auto result = cfs.instanceParam(4, STI_UINT128); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == sizeof(uint128)); + BEAST_EXPECT(bytes[15] == 0x01); + } + } + + // Test UINT160 + { + auto result = cfs.instanceParam(5, STI_UINT160); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == sizeof(uint160)); + BEAST_EXPECT(bytes[19] == 0x01); + } + } + + // Test UINT192 + { + auto result = cfs.instanceParam(6, STI_UINT192); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == sizeof(uint192)); + BEAST_EXPECT(bytes[23] == 0x01); + } + } + + // Test UINT256 + { + auto result = cfs.instanceParam(7, STI_UINT256); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == sizeof(uint256)); + BEAST_EXPECT(bytes[31] == 0x01); + } + } + + // Test VL + { + auto result = cfs.instanceParam(8, STI_VL); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 5); + BEAST_EXPECT(bytes[0] == 0x01); + BEAST_EXPECT(bytes[4] == 0x05); + } + } + + // Test ACCOUNT + { + auto result = cfs.instanceParam(9, STI_ACCOUNT); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 20); + } + } + + // Test AMOUNT (XRP) + { + auto result = cfs.instanceParam(10, STI_AMOUNT); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 8); // Native amount + } + } + + // Test AMOUNT (IOU) + { + auto result = cfs.instanceParam(11, STI_AMOUNT); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 48); // IOU amount + } + } + + // Test AMOUNT (MPT) + { + auto result = cfs.instanceParam(12, STI_AMOUNT); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 33); // MPT amount + } + } + + // Test NUMBER + { + auto result = cfs.instanceParam(13, STI_NUMBER); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 12); + } + } + + // Test ISSUE + { + auto result = cfs.instanceParam(14, STI_ISSUE); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 40); + } + } + + // Test CURRENCY + { + auto result = cfs.instanceParam(15, STI_CURRENCY); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 20); + } + } + + // Test index out of bounds + { + auto result = cfs.instanceParam(16, STI_UINT32); + BEAST_EXPECT(!result.has_value()); + if (!result.has_value()) + BEAST_EXPECT( + result.error() == HostFunctionError::INDEX_OUT_OF_BOUNDS); + } + + // Test type mismatch + { + auto result = cfs.instanceParam(0, STI_UINT64); // Index 0 is UINT8 + BEAST_EXPECT(!result.has_value()); + if (!result.has_value()) + BEAST_EXPECT( + result.error() == HostFunctionError::INVALID_PARAMS); + } + + // Test unsupported types + { + auto result = cfs.instanceParam(2, STI_PATHSET); + BEAST_EXPECT(!result.has_value()); + if (!result.has_value()) + BEAST_EXPECT( + result.error() == HostFunctionError::INVALID_PARAMS); + } + } + + void + testFunctionParam() + { + testcase("functionParam"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + // Add test function parameters (same as instance parameters for + // testing) [Similar parameter setup as instanceParam test...] + + ContractHostFunctionsImpl cfs(contractCtx); + + // [Similar tests as instanceParam but using functionParam method...] + } + + void + testContractDataFromKey() + { + testcase("contractDataFromKey"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const bob("bob"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, bob, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Test setDataObjectField - string value + { + // Create a properly formatted STJson::Value for a VL (string) + auto value = createJsonValue(STI_VL, [](Serializer& s) { + s.addVL(Blob{0x61, 0x62, 0x63}); // "abc" + }); + + auto setResult = cfs.setDataObjectField(alice.id(), "name", value); + BEAST_EXPECT(!setResult.has_value()); + if (!setResult.has_value()) + BEAST_EXPECT(setResult.error() == HostFunctionError::INTERNAL); + + // Verify data was cached and can be retrieved + auto getResult = cfs.getDataObjectField(alice.id(), "name"); + BEAST_EXPECT(getResult.has_value()); + if (getResult.has_value()) + { + auto& bytes = getResult.value(); + BEAST_EXPECT(bytes.size() > 0); + } + } + + // Test setDataObjectField - numeric value (UINT32) + { + auto value = + createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(30); }); + + auto setResult = cfs.setDataObjectField(alice.id(), "age", value); + BEAST_EXPECT(!setResult.has_value()); + if (!setResult.has_value()) + BEAST_EXPECT(setResult.error() == HostFunctionError::INTERNAL); + + auto getResult = cfs.getDataObjectField(alice.id(), "age"); + BEAST_EXPECT(getResult.has_value()); + } + + // Test setDataObjectField - UINT8 value (for boolean-like) + { + auto value = createJsonValue(STI_UINT8, [](Serializer& s) { + s.add8(1); // true + }); + + auto setResult = + cfs.setDataObjectField(alice.id(), "verified", value); + BEAST_EXPECT(!setResult.has_value()); + if (!setResult.has_value()) + BEAST_EXPECT(setResult.error() == HostFunctionError::INTERNAL); + + auto getResult = cfs.getDataObjectField(alice.id(), "verified"); + BEAST_EXPECT(getResult.has_value()); + } + + // Test getting non-existent key + { + auto getResult = cfs.getDataObjectField(alice.id(), "nonexistent"); + BEAST_EXPECT(!getResult.has_value()); + if (!getResult.has_value()) + BEAST_EXPECT( + getResult.error() == HostFunctionError::INVALID_FIELD); + } + + // Test updating existing key + { + auto value1 = createJsonValue( + STI_UINT32, [](Serializer& s) { s.add32(100); }); + auto setResult1 = + cfs.setDataObjectField(bob.id(), "balance", value1); + BEAST_EXPECT(!setResult1.has_value()); + + auto value2 = createJsonValue( + STI_UINT32, [](Serializer& s) { s.add32(200); }); + auto setResult2 = + cfs.setDataObjectField(bob.id(), "balance", value2); + BEAST_EXPECT(!setResult2.has_value()); + + auto getResult = cfs.getDataObjectField(bob.id(), "balance"); + BEAST_EXPECT(getResult.has_value()); + } + + // Test multiple keys for same account + { + auto value1 = createJsonValue(STI_VL, [](Serializer& s) { + Blob data = {'v', 'a', 'l', 'u', 'e', '1'}; + s.addVL(data); + }); + auto setResult1 = + cfs.setDataObjectField(alice.id(), "field1", value1); + BEAST_EXPECT(!setResult1.has_value()); + + auto value2 = createJsonValue(STI_VL, [](Serializer& s) { + Blob data = {'v', 'a', 'l', 'u', 'e', '2'}; + s.addVL(data); + }); + auto setResult2 = + cfs.setDataObjectField(alice.id(), "field2", value2); + BEAST_EXPECT(!setResult2.has_value()); + + auto value3 = createJsonValue( + STI_UINT32, [](Serializer& s) { s.add32(123); }); + auto setResult3 = + cfs.setDataObjectField(alice.id(), "field3", value3); + BEAST_EXPECT(!setResult3.has_value()); + + // Verify all keys exist + auto getResult1 = cfs.getDataObjectField(alice.id(), "field1"); + BEAST_EXPECT(getResult1.has_value()); + + auto getResult2 = cfs.getDataObjectField(alice.id(), "field2"); + BEAST_EXPECT(getResult2.has_value()); + + auto getResult3 = cfs.getDataObjectField(alice.id(), "field3"); + BEAST_EXPECT(getResult3.has_value()); + } + } + + void + testNestedContractDataFromKey() + { + testcase("nestedContractDataFromKey"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const bob("bob"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, bob, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Test setDataNestedObjectField + { + auto value = createJsonValue(STI_VL, [](Serializer& s) { + Blob data = {'A', 'l', 'i', 'c', 'e'}; + s.addVL(data); + }); + + auto setResult = cfs.setDataNestedObjectField( + alice.id(), "profile", "firstName", value); + BEAST_EXPECT(!setResult.has_value()); + if (!setResult.has_value()) + BEAST_EXPECT(setResult.error() == HostFunctionError::INTERNAL); + + // Add more nested fields + auto value2 = createJsonValue(STI_VL, [](Serializer& s) { + Blob data = {'S', 'm', 'i', 't', 'h'}; + s.addVL(data); + }); + auto setResult2 = cfs.setDataNestedObjectField( + alice.id(), "profile", "lastName", value2); + BEAST_EXPECT(!setResult2.has_value()); + + auto value3 = + createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(25); }); + auto setResult3 = cfs.setDataNestedObjectField( + alice.id(), "profile", "age", value3); + BEAST_EXPECT(!setResult3.has_value()); + + // Retrieve nested fields + auto getResult1 = cfs.getDataNestedObjectField( + alice.id(), "profile", "firstName"); + BEAST_EXPECT(getResult1.has_value()); + + auto getResult2 = + cfs.getDataNestedObjectField(alice.id(), "profile", "lastName"); + BEAST_EXPECT(getResult2.has_value()); + + auto getResult3 = + cfs.getDataNestedObjectField(alice.id(), "profile", "age"); + BEAST_EXPECT(getResult3.has_value()); + } + + // Test nested objects with different parent keys + { + auto value1 = createJsonValue(STI_VL, [](Serializer& s) { + Blob data = {'d', 'a', 'r', 'k'}; + s.addVL(data); + }); + auto setResult1 = cfs.setDataNestedObjectField( + alice.id(), "settings", "theme", value1); + BEAST_EXPECT(!setResult1.has_value()); + + auto value2 = createJsonValue(STI_UINT8, [](Serializer& s) { + s.add8(1); // true + }); + auto setResult2 = cfs.setDataNestedObjectField( + alice.id(), "settings", "notifications", value2); + BEAST_EXPECT(!setResult2.has_value()); + + auto value3 = createJsonValue(STI_VL, [](Serializer& s) { + Blob data = {'e', 'n'}; + s.addVL(data); + }); + auto setResult3 = cfs.setDataNestedObjectField( + alice.id(), "preferences", "language", value3); + BEAST_EXPECT(!setResult3.has_value()); + + // Verify nested data retrieval + auto getResult1 = + cfs.getDataNestedObjectField(alice.id(), "settings", "theme"); + BEAST_EXPECT(getResult1.has_value()); + + auto getResult2 = cfs.getDataNestedObjectField( + alice.id(), "settings", "notifications"); + BEAST_EXPECT(getResult2.has_value()); + + auto getResult3 = cfs.getDataNestedObjectField( + alice.id(), "preferences", "language"); + BEAST_EXPECT(getResult3.has_value()); + } + + // Test getting non-existent nested key + { + auto getResult = + cfs.getDataNestedObjectField(alice.id(), "nonexistent", "key"); + BEAST_EXPECT(!getResult.has_value()); + if (!getResult.has_value()) + BEAST_EXPECT( + getResult.error() == HostFunctionError::INVALID_FIELD); + + auto getResult2 = cfs.getDataNestedObjectField( + alice.id(), "profile", "nonexistent"); + BEAST_EXPECT(!getResult2.has_value()); + if (!getResult2.has_value()) + BEAST_EXPECT( + getResult2.error() == HostFunctionError::INVALID_FIELD); + } + + // Test updating existing nested key + { + auto value1 = createJsonValue( + STI_UINT32, [](Serializer& s) { s.add32(100); }); + auto setResult1 = cfs.setDataNestedObjectField( + bob.id(), "stats", "score", value1); + BEAST_EXPECT(!setResult1.has_value()); + + auto value2 = createJsonValue( + STI_UINT32, [](Serializer& s) { s.add32(150); }); + auto setResult2 = cfs.setDataNestedObjectField( + bob.id(), "stats", "score", value2); + BEAST_EXPECT(!setResult2.has_value()); + + auto getResult = + cfs.getDataNestedObjectField(bob.id(), "stats", "score"); + BEAST_EXPECT(getResult.has_value()); + } + } + + void + testNestedContractDataFromArrayKey() + { + testcase("nestedContractDataFromArrayKey"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const bob("bob"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, bob, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Test setDataNestedObjectField + { + auto value = createJsonValue(STI_VL, [](Serializer& s) { + Blob data = {'A', 'l', 'i', 'c', 'e'}; + s.addVL(data); + }); + + auto setResult = cfs.setDataNestedObjectField( + alice.id(), "profile", "firstName", value); + BEAST_EXPECT(!setResult.has_value()); + if (!setResult.has_value()) + BEAST_EXPECT(setResult.error() == HostFunctionError::INTERNAL); + + // Add more nested fields + auto value2 = createJsonValue(STI_VL, [](Serializer& s) { + Blob data = {'S', 'm', 'i', 't', 'h'}; + s.addVL(data); + }); + auto setResult2 = cfs.setDataNestedObjectField( + alice.id(), "profile", "lastName", value2); + BEAST_EXPECT(!setResult2.has_value()); + + auto value3 = + createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(25); }); + auto setResult3 = cfs.setDataNestedObjectField( + alice.id(), "profile", "age", value3); + BEAST_EXPECT(!setResult3.has_value()); + + // Retrieve nested fields + auto getResult1 = cfs.getDataNestedObjectField( + alice.id(), "profile", "firstName"); + BEAST_EXPECT(getResult1.has_value()); + + auto getResult2 = + cfs.getDataNestedObjectField(alice.id(), "profile", "lastName"); + BEAST_EXPECT(getResult2.has_value()); + + auto getResult3 = + cfs.getDataNestedObjectField(alice.id(), "profile", "age"); + BEAST_EXPECT(getResult3.has_value()); + } + + // Test nested objects with different parent keys + { + auto value1 = createJsonValue(STI_VL, [](Serializer& s) { + Blob data = {'d', 'a', 'r', 'k'}; + s.addVL(data); + }); + auto setResult1 = cfs.setDataNestedObjectField( + alice.id(), "settings", "theme", value1); + BEAST_EXPECT(!setResult1.has_value()); + + auto value2 = createJsonValue(STI_UINT8, [](Serializer& s) { + s.add8(1); // true + }); + auto setResult2 = cfs.setDataNestedObjectField( + alice.id(), "settings", "notifications", value2); + BEAST_EXPECT(!setResult2.has_value()); + + auto value3 = createJsonValue(STI_VL, [](Serializer& s) { + Blob data = {'e', 'n'}; + s.addVL(data); + }); + auto setResult3 = cfs.setDataNestedObjectField( + alice.id(), "preferences", "language", value3); + BEAST_EXPECT(!setResult3.has_value()); + + // Verify nested data retrieval + auto getResult1 = + cfs.getDataNestedObjectField(alice.id(), "settings", "theme"); + BEAST_EXPECT(getResult1.has_value()); + + auto getResult2 = cfs.getDataNestedObjectField( + alice.id(), "settings", "notifications"); + BEAST_EXPECT(getResult2.has_value()); + + auto getResult3 = cfs.getDataNestedObjectField( + alice.id(), "preferences", "language"); + BEAST_EXPECT(getResult3.has_value()); + } + + // Test getting non-existent nested key + { + auto getResult = + cfs.getDataNestedObjectField(alice.id(), "nonexistent", "key"); + BEAST_EXPECT(!getResult.has_value()); + if (!getResult.has_value()) + BEAST_EXPECT( + getResult.error() == HostFunctionError::INVALID_FIELD); + + auto getResult2 = cfs.getDataNestedObjectField( + alice.id(), "profile", "nonexistent"); + BEAST_EXPECT(!getResult2.has_value()); + if (!getResult2.has_value()) + BEAST_EXPECT( + getResult2.error() == HostFunctionError::INVALID_FIELD); + } + + // Test updating existing nested key + { + auto value1 = createJsonValue( + STI_UINT32, [](Serializer& s) { s.add32(100); }); + auto setResult1 = cfs.setDataNestedObjectField( + bob.id(), "stats", "score", value1); + BEAST_EXPECT(!setResult1.has_value()); + + auto value2 = createJsonValue( + STI_UINT32, [](Serializer& s) { s.add32(150); }); + auto setResult2 = cfs.setDataNestedObjectField( + bob.id(), "stats", "score", value2); + BEAST_EXPECT(!setResult2.has_value()); + + auto getResult = + cfs.getDataNestedObjectField(bob.id(), "stats", "score"); + BEAST_EXPECT(getResult.has_value()); + } + } + + void + testBuildTxn() + { + testcase("buildTxn"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Test building a Payment transaction + { + auto result = cfs.buildTxn(ttPAYMENT); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto txIndex = result.value(); + BEAST_EXPECT(txIndex == 0); + BEAST_EXPECT(contractCtx.built_txns.size() == 1); + + // Verify the transaction has required fields + auto& txn = contractCtx.built_txns[0]; + BEAST_EXPECT(txn.isFieldPresent(sfTransactionType)); + BEAST_EXPECT(txn.getFieldU16(sfTransactionType) == ttPAYMENT); + BEAST_EXPECT(txn.isFieldPresent(sfAccount)); + BEAST_EXPECT(txn.getAccountID(sfAccount) == contract.id()); + BEAST_EXPECT(txn.isFieldPresent(sfSequence)); + BEAST_EXPECT(txn.getFieldU32(sfSequence) == 1); + BEAST_EXPECT(txn.isFieldPresent(sfFee)); + BEAST_EXPECT(txn.getFieldAmount(sfFee) == XRP(0)); + BEAST_EXPECT(txn.isFieldPresent(sfFlags)); + BEAST_EXPECT(txn.getFieldU32(sfFlags) == 536870912); + + // Verify sequence incremented + BEAST_EXPECT(contractCtx.result.nextSequence == 2); + } + } + + // Test building an AccountSet transaction + { + auto result = cfs.buildTxn(ttACCOUNT_SET); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto txIndex = result.value(); + BEAST_EXPECT(txIndex == 1); + BEAST_EXPECT(contractCtx.built_txns.size() == 2); + + auto& txn = contractCtx.built_txns[1]; + BEAST_EXPECT( + txn.getFieldU16(sfTransactionType) == ttACCOUNT_SET); + BEAST_EXPECT(txn.getFieldU32(sfSequence) == 2); + BEAST_EXPECT(contractCtx.result.nextSequence == 3); + } + } + + // Test building a TrustSet transaction + { + auto result = cfs.buildTxn(ttTRUST_SET); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto txIndex = result.value(); + BEAST_EXPECT(txIndex == 2); + BEAST_EXPECT(contractCtx.built_txns.size() == 3); + + auto& txn = contractCtx.built_txns[2]; + BEAST_EXPECT(txn.getFieldU16(sfTransactionType) == ttTRUST_SET); + } + } + + // Test building multiple transactions in sequence + { + auto initialSize = contractCtx.built_txns.size(); + auto initialSeq = contractCtx.result.nextSequence; + + for (int i = 0; i < 5; ++i) + { + auto result = cfs.buildTxn(ttPAYMENT); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == initialSize + i); + } + } + + BEAST_EXPECT(contractCtx.built_txns.size() == initialSize + 5); + BEAST_EXPECT(contractCtx.result.nextSequence == initialSeq + 5); + } + } + + void + testAddTxnField() + { + testcase("addTxnField"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const bob("bob"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, bob, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Build a Payment transaction to add fields to + auto buildResult = cfs.buildTxn(ttPAYMENT); + BEAST_EXPECT(buildResult.has_value()); + if (!buildResult.has_value()) + return; + + uint32_t txIndex = buildResult.value(); + + // Test adding Destination (AccountID) with 0x14 prefix + { + AccountID data = bob.id(); + // Prepend the required type byte 0x14 before the 20-byte AccountID + Blob buf; + buf.reserve(1 + data.size()); + buf.push_back(0x14); + buf.insert(buf.end(), data.begin(), data.end()); + auto const s = Slice{buf.data(), buf.size()}; + auto result = cfs.addTxnField(txIndex, sfDestination, s); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[txIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfDestination)); + BEAST_EXPECT(txn.getAccountID(sfDestination) == bob.id()); + } + } + + // Test adding Amount (STAmount - XRP) + { + STAmount amount{XRP(1000)}; + Serializer s; + amount.add(s); + auto result = cfs.addTxnField(txIndex, sfAmount, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[txIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfAmount)); + BEAST_EXPECT(txn.getFieldAmount(sfAmount) == amount); + } + } + + // Test adding SendMax (STAmount - IOU) + { + auto const USD = alice["USD"]; + STAmount sendMax{USD.issue(), 500}; + Serializer s; + sendMax.add(s); + auto result = cfs.addTxnField(txIndex, sfSendMax, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[txIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfSendMax)); + BEAST_EXPECT(txn.getFieldAmount(sfSendMax) == sendMax); + } + } + + // Test adding DestinationTag (UInt32) + { + uint32_t tag = 12345; + Serializer s; + s.add32(tag); + auto result = cfs.addTxnField(txIndex, sfDestinationTag, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[txIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfDestinationTag)); + BEAST_EXPECT(txn.getFieldU32(sfDestinationTag) == tag); + } + } + + // Build a TrustSet transaction for testing additional fields + auto trustBuildResult = cfs.buildTxn(ttTRUST_SET); + BEAST_EXPECT(trustBuildResult.has_value()); + if (trustBuildResult.has_value()) + { + uint32_t trustIndex = trustBuildResult.value(); + + // Test adding LimitAmount (STAmount for TrustSet) + { + auto const EUR = alice["EUR"]; + STAmount limit{EUR.issue(), 10000}; + Serializer s; + limit.add(s); + auto result = + cfs.addTxnField(trustIndex, sfLimitAmount, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[trustIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfLimitAmount)); + BEAST_EXPECT(txn.getFieldAmount(sfLimitAmount) == limit); + } + } + + // Test adding QualityIn (UInt32) + { + uint32_t quality = 1000000; + Serializer s; + s.add32(quality); + auto result = + cfs.addTxnField(trustIndex, sfQualityIn, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[trustIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfQualityIn)); + BEAST_EXPECT(txn.getFieldU32(sfQualityIn) == quality); + } + } + } + + // Build an AccountSet transaction for testing more fields + auto accSetResult = cfs.buildTxn(ttACCOUNT_SET); + BEAST_EXPECT(accSetResult.has_value()); + if (accSetResult.has_value()) + { + uint32_t accSetIndex = accSetResult.value(); + + // Test adding Domain (Blob/VL) + { + Blob domain = { + 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm'}; + Serializer s; + s.addVL(domain); + auto result = cfs.addTxnField(accSetIndex, sfDomain, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[accSetIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfDomain)); + BEAST_EXPECT(txn.getFieldVL(sfDomain) == domain); + } + } + + // Test adding TransferRate (UInt32) + { + uint32_t fee = 500; + Serializer s; + s.add32(fee); + auto result = + cfs.addTxnField(accSetIndex, sfTransferRate, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[accSetIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfTransferRate)); + BEAST_EXPECT(txn.getFieldU32(sfTransferRate) == fee); + } + } + + // Test adding SetFlag (UInt32) + { + uint32_t flag = 8; // asfRequireAuth + Serializer s; + s.add32(flag); + auto result = + cfs.addTxnField(accSetIndex, sfSetFlag, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[accSetIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfSetFlag)); + BEAST_EXPECT(txn.getFieldU32(sfSetFlag) == flag); + } + } + } + + // Test error cases + + // Test adding field to non-existent transaction + { + Serializer s; + s.add32(123); + auto result = cfs.addTxnField(9999, sfDestinationTag, s.slice()); + BEAST_EXPECT(!result.has_value()); + // The implementation will likely throw or return an error + // when accessing out-of-bounds index + } + + // // Test adding invalid field for transaction type + // // Note: The current implementation checks against ttCONTRACT_CALL + // // format which might need adjustment for proper field validation + // { + // auto paymentResult = cfs.buildTxn(ttPAYMENT); + // if (paymentResult.has_value()) + // { + // uint32_t payIndex = paymentResult.value(); + + // // Try to add a field that doesn't belong to Payment + // // This test might need adjustment based on actual field + // // validation + // Serializer s; + // s.add32(100); + // // Using an obscure field that likely isn't in Payment format + // auto result = cfs.addTxnField(payIndex, sfTickSize, + // s.slice()); + // // The result depends on implementation's field validation + // BEAST_EXPECT(result.has_value()); + // } + // } + } + + void + testEmitBuiltTxn() + { + testcase("emitBuiltTxn"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + Account const contract("contract"); + env.fund(XRP(10000), alice, bob, carol, contract); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, alice); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Test emitting a valid Payment transaction + { + // Build a Payment transaction + auto buildResult = cfs.buildTxn(ttPAYMENT); + BEAST_EXPECT(buildResult.has_value()); + if (!buildResult.has_value()) + return; + + uint32_t txIndex = buildResult.value(); + + // Add required fields for Payment + AccountID destAccount = bob.id(); + Blob destBuf; + destBuf.reserve(1 + destAccount.size()); + destBuf.push_back(0x14); // Type prefix for AccountID + destBuf.insert( + destBuf.end(), destAccount.begin(), destAccount.end()); + auto destResult = cfs.addTxnField( + txIndex, sfDestination, Slice{destBuf.data(), destBuf.size()}); + BEAST_EXPECT(destResult.has_value()); + + // Add Amount field + STAmount amount{XRP(100)}; + Serializer amtSerializer; + amount.add(amtSerializer); + auto amtResult = + cfs.addTxnField(txIndex, sfAmount, amtSerializer.slice()); + BEAST_EXPECT(amtResult.has_value()); + + // Emit the transaction + auto emitResult = cfs.emitBuiltTxn(txIndex); + BEAST_EXPECT(emitResult.has_value()); + if (emitResult.has_value()) + { + // Check that the transaction was added to emitted transactions + BEAST_EXPECT(contractCtx.result.emittedTxns.size() == 1); + + // The result should be a TER code converted to int + int32_t terCode = emitResult.value(); + // We expect a success code + BEAST_EXPECT(terCode == 0); + } + } + + // // Test emitting multiple transactions + // { + // // Build first transaction - Payment + // auto build1 = cfs.buildTxn(ttPAYMENT); + // BEAST_EXPECT(build1.has_value()); + // if (build1.has_value()) + // { + // uint32_t tx1 = build1.value(); + + // // Add fields for first payment + // AccountID dest1 = alice.id(); + // Blob dest1Buf; + // dest1Buf.reserve(1 + dest1.size()); + // dest1Buf.push_back(0x14); + // dest1Buf.insert(dest1Buf.end(), dest1.begin(), dest1.end()); + // auto const result = cfs.addTxnField( + // tx1, + // sfDestination, + // Slice{dest1Buf.data(), dest1Buf.size()}); + // BEAST_EXPECT(result.has_value()); + + // STAmount amt1{XRP(50)}; + // Serializer amt1Ser; + // amt1.add(amt1Ser); + // cfs.addTxnField(tx1, sfAmount, amt1Ser.slice()); + // } + + // // Build second transaction - Another Payment + // auto build2 = cfs.buildTxn(ttPAYMENT); + // BEAST_EXPECT(build2.has_value()); + // if (build2.has_value()) + // { + // uint32_t tx2 = build2.value(); + + // // Add fields for second payment + // AccountID dest2 = carol.id(); + // Blob dest2Buf; + // dest2Buf.reserve(1 + dest2.size()); + // dest2Buf.push_back(0x14); + // dest2Buf.insert(dest2Buf.end(), dest2.begin(), dest2.end()); + // auto const result = cfs.addTxnField( + // tx2, + // sfDestination, + // Slice{dest2Buf.data(), dest2Buf.size()}); + // BEAST_EXPECT(result.has_value()); + + // STAmount amt2{XRP(75)}; + // Serializer amt2Ser; + // amt2.add(amt2Ser); + // cfs.addTxnField(tx2, sfAmount, amt2Ser.slice()); + // } + + // // Emit both transactions + // if (build1.has_value()) + // { + // auto emit1 = cfs.emitBuiltTxn(build1.value()); + // BEAST_EXPECT(emit1.has_value()); + // } + + // if (build2.has_value()) + // { + // auto emit2 = cfs.emitBuiltTxn(build2.value()); + // BEAST_EXPECT(emit2.has_value()); + // } + + // // Check that both were added to emitted transactions + // // (Note: actual count depends on previous test state) + // BEAST_EXPECT(contractCtx.result.emittedTxns.size() >= 2); + // } + + // // Test emitting transaction with invalid index + // { + // auto emitResult = cfs.emitBuiltTxn(9999); + // BEAST_EXPECT(!emitResult.has_value()); + // if (!emitResult.has_value()) + // { + // BEAST_EXPECT( + // emitResult.error() == + // HostFunctionError::INDEX_OUT_OF_BOUNDS); + // } + // } + + // // Test emitting AccountSet transaction + // { + // auto buildResult = cfs.buildTxn(ttACCOUNT_SET); + // BEAST_EXPECT(buildResult.has_value()); + // if (buildResult.has_value()) + // { + // uint32_t txIndex = buildResult.value(); + + // // Add optional fields for AccountSet + // uint32_t setFlag = 8; // asfRequireAuth + // Serializer flagSer; + // flagSer.add32(setFlag); + // auto flagResult = + // cfs.addTxnField(txIndex, sfSetFlag, flagSer.slice()); + // BEAST_EXPECT(flagResult.has_value()); + + // // Emit the transaction + // auto emitResult = cfs.emitBuiltTxn(txIndex); + // BEAST_EXPECT(emitResult.has_value()); + // } + // } + + // // Test emitting TrustSet transaction + // { + // auto buildResult = cfs.buildTxn(ttTRUST_SET); + // BEAST_EXPECT(buildResult.has_value()); + // if (buildResult.has_value()) + // { + // uint32_t txIndex = buildResult.value(); + + // // Add LimitAmount field (required for TrustSet) + // auto const USD = alice["USD"]; + // STAmount limit{USD.issue(), 1000}; + // Serializer limitSer; + // limit.add(limitSer); + // auto limitResult = + // cfs.addTxnField(txIndex, sfLimitAmount, + // limitSer.slice()); + // BEAST_EXPECT(limitResult.has_value()); + + // // Emit the transaction + // auto emitResult = cfs.emitBuiltTxn(txIndex); + // BEAST_EXPECT(emitResult.has_value()); + // } + // } + + // // Test emitting transaction without required fields + // { + // // Build a Payment but don't add required fields + // auto buildResult = cfs.buildTxn(ttPAYMENT); + // BEAST_EXPECT(buildResult.has_value()); + // if (buildResult.has_value()) + // { + // uint32_t txIndex = buildResult.value(); + + // // Try to emit without Destination and Amount + // auto emitResult = cfs.emitBuiltTxn(txIndex); + // BEAST_EXPECT(emitResult.has_value()); + // if (emitResult.has_value()) + // { + // // Should get an error code indicating missing fields + // int32_t terCode = emitResult.value(); + // // The transaction should fail validation + // BEAST_EXPECT(terCode != + // static_cast(tesSUCCESS)); + // } + // } + // } + + // // Test sequence of build and emit operations + // { + // auto initialEmittedCount = contractCtx.result.emittedTxns.size(); + + // // Build several transactions + // std::vector indices; + // for (int i = 0; i < 3; ++i) + // { + // auto buildResult = cfs.buildTxn(ttACCOUNT_SET); + // BEAST_EXPECT(buildResult.has_value()); + // if (buildResult.has_value()) + // { + // indices.push_back(buildResult.value()); + // } + // } + + // // Emit them in reverse order + // for (auto it = indices.rbegin(); it != indices.rend(); ++it) + // { + // auto emitResult = cfs.emitBuiltTxn(*it); + // BEAST_EXPECT(emitResult.has_value()); + // } + + // // Check that all were emitted + // BEAST_EXPECT( + // contractCtx.result.emittedTxns.size() == + // initialEmittedCount + indices.size()); + // } + } + + void + run() override + { + using namespace test::jtx; + // testInstanceParam(); + // testFunctionParam(); + // testContractDataFromKey(); + // testNestedContractDataFromKey(); + testNestedContractDataFromArrayKey(); + // testBuildTxn(); + // testAddTxnField(); + // testEmitBuiltTxn(); + } +}; + +BEAST_DEFINE_TESTSUITE(ContractHostFuncImpl, app, xrpl); + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/Contract_test.cpp b/src/test/app/Contract_test.cpp new file mode 100644 index 0000000000..501fa585d9 --- /dev/null +++ b/src/test/app/Contract_test.cpp @@ -0,0 +1,2141 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include + +#include +#include +#include + +namespace xrpl { +namespace test { + +class Contract_test : public beast::unit_test::suite +{ + struct TestLedgerData + { + int index; + std::string txType; + std::string result; + }; + + Json::Value + getLastLedger(jtx::Env& env) + { + Json::Value params; + params[jss::ledger_index] = env.closed()->seq(); + params[jss::transactions] = true; + params[jss::expand] = true; + return env.rpc("json", "ledger", to_string(params)); + } + + Json::Value + getTxByIndex(Json::Value const& jrr, int const index) + { + for (auto const& txn : jrr[jss::result][jss::ledger][jss::transactions]) + { + if (txn[jss::metaData][sfTransactionIndex.jsonName] == index) + return txn; + } + return {}; + } + + void + validateClosedLedger( + jtx::Env& env, + std::vector const& ledgerResults) + { + auto const jrr = getLastLedger(env); + auto const transactions = + jrr[jss::result][jss::ledger][jss::transactions]; + BEAST_EXPECT(transactions.size() == ledgerResults.size()); + for (TestLedgerData const& ledgerResult : ledgerResults) + { + auto const txn = getTxByIndex(jrr, ledgerResult.index); + BEAST_EXPECT(txn.isMember(jss::metaData)); + Json::Value const meta = txn[jss::metaData]; + BEAST_EXPECT( + txn[sfTransactionType.jsonName] == ledgerResult.txType); + BEAST_EXPECT( + meta[sfTransactionResult.jsonName] == ledgerResult.result); + } + } + + static std::pair> + contractSourceKeyAndSle(ReadView const& view, uint256 const& contractHash) + { + auto const k = keylet::contractSource(contractHash); + return {k.key, view.read(k)}; + } + + static std::pair> + contractKeyAndSle( + ReadView const& view, + uint256 const& contractHash, + AccountID const& owner, + std::uint32_t const& seq) + { + auto const k = keylet::contract(contractHash, owner, seq); + return {k.key, view.read(k)}; + } + + Json::Value + getContractCreateTx(Json::Value const& jrr) + { + for (auto const& txn : jrr[jss::result][jss::ledger][jss::transactions]) + { + if (txn[jss::TransactionType] == jss::ContractCreate) + return txn; + } + return {}; + } + + uint256 + getContractHash(Blob const& wasmBytes) + { + return xrpl::sha512Half_s( + xrpl::Slice(wasmBytes.data(), wasmBytes.size())); + } + + void + validateFunctions( + std::shared_ptr const& sle, + Json::Value const& functions) + { + auto const stored = sle->getFieldArray(sfFunctions); + BEAST_EXPECT(stored.size() == functions.size()); + for (std::size_t i = 0; i < stored.size(); ++i) + { + auto const sIPV = stored[i].getJson(JsonOptions::none); + auto const& eIPV = functions[i]["Function"]; + + // Compare function name. + BEAST_EXPECT(sIPV.isMember("FunctionName")); + BEAST_EXPECT(eIPV.isMember("FunctionName")); + BEAST_EXPECT( + sIPV["FunctionName"].asString() == + eIPV["FunctionName"].asString()); + + // Compare parameters if present. + if (eIPV.isMember("Parameters")) + { + BEAST_EXPECT(sIPV.isMember("Parameters")); + BEAST_EXPECT(sIPV["Parameters"].isArray()); + BEAST_EXPECT(eIPV["Parameters"].isArray()); + BEAST_EXPECT( + sIPV["Parameters"].size() == eIPV["Parameters"].size()); + + for (std::size_t j = 0; j < sIPV["Parameters"].size(); ++j) + { + auto const& sParam = sIPV["Parameters"][j]; + auto const& eParam = eIPV["Parameters"][j]["Parameter"]; + + // Compare ParameterFlag if present. + if (sParam.isMember("ParameterFlag")) + { + BEAST_EXPECT(eParam.isMember("ParameterFlag")); + BEAST_EXPECT( + sParam["ParameterFlag"].asUInt() == + eParam["ParameterFlag"].asUInt()); + } + + // Compare ParameterName if present. + if (sParam.isMember("ParameterName")) + { + BEAST_EXPECT(eParam.isMember("ParameterName")); + BEAST_EXPECT( + sParam["ParameterName"].asString() == + eParam["ParameterName"].asString()); + } + + // Compare ParameterType if present. + if (sParam.isMember("ParameterType")) + { + BEAST_EXPECT(eParam.isMember("ParameterType")); + BEAST_EXPECT( + sParam["ParameterType"]["type"].asString() == + eParam["ParameterType"]["type"].asString()); + } + } + } + } + } + + void + validateInstanceParams( + std::shared_ptr const& sle, + Json::Value const& instanceParamValues) + { + // Convert stored SLE array to JSON and compare against expected JSON. + auto const stored = sle->getFieldArray(sfInstanceParameterValues); + BEAST_EXPECT(stored.size() == instanceParamValues.size()); + + for (std::size_t i = 0; i < stored.size(); ++i) + { + // Convert the STObject entry to JSON for easy comparison. + auto const sIPV = stored[i].getJson(JsonOptions::none); + auto const& eIPV = instanceParamValues[i]["InstanceParameterValue"]; + + // Compare flag if present. + BEAST_EXPECT(sIPV.isMember("ParameterFlag")); + BEAST_EXPECT(eIPV.isMember("ParameterFlag")); + BEAST_EXPECT( + sIPV["ParameterFlag"].asUInt() == + eIPV["ParameterFlag"].asUInt()); + + // Compare ParameterValue contents (name/type/value) when present. + BEAST_EXPECT(sIPV.isMember("ParameterValue")); + BEAST_EXPECT(eIPV.isMember("ParameterValue")); + auto const& sPV = sIPV["ParameterValue"]; + auto const& ePV = eIPV["ParameterValue"]; + + if (ePV.isMember("name")) + BEAST_EXPECT( + sPV.isMember("name") && + sPV["name"].asString() == ePV["name"].asString()); + + if (ePV.isMember("type")) + BEAST_EXPECT( + sPV.isMember("type") && + sPV["type"].asString() == ePV["type"].asString()); + + if (ePV.isMember("value")) + { + // value can be number, string, or object; compare generically + BEAST_EXPECT(sPV.isMember("value")); + BEAST_EXPECT(sPV["value"] == ePV["value"]); + } + } + } + + void + validateInstanceParamValues( + std::shared_ptr const& sle, + Json::Value const& instanceParamValues) + { + // Convert stored SLE array to JSON and compare against expected JSON. + auto const stored = sle->getFieldArray(sfInstanceParameterValues); + BEAST_EXPECT(stored.size() == instanceParamValues.size()); + + for (std::size_t i = 0; i < stored.size(); ++i) + { + // Convert the STObject entry to JSON for easy comparison. + auto const sIPV = stored[i].getJson(JsonOptions::none); + auto const& eIPV = instanceParamValues[i]["InstanceParameterValue"]; + + // Compare flag if present. + BEAST_EXPECT(sIPV.isMember("ParameterFlag")); + BEAST_EXPECT(eIPV.isMember("ParameterFlag")); + BEAST_EXPECT( + sIPV["ParameterFlag"].asUInt() == + eIPV["ParameterFlag"].asUInt()); + + // Compare ParameterValue contents (name/type/value) when present. + BEAST_EXPECT(sIPV.isMember("ParameterValue")); + BEAST_EXPECT(eIPV.isMember("ParameterValue")); + auto const& sPV = sIPV["ParameterValue"]; + auto const& ePV = eIPV["ParameterValue"]; + + if (ePV.isMember("type")) + BEAST_EXPECT( + sPV.isMember("type") && + sPV["type"].asString() == ePV["type"].asString()); + + if (ePV.isMember("value")) + { + // value can be number, string, or object; compare generically + BEAST_EXPECT(sPV.isMember("value")); + BEAST_EXPECT(sPV["value"] == ePV["value"]); + } + } + } + + void + validateContract( + jtx::Env& env, + Keylet const& k, + AccountID const& contractAccount, + AccountID const& owner, + std::uint32_t const& flags, + std::uint32_t const& seq, + uint256 const& contractHash, + std::optional const& instanceParamValues = std::nullopt, + std::optional const& uri = std::nullopt) + { + auto const sle = env.current()->read(k); + if (!sle) + { + fail(); + return; + } + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->getAccountID(sfContractAccount) == contractAccount); + BEAST_EXPECT(sle->getAccountID(sfOwner) == owner); + BEAST_EXPECT(sle->getFieldU32(sfFlags) == flags); + BEAST_EXPECT(sle->getFieldU32(sfSequence) == seq); + BEAST_EXPECT(sle->getFieldH256(sfContractHash) == contractHash); + // if (instanceParamValues) + // validateInstanceParamValues(sle, *instanceParamValues); + // if (uri) + // { + // std::cout << "URI: " << *uri << std::endl; + // BEAST_EXPECT(sle->getFieldVL(sfURI) == strUnHex(*uri)); + // } + } + + void + validateContractSource( + jtx::Env& env, + Blob const& wasmBytes, + uint256 const& contractHash, + std::uint64_t const& referenceCount, + Json::Value const& functions, + std::optional const& instanceParams = std::nullopt) + { + auto const [id, sle] = + contractSourceKeyAndSle(*env.current(), contractHash); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->getFieldVL(sfContractCode) == wasmBytes); + BEAST_EXPECT(sle->getFieldH256(sfContractHash) == contractHash); + BEAST_EXPECT(sle->getFieldU64(sfReferenceCount) == referenceCount); + validateFunctions(sle, functions); + } + + template + std::tuple + setContract(jtx::Env& env, TER const& result, Args&&... args) + { + auto jt = env.jt(std::forward(args)...); + env(jt, jtx::ter(result)); + env.close(); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + // } + + // if (jt.jv.isMember(sfContractHash.jsonName)) + // { + // auto const accountID = + // parseBase58(jt.jv[sfContractAccount].asString()); + // jtx::Account const contractAccount{ + // "Contract pseudo-account", + // *accountID}; + // return std::make_pair(contractAccount, + // uint256(jt.jv[sfContractHash])); + // } + + auto const wasmBytes = + strUnHex(jt.jv[sfContractCode.jsonName].asString()); + // std::cout << "WASM Size: " << wasmBytes->size() << std::endl; + uint256 const contractHash = xrpl::sha512Half_s( + xrpl::Slice(wasmBytes->data(), wasmBytes->size())); + auto const accountID = + parseBase58(jt.jv[sfAccount].asString()); + auto const [contractKey, sle] = contractKeyAndSle( + *env.current(), + contractHash, + *accountID, + jt.jv[sfSequence.jsonName].asUInt()); + jtx::Account const contractAccount{ + "Contract pseudo-account", sle->getAccountID(sfContractAccount)}; + return std::make_tuple(contractAccount, contractHash, jt.jv); + } + + std::string const BaseContractWasm = + "0061736D01000000010E0260057F7F7F7F7F017F6000017F02120108686F73745F" + "6C696205747261636500000302010105030100110619037F01418080C0000B7F00" + "419E80C0000B7F0041A080C0000B072C04066D656D6F7279020004626173650001" + "0A5F5F646174615F656E6403010B5F5F686561705F6261736503020A6C016A0101" + "7F23808080800041206B2200248080808000200041186A410028009080C0800036" + "0200200041106A410029008880C080003703002000410029008080C08000370308" + "419480C08000410A200041086A411441011080808080001A200041206A24808080" + "800041000B0B270100418080C0000B1EAE123A8556F3CF91154711376AFB0F894F" + "832B3D20204163636F756E743A"; + + std::string const Base2ContractWasm = + "0061736D01000000010E0260057F7F7F7F7F017F6000017F02120108686F73745F6C69" + "6205747261636500000302010105030100110619037F01418080C0000B7F0041A380C0" + "000B7F0041B080C0000B072C04066D656D6F72790200046261736500010A5F5F646174" + "615F656E6403010B5F5F686561705F6261736503020A1B011900418080C08000412341" + "00410041001080808080001A41000B0B2C0100418080C0000B23242424242420535441" + "5254494E47204241534520455845435554494F4E202424242424"; + + void + testCreatePreflight(FeatureBitset features) + { + testcase("create preflight"); + + using namespace jtx; + + // temDISABLED: Feature not enabled + { + test::jtx::Env env{*this, features - featureSmartContract}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), ter(temDISABLED)); + } + + // temINVALID_FLAG: tfContractMask is not allowed. + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + txflags(tfBurnable), + ter(temINVALID_FLAG)); + } + + // temMALFORMED: Neither ContractCode nor ContractHash present + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + // Missing both ContractCode and ContractHash + + env(jv, ter(temMALFORMED)); + } + + // temMALFORMED: Both ContractCode and ContractHash present + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + jv[sfContractHash.jsonName] = + "D955DAC2E77519F05AD151A5D3C99FC8125FB39D58FF9F106F1ACA4491902C" + "25"; + + env(jv, ter(temMALFORMED)); + } + + // temARRAY_EMPTY: ContractCode present but Functions missing + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + // Missing Functions array + + env(jv, ter(temARRAY_EMPTY)); + } + + // temARRAY_EMPTY: ContractCode present but Functions missing + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + jv[sfFunctions.jsonName] = Json::arrayValue; // Empty array + + env(jv, ter(temARRAY_EMPTY)); + } + + // temARRAY_TOO_LARGE: Functions array too large + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + contract::add_function("func1", {}), + contract::add_function("func2", {}), + contract::add_function("func3", {}), + contract::add_function("func4", {}), + contract::add_function("func5", {}), + contract::add_function("func6", {}), + contract::add_function("func7", {}), + contract::add_function("func8", {}), + contract::add_function("func9", {}), + contract::add_function("func10", {}), + contract::add_function("func11", {}), + contract::add_function("func12", {}), + contract::add_function("func13", {}), + ter(temARRAY_TOO_LARGE)); + } + + // temREDUNDANT: Duplicate function name + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + contract::add_function("test", {}), + contract::add_function("test", {}), // Duplicate + ter(temREDUNDANT)); + } + + // temARRAY_TOO_LARGE: Function Parameters array too large + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + contract::add_function( + "test", + { + {0, "param1", "UINT8"}, {0, "param2", "UINT8"}, + {0, "param3", "UINT8"}, {0, "param4", "UINT8"}, + {0, "param5", "UINT8"}, {0, "param6", "UINT8"}, + {0, "param7", "UINT8"}, {0, "param8", "UINT8"}, + {0, "param9", "UINT8"}, {0, "param10", "UINT8"}, + {0, "param11", "UINT8"}, {0, "param12", "UINT8"}, + {0, "param13", "UINT8"}, {0, "param14", "UINT8"}, + {0, "param15", "UINT8"}, {0, "param16", "UINT8"}, + {0, "param17", "UINT8"}, {0, "param18", "UINT8"}, + {0, "param19", "UINT8"}, {0, "param20", "UINT8"}, + {0, "param21", "UINT8"}, {0, "param22", "UINT8"}, + {0, "param23", "UINT8"}, {0, "param24", "UINT8"}, + {0, "param25", "UINT8"}, {0, "param26", "UINT8"}, + {0, "param27", "UINT8"}, {0, "param28", "UINT8"}, + {0, "param29", "UINT8"}, {0, "param30", "UINT8"}, + {0, "param31", "UINT8"}, {0, "param32", "UINT8"}, + {0, "param33", "UINT8"}, // 33rd parameter + }), + ter(temARRAY_TOO_LARGE)); + } + + // temMALFORMED: Function Parameter is missing flag + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + jv[sfFunctions.jsonName] = Json::arrayValue; + + Json::Value func; + func[sfFunction.jsonName][sfFunctionName.jsonName] = + strHex(std::string("test")); + func[sfFunction.jsonName][sfParameters.jsonName] = Json::arrayValue; + + Json::Value param; + param[sfParameter.jsonName][sfParameterType.jsonName]["type"] = + "UINT8"; + func[sfFunction.jsonName][sfParameters.jsonName].append(param); + + jv[sfFunctions.jsonName].append(func); + env(jv, ter(temMALFORMED)); + } + + // temMALFORMED: Function Parameter is missing type + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + jv[sfFunctions.jsonName] = Json::arrayValue; + + Json::Value func; + func[sfFunction.jsonName][sfFunctionName.jsonName] = + strHex(std::string("test")); + func[sfFunction.jsonName][sfParameters.jsonName] = Json::arrayValue; + + Json::Value param; + param[sfParameter.jsonName][sfParameterFlag.jsonName] = 0; + // Missing sfParameterType + func[sfFunction.jsonName][sfParameters.jsonName].append(param); + + jv[sfFunctions.jsonName].append(func); + env(jv, ter(temMALFORMED)); + } + + // temINVALID_FLAG: Invalid parameter flag in Function. + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + contract::add_function( + "test", {{0xFF000000, "param", "UINT8"}}), // Invalid flag + ter(temINVALID_FLAG)); + } + + // temARRAY_EMPTY: InstanceParameters empty array + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + jv[sfFunctions.jsonName] = Json::arrayValue; + Json::Value func; + func[sfFunction.jsonName][sfFunctionName.jsonName] = + strHex(std::string("test")); + jv[sfFunctions.jsonName].append(func); + jv[sfInstanceParameters.jsonName] = + Json::arrayValue; // Empty array + + env(jv, ter(temARRAY_EMPTY)); + } + + // temARRAY_TOO_LARGE: InstanceParameters array is too large + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + contract::add_function("test", {}), + contract::add_instance_param(0, "param1", "UINT8", 1), + contract::add_instance_param(0, "param2", "UINT8", 2), + contract::add_instance_param(0, "param3", "UINT8", 3), + contract::add_instance_param(0, "param4", "UINT8", 4), + contract::add_instance_param(0, "param5", "UINT8", 5), + contract::add_instance_param(0, "param6", "UINT8", 6), + contract::add_instance_param(0, "param7", "UINT8", 7), + contract::add_instance_param(0, "param8", "UINT8", 8), + contract::add_instance_param(0, "param9", "UINT8", 9), + contract::add_instance_param(0, "param10", "UINT8", 10), + contract::add_instance_param(0, "param11", "UINT8", 11), + contract::add_instance_param(0, "param12", "UINT8", 12), + contract::add_instance_param(0, "param13", "UINT8", 13), + contract::add_instance_param(0, "param14", "UINT8", 14), + contract::add_instance_param(0, "param15", "UINT8", 15), + contract::add_instance_param(0, "param16", "UINT8", 16), + contract::add_instance_param(0, "param17", "UINT8", 17), + contract::add_instance_param(0, "param18", "UINT8", 18), + contract::add_instance_param(0, "param19", "UINT8", 19), + contract::add_instance_param(0, "param20", "UINT8", 20), + contract::add_instance_param(0, "param21", "UINT8", 21), + contract::add_instance_param(0, "param22", "UINT8", 22), + contract::add_instance_param(0, "param23", "UINT8", 23), + contract::add_instance_param(0, "param24", "UINT8", 24), + contract::add_instance_param(0, "param25", "UINT8", 25), + contract::add_instance_param(0, "param26", "UINT8", 26), + contract::add_instance_param(0, "param27", "UINT8", 27), + contract::add_instance_param(0, "param28", "UINT8", 28), + contract::add_instance_param(0, "param29", "UINT8", 29), + contract::add_instance_param(0, "param30", "UINT8", 30), + contract::add_instance_param(0, "param31", "UINT8", 31), + contract::add_instance_param(0, "param32", "UINT8", 32), + contract::add_instance_param(0, "param33", "UINT8", 33), + ter(temARRAY_TOO_LARGE)); + } + + // temARRAY_EMPTY: InstanceParameterValues is missing + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + jv[sfFunctions.jsonName] = Json::arrayValue; + Json::Value func; + func[sfFunction.jsonName][sfFunctionName.jsonName] = + strHex(std::string("test")); + jv[sfFunctions.jsonName].append(func); + jv[sfInstanceParameterValues.jsonName] = + Json::arrayValue; // Empty array + + env(jv, ter(temARRAY_EMPTY)); + } + + // // Test 18: InstanceParameterValues array is too large. + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // Json::Value jv; + // jv[jss::TransactionType] = jss::ContractCreate; + // jv[jss::Account] = alice.human(); + // jv[jss::Fee] = to_string(XRP(10).value()); + // jv[sfContractCode.jsonName] = BaseContractWasm; + // jv[sfFunctions.jsonName] = Json::arrayValue; + // Json::Value func; + // func[sfFunction.jsonName][sfFunctionName.jsonName] = "test"; + // func[sfFunction.jsonName][sfParameters.jsonName] = + // Json::arrayValue; jv[sfFunctions.jsonName].append(func); + // jv[sfInstanceParameterValues.jsonName] = Json::arrayValue; + + // // Add more than maxContractParams + // for (int i = 0; i < 257; ++i) + // { + // Json::Value param; + // param[sfInstanceParameterValue.jsonName] + // [sfParameterFlag.jsonName] = 0; + // param[sfInstanceParameterValue.jsonName] + // [sfParameterValue.jsonName]["type"] = "UINT8"; + // param[sfInstanceParameterValue.jsonName] + // [sfParameterValue.jsonName]["value"] = i; + // jv[sfInstanceParameterValues.jsonName].append(param); + // } + + // env(jv, ter(temARRAY_TOO_LARGE)); + // } + + // // Test 19: InstanceParameterValue missing flag + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // Json::Value jv; + // jv[jss::TransactionType] = jss::ContractCreate; + // jv[jss::Account] = alice.human(); + // jv[jss::Fee] = to_string(XRP(10).value()); + // jv[sfContractCode.jsonName] = BaseContractWasm; + // jv[sfFunctions.jsonName] = Json::arrayValue; + // Json::Value func; + // func[sfFunction.jsonName][sfFunctionName.jsonName] = "test"; + // func[sfFunction.jsonName][sfParameters.jsonName] = + // Json::arrayValue; jv[sfFunctions.jsonName].append(func); + // jv[sfInstanceParameterValues.jsonName] = Json::arrayValue; + + // Json::Value param; + // // Missing sfParameterFlag + // param[sfInstanceParameterValue.jsonName][sfParameterValue.jsonName] + // ["type"] = "UINT8"; + // param[sfInstanceParameterValue.jsonName][sfParameterValue.jsonName] + // ["value"] = 1; + // jv[sfInstanceParameterValues.jsonName].append(param); + + // env(jv, ter(temMALFORMED)); + // } + + // // Test 20: InstanceParameterValue missing value + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // Json::Value jv; + // jv[jss::TransactionType] = jss::ContractCreate; + // jv[jss::Account] = alice.human(); + // jv[jss::Fee] = to_string(XRP(10).value()); + // jv[sfContractCode.jsonName] = BaseContractWasm; + // jv[sfFunctions.jsonName] = Json::arrayValue; + // Json::Value func; + // func[sfFunction.jsonName][sfFunctionName.jsonName] = "test"; + // func[sfFunction.jsonName][sfParameters.jsonName] = + // Json::arrayValue; jv[sfFunctions.jsonName].append(func); + // jv[sfInstanceParameterValues.jsonName] = Json::arrayValue; + + // Json::Value param; + // param[sfInstanceParameterValue.jsonName][sfParameterFlag.jsonName] + // = + // 0; + // // Missing sfParameterValue + // jv[sfInstanceParameterValues.jsonName].append(param); + + // env(jv, ter(temMALFORMED)); + // } + + // // Test 21: InstanceParameterValue invalid flag + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // env(contract::create(alice, BaseContractWasm), + // contract::add_function("test", {}), + // contract::add_instance_param( + // 0xFF000000, "param", "UINT8", 1), // Invalid flag + // ter(temINVALID_FLAG)); + // } + + // // Test 22: Success - ContractCode with Functions + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // env(contract::create(alice, BaseContractWasm), + // contract::add_function("base", {}), + // fee(XRP(200)), + // ter(tesSUCCESS)); + // } + + // // Test 23: Success - ContractCode with Functions and parameters + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // env(contract::create(alice, BaseContractWasm), + // contract::add_function( + // "base", {{0, "param1", "UINT8"}, {0, "param2", + // "UINT32"}}), + // fee(XRP(200)), + // ter(tesSUCCESS)); + // } + + // // Test 24: Success - with InstanceParameters and + // // InstanceParameterValues + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // env(contract::create(alice, BaseContractWasm), + // contract::add_instance_param(0, "uint8", "UINT8", 1), + // contract::add_instance_param(0, "uint32", "UINT32", 100), + // contract::add_function("base", {}), + // fee(XRP(200)), + // ter(tesSUCCESS)); + // } + } + + void + testCreatePreclaim(FeatureBitset features) + { + testcase("create preclaim"); + + using namespace jtx; + + // temMALFORMED: ContractHash provided but no corresponding + // ContractSource exists + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create( + alice, + uint256{"D955DAC2E77519F05AD151A5D3C99FC8125FB39D58FF9F106F" + "1ACA4491902C25"}), + fee(XRP(200)), + ter(temMALFORMED)); + } + + // temMALFORMED: ContractCode provided is empty + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, ""), // Empty code + contract::add_function("test", {}), + fee(XRP(200)), + ter(temMALFORMED)); + } + + // tesSUCCESS: ContractCode provided, ContractSource doesn't exist yet + // (first create) + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + + // TODO: Validate + } + + // tesSUCCESS: ContractCode provided, ContractSource already exists + // (install) + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // First create + env(contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + env.close(); + + // Second create with same code (install) + env(contract::create(alice, BaseContractWasm), + contract::add_instance_param( + 0, "uint8", "UINT8", 2), // Different value + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + + // TODO: Validate + } + + // tesSUCCESS: ContractHash provided with valid ContractSource + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // First create to establish ContractSource + env(contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + env.close(); + + // Get the hash of the contract + auto const wasmBytes = strUnHex(BaseContractWasm); + uint256 const contractHash = getContractHash(*wasmBytes); + + // Install using ContractHash + env(contract::create(alice, contractHash), + contract::add_instance_param(0, "uint8", "UINT8", 2), + fee(XRP(200)), + ter(tesSUCCESS)); + + // TODO: Validate + } + + // temMALFORMED: Install with InstanceParameterValues that don't + // match + // ContractSource + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // First create with specific InstanceParameters + env(contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_instance_param(0, "uint32", "UINT32", 100), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + env.close(); + + // Get the hash + auto const wasmBytes = strUnHex(BaseContractWasm); + uint256 const contractHash = getContractHash(*wasmBytes); + + // Try to install with mismatched InstanceParameterValues + // Only providing one parameter when ContractSource expects two + env(contract::create(alice, contractHash), + contract::add_instance_param(0, "uint8", "UINT8", 2), + fee(XRP(200)), + ter(temMALFORMED)); + } + + // temMALFORMED: ContractHash provided but ContractSource doesn't + // exist + // (should fail in preclaim) + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // Use Base2ContractWasm hash which hasn't been created yet + auto const wasmBytes = strUnHex(Base2ContractWasm); + uint256 const contractHash = getContractHash(*wasmBytes); + + env(contract::create(alice, contractHash), + fee(XRP(200)), + ter(temMALFORMED)); + } + + // tesSUCCESS: ContractCode with InstanceParameters for first + // creation + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, Base2ContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 255), + contract::add_instance_param( + tfSendAmount, "amount", "AMOUNT", XRP(100)), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + } + + // tesSUCCESS: Multiple installs of same contract + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + // Alice creates first instance + env(contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + env.close(); + + // Bob installs same contract + env(contract::create(bob, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 2), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + env.close(); + + // Alice installs another instance + env(contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 3), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + } + } + + void + testCreateDoApply(FeatureBitset features) + { + testcase("create doApply"); + + using namespace jtx; + + //---------------------------------------------------------------------- + // doApply.ContractCode.tesSUCCESS + + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // auto const seq = env.current()->seq(); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + token::uri("https://example.com/contract"), + fee(XRP(200))); + + // validate contract + // validateContract( + // env, + // contractAccount.id(), + // alice.id(), + // 0, + // seq, + // contractHash, + // jv[sfInstanceParameterValues], + // to_string(jv[sfURI])); + + // validate contract source + // validateContractSource( + // env, *wasmBytes, contractHash, 1, jv[sfFunctions]); + } + + //---------------------------------------------------------------------- + // doApply.ContractHash.tesSUCCESS + + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // auto const wasmBytes = strUnHex(BaseContractWasm); + // uint256 const contractHash = getContractHash(*wasmBytes); + + // Create Contract. + { + // auto const seq = env.current()->seq(); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + // validate contract + // validateContract( + // env, + // contractAccount.id(), + // alice.id(), + // 0, + // seq, + // contractHash, + // jv[sfInstanceParameterValues]); + + // validate contract source + // validateContractSource( + // env, *wasmBytes, contractHash, 1, jv[sfFunctions]); + } + + // Install Contract. + { + // auto const seq = env.current()->seq(); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + // validate contract + // validateContract( + // env, + // contractAccount.id(), + // alice.id(), + // 0, + // seq, + // contractHash, + // jv[sfInstanceParameterValues]); + + // validate contract source + // validateContractSource( + // env, *wasmBytes, contractHash, 2, jv[sfFunctions]); + } + } + } + + void + testModifyDoApply(FeatureBitset features) + { + testcase("modify doApply"); + + using namespace jtx; + + //---------------------------------------------------------------------- + // doApply.ContractCode.tesSUCCESS + + { + test::jtx::Env env{*this, features}; + + jtx::Account const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // Create initial contract + auto const seq = env.seq(alice); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + // Modify contract + auto jt = env.jt( + contract::modify(alice, contractAccount, Base2ContractWasm), + contract::add_instance_param(0, "uint16", "UINT16", 1), + contract::add_function("base", {{0, "uint16", "UINT16"}}), + fee(XRP(200))); + env(jt, ter(tesSUCCESS)); + env.close(); + + // old contract source is deleted + auto const [sourceKey, sourceSle] = + contractSourceKeyAndSle(*env.current(), contractHash); + BEAST_EXPECT(!sourceSle); + + // new contract source exists + auto const wasmBytes = strUnHex(Base2ContractWasm); + uint256 const newContractHash = xrpl::sha512Half_s( + xrpl::Slice(wasmBytes->data(), wasmBytes->size())); + auto const [contractKey, contractSle] = + contractSourceKeyAndSle(*env.current(), newContractHash); + BEAST_EXPECT(contractSle); + + // validate modified contract + auto const k = keylet::contract(contractHash, alice, seq); + validateContract( + env, + k, + contractAccount.id(), + alice.id(), + 0, + seq, + newContractHash, + jt.jv[sfInstanceParameterValues]); + } + + //---------------------------------------------------------------------- + // doApply.ContractHash.tesSUCCESS + + { + test::jtx::Env env{*this, features}; + + jtx::Account const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // Create initial contract + // auto const seq = env.seq(alice); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + auto const [contractAccount2, contractHash2, jv2] = setContract( + env, + tesSUCCESS, + contract::create(alice, Base2ContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + // Modify contract + auto jt = env.jt( + contract::modify(alice, contractAccount, contractHash2), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + env(jt, ter(tesSUCCESS)); + env.close(); + + // old contract source is deleted + auto const [oldSourceKey, oldSourceSle] = + contractSourceKeyAndSle(*env.current(), contractHash); + BEAST_EXPECT(!oldSourceSle); + + // new contract source exists + auto const [sourceKey, sourceSle] = + contractSourceKeyAndSle(*env.current(), contractHash2); + BEAST_EXPECT(sourceSle); + BEAST_EXPECT(sourceSle->getFieldU64(sfReferenceCount) == 2); + + // // validate modified contract + // auto const k = keylet::contract(contractHash, seq); + // validateContract( + // env, + // k, + // contractAccount.id(), + // alice.id(), + // 0, + // seq, + // newContractHash, + // jt.jv[sfInstanceParameterValues]); + } + + //---------------------------------------------------------------------- + // doApply.ContractOwner.tesSUCCESS + + { + test::jtx::Env env{*this, features}; + + jtx::Account const alice = Account{"alice"}; + jtx::Account const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + // Create initial contract + auto const seq = env.seq(alice); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + // Modify contract + auto jt = env.jt( + contract::modify(alice, contractAccount, bob), fee(XRP(200))); + env(jt, ter(tesSUCCESS)); + env.close(); + + // validate modified contract + auto const k = keylet::contract(contractHash, alice, seq); + validateContract( + env, + k, + contractAccount.id(), + bob.id(), + 0, + seq, + contractHash, + jv[sfInstanceParameterValues]); + } + } + + void + testDeleteDoApply(FeatureBitset features) + { + testcase("delete doApply"); + + using namespace jtx; + + //------------------------------------------------------------------------- + // doApply.tesSUCCESS - Single Reference + { + test::jtx::Env env{*this, features}; + + jtx::Account const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + auto const seq = env.current()->seq(); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + env(contract::del(alice, contractAccount), ter(tesSUCCESS)); + env.close(); + + // Pseudo Account is deleted + auto const pseudoAccountKey = keylet::account(contractAccount); + BEAST_EXPECT(!env.le(pseudoAccountKey)); + + // Contract instance is deleted + auto const wasmBytes = strUnHex(BaseContractWasm); + auto const contractKey = keylet::contract(contractHash, alice, seq); + BEAST_EXPECT(!env.le(contractKey)); + + // ContractSource is deleted - because it had a single reference + auto const contractSourceKey = keylet::contractSource(contractHash); + BEAST_EXPECT(!env.le(contractSourceKey)); + } + + // doApply.tesSUCCESS - Multiple Reference + { + test::jtx::Env env{*this, features}; + + jtx::Account const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + auto const seq = env.current()->seq(); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + auto const seq2 = env.current()->seq(); + auto const [contractAccount2, contractHash2, jv2] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + env(contract::del(alice, contractAccount), ter(tesSUCCESS)); + env.close(); + + // Pseudo Account is deleted + auto const pseudoAccountKey = keylet::account(contractAccount); + BEAST_EXPECT(!env.le(pseudoAccountKey)); + + // Contract instance is deleted + auto const wasmBytes = strUnHex(BaseContractWasm); + auto const contractKey = keylet::contract(contractHash, alice, seq); + BEAST_EXPECT(!env.le(contractKey)); + + // Ensure ContractSource still exists + auto const contractSourceKey = keylet::contractSource(contractHash); + BEAST_EXPECT(env.le(contractSourceKey)); + BEAST_EXPECT( + env.le(contractSourceKey)->getFieldU64(sfReferenceCount) == 1); + + // Pseudo Account of second instance still exists + auto const pseudoAccountKey2 = keylet::account(contractAccount2); + BEAST_EXPECT(env.le(pseudoAccountKey2)); + + // Ensure second contract instance still exists + auto const contractKey2 = + keylet::contract(contractHash2, alice, seq2); + BEAST_EXPECT(env.le(contractKey2)); + } + } + + void + testUserDeletePreflight(FeatureBitset features) + { + testcase("user delete preflight"); + + using namespace jtx; + + // temDISABLED: Feature not enabled + { + test::jtx::Env env{*this, features - featureSmartContract}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::userDelete(alice, BaseContractWasm), + ter(temDISABLED)); + } + } + + std::string + loadContractWasmStr(std::string const& contract_name = "") + { + std::string const& dir = "e2e-tests"; + std::string name = + "/Users/darkmatter/projects/ledger-works/xrpl-wasm-std/" + dir + + "/" + contract_name + "/target/wasm32v1-none/release/" + + contract_name + ".wasm"; + if (!boost::filesystem::exists(name)) + { + std::cout << "File does not exist: " << name << "\n"; + return ""; + } + + std::ifstream file(name, std::ios::binary); + + if (!file) + { + std::cout << "Failed to open file: " << name << "\n"; + return ""; + } + + // Read the file into a vector + std::vector buffer( + (std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + // Check if the buffer is empty + if (buffer.empty()) + { + std::cout << "File is empty or could not be read properly.\n"; + return ""; + } + + return strHex(buffer); + } + + void + testContractDataSimple(FeatureBitset features) + { + testcase("contract data simple"); + + using namespace jtx; + + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + // std::string wasmHex = loadContractWasmStr("contract_data"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, contractDataWasmHex), + contract::add_instance_param( + tfSendAmount, "value", "AMOUNT", XRP(2000)), + contract::add_function("object_simple_create", {}), + contract::add_function("object_simple_update", {}), + fee(XRP(2000))); + + env(contract::call(alice, contractAccount, "object_simple_create"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + + env(contract::call(alice, contractAccount, "object_simple_update"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + } + + void + testContractDataNested(FeatureBitset features) + { + testcase("contract data nested"); + + using namespace jtx; + + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + // std::string wasmHex = loadContractWasmStr("contract_data"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, contractDataWasmHex), + contract::add_instance_param( + tfSendAmount, "value", "AMOUNT", XRP(2000)), + contract::add_function("object_nested_create", {}), + contract::add_function("object_nested_update", {}), + fee(XRP(2000))); + + env(contract::call(alice, contractAccount, "object_nested_create"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + + env(contract::call(alice, contractAccount, "object_nested_update"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + } + + void + testContractDataArray(FeatureBitset features) + { + testcase("contract data array"); + + using namespace jtx; + + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + // std::string wasmHex = loadContractWasmStr("contract_data"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, contractDataWasmHex), + contract::add_instance_param( + tfSendAmount, "value", "AMOUNT", XRP(2000)), + contract::add_function("object_with_arrays_create", {}), + contract::add_function("object_with_arrays_update", {}), + fee(XRP(2000))); + + env(contract::call(alice, contractAccount, "object_with_arrays_create"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + + env(contract::call(alice, contractAccount, "object_with_arrays_update"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + } + + void + testContractDataNestedArray(FeatureBitset features) + { + testcase("contract data nested array"); + + using namespace jtx; + + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + // std::string wasmHex = loadContractWasmStr("contract_data"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, contractDataWasmHex), + contract::add_instance_param( + tfSendAmount, "value", "AMOUNT", XRP(2000)), + contract::add_function("object_with_nested_arrays_create", {}), + contract::add_function("object_with_nested_arrays_update", {}), + fee(XRP(2000))); + + env(contract::call( + alice, contractAccount, "object_with_nested_arrays_create"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + + env(contract::call( + alice, contractAccount, "object_with_nested_arrays_update"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + } + + void + testInstanceParameters(FeatureBitset features) + { + testcase("instance parameters"); + + using namespace jtx; + + Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const gw = Account{"gw"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob); + env.close(); + + // Test Instance Parameter (1 of 2) + // uint8, uint16, uint32, uint64, uint128, uint160, uint192, uint256 + { + std::string wasmHex = loadContractWasmStr("instance_params_uint"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, wasmHex), + contract::add_instance_param(0, "uint8", "UINT8", 255), + contract::add_instance_param(0, "uint16", "UINT16", 65535), + contract::add_instance_param( + 0, + "uint32", + "UINT32", + static_cast(4294967295)), + contract::add_instance_param( + 0, "uint64", "UINT64", "FFFFFFFFFFFFFFFF"), + contract::add_instance_param( + 0, + "uint128", + "UINT128", + "00000000000000000000000000000001"), + contract::add_instance_param( + 0, + "uint160", + "UINT160", + "0000000000000000000000000000000000000001"), + contract::add_instance_param( + 0, + "uint192", + "UINT192", + "000000000000000000000000000000000000000000000001"), + contract::add_instance_param( + 0, + "uint256", + "UINT256", + "D955DAC2E77519F05AD151A5D3C99FC8125FB39D58FF9F106F1ACA4491" + "902C" + "25"), + contract::add_function("instance_params_uint", {}), + fee(XRP(200))); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", + // to_string(params)); std::cout << jrr << std::endl; + // } + + env(contract::call(alice, contractAccount, "instance_params_uint"), + escrow::comp_allowance(1000000), + ter(tesSUCCESS)); + env.close(); + } + + { + // Test Instance Parameter (2 of 2) + // vl, account, amount (XRP), amount (IOU), number, currency, issue + std::string wasmHex = loadContractWasmStr("instance_params_other"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, wasmHex), + contract::add_instance_param(0, "vl", "VL", "DEADBEEF"), + contract::add_instance_param( + 0, "account", "ACCOUNT", alice.human()), + contract::add_instance_param( + 0, + "amountXRP", + "AMOUNT", + XRP(1).value().getJson(JsonOptions::none)), + contract::add_instance_param( + 0, + "amountIOU", + "AMOUNT", + USD(1.2).value().getJson(JsonOptions::none)), + contract::add_instance_param(0, "number", "NUMBER", "1.2"), + contract::add_function("instance_params_other", {}), + fee(XRP(200))); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", + // to_string(params)); std::cout << jrr << std::endl; + // } + + env(contract::call(alice, contractAccount, "instance_params_other"), + escrow::comp_allowance(1000000), + ter(tesSUCCESS)); + env.close(); + } + } + + void + testFunctionParameters(FeatureBitset features) + { + testcase("function parameters"); + + using namespace jtx; + + Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const gw = Account{"gw"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob); + env.close(); + + std::string wasmHex = loadContractWasmStr("function_params"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, wasmHex), + contract::add_instance_param( + tfSendAmount, "amount", "AMOUNT", XRP(2000)), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function( + "function_params_uint", + { + {0, "uint8", "UINT8"}, + {0, "uint16", "UINT16"}, + {0, "uint32", "UINT32"}, + {0, "uint64", "UINT64"}, + {0, "uint128", "UINT128"}, + {0, "uint160", "UINT160"}, + {0, "uint192", "UINT192"}, + {0, "uint256", "UINT256"}, + }), + contract::add_function( + "function_params_other", + { + {0, "vl", "VL"}, + {0, "account", "ACCOUNT"}, + {0, "amountXRP", "AMOUNT"}, + {0, "amountIOU", "AMOUNT"}, + {0, "number", "NUMBER"}, + // {0, "issue", "ISSUE"}, + // {0, "currency", "CURRENCY"} + }), + fee(XRP(200))); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + // } + + env(contract::call(alice, contractAccount, "function_params_uint"), + escrow::comp_allowance(1000000), + contract::add_param(0, "uint8", "UINT8", 255), + contract::add_param(0, "uint16", "UINT16", 65535), + contract::add_param( + 0, "uint32", "UINT32", static_cast(4294967295)), + contract::add_param(0, "uint64", "UINT64", "FFFFFFFFFFFFFFFF"), + contract::add_param( + 0, "uint128", "UINT128", "00000000000000000000000000000001"), + contract::add_param( + 0, + "uint160", + "UINT160", + "0000000000000000000000000000000000000001"), + contract::add_param( + 0, + "uint192", + "UINT192", + "000000000000000000000000000000000000000000000001"), + contract::add_param( + 0, + "uint256", + "UINT256", + "D955DAC2E77519F05AD151A5D3C99FC8125FB39D58FF9F106F1ACA4491902C" + "25"), + ter(tesSUCCESS)); + + env(contract::call(alice, contractAccount, "function_params_other"), + escrow::comp_allowance(1000000), + contract::add_param(0, "vl", "VL", "DEADBEEF"), + contract::add_param(0, "account", "ACCOUNT", alice.human()), + contract::add_param( + 0, + "amountXRP", + "AMOUNT", + XRP(1).value().getJson(JsonOptions::none)), + contract::add_param( + 0, + "amountIOU", + "AMOUNT", + USD(1.2).value().getJson(JsonOptions::none)), + contract::add_param(0, "number", "NUMBER", "1.2"), + // contract::add_param(0, "issue", "ISSUE", + // to_json(USD(1).value().issue())), contract::add_param(0, + // "currency", "CURRENCY", "USD"), + ter(tesSUCCESS)); + env.close(); + } + + void + testEmit(FeatureBitset features) + { + testcase("emit"); + + using namespace jtx; + + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + // std::string emitTxWasmHex = loadContractWasmStr("emit_txn"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, emitTxWasmHex), + contract::add_instance_param( + tfSendAmount, "value", "AMOUNT", XRP(2000)), + contract::add_function("emit", {}), + fee(XRP(200))); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + // } + + env(contract::call(alice, contractAccount, "emit"), + escrow::comp_allowance(1000000), + ter(tesSUCCESS)); + env.close(); + } + + void + testEvents(FeatureBitset features) + { + testcase("events"); + + using namespace std::chrono_literals; + using namespace jtx; + + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const gw = Account{"gw"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob); + env.close(); + + auto wsc = makeWSClient(env.app().config()); + Json::Value stream; + + { + // RPC subscribe to contract events stream + stream[jss::streams] = Json::arrayValue; + stream[jss::streams].append("contract_events"); + auto jv = wsc->invoke("subscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT( + jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT( + jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } + BEAST_EXPECT(jv[jss::result][jss::status] == "success"); + } + + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, eventsWasmHex), + contract::add_instance_param( + tfSendAmount, "amount", "AMOUNT", XRP(2000)), + contract::add_function("events", {}), + fee(XRP(200))); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + // } + + env(contract::call(alice, contractAccount, "events"), + escrow::comp_allowance(1000000), + ter(tesSUCCESS)); + env.close(); + + { + // Get contract info + Json::Value params; + params[jss::contract_account] = contractAccount.human(); + params[jss::account] = alice.human(); + auto const jrr = + env.rpc("json", "contract_info", to_string(params)); + std::cout << jrr << std::endl; + } + + // Check stream update + BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { + auto const data = jv[jss::data]; + // std::cout << "Event: " << data << std::endl; + BEAST_EXPECT(data["amount"] == "192"); + BEAST_EXPECT(data["currency"] == "USD"); + BEAST_EXPECT( + data["destination"] == "r99mpXDsCPybsGs9XzGJmuxa8gWLTn8aCz"); + BEAST_EXPECT(data["uint128"] == "00000000000000000000000000000000"); + BEAST_EXPECT(data["uint16"] == 16); + BEAST_EXPECT( + data["uint160"] == "0000000000000000000000000000000000000000"); + BEAST_EXPECT( + data["uint192"] == + "000000000000000000000000000000000000000000000000"); + BEAST_EXPECT( + data["uint256"] == + "00000000000000000000000000000000000000000000000000000000000000" + "00"); + BEAST_EXPECT(data["uint32"] == 32); + BEAST_EXPECT(data["uint64"] == "40"); + BEAST_EXPECT(data["uint8"] == 8); + BEAST_EXPECT(data["vl"] == "48656C6C6F2C20576F726C6421"); + return jv[jss::type] == "contractEvent" && + jv[jss::name] == "event1"; + })); + + // RPC unsubscribe + auto jv = wsc->invoke("unsubscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT( + jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT( + jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } + BEAST_EXPECT(jv[jss::status] == "success"); + } + + void + testEasyMode(FeatureBitset features) + { + testcase("easy mode"); + + using namespace jtx; + + Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const gw = Account{"gw"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob); + env.close(); + + std::string wasmHex = loadContractWasmStr("easymode"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, wasmHex), + contract::add_instance_param( + tfSendAmount, "amount", "AMOUNT", XRP(2000)), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function( + "easymode", + { + {0, "account", "ACCOUNT"}, + {0, "amount", "AMOUNT"}, + }), + fee(XRP(200))); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + // } + + env(contract::call(alice, contractAccount, "easymode"), + escrow::comp_allowance(1000000), + contract::add_param(0, "account", "ACCOUNT", bob.human()), + contract::add_param(0, "amount", "AMOUNT", XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + // } + } + + void + testWithFeats(FeatureBitset features) + { + testCreatePreflight(features); + testCreatePreclaim(features); + testCreateDoApply(features); + // testModifyPreflight(features); + // testModifyPreclaim(features); + testModifyDoApply(features); + // testDeletePreflight(features); + // testDeletePreclaim(features); + testDeleteDoApply(features); + // testUserDeletePreflight(features); + // testUserDeletePreclaim(features); + // testUserDeleteDoApply(features); + testContractDataSimple(features); + testContractDataNested(features); + testContractDataArray(features); + testContractDataNestedArray(features); + testInstanceParameters(features); + testFunctionParameters(features); + testEmit(features); + // testEvents(features); + } + +public: + void + run() override + { + using namespace test::jtx; + auto const sa = testable_amendments(); + testWithFeats(sa); + } +}; + +BEAST_DEFINE_TESTSUITE(Contract, app, xrpl); + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/EscrowSmart_test.cpp b/src/test/app/EscrowSmart_test.cpp new file mode 100644 index 0000000000..1ff82dff40 --- /dev/null +++ b/src/test/app/EscrowSmart_test.cpp @@ -0,0 +1,1012 @@ +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { +namespace test { + +struct EscrowSmart_test : public beast::unit_test::suite +{ + void + testCreateFinishFunctionPreflight(FeatureBitset features) + { + testcase("Test preflight checks involving FinishFunction"); + + using namespace jtx; + using namespace std::chrono; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + // Tests whether the ledger index is >= 5 + // getLedgerSqn() >= 5} + static auto wasmHex = ledgerSqnWasmHex; + + { + // featureSmartEscrow disabled + Env env(*this, features - featureSmartEscrow); + env.fund(XRP(5000), alice, carol); + XRPAmount const txnFees = env.current()->fees().base + 1000; + auto escrowCreate = escrow::create(alice, carol, XRP(1000)); + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::cancel_time(env.now() + 100s), + fee(txnFees), + ter(temDISABLED)); + env.close(); + + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::cancel_time(env.now() + 100s), + escrow::data("00112233"), + fee(txnFees), + ter(temDISABLED)); + env.close(); + } + + { + // FinishFunction > max length + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.extension_size_limit = 10; // 10 bytes + return cfg; + }), + features); + XRPAmount const txnFees = env.current()->fees().base + 1000; + // create escrow + env.fund(XRP(5000), alice, carol); + + auto escrowCreate = escrow::create(alice, carol, XRP(500)); + + // 11-byte string + std::string longWasmHex = "00112233445566778899AA"; + env(escrowCreate, + escrow::finish_function(longWasmHex), + escrow::cancel_time(env.now() + 100s), + fee(txnFees), + ter(temMALFORMED)); + env.close(); + } + + { + // Data without FinishFunction + Env env(*this, features); + XRPAmount const txnFees = env.current()->fees().base + 100000; + // create escrow + env.fund(XRP(5000), alice, carol); + + auto escrowCreate = escrow::create(alice, carol, XRP(500)); + + std::string longData(4, 'A'); + env(escrowCreate, + escrow::data(longData), + escrow::finish_time(env.now() + 100s), + fee(txnFees), + ter(temMALFORMED)); + env.close(); + } + + { + // Data > max length + Env env(*this, features); + XRPAmount const txnFees = env.current()->fees().base + 100000; + // create escrow + env.fund(XRP(5000), alice, carol); + + auto escrowCreate = escrow::create(alice, carol, XRP(500)); + + // string of length maxWasmDataLength * 2 + 2 + std::string longData(maxWasmDataLength * 2 + 2, 'B'); + env(escrowCreate, + escrow::data(longData), + escrow::finish_function(wasmHex), + escrow::cancel_time(env.now() + 100s), + fee(txnFees), + ter(temMALFORMED)); + env.close(); + } + + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->START_UP = Config::FRESH; + return cfg; + }), + features); + XRPAmount const txnFees = + env.current()->fees().base * 10 + wasmHex.size() / 2 * 5; + // create escrow + env.fund(XRP(5000), alice, carol); + + auto escrowCreate = escrow::create(alice, carol, XRP(500)); + + // Success situations + { + // FinishFunction + CancelAfter + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::cancel_time(env.now() + 20s), + fee(txnFees)); + env.close(); + } + { + // FinishFunction + Condition + CancelAfter + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::cancel_time(env.now() + 30s), + escrow::condition(escrow::cb1), + fee(txnFees)); + env.close(); + } + { + // FinishFunction + FinishAfter + CancelAfter + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::cancel_time(env.now() + 40s), + escrow::finish_time(env.now() + 2s), + fee(txnFees)); + env.close(); + } + { + // FinishFunction + FinishAfter + Condition + CancelAfter + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::cancel_time(env.now() + 50s), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 2s), + fee(txnFees)); + env.close(); + } + + // Failure situations (i.e. all other combinations) + { + // only FinishFunction + env(escrowCreate, + escrow::finish_function(wasmHex), + fee(txnFees), + ter(temBAD_EXPIRATION)); + env.close(); + } + { + // FinishFunction + FinishAfter + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::finish_time(env.now() + 2s), + fee(txnFees), + ter(temBAD_EXPIRATION)); + env.close(); + } + { + // FinishFunction + Condition + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::condition(escrow::cb1), + fee(txnFees), + ter(temBAD_EXPIRATION)); + env.close(); + } + { + // FinishFunction + FinishAfter + Condition + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 2s), + fee(txnFees), + ter(temBAD_EXPIRATION)); + env.close(); + } + { + // FinishFunction 0 length + env(escrowCreate, + escrow::finish_function(""), + escrow::cancel_time(env.now() + 60s), + fee(txnFees), + ter(temMALFORMED)); + env.close(); + } + { + // Not enough fees + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::cancel_time(env.now() + 70s), + fee(txnFees - 1), + ter(telINSUF_FEE_P)); + env.close(); + } + + { + // FinishFunction nonexistent host function + // pub fn finish() -> bool { + // unsafe { host_lib::bad() >= 5 } + // } + auto const badWasmHex = + "0061736d010000000105016000017f02100108686f73745f6c696203626164" + "00000302010005030100100611027f00418080c0000b7f00418080c0000b07" + "2e04066d656d6f727902000666696e69736800010a5f5f646174615f656e64" + "03000b5f5f686561705f6261736503010a09010700100041044a0b004d0970" + "726f64756365727302086c616e6775616765010452757374000c70726f6365" + "737365642d6279010572757374631d312e38352e3120283465623136313235" + "3020323032352d30332d31352900490f7461726765745f6665617475726573" + "042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f72" + "65666572656e63652d74797065732b0a6d756c746976616c7565"; + env(escrowCreate, + escrow::finish_function(badWasmHex), + escrow::cancel_time(env.now() + 100s), + fee(txnFees), + ter(temBAD_WASM)); + env.close(); + } + } + + void + testFinishWasmFailures(FeatureBitset features) + { + testcase("EscrowFinish Smart Escrow failures"); + + using namespace jtx; + using namespace std::chrono; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + // Tests whether the ledger index is >= 5 + // getLedgerSqn() >= 5} + static auto wasmHex = ledgerSqnWasmHex; + + { + // featureSmartEscrow disabled + Env env(*this, features - featureSmartEscrow); + env.fund(XRP(5000), alice, carol); + XRPAmount const txnFees = + env.current()->fees().base * 10 + wasmHex.size() / 2 * 5; + env(escrow::finish(carol, alice, 1), + fee(txnFees), + escrow::comp_allowance(4), + ter(temDISABLED)); + env.close(); + } + + { + // ComputationAllowance > max compute limit + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.extension_compute_limit = 1'000; // in gas + return cfg; + }), + features); + env.fund(XRP(5000), alice, carol); + // Run past the flag ledger so that a Fee change vote occurs and + // updates FeeSettings. (It also activates all supported + // amendments.) + for (auto i = env.current()->seq(); i <= 257; ++i) + env.close(); + + auto const allowance = 1'001; + env(escrow::finish(carol, alice, 1), + fee(env.current()->fees().base + allowance), + escrow::comp_allowance(allowance), + ter(temBAD_LIMIT)); + } + + Env env(*this, features); + + // Run past the flag ledger so that a Fee change vote occurs and + // updates FeeSettings. (It also activates all supported + // amendments.) + for (auto i = env.current()->seq(); i <= 257; ++i) + env.close(); + + XRPAmount const txnFees = + env.current()->fees().base * 10 + wasmHex.size() / 2 * 5; + env.fund(XRP(5000), alice, carol); + + // create escrow + auto const seq = env.seq(alice); + env(escrow::create(alice, carol, XRP(500)), + escrow::finish_function(wasmHex), + escrow::cancel_time(env.now() + 100s), + fee(txnFees)); + env.close(); + + { + // no ComputationAllowance field + env(escrow::finish(carol, alice, seq), + ter(tefWASM_FIELD_NOT_INCLUDED)); + } + + { + // ComputationAllowance value of 0 + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(0), + ter(temBAD_LIMIT)); + } + + { + // not enough fees + // This function takes 4 gas + // In testing, 1 gas costs 1 drop + auto const finishFee = env.current()->fees().base + 3; + env(escrow::finish(carol, alice, seq), + fee(finishFee), + escrow::comp_allowance(4), + ter(telINSUF_FEE_P)); + } + + { + // not enough gas + // This function takes 4 gas + // In testing, 1 gas costs 1 drop + auto const finishFee = env.current()->fees().base + 4; + env(escrow::finish(carol, alice, seq), + fee(finishFee), + escrow::comp_allowance(2), + ter(tecFAILED_PROCESSING)); + } + + { + // ComputationAllowance field included w/no FinishFunction on + // escrow + auto const seq2 = env.seq(alice); + env(escrow::create(alice, carol, XRP(500)), + escrow::finish_time(env.now() + 10s), + escrow::cancel_time(env.now() + 100s)); + env.close(); + + auto const allowance = 100; + env(escrow::finish(carol, alice, seq2), + fee(env.current()->fees().base + + (allowance * env.current()->fees().gasPrice) / + MICRO_DROPS_PER_DROP + + 1), + escrow::comp_allowance(allowance), + ter(tefNO_WASM)); + } + } + + void + testFinishFunction(FeatureBitset features) + { + testcase("Example escrow function"); + + using namespace jtx; + using namespace std::chrono; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + // Tests whether the ledger index is >= 5 + // getLedgerSqn() >= 5} + auto const& wasmHex = ledgerSqnWasmHex; + std::uint32_t const allowance = 65; + auto escrowCreate = escrow::create(alice, carol, XRP(1000)); + auto [createFee, finishFee] = [&]() { + Env env(*this, features); + auto createFee = + env.current()->fees().base * 10 + wasmHex.size() / 2 * 5; + auto finishFee = env.current()->fees().base + + (allowance * env.current()->fees().gasPrice) / + MICRO_DROPS_PER_DROP + + 1; + return std::make_pair(createFee, finishFee); + }(); + + { + // basic FinishFunction situation + Env env(*this, features); + // create escrow + env.fund(XRP(5000), alice, carol); + auto const seq = env.seq(alice); + BEAST_EXPECT(env.ownerCount(alice) == 0); + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::cancel_time(env.now() + 100s), + fee(createFee)); + env.close(); + + if (BEAST_EXPECT(env.ownerCount(alice) == 2)) + { + env.require(balance(alice, XRP(4000) - createFee)); + env.require(balance(carol, XRP(5000))); + + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + env(escrow::finish(alice, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + env(escrow::finish(alice, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + env.close(); + + { + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + env.meta()->getFieldU32(sfGasUsed) == allowance, + std::to_string(env.meta()->getFieldU32(sfGasUsed))); + } + + env(escrow::finish(alice, alice, seq), + fee(finishFee), + escrow::comp_allowance(allowance), + ter(tesSUCCESS)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + txMeta->getFieldU32(sfGasUsed) == allowance, + std::to_string(txMeta->getFieldU32(sfGasUsed))); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECTS( + txMeta->getFieldI32(sfWasmReturnCode) == 5, + std::to_string(txMeta->getFieldI32(sfWasmReturnCode))); + + BEAST_EXPECT(env.ownerCount(alice) == 0); + } + } + + { + // FinishFunction + Condition + Env env(*this, features); + env.fund(XRP(5000), alice, carol); + BEAST_EXPECT(env.ownerCount(alice) == 0); + auto const seq = env.seq(alice); + // create escrow + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::condition(escrow::cb1), + escrow::cancel_time(env.now() + 100s), + fee(createFee)); + env.close(); + auto const conditionFinishFee = finishFee + + env.current()->fees().base * (32 + (escrow::fb1.size() / 16)); + + if (BEAST_EXPECT(env.ownerCount(alice) == 2)) + { + env.require(balance(alice, XRP(4000) - createFee)); + env.require(balance(carol, XRP(5000))); + + // no fulfillment provided, function fails + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecCRYPTOCONDITION_ERROR)); + // fulfillment provided, function fails + env(escrow::finish(carol, alice, seq), + escrow::condition(escrow::cb1), + escrow::fulfillment(escrow::fb1), + escrow::comp_allowance(allowance), + fee(conditionFinishFee), + ter(tecWASM_REJECTED)); + if (BEAST_EXPECT(env.meta()->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + env.meta()->getFieldU32(sfGasUsed) == allowance, + std::to_string(env.meta()->getFieldU32(sfGasUsed))); + env.close(); + // no fulfillment provided, function succeeds + env(escrow::finish(alice, alice, seq), + escrow::comp_allowance(allowance), + fee(conditionFinishFee), + ter(tecCRYPTOCONDITION_ERROR)); + // wrong fulfillment provided, function succeeds + env(escrow::finish(alice, alice, seq), + escrow::condition(escrow::cb1), + escrow::fulfillment(escrow::fb2), + escrow::comp_allowance(allowance), + fee(conditionFinishFee), + ter(tecCRYPTOCONDITION_ERROR)); + // fulfillment provided, function succeeds, tx succeeds + env(escrow::finish(alice, alice, seq), + escrow::condition(escrow::cb1), + escrow::fulfillment(escrow::fb1), + escrow::comp_allowance(allowance), + fee(conditionFinishFee), + ter(tesSUCCESS)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECTS( + txMeta->getFieldI32(sfWasmReturnCode) == 6, + std::to_string(txMeta->getFieldI32(sfWasmReturnCode))); + + env.close(); + BEAST_EXPECT(env.ownerCount(alice) == 0); + } + } + + { + // FinishFunction + FinishAfter + Env env(*this, features); + // create escrow + env.fund(XRP(5000), alice, carol); + auto const seq = env.seq(alice); + BEAST_EXPECT(env.ownerCount(alice) == 0); + auto const ts = env.now() + 97s; + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::finish_time(ts), + escrow::cancel_time(env.now() + 1000s), + fee(createFee)); + env.close(); + + if (BEAST_EXPECT(env.ownerCount(alice) == 2)) + { + env.require(balance(alice, XRP(4000) - createFee)); + env.require(balance(carol, XRP(5000))); + + // finish time hasn't passed, function fails + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee + 1), + ter(tecNO_PERMISSION)); + env.close(); + // finish time hasn't passed, function succeeds + for (; env.now() < ts; env.close()) + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee + 2), + ter(tecNO_PERMISSION)); + + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee + 1), + ter(tesSUCCESS)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECTS( + txMeta->getFieldI32(sfWasmReturnCode) == 13, + std::to_string(txMeta->getFieldI32(sfWasmReturnCode))); + + BEAST_EXPECT(env.ownerCount(alice) == 0); + } + } + + { + // FinishFunction + FinishAfter #2 + Env env(*this, features); + // create escrow + env.fund(XRP(5000), alice, carol); + auto const seq = env.seq(alice); + BEAST_EXPECT(env.ownerCount(alice) == 0); + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::finish_time(env.now() + 2s), + escrow::cancel_time(env.now() + 100s), + fee(createFee)); + // Don't close the ledger here + + if (BEAST_EXPECT(env.ownerCount(alice) == 2)) + { + env.require(balance(alice, XRP(4000) - createFee)); + env.require(balance(carol, XRP(5000))); + + // finish time hasn't passed, function fails + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecNO_PERMISSION)); + env.close(); + + // finish time has passed, function fails + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + if (BEAST_EXPECT(env.meta()->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + env.meta()->getFieldU32(sfGasUsed) == allowance, + std::to_string(env.meta()->getFieldU32(sfGasUsed))); + env.close(); + // finish time has passed, function succeeds, tx succeeds + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tesSUCCESS)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECTS( + txMeta->getFieldI32(sfWasmReturnCode) == 6, + std::to_string(txMeta->getFieldI32(sfWasmReturnCode))); + + env.close(); + BEAST_EXPECT(env.ownerCount(alice) == 0); + } + } + } + + void + testUpdateDataOnFailure(FeatureBitset features) + { + testcase("Update escrow data on failure"); + + using namespace jtx; + using namespace std::chrono; + + // wasm that always fails + static auto const wasmHex = updateDataWasmHex; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + Env env(*this, features); + // create escrow + env.fund(XRP(5000), alice); + auto const seq = env.seq(alice); + BEAST_EXPECT(env.ownerCount(alice) == 0); + auto escrowCreate = escrow::create(alice, alice, XRP(1000)); + XRPAmount txnFees = + env.current()->fees().base * 10 + wasmHex.size() / 2 * 5; + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::finish_time(env.now() + 2s), + escrow::cancel_time(env.now() + 100s), + fee(txnFees)); + env.close(); + env.close(); + env.close(); + + if (BEAST_EXPECT( + env.ownerCount(alice) == (1 + wasmHex.size() / 2 / 500))) + { + env.require(balance(alice, XRP(4000) - txnFees)); + + auto const allowance = 1014; + XRPAmount const finishFee = env.current()->fees().base + + (allowance * env.current()->fees().gasPrice) / + MICRO_DROPS_PER_DROP + + 1; + + // FinishAfter time hasn't passed + env(escrow::finish(alice, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + txMeta->getFieldU32(sfGasUsed) == allowance, + std::to_string(txMeta->getFieldU32(sfGasUsed))); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECTS( + txMeta->getFieldI32(sfWasmReturnCode) == -256, + std::to_string(txMeta->getFieldI32(sfWasmReturnCode))); + + auto const sle = env.le(keylet::escrow(alice, seq)); + if (BEAST_EXPECT(sle && sle->isFieldPresent(sfData))) + BEAST_EXPECTS( + checkVL(sle, sfData, "Data"), + strHex(sle->getFieldVL(sfData))); + } + } + + void + testFees(FeatureBitset features) + { + testcase("Fees"); + + using namespace jtx; + using namespace std::chrono; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + // Tests whether the ledger index is >= 5 + // getLedgerSqn() >= 5} + auto const& wasmHex = ledgerSqnWasmHex; + uint64_t const allowance = 65; + auto escrowCreate = escrow::create(alice, carol, XRP(1000)); + auto createFee = [&]() { + Env env(*this, features); + auto createFee = + env.current()->fees().base * 10 + wasmHex.size() / 2 * 5; + return createFee; + }(); + + { + // ensure fees don't overflow + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.gas_price = 1'000'000; // in gas + return cfg; + }), + features); + // Run past the flag ledger so that a Fee change vote occurs and + // updates FeeSettings. (It also activates all supported + // amendments.) + for (auto i = env.current()->seq(); i <= 257; ++i) + env.close(); + + // create escrow + env.fund(XRP(5000), alice, carol); + auto const seq = env.seq(alice); + BEAST_EXPECT(env.ownerCount(alice) == 0); + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::cancel_time(env.now() + 100s), + fee(createFee)); + env.close(); + + if (BEAST_EXPECT(env.ownerCount(alice) == 2)) + { + env.require(balance(alice, XRP(4000) - createFee)); + env.require(balance(carol, XRP(5000))); + env.close(); + + auto const bigAllowance = 996'433; + uint64_t partialFeeCalc = + (static_cast(bigAllowance) * 1'000'000) / + MICRO_DROPS_PER_DROP + + 1; // to avoid an overflow + auto finishFee = env.current()->fees().base + partialFeeCalc; + BEAST_EXPECT(finishFee.drops() > bigAllowance); + + // Intentional low value to test overflow handling + auto finishFeeOverflow = drops(30); + + env(escrow::finish(alice, alice, seq), + fee(finishFeeOverflow), // enough if there's an overflow + escrow::comp_allowance(bigAllowance), + ter(telINSUF_FEE_P)); + + env(escrow::finish(alice, alice, seq), + fee(finishFee - 1), + escrow::comp_allowance(bigAllowance), + ter(telINSUF_FEE_P)); + + env(escrow::finish(alice, alice, seq), + fee(finishFee), + escrow::comp_allowance(bigAllowance), + ter(tesSUCCESS)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + txMeta->getFieldU32(sfGasUsed) == allowance, + std::to_string(txMeta->getFieldU32(sfGasUsed))); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECTS( + txMeta->getFieldI32(sfWasmReturnCode) == 260, + std::to_string(txMeta->getFieldI32(sfWasmReturnCode))); + + BEAST_EXPECT(env.ownerCount(alice) == 0); + } + } + } + + void + testAllHostFunctions(FeatureBitset features) + { + testcase("Test all host functions"); + + using namespace jtx; + using namespace std::chrono; + + // TODO: create wasm module for all host functions + static auto wasmHex = allHostFunctionsWasmHex; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + { + Env env(*this, features); + // create escrow + env.fund(XRP(5000), alice, carol); + auto const seq = env.seq(alice); + BEAST_EXPECT(env.ownerCount(alice) == 0); + auto escrowCreate = escrow::create(alice, carol, XRP(1000)); + XRPAmount txnFees = + env.current()->fees().base * 10 + wasmHex.size() / 2 * 5; + env(escrowCreate, + escrow::finish_function(wasmHex), + escrow::finish_time(env.now() + 11s), + escrow::cancel_time(env.now() + 100s), + escrow::data("1000000000"), // 1000 XRP in drops + fee(txnFees)); + env.close(); + + if (BEAST_EXPECT( + env.ownerCount(alice) == (1 + wasmHex.size() / 2 / 500))) + { + env.require(balance(alice, XRP(4000) - txnFees)); + env.require(balance(carol, XRP(5000))); + + auto const allowance = 1'000'000; + XRPAmount const finishFee = env.current()->fees().base + + (allowance * env.current()->fees().gasPrice) / + MICRO_DROPS_PER_DROP + + 1; + + // FinishAfter time hasn't passed + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecNO_PERMISSION)); + env.close(); + env.close(); + env.close(); + + // reduce the destination balance + env(pay(carol, alice, XRP(4500))); + env.close(); + env.close(); + + env(escrow::finish(alice, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tesSUCCESS)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + txMeta->getFieldU32(sfGasUsed) == 38'054, + std::to_string(txMeta->getFieldU32(sfGasUsed))); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECT(txMeta->getFieldI32(sfWasmReturnCode) == 1); + + env.close(); + BEAST_EXPECT(env.ownerCount(alice) == 0); + } + } + } + + void + testKeyletHostFunctions(FeatureBitset features) + { + testcase("Test all keylet host functions"); + + using namespace jtx; + using namespace std::chrono; + + // TODO: create wasm module for all host functions + static auto wasmHex = allKeyletsWasmHex; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + { + Env env{*this}; + env.fund(XRP(10000), alice, carol); + + BEAST_EXPECT(env.seq(alice) == 4); + BEAST_EXPECT(env.ownerCount(alice) == 0); + + // base objects that need to be created first + auto const tokenId = + token::getNextID(env, alice, 0, tfTransferable); + env(token::mint(alice, 0u), txflags(tfTransferable)); + env(trust(alice, carol["USD"](1'000'000))); + env.close(); + BEAST_EXPECT(env.seq(alice) == 6); + BEAST_EXPECT(env.ownerCount(alice) == 2); + + // set up a bunch of objects to check their keylets + AMM amm(env, carol, XRP(10), carol["USD"](1000)); + env(check::create(alice, carol, XRP(100))); + env(credentials::create(alice, alice, "termsandconditions")); + env(delegate::set(alice, carol, {"TrustSet"})); + env(deposit::auth(alice, carol)); + env(did::set(alice), did::data("alice_did")); + env(escrow::create(alice, carol, XRP(100)), + escrow::finish_time(env.now() + 100s)); + MPTTester mptTester{env, alice, {.fund = false}}; + mptTester.create(); + mptTester.authorize({.account = carol}); + env(token::createOffer(carol, tokenId, XRP(100)), + token::owner(alice)); + env(offer(alice, carol["GBP"](0.1), XRP(100))); + env(paychan::create(alice, carol, XRP(1000), 100s, alice.pk())); + pdomain::Credentials credentials{{alice, "first credential"}}; + env(pdomain::setTx(alice, credentials)); + env(signers(alice, 1, {{carol, 1}})); + env(ticket::create(alice, 1)); + Vault vault{env}; + auto [tx, _keylet] = + vault.create({.owner = alice, .asset = xrpIssue()}); + env(tx); + env.close(); + + BEAST_EXPECTS( + env.ownerCount(alice) == 17, + std::to_string(env.ownerCount(alice))); + if (BEAST_EXPECTS( + env.seq(alice) == 20, std::to_string(env.seq(alice)))) + { + auto const seq = env.seq(alice); + XRPAmount txnFees = + env.current()->fees().base * 10 + wasmHex.size() / 2 * 5; + env(escrow::create(alice, carol, XRP(1000)), + escrow::finish_function(wasmHex), + escrow::finish_time(env.now() + 2s), + escrow::cancel_time(env.now() + 100s), + fee(txnFees)); + env.close(); + env.close(); + env.close(); + + auto const allowance = 138'485; + auto const finishFee = env.current()->fees().base + + (allowance * env.current()->fees().gasPrice) / + MICRO_DROPS_PER_DROP + + 1; + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee)); + env.close(); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed))) + { + auto const gasUsed = txMeta->getFieldU32(sfGasUsed); + BEAST_EXPECTS( + gasUsed == allowance, std::to_string(gasUsed)); + } + BEAST_EXPECTS( + env.ownerCount(alice) == 17, + std::to_string(env.ownerCount(alice))); + } + } + } + + void + testWithFeats(FeatureBitset features) + { + testCreateFinishFunctionPreflight(features); + testFinishWasmFailures(features); + testFinishFunction(features); + testUpdateDataOnFailure(features); + testFees(features); + + // TODO: Update module with new host functions + testAllHostFunctions(features); + testKeyletHostFunctions(features); + } + +public: + void + run() override + { + using namespace test::jtx; + FeatureBitset const all{testable_amendments()}; + testWithFeats(all); + } +}; + +BEAST_DEFINE_TESTSUITE(EscrowSmart, app, xrpl); + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/EscrowToken_test.cpp b/src/test/app/EscrowToken_test.cpp index ff8b2cfb49..9acfbea2ca 100644 --- a/src/test/app/EscrowToken_test.cpp +++ b/src/test/app/EscrowToken_test.cpp @@ -3506,6 +3506,76 @@ struct EscrowToken_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(gw, MPT) == -outstandingWithFix); } + // test locked rate: finish + { + Env env{*this, features}; + auto const baseFee = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.transferFee = 25000, + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(50'000))); + env(pay(gw, bob, MPT(50'000))); + env.close(); + + // alice can create escrow w/ xfer rate + auto const preAlice = env.balance(alice, MPT); + auto const seq1 = env.seq(alice); + auto const delta = MPT(20000); + env(escrow::create(alice, bob, MPT(20000)), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 1s), + fee(baseFee * 150)); + env.close(); + auto const transferRate = escrow::rate(env, alice, seq1); + BEAST_EXPECT( + transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + + // BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 125); + // BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 125); + // BEAST_EXPECT(env.balance(gw, MPT) == MPT(-20'000)); + + // bob can finish escrow + env(escrow::finish(bob, alice, seq1), + escrow::condition(escrow::cb1), + escrow::fulfillment(escrow::fb1), + fee(baseFee * 150)); + env.close(); + + env(pay(bob, alice, MPT(20'000)), txflags(tfPartialPayment)); + env.close(); + + { + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + std::cout << jrr << std::endl; + } + + BEAST_EXPECT(env.balance(alice, MPT) == preAlice - delta); + BEAST_EXPECT(env.balance(bob, MPT) == MPT(10'100)); + + auto const escrowedWithFix = + env.current()->rules().enabled(fixTokenEscrowV1) ? 0 : 25; + auto const outstandingWithFix = + env.current()->rules().enabled(fixTokenEscrowV1) ? MPT(19'975) + : MPT(20'000); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == escrowedWithFix); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == escrowedWithFix); + BEAST_EXPECT(env.balance(gw, MPT) == -outstandingWithFix); + } + // test locked rate: cancel { Env env{*this, features}; diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index d4a9fc5b9d..caffb4ff09 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -1507,7 +1507,7 @@ struct Escrow_test : public beast::unit_test::suite Account const alice{"alice"}; Account const bob{"bob"}; Account const carol{"carol"}; - Account const dillon{"dillon "}; + Account const dillon{"dillon"}; Account const zelda{"zelda"}; char const credType[] = "abcde"; @@ -1674,6 +1674,8 @@ public: FeatureBitset const all{testable_amendments()}; testWithFeats(all); testWithFeats(all - featureTokenEscrow); + testWithFeats(all - featureSmartEscrow); + testWithFeats(all - featureTokenEscrow - featureSmartEscrow); testTags(all - fixIncludeKeyletFields); } }; diff --git a/src/test/app/FeeVote_test.cpp b/src/test/app/FeeVote_test.cpp index c7f33544f9..71aa84eda2 100644 --- a/src/test/app/FeeVote_test.cpp +++ b/src/test/app/FeeVote_test.cpp @@ -24,13 +24,17 @@ struct FeeSettingsFields std::optional baseFeeDrops = std::nullopt; std::optional reserveBaseDrops = std::nullopt; std::optional reserveIncrementDrops = std::nullopt; + std::optional extensionComputeLimit = std::nullopt; + std::optional extensionSizeLimit = std::nullopt; + std::optional gasPrice = std::nullopt; }; STTx createFeeTx( Rules const& rules, std::uint32_t seq, - FeeSettingsFields const& fields) + FeeSettingsFields const& fields, + bool forceAllFields = false) { auto fill = [&](auto& obj) { obj.setAccountID(sfAccount, AccountID()); @@ -64,6 +68,17 @@ createFeeTx( 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); } @@ -112,6 +127,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) @@ -119,12 +140,12 @@ 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 @@ -180,6 +201,25 @@ 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; } @@ -202,6 +242,7 @@ class FeeVote_test : public beast::unit_test::suite void testSetup() { + testcase("FeeVote setup"); FeeSetup const defaultSetup; { // defaults @@ -210,36 +251,62 @@ 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"}); + "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"}); + "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"}); + "owner_reserve = -1234", + "extension_compute_limit = -100", + "extension_size_limit = -200", + "gas_price = -300"}); // Illegal values are ignored, and the defaults left unchanged auto setup = setup_FeeVote(config); BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); @@ -247,6 +314,12 @@ class FeeVote_test : public beast::unit_test::suite setup.account_reserve == static_cast(-1234567)); BEAST_EXPECT( setup.owner_reserve == static_cast(-1234)); + BEAST_EXPECT( + setup.extension_compute_limit == + static_cast(-100)); + BEAST_EXPECT( + setup.extension_size_limit == static_cast(-200)); + BEAST_EXPECT(setup.gas_price == static_cast(-300)); } { auto const big64 = std::to_string( @@ -257,12 +330,22 @@ 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); } } @@ -273,7 +356,10 @@ 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( create_genesis, env.app().config(), @@ -294,7 +380,8 @@ 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 @@ -303,7 +390,8 @@ 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( create_genesis, env.app().config(), @@ -322,12 +410,76 @@ 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( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared( + *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( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared( + *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 @@ -336,7 +488,10 @@ 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( create_genesis, env.app().config(), @@ -351,16 +506,19 @@ class FeeVote_test : public beast::unit_test::suite 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( create_genesis, env.app().config(), @@ -375,12 +533,43 @@ class FeeVote_test : public beast::unit_test::suite 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( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared( + *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))); } } @@ -389,7 +578,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( create_genesis, env.app().config(), @@ -419,7 +608,8 @@ 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))); } } @@ -428,7 +618,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( create_genesis, env.app().config(), @@ -446,7 +636,8 @@ 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); } @@ -464,7 +655,8 @@ 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); } @@ -477,7 +669,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( create_genesis, env.app().config(), @@ -500,7 +692,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 @@ -508,7 +700,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( create_genesis, env.app().config(), @@ -526,7 +718,8 @@ 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); } @@ -543,7 +736,8 @@ 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); } @@ -556,7 +750,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( create_genesis, env.app().config(), @@ -579,7 +773,8 @@ 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 @@ -596,7 +791,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( @@ -631,7 +826,9 @@ 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( @@ -674,7 +871,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( @@ -764,6 +961,128 @@ class FeeVote_test : public beast::unit_test::suite XRPAmount{setup.owner_reserve}); } + void + testDoVotingSmartEscrow() + { + testcase("doVoting with Smart Escrow"); + + using namespace jtx; + + 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; + + 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 feeVote = make_FeeVote(setup, env.app().journal("FeeVote")); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + 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, env.app().timeKeeper().closeTime()); + } + BEAST_EXPECT(ledger->isFlagLedger()); + + // Create some mock validations with fee votes + std::vector> validations; + + for (int i = 0; i < 5; i++) + { + auto sec = randomSecretKey(); + auto pub = derivePublicKey(KeyType::secp256k1, sec); + + auto val = std::make_shared( + 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( + 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); + auto const& feeTx = txs[0]; + + BEAST_EXPECT(feeTx.getTxnType() == ttFEE); + + BEAST_EXPECT(feeTx.getAccountID(sfAccount) == AccountID()); + BEAST_EXPECT(feeTx.getFieldU32(sfLedgerSequence) == ledger->seq() + 1); + + BEAST_EXPECT(feeTx.isFieldPresent(sfBaseFeeDrops)); + BEAST_EXPECT(feeTx.isFieldPresent(sfReserveBaseDrops)); + BEAST_EXPECT(feeTx.isFieldPresent(sfReserveIncrementDrops)); + + // The legacy fields should NOT be present + BEAST_EXPECT(!feeTx.isFieldPresent(sfBaseFee)); + BEAST_EXPECT(!feeTx.isFieldPresent(sfReserveBase)); + BEAST_EXPECT(!feeTx.isFieldPresent(sfReserveIncrement)); + BEAST_EXPECT(!feeTx.isFieldPresent(sfReferenceFeeUnits)); + + // Check the values + BEAST_EXPECT( + feeTx.getFieldAmount(sfBaseFeeDrops) == + XRPAmount{setup.reference_fee}); + BEAST_EXPECT( + feeTx.getFieldAmount(sfReserveBaseDrops) == + XRPAmount{setup.account_reserve}); + BEAST_EXPECT( + feeTx.getFieldAmount(sfReserveIncrementDrops) == + XRPAmount{setup.owner_reserve}); + BEAST_EXPECT( + feeTx.getFieldU32(sfExtensionComputeLimit) == + setup.extension_compute_limit); + BEAST_EXPECT( + feeTx.getFieldU32(sfExtensionSizeLimit) == + setup.extension_size_limit); + BEAST_EXPECT(feeTx.getFieldU32(sfGasPrice) == setup.gas_price); + } + void run() override { @@ -777,6 +1096,7 @@ class FeeVote_test : public beast::unit_test::suite testSingleInvalidTransaction(); testDoValidation(); testDoVoting(); + testDoVotingSmartEscrow(); } }; diff --git a/src/test/app/HostFuncImpl_test.cpp b/src/test/app/HostFuncImpl_test.cpp new file mode 100644 index 0000000000..06d69b3b12 --- /dev/null +++ b/src/test/app/HostFuncImpl_test.cpp @@ -0,0 +1,2967 @@ +#include + +#include + +#include + +namespace xrpl { +namespace test { + +static Bytes +toBytes(std::uint8_t value) +{ + return {value}; +} + +static Bytes +toBytes(std::uint16_t value) +{ + auto const* b = reinterpret_cast(&value); + auto const* e = reinterpret_cast(&value + 1); + return Bytes{b, e}; +} + +static Bytes +toBytes(std::uint32_t value) +{ + auto const* b = reinterpret_cast(&value); + auto const* e = reinterpret_cast(&value + 1); + return Bytes{b, e}; +} + +static Bytes +toBytes(Asset const& asset) +{ + if (asset.holds()) + { + Serializer s; + auto const& issue = asset.get(); + s.addBitString(issue.currency); + if (!isXRP(issue.currency)) + s.addBitString(issue.account); + auto const data = s.getData(); + return data; + } + + auto const& mptIssue = asset.get(); + auto const& mptID = mptIssue.getMptID(); + return Bytes{mptID.cbegin(), mptID.cend()}; +} + +static Bytes +toBytes(STAmount const& amount) +{ + Serializer msg; + amount.add(msg); + auto const data = msg.getData(); + + return data; +} + +static ApplyContext +createApplyContext( + test::jtx::Env& env, + OpenView& ov, + STTx const& tx = STTx(ttESCROW_FINISH, [](STObject&) {})) +{ + ApplyContext ac{ + env.app(), + ov, + tx, + tesSUCCESS, + env.current()->fees().base, + tapNONE, + env.journal}; + return ac; +} + +struct HostFuncImpl_test : public beast::unit_test::suite +{ + void + testGetLedgerSqn() + { + testcase("getLedgerSqn"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto const result = hfs.getLedgerSqn(); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == env.current()->header().seq); + } + + void + testGetParentLedgerTime() + { + testcase("getParentLedgerTime"); + using namespace test::jtx; + + Env env{*this}; + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + + { + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + auto const result = hfs.getParentLedgerTime(); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT( + result.value() == + env.current() + ->parentCloseTime() + .time_since_epoch() + .count()); + } + + env.close( + env.now() + + std::chrono::seconds(std::numeric_limits::max() - 1)); + { + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + auto const result = hfs.getParentLedgerTime(); + if (BEAST_EXPECTS( + !result.has_value(), std::to_string(result.value()))) + BEAST_EXPECT(result.error() == HostFunctionError::INTERNAL); + } + } + + void + testGetParentLedgerHash() + { + testcase("getParentLedgerHash"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto const result = hfs.getParentLedgerHash(); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == env.current()->header().parentHash); + } + + void + testGetBaseFee() + { + testcase("getBaseFee"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto const result = hfs.getBaseFee(); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == env.current()->fees().base.drops()); + + { + Env env2( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.reference_fee = + static_cast( + std::numeric_limits::max()) + + 1; + return cfg; + }), + testable_amendments()); + // Run past the flag ledger so that a Fee change vote occurs and + // updates FeeSettings. (It also activates all supported + // amendments.) + for (auto i = env.current()->seq(); i <= 257; ++i) + env.close(); + + OpenView ov2{*env2.current()}; + ApplyContext ac2 = createApplyContext(env2, ov2); + WasmHostFunctionsImpl hfs2(ac2, dummyEscrow); + auto const result2 = hfs2.getBaseFee(); + if (BEAST_EXPECT(!result2.has_value())) + BEAST_EXPECT(result2.error() == HostFunctionError::INTERNAL); + } + } + + void + testIsAmendmentEnabled() + { + testcase("isAmendmentEnabled"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + // Use featureTokenEscrow for testing + auto const amendmentId = featureTokenEscrow; + + // Test by id + { + auto const result = hfs.isAmendmentEnabled(amendmentId); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 1); + } + + // Test by name + std::string const amendmentName = "TokenEscrow"; + { + auto const result = hfs.isAmendmentEnabled(amendmentName); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 1); + } + + // Test with a fake amendment id (all zeros) + uint256 fakeId; + { + auto const result = hfs.isAmendmentEnabled(fakeId); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 0); + } + + // Test with a fake amendment name + std::string fakeName = "FakeAmendment"; + { + auto const result = hfs.isAmendmentEnabled(fakeName); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 0); + } + } + + void + testCacheLedgerObj() + { + testcase("cacheLedgerObj"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, 2); + auto const accountKeylet = keylet::account(env.master); + { + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + BEAST_EXPECT( + hfs.cacheLedgerObj(accountKeylet.key, -1).error() == + HostFunctionError::SLOT_OUT_RANGE); + BEAST_EXPECT( + hfs.cacheLedgerObj(accountKeylet.key, 257).error() == + HostFunctionError::SLOT_OUT_RANGE); + BEAST_EXPECT( + hfs.cacheLedgerObj(dummyEscrow.key, 0).error() == + HostFunctionError::LEDGER_OBJ_NOT_FOUND); + BEAST_EXPECT(hfs.cacheLedgerObj(accountKeylet.key, 0).value() == 1); + + for (int i = 1; i <= 256; ++i) + { + auto const result = hfs.cacheLedgerObj(accountKeylet.key, i); + BEAST_EXPECT(result.has_value() && result.value() == i); + } + BEAST_EXPECT( + hfs.cacheLedgerObj(accountKeylet.key, 0).error() == + HostFunctionError::SLOTS_FULL); + } + + { + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + for (int i = 1; i <= 256; ++i) + { + auto const result = hfs.cacheLedgerObj(accountKeylet.key, 0); + BEAST_EXPECT(result.has_value() && result.value() == i); + } + BEAST_EXPECT( + hfs.cacheLedgerObj(accountKeylet.key, 0).error() == + HostFunctionError::SLOTS_FULL); + } + } + + void + testGetTxField() + { + testcase("getTxField"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { + obj.setAccountID(sfAccount, env.master.id()); + obj.setAccountID(sfOwner, env.master.id()); + obj.setFieldU32(sfOfferSequence, env.seq(env.master)); + obj.setFieldArray(sfMemos, STArray{}); + }); + ApplyContext ac = createApplyContext(env, ov, stx); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + + { + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + auto const account = hfs.getTxField(sfAccount); + BEAST_EXPECT( + account && std::ranges::equal(*account, env.master.id())); + + auto const owner = hfs.getTxField(sfOwner); + BEAST_EXPECT(owner && std::ranges::equal(*owner, env.master.id())); + + auto const txType = hfs.getTxField(sfTransactionType); + BEAST_EXPECT(txType && *txType == toBytes(ttESCROW_FINISH)); + + auto const offerSeq = hfs.getTxField(sfOfferSequence); + BEAST_EXPECT(offerSeq && *offerSeq == toBytes(env.seq(env.master))); + + auto const notPresent = hfs.getTxField(sfDestination); + if (BEAST_EXPECT(!notPresent.has_value())) + BEAST_EXPECT( + notPresent.error() == HostFunctionError::FIELD_NOT_FOUND); + + auto const memos = hfs.getTxField(sfMemos); + if (BEAST_EXPECT(!memos.has_value())) + BEAST_EXPECT( + memos.error() == HostFunctionError::NOT_LEAF_FIELD); + + auto const nonField = hfs.getTxField(sfInvalid); + if (BEAST_EXPECT(!nonField.has_value())) + BEAST_EXPECT( + nonField.error() == HostFunctionError::FIELD_NOT_FOUND); + + auto const nonField2 = hfs.getTxField(sfGeneric); + if (BEAST_EXPECT(!nonField2.has_value())) + BEAST_EXPECT( + nonField2.error() == HostFunctionError::FIELD_NOT_FOUND); + } + + { + auto const iouAsset = env.master["USD"]; + STTx const stx2 = STTx(ttAMM_DEPOSIT, [&](auto& obj) { + obj.setAccountID(sfAccount, env.master.id()); + obj.setFieldIssue(sfAsset, STIssue{sfAsset, xrpIssue()}); + obj.setFieldIssue( + sfAsset2, STIssue{sfAsset2, iouAsset.issue()}); + }); + ApplyContext ac2 = createApplyContext(env, ov, stx2); + WasmHostFunctionsImpl hfs(ac2, dummyEscrow); + + auto const asset = hfs.getTxField(sfAsset); + std::vector expectedAsset(20, 0); + BEAST_EXPECT(asset && *asset == expectedAsset); + + auto const asset2 = hfs.getTxField(sfAsset2); + BEAST_EXPECT(asset2 && *asset2 == toBytes(Asset(iouAsset))); + } + + { + auto const iouAsset = env.master["GBP"]; + auto const mptId = makeMptID(1, env.master); + STTx const stx2 = STTx(ttAMM_DEPOSIT, [&](auto& obj) { + obj.setAccountID(sfAccount, env.master.id()); + obj.setFieldIssue(sfAsset, STIssue{sfAsset, iouAsset.issue()}); + obj.setFieldIssue(sfAsset2, STIssue{sfAsset2, MPTIssue{mptId}}); + }); + ApplyContext ac2 = createApplyContext(env, ov, stx2); + WasmHostFunctionsImpl hfs(ac2, dummyEscrow); + + auto const asset = hfs.getTxField(sfAsset); + if (BEAST_EXPECT(asset.has_value())) + { + BEAST_EXPECT(*asset == toBytes(Asset(iouAsset))); + } + + auto const asset2 = hfs.getTxField(sfAsset2); + if (BEAST_EXPECT(asset2.has_value())) + { + BEAST_EXPECT(*asset2 == toBytes(Asset(mptId))); + } + } + + { + std::uint8_t const expectedScale = 8; + STTx const stx2 = STTx(ttMPTOKEN_ISSUANCE_CREATE, [&](auto& obj) { + obj.setAccountID(sfAccount, env.master.id()); + obj.setFieldU8(sfAssetScale, expectedScale); + }); + ApplyContext ac2 = createApplyContext(env, ov, stx2); + WasmHostFunctionsImpl hfs(ac2, dummyEscrow); + + auto const actualScale = hfs.getTxField(sfAssetScale); + if (BEAST_EXPECT(actualScale.has_value())) + { + BEAST_EXPECT( + std::ranges::equal(*actualScale, toBytes(expectedScale))); + } + } + } + + void + testGetCurrentLedgerObjField() + { + testcase("getCurrentLedgerObjField"); + using namespace test::jtx; + using namespace std::chrono; + + Env env{*this}; + + // Fund the account and create an escrow so the ledger object exists + env(escrow::create(env.master, env.master, XRP(100)), + escrow::finish_time(env.now() + 1s)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + // Find the escrow ledger object + auto const escrowKeylet = + keylet::escrow(env.master, env.seq(env.master) - 1); + BEAST_EXPECT(env.le(escrowKeylet)); + + WasmHostFunctionsImpl hfs(ac, escrowKeylet); + + // Should return the Account field from the escrow ledger object + auto const account = hfs.getCurrentLedgerObjField(sfAccount); + if (BEAST_EXPECTS( + account.has_value(), + std::to_string(static_cast(account.error())))) + BEAST_EXPECT(std::ranges::equal(*account, env.master.id())); + + // Should return the Amount field from the escrow ledger object + auto const amountField = hfs.getCurrentLedgerObjField(sfAmount); + if (BEAST_EXPECT(amountField.has_value())) + { + BEAST_EXPECT(*amountField == toBytes(XRP(100))); + } + + // Should return nullopt for a field not present + auto const notPresent = hfs.getCurrentLedgerObjField(sfOwner); + BEAST_EXPECT( + !notPresent.has_value() && + notPresent.error() == HostFunctionError::FIELD_NOT_FOUND); + + { + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master) + 5); + WasmHostFunctionsImpl hfs2(ac, dummyEscrow); + auto const account = hfs2.getCurrentLedgerObjField(sfAccount); + if (BEAST_EXPECT(!account.has_value())) + { + BEAST_EXPECT( + account.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND); + } + } + } + + void + testGetLedgerObjField() + { + testcase("getLedgerObjField"); + using namespace test::jtx; + using namespace std::chrono; + + Env env{*this}; + // Fund the account and create an escrow so the ledger object exists + env(escrow::create(env.master, env.master, XRP(100)), + escrow::finish_time(env.now() + 1s)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const accountKeylet = keylet::account(env.master.id()); + auto const escrowKeylet = + keylet::escrow(env.master.id(), env.seq(env.master) - 1); + WasmHostFunctionsImpl hfs(ac, escrowKeylet); + + // Cache the escrow ledger object in slot 1 + auto cacheResult = hfs.cacheLedgerObj(accountKeylet.key, 1); + BEAST_EXPECT(cacheResult.has_value() && cacheResult.value() == 1); + + // Should return the Account field from the cached ledger object + auto const account = hfs.getLedgerObjField(1, sfAccount); + if (BEAST_EXPECTS( + account.has_value(), + std::to_string(static_cast(account.error())))) + BEAST_EXPECT(std::ranges::equal(*account, env.master.id())); + + // Should return the Balance field from the cached ledger object + auto const balanceField = hfs.getLedgerObjField(1, sfBalance); + if (BEAST_EXPECT(balanceField.has_value())) + { + BEAST_EXPECT(*balanceField == toBytes(env.balance(env.master))); + } + + // Should return error for slot out of range + auto const outOfRange = hfs.getLedgerObjField(0, sfAccount); + BEAST_EXPECT( + !outOfRange.has_value() && + outOfRange.error() == HostFunctionError::SLOT_OUT_RANGE); + + auto const tooHigh = hfs.getLedgerObjField(257, sfAccount); + BEAST_EXPECT( + !tooHigh.has_value() && + tooHigh.error() == HostFunctionError::SLOT_OUT_RANGE); + + // Should return error for empty slot + auto const emptySlot = hfs.getLedgerObjField(2, sfAccount); + BEAST_EXPECT( + !emptySlot.has_value() && + emptySlot.error() == HostFunctionError::EMPTY_SLOT); + + // Should return error for field not present + auto const notPresent = hfs.getLedgerObjField(1, sfOwner); + BEAST_EXPECT( + !notPresent.has_value() && + notPresent.error() == HostFunctionError::FIELD_NOT_FOUND); + } + + void + testGetTxNestedField() + { + testcase("getTxNestedField"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + + // Create a transaction with a nested array field + STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { + obj.setAccountID(sfAccount, env.master.id()); + STArray memos; + STObject memoObj(sfMemo); + memoObj.setFieldVL(sfMemoData, Slice("hello", 5)); + memos.push_back(memoObj); + obj.setFieldArray(sfMemos, memos); + }); + + ApplyContext ac = createApplyContext(env, ov, stx); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + // Locator for sfMemos[0].sfMemo.sfMemoData + // Locator is a sequence of int32_t codes: + // [sfMemos.fieldCode, 0, sfMemoData.fieldCode] + std::vector locatorVec = { + sfMemos.fieldCode, 0, sfMemoData.fieldCode}; + Slice locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + + auto const result = hfs.getTxNestedField(locator); + if (BEAST_EXPECTS( + result.has_value(), + std::to_string(static_cast(result.error())))) + { + std::string memoData( + result.value().begin(), result.value().end()); + BEAST_EXPECT(memoData == "hello"); + } + } + + { + // can use the nested locator for base fields too + std::vector locatorVec = {sfAccount.fieldCode}; + Slice locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + + auto const account = hfs.getTxNestedField(locator); + if (BEAST_EXPECTS( + account.has_value(), + std::to_string(static_cast(account.error())))) + { + BEAST_EXPECT(std::ranges::equal(*account, env.master.id())); + } + } + + auto expectError = [&](std::vector const& locatorVec, + HostFunctionError expectedError) { + Slice locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = hfs.getTxNestedField(locator); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECTS( + result.error() == expectedError, + std::to_string(static_cast(result.error()))); + }; + // Locator for non-existent base field + expectError( + {sfSigners.fieldCode, // sfSigners does not exist + 0, + sfAccount.fieldCode}, + HostFunctionError::FIELD_NOT_FOUND); + + // Locator for non-existent index + expectError( + {sfMemos.fieldCode, + 1, // index 1 does not exist + sfMemoData.fieldCode}, + HostFunctionError::INDEX_OUT_OF_BOUNDS); + + // Locator for non-existent nested field + expectError( + {sfMemos.fieldCode, + 0, + sfURI.fieldCode}, // sfURI does not exist in the memo + HostFunctionError::FIELD_NOT_FOUND); + + // Locator for non-existent base sfield + expectError( + {field_code(20000, 20000), // nonexistent SField code + 0, + sfAccount.fieldCode}, + HostFunctionError::INVALID_FIELD); + + // Locator for non-existent nested sfield + expectError( + {sfMemos.fieldCode, // nonexistent SField code + 0, + field_code(20000, 20000)}, + HostFunctionError::INVALID_FIELD); + + // Locator for STArray + expectError({sfMemos.fieldCode}, HostFunctionError::NOT_LEAF_FIELD); + + // Locator for nesting into non-array/object field + expectError( + {sfAccount.fieldCode, // sfAccount is not an array or object + 0, + sfAccount.fieldCode}, + HostFunctionError::LOCATOR_MALFORMED); + + // Locator for empty locator + expectError({}, HostFunctionError::LOCATOR_MALFORMED); + + // Locator for malformed locator (not multiple of 4) + { + std::vector locatorVec = {sfMemos.fieldCode}; + Slice malformedLocator( + reinterpret_cast(locatorVec.data()), 3); + auto const malformedResult = hfs.getTxNestedField(malformedLocator); + BEAST_EXPECT( + !malformedResult.has_value() && + malformedResult.error() == + HostFunctionError::LOCATOR_MALFORMED); + } + } + + void + testGetCurrentLedgerObjNestedField() + { + testcase("getCurrentLedgerObjNestedField"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const becky("becky"); + // Create a SignerList for env.master + env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + // Find the signer ledger object + auto const signerKeylet = keylet::signers(env.master.id()); + BEAST_EXPECT(env.le(signerKeylet)); + + WasmHostFunctionsImpl hfs(ac, signerKeylet); + + // Locator for base field + std::vector baseLocator = {sfSignerQuorum.fieldCode}; + Slice baseLocatorSlice( + reinterpret_cast(baseLocator.data()), + baseLocator.size() * sizeof(int32_t)); + auto const signerQuorum = + hfs.getCurrentLedgerObjNestedField(baseLocatorSlice); + if (BEAST_EXPECTS( + signerQuorum.has_value(), + std::to_string(static_cast(signerQuorum.error())))) + { + BEAST_EXPECT(*signerQuorum == toBytes(static_cast(2))); + } + + auto expectError = [&](std::vector const& locatorVec, + HostFunctionError expectedError) { + Slice locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = hfs.getCurrentLedgerObjNestedField(locator); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECTS( + result.error() == expectedError, + std::to_string(static_cast(result.error()))); + }; + // Locator for non-existent base field + expectError( + {sfSigners.fieldCode, // sfSigners does not exist + 0, + sfAccount.fieldCode}, + HostFunctionError::FIELD_NOT_FOUND); + // Locator for nesting into non-array/object field + expectError( + {sfSignerQuorum + .fieldCode, // sfSignerQuorum is not an array or object + 0, + sfAccount.fieldCode}, + HostFunctionError::LOCATOR_MALFORMED); + + // Locator for empty locator + Slice emptyLocator(nullptr, 0); + auto const emptyResult = + hfs.getCurrentLedgerObjNestedField(emptyLocator); + BEAST_EXPECT( + !emptyResult.has_value() && + emptyResult.error() == HostFunctionError::LOCATOR_MALFORMED); + + // Locator for malformed locator (not multiple of 4) + std::vector malformedLocatorVec = {sfMemos.fieldCode}; + Slice malformedLocator( + reinterpret_cast(malformedLocatorVec.data()), 3); + auto const malformedResult = + hfs.getCurrentLedgerObjNestedField(malformedLocator); + BEAST_EXPECT( + !malformedResult.has_value() && + malformedResult.error() == HostFunctionError::LOCATOR_MALFORMED); + + { + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master) + 5); + WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow); + std::vector const locatorVec = {sfAccount.fieldCode}; + Slice locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = + dummyHfs.getCurrentLedgerObjNestedField(locator); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECTS( + result.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND, + std::to_string(static_cast(result.error()))); + } + } + + void + testGetLedgerObjNestedField() + { + testcase("getLedgerObjNestedField"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const becky("becky"); + // Create a SignerList for env.master + env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + // Cache the SignerList ledger object in slot 1 + auto const signerListKeylet = keylet::signers(env.master.id()); + auto cacheResult = hfs.cacheLedgerObj(signerListKeylet.key, 1); + BEAST_EXPECT(cacheResult.has_value() && cacheResult.value() == 1); + + // Locator for sfSignerEntries[0].sfAccount + { + std::vector const locatorVec = { + sfSignerEntries.fieldCode, 0, sfAccount.fieldCode}; + Slice locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + + auto const result = hfs.getLedgerObjNestedField(1, locator); + if (BEAST_EXPECTS( + result.has_value(), + std::to_string(static_cast(result.error())))) + { + BEAST_EXPECT(std::ranges::equal(*result, alice.id())); + } + } + + // Locator for sfSignerEntries[1].sfAccount + { + std::vector const locatorVec = { + sfSignerEntries.fieldCode, 1, sfAccount.fieldCode}; + Slice const locator = Slice( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result2 = hfs.getLedgerObjNestedField(1, locator); + if (BEAST_EXPECTS( + result2.has_value(), + std::to_string(static_cast(result2.error())))) + { + BEAST_EXPECT(std::ranges::equal(*result2, becky.id())); + } + } + + // Locator for sfSignerEntries[0].sfSignerWeight + { + std::vector const locatorVec = { + sfSignerEntries.fieldCode, 0, sfSignerWeight.fieldCode}; + Slice const locator = Slice( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const weightResult = hfs.getLedgerObjNestedField(1, locator); + if (BEAST_EXPECTS( + weightResult.has_value(), + std::to_string(static_cast(weightResult.error())))) + { + // Should be 1 + auto const expected = toBytes(static_cast(1)); + BEAST_EXPECT(*weightResult == expected); + } + } + + // Locator for base field sfSignerQuorum + { + std::vector const locatorVec = {sfSignerQuorum.fieldCode}; + Slice const locator = Slice( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const quorumResult = hfs.getLedgerObjNestedField(1, locator); + if (BEAST_EXPECTS( + quorumResult.has_value(), + std::to_string(static_cast(quorumResult.error())))) + { + auto const expected = toBytes(static_cast(2)); + BEAST_EXPECT(*quorumResult == expected); + } + } + + // Helper for error checks + auto expectError = [&](std::vector const& locatorVec, + HostFunctionError expectedError, + int slot = 1) { + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = hfs.getLedgerObjNestedField(slot, locator); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECTS( + result.error() == expectedError, + std::to_string(static_cast(result.error()))); + }; + + // Error: base field not found + expectError( + {sfSigners.fieldCode, // sfSigners does not exist + 0, + sfAccount.fieldCode}, + HostFunctionError::FIELD_NOT_FOUND); + + // Error: index out of bounds + expectError( + {sfSignerEntries.fieldCode, + 2, // index 2 does not exist + sfAccount.fieldCode}, + HostFunctionError::INDEX_OUT_OF_BOUNDS); + + // Error: nested field not found + expectError( + { + sfSignerEntries.fieldCode, + 0, + sfDestination.fieldCode // sfDestination does not exist + }, + HostFunctionError::FIELD_NOT_FOUND); + + // Error: invalid field code + expectError( + {field_code(99999, 99999), 0, sfAccount.fieldCode}, + HostFunctionError::INVALID_FIELD); + + // Error: invalid nested field code + expectError( + {sfSignerEntries.fieldCode, 0, field_code(99999, 99999)}, + HostFunctionError::INVALID_FIELD); + + // Error: slot out of range + expectError( + {sfSignerQuorum.fieldCode}, HostFunctionError::SLOT_OUT_RANGE, 0); + expectError( + {sfSignerQuorum.fieldCode}, HostFunctionError::SLOT_OUT_RANGE, 257); + + // Error: empty slot + expectError( + {sfSignerQuorum.fieldCode}, HostFunctionError::EMPTY_SLOT, 2); + + // Error: locator for STArray (not leaf field) + expectError( + {sfSignerEntries.fieldCode}, HostFunctionError::NOT_LEAF_FIELD); + + // Error: nesting into non-array/object field + expectError( + {sfSignerQuorum.fieldCode, 0, sfAccount.fieldCode}, + HostFunctionError::LOCATOR_MALFORMED); + + // Error: empty locator + expectError({}, HostFunctionError::LOCATOR_MALFORMED); + + // Error: locator malformed (not multiple of 4) + std::vector const locatorVec = {sfSignerEntries.fieldCode}; + Slice const locator = + Slice(reinterpret_cast(locatorVec.data()), 3); + auto const malformed = hfs.getLedgerObjNestedField(1, locator); + BEAST_EXPECT( + !malformed.has_value() && + malformed.error() == HostFunctionError::LOCATOR_MALFORMED); + } + + void + testGetTxArrayLen() + { + testcase("getTxArrayLen"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + + // Transaction with an array field + STTx stx = STTx(ttESCROW_FINISH, [&](auto& obj) { + obj.setAccountID(sfAccount, env.master.id()); + STArray memos; + { + STObject memoObj(sfMemo); + memoObj.setFieldVL(sfMemoData, Slice("hello", 5)); + memos.push_back(memoObj); + } + { + STObject memoObj(sfMemo); + memoObj.setFieldVL(sfMemoData, Slice("world", 5)); + memos.push_back(memoObj); + } + obj.setFieldArray(sfMemos, memos); + }); + + ApplyContext ac = createApplyContext(env, ov, stx); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + // Should return 1 for sfMemos + auto const memosLen = hfs.getTxArrayLen(sfMemos); + if (BEAST_EXPECT(memosLen.has_value())) + BEAST_EXPECT(memosLen.value() == 2); + + // Should return error for non-array field + auto const notArray = hfs.getTxArrayLen(sfAccount); + if (BEAST_EXPECT(!notArray.has_value())) + BEAST_EXPECT(notArray.error() == HostFunctionError::NO_ARRAY); + + // Should return error for missing array field + auto const missingArray = hfs.getTxArrayLen(sfSigners); + if (BEAST_EXPECT(!missingArray.has_value())) + BEAST_EXPECT( + missingArray.error() == HostFunctionError::FIELD_NOT_FOUND); + } + + void + testGetCurrentLedgerObjArrayLen() + { + testcase("getCurrentLedgerObjArrayLen"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const becky("becky"); + // Create a SignerList for env.master + env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const signerKeylet = keylet::signers(env.master.id()); + WasmHostFunctionsImpl hfs(ac, signerKeylet); + + auto const entriesLen = + hfs.getCurrentLedgerObjArrayLen(sfSignerEntries); + if (BEAST_EXPECT(entriesLen.has_value())) + BEAST_EXPECT(entriesLen.value() == 2); + + auto const arrLen = hfs.getCurrentLedgerObjArrayLen(sfMemos); + if (BEAST_EXPECT(!arrLen.has_value())) + BEAST_EXPECT(arrLen.error() == HostFunctionError::FIELD_NOT_FOUND); + + // Should return NO_ARRAY for non-array field + auto const notArray = hfs.getCurrentLedgerObjArrayLen(sfAccount); + if (BEAST_EXPECT(!notArray.has_value())) + BEAST_EXPECT(notArray.error() == HostFunctionError::NO_ARRAY); + + { + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master) + 5); + WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow); + auto const len = dummyHfs.getCurrentLedgerObjArrayLen(sfMemos); + if (BEAST_EXPECT(!len.has_value())) + BEAST_EXPECT( + len.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND); + } + } + + void + testGetLedgerObjArrayLen() + { + testcase("getLedgerObjArrayLen"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const becky("becky"); + // Create a SignerList for env.master + env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto const signerListKeylet = keylet::signers(env.master.id()); + auto cacheResult = hfs.cacheLedgerObj(signerListKeylet.key, 1); + BEAST_EXPECT(cacheResult.has_value() && cacheResult.value() == 1); + + { + auto const arrLen = hfs.getLedgerObjArrayLen(1, sfSignerEntries); + if (BEAST_EXPECT(arrLen.has_value())) + // Should return 2 for sfSignerEntries + BEAST_EXPECT(arrLen.value() == 2); + } + { + auto const arrLen = hfs.getLedgerObjArrayLen(0, sfSignerEntries); + if (BEAST_EXPECT(!arrLen.has_value())) + BEAST_EXPECT( + arrLen.error() == HostFunctionError::SLOT_OUT_RANGE); + } + + { + // Should return error for non-array field + auto const notArray = hfs.getLedgerObjArrayLen(1, sfAccount); + if (BEAST_EXPECT(!notArray.has_value())) + BEAST_EXPECT(notArray.error() == HostFunctionError::NO_ARRAY); + } + + { + // Should return error for empty slot + auto const emptySlot = hfs.getLedgerObjArrayLen(2, sfSignerEntries); + if (BEAST_EXPECT(!emptySlot.has_value())) + BEAST_EXPECT( + emptySlot.error() == HostFunctionError::EMPTY_SLOT); + } + + { + // Should return error for missing array field + auto const missingArray = hfs.getLedgerObjArrayLen(1, sfMemos); + if (BEAST_EXPECT(!missingArray.has_value())) + BEAST_EXPECT( + missingArray.error() == HostFunctionError::FIELD_NOT_FOUND); + } + } + + void + testGetTxNestedArrayLen() + { + testcase("getTxNestedArrayLen"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + + STTx stx = STTx(ttESCROW_FINISH, [&](auto& obj) { + STArray memos; + STObject memoObj(sfMemo); + memoObj.setFieldVL(sfMemoData, Slice("hello", 5)); + memos.push_back(memoObj); + obj.setFieldArray(sfMemos, memos); + }); + + ApplyContext ac = createApplyContext(env, ov, stx); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + // Helper for error checks + auto expectError = [&](std::vector const& locatorVec, + HostFunctionError expectedError, + int slot = 1) { + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = hfs.getTxNestedArrayLen(locator); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECTS( + result.error() == expectedError, + std::to_string(static_cast(result.error()))); + }; + + // Locator for sfMemos + { + std::vector locatorVec = {sfMemos.fieldCode}; + Slice locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const arrLen = hfs.getTxNestedArrayLen(locator); + BEAST_EXPECT(arrLen.has_value() && arrLen.value() == 1); + } + + // Error: non-array field + expectError({sfAccount.fieldCode}, HostFunctionError::NO_ARRAY); + + // Error: missing field + expectError({sfSigners.fieldCode}, HostFunctionError::FIELD_NOT_FOUND); + } + + void + testGetCurrentLedgerObjNestedArrayLen() + { + testcase("getCurrentLedgerObjNestedArrayLen"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const becky("becky"); + // Create a SignerList for env.master + env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const signerKeylet = keylet::signers(env.master.id()); + WasmHostFunctionsImpl hfs(ac, signerKeylet); + + // Helper for error checks + auto expectError = [&](std::vector const& locatorVec, + HostFunctionError expectedError, + int slot = 1) { + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = hfs.getCurrentLedgerObjNestedArrayLen(locator); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECTS( + result.error() == expectedError, + std::to_string(static_cast(result.error()))); + }; + + // Locator for sfSignerEntries + { + std::vector locatorVec = {sfSignerEntries.fieldCode}; + Slice locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const arrLen = hfs.getCurrentLedgerObjNestedArrayLen(locator); + BEAST_EXPECT(arrLen.has_value() && arrLen.value() == 2); + } + + // Error: non-array field + expectError({sfSignerQuorum.fieldCode}, HostFunctionError::NO_ARRAY); + + // Error: missing field + expectError({sfSigners.fieldCode}, HostFunctionError::FIELD_NOT_FOUND); + + { + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master) + 5); + WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow); + std::vector locatorVec = {sfAccount.fieldCode}; + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = + dummyHfs.getCurrentLedgerObjNestedArrayLen(locator); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECTS( + result.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND, + std::to_string(static_cast(result.error()))); + } + } + + void + testGetLedgerObjNestedArrayLen() + { + testcase("getLedgerObjNestedArrayLen"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const becky("becky"); + env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto const signerListKeylet = keylet::signers(env.master.id()); + auto cacheResult = hfs.cacheLedgerObj(signerListKeylet.key, 1); + BEAST_EXPECT(cacheResult.has_value() && cacheResult.value() == 1); + + // Locator for sfSignerEntries + std::vector locatorVec = {sfSignerEntries.fieldCode}; + Slice locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const arrLen = hfs.getLedgerObjNestedArrayLen(1, locator); + if (BEAST_EXPECT(arrLen.has_value())) + BEAST_EXPECT(arrLen.value() == 2); + + // Helper for error checks + auto expectError = [&](std::vector const& locatorVec, + HostFunctionError expectedError, + int slot = 1) { + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = hfs.getLedgerObjNestedArrayLen(slot, locator); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECTS( + result.error() == expectedError, + std::to_string(static_cast(result.error()))); + }; + + // Error: non-array field + expectError({sfSignerQuorum.fieldCode}, HostFunctionError::NO_ARRAY); + + // Error: missing field + expectError({sfSigners.fieldCode}, HostFunctionError::FIELD_NOT_FOUND); + + // Slot out of range + expectError(locatorVec, HostFunctionError::SLOT_OUT_RANGE, 0); + expectError(locatorVec, HostFunctionError::SLOT_OUT_RANGE, 257); + + // Empty slot + expectError(locatorVec, HostFunctionError::EMPTY_SLOT, 2); + + // Error: empty locator + expectError({}, HostFunctionError::LOCATOR_MALFORMED); + + // Error: locator malformed (not multiple of 4) + Slice malformedLocator( + reinterpret_cast(locator.data()), 3); + auto const malformed = + hfs.getLedgerObjNestedArrayLen(1, malformedLocator); + BEAST_EXPECT( + !malformed.has_value() && + malformed.error() == HostFunctionError::LOCATOR_MALFORMED); + + // Error: locator for non-STArray field + expectError( + {sfSignerQuorum.fieldCode, 0, sfAccount.fieldCode}, + HostFunctionError::LOCATOR_MALFORMED); + } + + void + testUpdateData() + { + testcase("updateData"); + using namespace test::jtx; + + Env env{*this}; + env(escrow::create(env.master, env.master, XRP(100)), + escrow::finish_time(env.now() + std::chrono::seconds(1))); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const escrowKeylet = + keylet::escrow(env.master, env.seq(env.master) - 1); + WasmHostFunctionsImpl hfs(ac, escrowKeylet); + + // Should succeed for small data + std::vector data(10, 0x42); + auto const result = hfs.updateData(Slice(data.data(), data.size())); + BEAST_EXPECT(result.has_value() && result.value() == data.size()); + + // Should fail for too large data + std::vector bigData( + 1024 * 1024 + 1, 0x42); // > maxWasmDataLength + auto const tooBig = + hfs.updateData(Slice(bigData.data(), bigData.size())); + if (BEAST_EXPECT(!tooBig.has_value())) + BEAST_EXPECT( + tooBig.error() == HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + void + testCheckSignature() + { + testcase("checkSignature"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + // Generate a keypair and sign a message + auto const kp = generateKeyPair(KeyType::secp256k1, randomSeed()); + PublicKey const& pk = kp.first; + SecretKey const& sk = kp.second; + std::string const& message = "hello signature"; + auto const sig = sign(pk, sk, Slice(message.data(), message.size())); + + // Should succeed for valid signature + { + auto const result = hfs.checkSignature( + Slice(message.data(), message.size()), + Slice(sig.data(), sig.size()), + Slice(pk.data(), pk.size())); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 1); + } + + // Should fail for invalid signature + { + std::string badSig(sig.size(), 0xFF); + auto const result = hfs.checkSignature( + Slice(message.data(), message.size()), + Slice(badSig.data(), badSig.size()), + Slice(pk.data(), pk.size())); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 0); + } + + // Should fail for invalid public key + { + std::string badPk(pk.size(), 0x00); + auto const result = hfs.checkSignature( + Slice(message.data(), message.size()), + Slice(sig.data(), sig.size()), + Slice(badPk.data(), badPk.size())); + BEAST_EXPECT(!result.has_value()); + BEAST_EXPECT(result.error() == HostFunctionError::INVALID_PARAMS); + } + + // Should fail for empty public key + { + auto const result = hfs.checkSignature( + Slice(message.data(), message.size()), + Slice(sig.data(), sig.size()), + Slice(nullptr, 0)); + BEAST_EXPECT(!result.has_value()); + BEAST_EXPECT(result.error() == HostFunctionError::INVALID_PARAMS); + } + + // Should fail for empty signature + { + auto const result = hfs.checkSignature( + Slice(message.data(), message.size()), + Slice(nullptr, 0), + Slice(pk.data(), pk.size())); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 0); + } + + // Should fail for empty message + { + auto const result = hfs.checkSignature( + Slice(nullptr, 0), + Slice(sig.data(), sig.size()), + Slice(pk.data(), pk.size())); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 0); + } + } + + void + testComputeSha512HalfHash() + { + testcase("computeSha512HalfHash"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + std::string data = "hello world"; + auto const result = + hfs.computeSha512HalfHash(Slice(data.data(), data.size())); + BEAST_EXPECT(result.has_value()); + + // Should match direct call to sha512Half + auto expected = sha512Half(Slice(data.data(), data.size())); + BEAST_EXPECT(result.value() == expected); + } + + void + testKeyletFunctions() + { + testcase("keylet functions"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto compareKeylet = [](std::vector const& bytes, + Keylet const& kl) { + return std::ranges::equal(bytes, kl.key); + }; +// Lambda to compare a Bytes (std::vector) to a keylet +#define COMPARE_KEYLET(hfsFunc, keyletFunc, ...) \ + { \ + auto actual = hfs.hfsFunc(__VA_ARGS__); \ + auto expected = keyletFunc(__VA_ARGS__); \ + if (BEAST_EXPECT(actual.has_value())) \ + { \ + BEAST_EXPECT(compareKeylet(actual.value(), expected)); \ + } \ + } +#define COMPARE_KEYLET_FAIL(hfsFunc, expected, ...) \ + { \ + auto actual = hfs.hfsFunc(__VA_ARGS__); \ + if (BEAST_EXPECT(!actual.has_value())) \ + { \ + BEAST_EXPECTS( \ + actual.error() == expected, \ + std::to_string(HfErrorToInt(actual.error()))); \ + } \ + } + + COMPARE_KEYLET(accountKeylet, keylet::account, env.master.id()); + COMPARE_KEYLET_FAIL( + accountKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount()); + + COMPARE_KEYLET( + ammKeylet, keylet::amm, xrpIssue(), env.master["USD"].issue()); + COMPARE_KEYLET_FAIL( + ammKeylet, + HostFunctionError::INVALID_PARAMS, + xrpIssue(), + xrpIssue()); + COMPARE_KEYLET_FAIL( + ammKeylet, + HostFunctionError::INVALID_PARAMS, + makeMptID(1, env.master.id()), + xrpIssue()); + + COMPARE_KEYLET(checkKeylet, keylet::check, env.master.id(), 1); + COMPARE_KEYLET_FAIL( + checkKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + + std::string const credType = "test"; + COMPARE_KEYLET( + credentialKeylet, + keylet::credential, + env.master.id(), + env.master.id(), + Slice(credType.data(), credType.size())); + + Account const alice("alice"); + constexpr std::string_view longCredType = + "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]" + "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p"; + static_assert(longCredType.size() > maxCredentialTypeLength); + COMPARE_KEYLET_FAIL( + credentialKeylet, + HostFunctionError::INVALID_PARAMS, + env.master.id(), + alice.id(), + Slice(longCredType.data(), longCredType.size())); + COMPARE_KEYLET_FAIL( + credentialKeylet, + HostFunctionError::INVALID_ACCOUNT, + xrpAccount(), + alice.id(), + Slice(credType.data(), credType.size())); + COMPARE_KEYLET_FAIL( + credentialKeylet, + HostFunctionError::INVALID_ACCOUNT, + env.master.id(), + xrpAccount(), + Slice(credType.data(), credType.size())); + + COMPARE_KEYLET(didKeylet, keylet::did, env.master.id()); + COMPARE_KEYLET_FAIL( + didKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount()); + + COMPARE_KEYLET( + delegateKeylet, keylet::delegate, env.master.id(), alice.id()); + COMPARE_KEYLET_FAIL( + delegateKeylet, + HostFunctionError::INVALID_PARAMS, + env.master.id(), + env.master.id()); + COMPARE_KEYLET_FAIL( + delegateKeylet, + HostFunctionError::INVALID_ACCOUNT, + env.master.id(), + xrpAccount()); + COMPARE_KEYLET_FAIL( + delegateKeylet, + HostFunctionError::INVALID_ACCOUNT, + xrpAccount(), + env.master.id()); + + COMPARE_KEYLET( + depositPreauthKeylet, + keylet::depositPreauth, + env.master.id(), + alice.id()); + COMPARE_KEYLET_FAIL( + depositPreauthKeylet, + HostFunctionError::INVALID_PARAMS, + env.master.id(), + env.master.id()); + COMPARE_KEYLET_FAIL( + depositPreauthKeylet, + HostFunctionError::INVALID_ACCOUNT, + env.master.id(), + xrpAccount()); + COMPARE_KEYLET_FAIL( + depositPreauthKeylet, + HostFunctionError::INVALID_ACCOUNT, + xrpAccount(), + env.master.id()); + + COMPARE_KEYLET(escrowKeylet, keylet::escrow, env.master.id(), 1); + COMPARE_KEYLET_FAIL( + escrowKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + + Currency usd = to_currency("USD"); + COMPARE_KEYLET( + lineKeylet, keylet::line, env.master.id(), alice.id(), usd); + COMPARE_KEYLET_FAIL( + lineKeylet, + HostFunctionError::INVALID_PARAMS, + env.master.id(), + env.master.id(), + usd); + COMPARE_KEYLET_FAIL( + lineKeylet, + HostFunctionError::INVALID_ACCOUNT, + env.master.id(), + xrpAccount(), + usd); + COMPARE_KEYLET_FAIL( + lineKeylet, + HostFunctionError::INVALID_ACCOUNT, + xrpAccount(), + env.master.id(), + usd); + COMPARE_KEYLET_FAIL( + lineKeylet, + HostFunctionError::INVALID_PARAMS, + env.master.id(), + alice.id(), + to_currency("")); + + { + auto actual = hfs.mptIssuanceKeylet(env.master.id(), 1); + auto expected = keylet::mptIssuance(1, env.master.id()); + if (BEAST_EXPECT(actual.has_value())) + { + BEAST_EXPECT(compareKeylet(actual.value(), expected)); + } + } + { + auto actual = hfs.mptIssuanceKeylet(xrpAccount(), 1); + if (BEAST_EXPECT(!actual.has_value())) + BEAST_EXPECT( + actual.error() == HostFunctionError::INVALID_ACCOUNT); + } + + auto const sampleMPTID = makeMptID(1, env.master.id()); + COMPARE_KEYLET(mptokenKeylet, keylet::mptoken, sampleMPTID, alice.id()); + COMPARE_KEYLET_FAIL( + mptokenKeylet, + HostFunctionError::INVALID_PARAMS, + MPTID{}, + alice.id()); + COMPARE_KEYLET_FAIL( + mptokenKeylet, + HostFunctionError::INVALID_ACCOUNT, + sampleMPTID, + xrpAccount()); + + COMPARE_KEYLET(nftOfferKeylet, keylet::nftoffer, env.master.id(), 1); + COMPARE_KEYLET_FAIL( + nftOfferKeylet, + HostFunctionError::INVALID_ACCOUNT, + xrpAccount(), + 1); + + COMPARE_KEYLET(offerKeylet, keylet::offer, env.master.id(), 1); + COMPARE_KEYLET_FAIL( + offerKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + + COMPARE_KEYLET(oracleKeylet, keylet::oracle, env.master.id(), 1); + COMPARE_KEYLET_FAIL( + oracleKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + + COMPARE_KEYLET( + paychanKeylet, keylet::payChan, env.master.id(), alice.id(), 1); + COMPARE_KEYLET_FAIL( + paychanKeylet, + HostFunctionError::INVALID_PARAMS, + env.master.id(), + env.master.id(), + 1); + COMPARE_KEYLET_FAIL( + paychanKeylet, + HostFunctionError::INVALID_ACCOUNT, + env.master.id(), + xrpAccount(), + 1); + COMPARE_KEYLET_FAIL( + paychanKeylet, + HostFunctionError::INVALID_ACCOUNT, + xrpAccount(), + env.master.id(), + 1); + + COMPARE_KEYLET( + permissionedDomainKeylet, + keylet::permissionedDomain, + env.master.id(), + 1); + COMPARE_KEYLET_FAIL( + permissionedDomainKeylet, + HostFunctionError::INVALID_ACCOUNT, + xrpAccount(), + 1); + + COMPARE_KEYLET(signersKeylet, keylet::signers, env.master.id()); + COMPARE_KEYLET_FAIL( + signersKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount()); + + COMPARE_KEYLET(ticketKeylet, keylet::ticket, env.master.id(), 1); + COMPARE_KEYLET_FAIL( + ticketKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + + COMPARE_KEYLET(vaultKeylet, keylet::vault, env.master.id(), 1); + COMPARE_KEYLET_FAIL( + vaultKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + } + + void + testGetNFT() + { + testcase("getNFT"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + env.fund(XRP(1000), alice); + env.close(); + + // Mint NFT for alice + uint256 const nftId = token::getNextID(env, alice, 0u, 0u); + std::string const uri = "https://example.com/nft"; + env(token::mint(alice), token::uri(uri)); + env.close(); + uint256 const nftId2 = token::getNextID(env, alice, 0u, 0u); + env(token::mint(alice)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(alice, env.seq(alice)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + // Should succeed for valid NFT + { + auto const result = hfs.getNFT(alice.id(), nftId); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(std::ranges::equal(*result, uri)); + } + + // Should fail for invalid account + { + auto const result = hfs.getNFT(xrpAccount(), nftId); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECT( + result.error() == HostFunctionError::INVALID_ACCOUNT); + } + + // Should fail for invalid nftId + { + auto const result = hfs.getNFT(alice.id(), uint256()); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECT( + result.error() == HostFunctionError::INVALID_PARAMS); + } + + // Should fail for invalid nftId + { + auto const badId = token::getNextID(env, alice, 0u, 1u); + auto const result = hfs.getNFT(alice.id(), badId); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECT( + result.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND); + } + + { + auto const result = hfs.getNFT(alice.id(), nftId2); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECT( + result.error() == HostFunctionError::FIELD_NOT_FOUND); + } + } + + void + testGetNFTIssuer() + { + testcase("getNFTIssuer"); + using namespace test::jtx; + + Env env{*this}; + // Mint NFT for env.master + uint32_t const taxon = 12345; + uint256 const nftId = token::getNextID(env, env.master, taxon); + env(token::mint(env.master, taxon)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + // Should succeed for valid NFT id + { + auto const result = hfs.getNFTIssuer(nftId); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(std::ranges::equal(*result, env.master.id())); + } + + // Should fail for zero NFT id + { + auto const result = hfs.getNFTIssuer(uint256()); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECT( + result.error() == HostFunctionError::INVALID_PARAMS); + } + } + + void + testGetNFTTaxon() + { + testcase("getNFTTaxon"); + using namespace test::jtx; + + Env env{*this}; + + uint32_t const taxon = 54321; + uint256 const nftId = token::getNextID(env, env.master, taxon); + env(token::mint(env.master, taxon)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto const result = hfs.getNFTTaxon(nftId); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == taxon); + } + + void + testGetNFTFlags() + { + testcase("getNFTFlags"); + using namespace test::jtx; + + Env env{*this}; + + // Mint NFT with default flags + uint256 const nftId = + token::getNextID(env, env.master, 0u, tfTransferable); + env(token::mint(env.master, 0), txflags(tfTransferable)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = hfs.getNFTFlags(nftId); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == tfTransferable); + } + + // Should return 0 for zero NFT id + { + auto const result = hfs.getNFTFlags(uint256()); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == 0); + } + } + + void + testGetNFTTransferFee() + { + testcase("getNFTTransferFee"); + using namespace test::jtx; + + Env env{*this}; + + uint16_t const transferFee = 250; + uint256 const nftId = + token::getNextID(env, env.master, 0u, tfTransferable, transferFee); + env(token::mint(env.master, 0), + token::xferFee(transferFee), + txflags(tfTransferable)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = hfs.getNFTTransferFee(nftId); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == transferFee); + } + + // Should return 0 for zero NFT id + { + auto const result = hfs.getNFTTransferFee(uint256()); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == 0); + } + } + + void + testGetNFTSerial() + { + testcase("getNFTSerial"); + using namespace test::jtx; + + Env env{*this}; + + // Mint NFT with serial 0 + uint256 const nftId = token::getNextID(env, env.master, 0u); + auto const serial = env.seq(env.master); + env(token::mint(env.master)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = hfs.getNFTSerial(nftId); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == serial); + } + + // Should return 0 for zero NFT id + { + auto const result = hfs.getNFTSerial(uint256()); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == 0); + } + } + + void + testTrace() + { + testcase("trace"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + std::string msg = "test trace"; + std::string data = "abc"; + auto const slice = Slice(data.data(), data.size()); + auto const result = hfs.trace(msg, slice, false); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == msg.size() + data.size()); + + auto const resultHex = hfs.trace(msg, slice, true); + BEAST_EXPECT(resultHex.has_value()); + BEAST_EXPECT(resultHex.value() == msg.size() + data.size() * 2); + } + + void + testTraceNum() + { + testcase("traceNum"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + std::string msg = "trace number"; + int64_t num = 123456789; + auto const result = hfs.traceNum(msg, num); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == msg.size() + sizeof(num)); + } + + void + testTraceAccount() + { + testcase("traceAccount"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + std::string msg = "trace account"; + // Valid account + { + auto const result = hfs.traceAccount(msg, env.master.id()); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT( + result.value() == + msg.size() + toBase58(env.master.id()).size()); + } + } + + void + testTraceAmount() + { + testcase("traceAmount"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + std::string msg = "trace amount"; + STAmount amount = XRP(12345); + { + auto const result = hfs.traceAmount(msg, amount); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT( + result.value() == msg.size() + amount.getFullText().size()); + } + + // IOU amount + Account const alice("alice"); + env.fund(XRP(1000), alice); + env.close(); + STAmount iouAmount = env.master["USD"](100); + { + auto const result = hfs.traceAmount(msg, iouAmount); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT( + result.value() == + msg.size() + iouAmount.getFullText().size()); + } + + // MPT amount + { + auto const mptId = makeMptID(42, env.master.id()); + Asset mptAsset = Asset(mptId); + STAmount mptAmount(mptAsset, 123456); + auto const result = hfs.traceAmount(msg, mptAmount); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT( + result.value() == + msg.size() + mptAmount.getFullText().size()); + } + } + + // clang-format off + + int const normalExp = 15; + + Bytes const floatIntMin = {0x99, 0x20, 0xc4, 0x9b, 0xa5, 0xe3, 0x53, 0xf8}; // -2^63 + Bytes const floatIntZero = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 0 + Bytes const floatIntMax = {0xd9, 0x20, 0xc4, 0x9b, 0xa5, 0xe3, 0x53, 0xf8}; // 2^63-1 + Bytes const floatUIntMax = {0xd9, 0x46, 0x8d, 0xb8, 0xba, 0xc7, 0x10, 0xcb}; // 2^64 + Bytes const floatMaxExp = {0xEC, 0x43, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 1e(80+15) + Bytes const floatPreMaxExp = {0xEC, 0x03, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 1e(79+15) + Bytes const floatMinusMaxExp = {0xAC, 0x43, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // -1e(80+15) + Bytes const floatMaxIOU = {0xEC, 0x63, 0x86, 0xF2, 0x6F, 0xC0, 0xFF, 0xFF}; // 1e(81+15)-1 + Bytes const floatMinExp = {0xC0, 0x43, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 1e-96 + Bytes const float1 = {0xD4, 0x83, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 1 + Bytes const floatMinus1 = {0x94, 0x83, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // -1 + Bytes const float1More = {0xD4, 0x83, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x01}; // 1.000 000 000 000 001 + Bytes const float2 = {0xD4, 0x87, 0x1A, 0xFD, 0x49, 0x8D, 0x00, 0x00}; // 2 + Bytes const float10 = {0xD4, 0xC3, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 10 + Bytes const floatInvalidZero = {0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // INVALID + Bytes const floatPi = {0xD4, 0x8B, 0x29, 0x43, 0x0A, 0x25, 0x6D, 0x21}; // 3.141592653589793 + + std::string const invalid = "invalid_data"; + + // clang-format on + + void + testFloatTrace() + { + testcase("FloatTrace"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + std::string msg = "trace float"; + + { + auto const result = hfs.traceFloat(msg, makeSlice(invalid)); + BEAST_EXPECT( + result && + *result == + msg.size() + 14 /* error msg size*/ + invalid.size() * 2); + } + + { + auto const result = hfs.traceFloat(msg, makeSlice(floatMaxExp)); + BEAST_EXPECT( + result && *result == msg.size() + 19 /* string represenation*/); + } + } + + void + testFloatFromInt() + { + testcase("FloatFromInt"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = + hfs.floatFromInt(std::numeric_limits::min(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatFromInt(std::numeric_limits::min(), 4); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatFromInt(std::numeric_limits::min(), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntMin); + } + + { + auto const result = hfs.floatFromInt(0, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + + { + auto const result = + hfs.floatFromInt(std::numeric_limits::max(), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntMax); + } + } + + void + testFloatFromUint() + { + testcase("FloatFromUint"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = + hfs.floatFromUint(std::numeric_limits::min(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatFromUint(std::numeric_limits::min(), 4); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatFromUint(0, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + + { + auto const result = + hfs.floatFromUint(std::numeric_limits::max(), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatUIntMax); + } + } + + void + testFloatSet() + { + testcase("FloatSet"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatSet(1, 0, -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatSet(1, 0, 4); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatSet(1, Number::maxExponent + normalExp + 1, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == + HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = + hfs.floatSet(1, IOUAmount::maxExponent + normalExp + 1, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == + HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = + hfs.floatSet(1, IOUAmount::minExponent + normalExp - 1, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + + { + auto const result = + hfs.floatSet(1, IOUAmount::maxExponent + normalExp, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxExp); + } + + { + auto const result = + hfs.floatSet(-1, IOUAmount::maxExponent + normalExp, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMinusMaxExp); + } + + { + auto const result = + hfs.floatSet(1, IOUAmount::maxExponent + normalExp - 1, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatPreMaxExp); + } + + { + auto const result = + hfs.floatSet(IOUAmount::maxMantissa, IOUAmount::maxExponent, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxIOU); + } + + { + auto const result = + hfs.floatSet(1, IOUAmount::minExponent + normalExp, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMinExp); + } + + { + auto const result = hfs.floatSet(10, -1, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == float1); + } + } + + void + testFloatCompare() + { + testcase("FloatCompare"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatCompare(Slice(), Slice()); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatCompare(makeSlice(floatInvalidZero), Slice()); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatCompare(makeSlice(float1), makeSlice(invalid)); + BEAST_EXPECT( + !result && + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto x = floatMaxExp; + // exp = 81 + 97 = 178 + x[1] |= 0x80; + x[1] &= 0xBF; + auto const result = + hfs.floatCompare(makeSlice(x), makeSlice(floatMaxExp)); + BEAST_EXPECT( + !result && + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatCompare( + makeSlice(floatIntMin), makeSlice(floatIntZero)); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == 2); + } + + { + auto const result = hfs.floatCompare( + makeSlice(floatIntMax), makeSlice(floatIntZero)); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == 1); + } + + { + auto const result = + hfs.floatCompare(makeSlice(float1), makeSlice(float1)); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == 0); + } + } + + void + testFloatAdd() + { + testcase("floatAdd"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatAdd(Slice(), Slice(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatAdd(Slice(), Slice(), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatAdd(makeSlice(float1), makeSlice(invalid), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatAdd(makeSlice(floatMaxIOU), makeSlice(floatMaxExp), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == + HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = hfs.floatAdd( + makeSlice(floatIntMin), makeSlice(floatIntZero), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntMin); + } + + { + auto const result = + hfs.floatAdd(makeSlice(floatIntMax), makeSlice(floatIntMin), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + } + + void + testFloatSubtract() + { + testcase("floatSubtract"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatSubtract(Slice(), Slice(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatSubtract(Slice(), Slice(), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatSubtract(makeSlice(float1), makeSlice(invalid), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatSubtract( + makeSlice(floatMaxIOU), makeSlice(floatMinusMaxExp), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == + HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = hfs.floatSubtract( + makeSlice(floatIntMin), makeSlice(floatIntZero), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntMin); + } + + { + auto const result = hfs.floatSubtract( + makeSlice(floatIntZero), makeSlice(float1), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMinus1); + } + } + + void + testFloatMultiply() + { + testcase("floatMultiply"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatMultiply(Slice(), Slice(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatMultiply(Slice(), Slice(), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatMultiply(makeSlice(float1), makeSlice(invalid), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatMultiply( + makeSlice(floatMaxIOU), makeSlice(float1More), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == + HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = + hfs.floatMultiply(makeSlice(float1), makeSlice(float1), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == float1); + } + + { + auto const result = hfs.floatMultiply( + makeSlice(floatIntZero), makeSlice(floatMaxIOU), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + + { + auto const result = hfs.floatMultiply( + makeSlice(float10), makeSlice(floatPreMaxExp), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxExp); + } + } + + void + testFloatDivide() + { + testcase("floatDivide"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatDivide(Slice(), Slice(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatDivide(Slice(), Slice(), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatDivide(makeSlice(float1), makeSlice(invalid), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatDivide(makeSlice(float1), makeSlice(floatIntZero), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == + HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const y = hfs.floatSet( + IOUAmount::maxMantissa, -normalExp - 1, 0); // 0.9999999... + if (BEAST_EXPECT(y)) + { + auto const result = + hfs.floatDivide(makeSlice(floatMaxIOU), makeSlice(*y), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == + HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + } + + { + auto const result = + hfs.floatDivide(makeSlice(floatIntZero), makeSlice(float1), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + + { + auto const result = + hfs.floatDivide(makeSlice(floatMaxExp), makeSlice(float10), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatPreMaxExp); + } + } + + void + testFloatRoot() + { + testcase("floatRoot"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatRoot(Slice(), 2, -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatRoot(makeSlice(invalid), 3, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatRoot(makeSlice(float1), -2, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatRoot(makeSlice(floatIntZero), 2, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + + { + auto const result = hfs.floatRoot(makeSlice(floatMaxIOU), 1, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxIOU); + } + + { + auto const x = hfs.floatSet(100, 0, 0); // 100 + if (BEAST_EXPECT(x)) + { + auto const result = hfs.floatRoot(makeSlice(*x), 2, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == float10); + } + } + + { + auto const x = hfs.floatSet(1000, 0, 0); // 1000 + if (BEAST_EXPECT(x)) + { + auto const result = hfs.floatRoot(makeSlice(*x), 3, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == float10); + } + } + + { + auto const x = hfs.floatSet(1, -2, 0); // 0.01 + auto const y = hfs.floatSet(1, -1, 0); // 0.1 + if (BEAST_EXPECT(x && y)) + { + auto const result = hfs.floatRoot(makeSlice(*x), 2, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == *y); + } + } + } + + void + testFloatPower() + { + testcase("floatPower"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatPower(Slice(), 2, -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatPower(makeSlice(invalid), 3, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatPower(makeSlice(float1), -2, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 2, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == + HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 81, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 2, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == + HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 0, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == float1); + } + + { + auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 1, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxIOU); + } + + { + auto const x = hfs.floatSet(100, 0, 0); // 100 + if (BEAST_EXPECT(x)) + { + auto const result = hfs.floatPower(makeSlice(float10), 2, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == *x); + } + } + + { + auto const x = hfs.floatSet(1, -1, 0); // 0.1 + auto const y = hfs.floatSet(1, -2, 0); // 0.01 + if (BEAST_EXPECT(x && y)) + { + auto const result = hfs.floatPower(makeSlice(*x), 2, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == *y); + } + } + } + + void + testFloatLog() + { + testcase("floatLog"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatLog(Slice(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatLog(makeSlice(invalid), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT( + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + // perf test logs + // { + // auto const result = hfs.floatLog(makeSlice(floatPi), 0); + // if (BEAST_EXPECT(result)) + // { + // std::cout << "lg(" << floatToString(makeSlice(floatPi)) + // << ") = " << floatToString(makeSlice(*result)) + // << std::endl; + // } + // } + // { + // auto const result = hfs.floatLog(makeSlice(floatIntMax), 0); + // if (BEAST_EXPECT(result)) + // { + // std::cout << "lg(" << floatToString(makeSlice(floatIntMax)) + // << ") = " << floatToString(makeSlice(*result)) + // << std::endl; + // } + // } + + // { + // auto const result = hfs.floatLog(makeSlice(floatMaxExp), 0); + // if (BEAST_EXPECT(result)) + // { + // std::cout << "lg(" << floatToString(makeSlice(floatMaxExp)) + // << ") = " << floatToString(makeSlice(*result)) + // << std::endl; + // } + // } + + // { + // auto const result = hfs.floatLog(makeSlice(floatMaxIOU), 0); + // if (BEAST_EXPECT(result)) + // { + // std::cout << "lg(" << floatToString(makeSlice(floatMaxIOU)) + // << ") = " << floatToString(makeSlice(*result)) + // << std::endl; + // } + // } + + { + auto const x = + hfs.floatSet(9'500'000'000'000'001, -14, 0); // almost 80+15 + if (BEAST_EXPECT(x)) + { + auto const result = hfs.floatLog(makeSlice(floatMaxExp), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == *x); + } + } + + { + auto const x = hfs.floatSet(100, 0, 0); // 100 + if (BEAST_EXPECT(x)) + { + auto const result = hfs.floatLog(makeSlice(*x), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == float2); + } + } + + { + auto const x = hfs.floatSet(1000, 0, 0); // 1000 + auto const y = hfs.floatSet(3, 0, 0); // 0.1 + if (BEAST_EXPECT(x && y)) + { + auto const result = hfs.floatLog(makeSlice(*x), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == *y); + } + } + + { + auto const x = hfs.floatSet(1, -2, 0); // 0.01 + auto const y = + hfs.floatSet(-1999999993734431, -15, 0); // almost -2 + if (BEAST_EXPECT(x && y)) + { + auto const result = hfs.floatLog(makeSlice(*x), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == *y); + } + } + } + + void + testFloatNonIOU() + { + testcase("float Xrp+Mpt"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = + keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto const y = hfs.floatSet(20, 0, 0); + if (!BEAST_EXPECT(y)) + return; + + Bytes x(8); + + // XRP + memset(x.data(), 0, x.size()); + x[0] = 0x40; + x[7] = 10; + + { + auto const result = + hfs.floatCompare(makeSlice(x), makeSlice(float10)); + BEAST_EXPECT( + !result && + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatAdd(makeSlice(float10), makeSlice(x), 0); + BEAST_EXPECT( + !result && + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + // MPT + memset(x.data(), 0, x.size()); + x[0] = 0x60; + x[7] = 10; + + { + auto const result = + hfs.floatCompare(makeSlice(x), makeSlice(float10)); + BEAST_EXPECT( + !result && + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatAdd(makeSlice(float10), makeSlice(x), 0); + BEAST_EXPECT( + !result && + result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + } + + void + testFloats() + { + testFloatTrace(); + testFloatFromInt(); + testFloatFromUint(); + testFloatSet(); + testFloatCompare(); + testFloatAdd(); + testFloatSubtract(); + testFloatMultiply(); + testFloatDivide(); + testFloatRoot(); + testFloatPower(); + testFloatLog(); + testFloatNonIOU(); + } + + void + run() override + { + testGetLedgerSqn(); + testGetParentLedgerTime(); + testGetParentLedgerHash(); + testGetBaseFee(); + testIsAmendmentEnabled(); + testCacheLedgerObj(); + testGetTxField(); + testGetCurrentLedgerObjField(); + testGetLedgerObjField(); + testGetTxNestedField(); + testGetCurrentLedgerObjNestedField(); + testGetLedgerObjNestedField(); + testGetTxArrayLen(); + testGetCurrentLedgerObjArrayLen(); + testGetLedgerObjArrayLen(); + testGetTxNestedArrayLen(); + testGetCurrentLedgerObjNestedArrayLen(); + testGetLedgerObjNestedArrayLen(); + testUpdateData(); + testCheckSignature(); + testComputeSha512HalfHash(); + testKeyletFunctions(); + testGetNFT(); + testGetNFTIssuer(); + testGetNFTTaxon(); + testGetNFTFlags(); + testGetNFTTransferFee(); + testGetNFTSerial(); + testTrace(); + testTraceNum(); + testTraceAccount(); + testTraceAmount(); + testFloats(); + } +}; + +BEAST_DEFINE_TESTSUITE(HostFuncImpl, app, xrpl); + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/NetworkOPs_test.cpp b/src/test/app/NetworkOPs_test.cpp index d5616009fa..582f9a8084 100644 --- a/src/test/app/NetworkOPs_test.cpp +++ b/src/test/app/NetworkOPs_test.cpp @@ -38,14 +38,14 @@ public: auto const jtx = env.jt(ticket::create(alice, 1), seq(1), fee(10)); - auto transacionId = jtx.stx->getTransactionID(); + auto transactionId = jtx.stx->getTransactionID(); env.app().getHashRouter().setFlags( - transacionId, HashRouterFlags::HELD); + transactionId, HashRouterFlags::HELD); env(jtx, json(jss::Sequence, 1), ter(terNO_ACCOUNT)); env.app().getHashRouter().setFlags( - transacionId, HashRouterFlags::BAD); + transactionId, HashRouterFlags::BAD); env.close(); } diff --git a/src/test/app/PseudoTx_test.cpp b/src/test/app/PseudoTx_test.cpp index f01ba616c8..94d3d17d38 100644 --- a/src/test/app/PseudoTx_test.cpp +++ b/src/test/app/PseudoTx_test.cpp @@ -33,6 +33,12 @@ struct PseudoTx_test : public beast::unit_test::suite obj[sfReserveIncrement] = 0; obj[sfReferenceFeeUnits] = 0; } + if (rules.enabled(featureSmartEscrow)) + { + obj[sfExtensionComputeLimit] = 0; + obj[sfExtensionSizeLimit] = 0; + obj[sfGasPrice] = 0; + } })); res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) { @@ -101,7 +107,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(); } diff --git a/src/test/app/TestHostFunctions.h b/src/test/app/TestHostFunctions.h new file mode 100644 index 0000000000..0cb010b0e9 --- /dev/null +++ b/src/test/app/TestHostFunctions.h @@ -0,0 +1,1329 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include + +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 + getLedgerSqn() 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 + getLedgerSqn() override + { + return 12345; + } + + Expected + getParentLedgerTime() override + { + return 67890; + } + + Expected + getParentLedgerHash() override + { + return env_.current()->header().parentHash; + } + + Expected + getBaseFee() override + { + return 10; + } + + Expected + isAmendmentEnabled(uint256 const& amendmentId) override + { + return 1; + } + + Expected + isAmendmentEnabled(std::string_view const& amendmentName) override + { + return 1; + } + + virtual Expected + cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override + { + return 1; + } + + Expected + getTxField(SField const& fname) override + { + if (fname == sfAccount) + return Bytes(accountID_.begin(), accountID_.end()); + else if (fname == sfFee) + { + int64_t x = 235; + uint8_t const* p = reinterpret_cast(&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(&data); + auto const* e = reinterpret_cast(&data + 1); + return Bytes{b, e}; + } + return Bytes(); + } + + Expected + getCurrentLedgerObjField(SField const& fname) 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 + getLedgerObjField(int32_t cacheIdx, SField const& fname) override + { + if (fname == sfBalance) + { + int64_t x = 10'000; + uint8_t const* p = reinterpret_cast(&x); + return Bytes{p, p + sizeof(x)}; + } + else if (fname == sfAccount) + { + return Bytes(accountID_.begin(), accountID_.end()); + } + return data_; + } + + Expected + getTxNestedField(Slice const& locator) override + { + if (locator.size() == 4) + { + int32_t const* l = reinterpret_cast(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 + getCurrentLedgerObjNestedField(Slice const& locator) override + { + if (locator.size() == 4) + { + int32_t const* l = reinterpret_cast(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 + getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) override + { + if (locator.size() == 4) + { + int32_t const* l = reinterpret_cast(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 + getTxArrayLen(SField const& fname) override + { + return 32; + } + + Expected + getCurrentLedgerObjArrayLen(SField const& fname) override + { + return 32; + } + + Expected + getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) override + { + return 32; + } + + Expected + getTxNestedArrayLen(Slice const& locator) override + { + return 32; + } + + Expected + getCurrentLedgerObjNestedArrayLen(Slice const& locator) override + { + return 32; + } + + Expected + getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) override + { + return 32; + } + + Expected + updateData(Slice const& data) override + { + return data.size(); + } + + Expected + checkSignature( + Slice const& message, + Slice const& signature, + Slice const& pubkey) override + { + return 1; + } + + Expected + computeSha512HalfHash(Slice const& data) override + { + return env_.current()->header().parentHash; + } + + Expected + accountKeylet(AccountID const& account) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::account(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + ammKeylet(Asset const& issue1, Asset const& issue2) override + { + if (issue1 == issue2) + return Unexpected(HostFunctionError::INVALID_PARAMS); + if (issue1.holds() || issue2.holds()) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto const keylet = keylet::amm(issue1, issue2); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + credentialKeylet( + AccountID const& subject, + AccountID const& issuer, + Slice const& credentialType) 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 + escrowKeylet(AccountID const& account, std::uint32_t seq) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::escrow(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + oracleKeylet(AccountID const& account, std::uint32_t documentId) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::oracle(account, documentId); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + getNFT(AccountID const& account, uint256 const& nftId) override + { + if (!account || !nftId) + { + return Unexpected(HostFunctionError::INVALID_PARAMS); + } + + std::string s = "https://ripple.com"; + return Bytes(s.begin(), s.end()); + } + + Expected + getNFTIssuer(uint256 const& nftId) override + { + return Bytes(accountID_.begin(), accountID_.end()); + } + + Expected + getNFTTaxon(uint256 const& nftId) override + { + return 4; + } + + Expected + getNFTFlags(uint256 const& nftId) override + { + return 8; + } + + Expected + getNFTTransferFee(uint256 const& nftId) override + { + return 10; + } + + Expected + getNFTSerial(uint256 const& nftId) override + { + return 4; + } + + Expected + trace(std::string_view const& msg, Slice const& data, bool asHex) override + { +#ifdef DEBUG_OUTPUT + auto& j = std::cerr; +#else + auto j = getJournal().trace(); +#endif + if (!asHex) + { + j << "WASM TRACE: " << msg << " " + << std::string_view( + reinterpret_cast(data.data()), data.size()); + } + else + { + std::string hex; + hex.reserve(data.size() * 2); + boost::algorithm::hex( + data.begin(), data.end(), std::back_inserter(hex)); + j << "WASM DEV TRACE: " << msg << " " << hex; + } + +#ifdef DEBUG_OUTPUT + j << std::endl; +#endif + + return msg.size() + data.size() * (asHex ? 2 : 1); + } + + Expected + traceNum(std::string_view const& msg, int64_t data) override + { +#ifdef DEBUG_OUTPUT + auto& j = std::cerr; +#else + auto j = getJournal().trace(); +#endif + j << "WASM TRACE NUM: " << msg << " " << data; + +#ifdef DEBUG_OUTPUT + j << std::endl; +#endif + return msg.size() + sizeof(data); + } + + Expected + traceAccount(std::string_view const& msg, AccountID const& account) override + { +#ifdef DEBUG_OUTPUT + auto& j = std::cerr; +#else + auto j = getJournal().trace(); +#endif + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + auto const accountStr = toBase58(account); + + j << "WASM TRACE ACCOUNT: " << msg << " " << accountStr; + return msg.size() + accountStr.size(); + } + + Expected + traceFloat(std::string_view const& msg, Slice const& data) override + { +#ifdef DEBUG_OUTPUT + auto& j = std::cerr; +#else + auto j = getJournal().trace(); +#endif + auto const s = floatToString(data); + j << "WASM TRACE FLOAT: " << msg << " " << s; + +#ifdef DEBUG_OUTPUT + j << std::endl; +#endif + return msg.size() + s.size(); + } + + Expected + traceAmount(std::string_view const& msg, STAmount const& amount) override + { +#ifdef DEBUG_OUTPUT + auto& j = std::cerr; +#else + auto j = getJournal().trace(); +#endif + auto const amountStr = amount.getFullText(); + j << "WASM TRACE AMOUNT: " << msg << " " << amountStr; + return msg.size() + amountStr.size(); + } + + Expected + floatFromInt(int64_t x, int32_t mode) override + { + return floatFromIntImpl(x, mode); + } + + Expected + floatFromUint(uint64_t x, int32_t mode) override + { + return floatFromUintImpl(x, mode); + } + + Expected + floatSet(int64_t mantissa, int32_t exponent, int32_t mode) override + { + return floatSetImpl(mantissa, exponent, mode); + } + + Expected + floatCompare(Slice const& x, Slice const& y) override + { + return floatCompareImpl(x, y); + } + + Expected + floatAdd(Slice const& x, Slice const& y, int32_t mode) override + { + return floatAddImpl(x, y, mode); + } + + Expected + floatSubtract(Slice const& x, Slice const& y, int32_t mode) override + { + return floatSubtractImpl(x, y, mode); + } + + Expected + floatMultiply(Slice const& x, Slice const& y, int32_t mode) override + { + return floatMultiplyImpl(x, y, mode); + } + + Expected + floatDivide(Slice const& x, Slice const& y, int32_t mode) override + { + return floatDivideImpl(x, y, mode); + } + + Expected + floatRoot(Slice const& x, int32_t n, int32_t mode) override + { + return floatRootImpl(x, n, mode); + } + + Expected + floatPower(Slice const& x, int32_t n, int32_t mode) override + { + return floatPowerImpl(x, n, mode); + } + + Expected + floatLog(Slice const& x, int32_t mode) override + { + return 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_; + } +}; + +struct PerfHostFunctions : public TestHostFunctions +{ + Keylet leKey; + std::shared_ptr currentLedgerObj = nullptr; + bool isLedgerObjCached = false; + + static int constexpr MAX_CACHE = 256; + std::array, MAX_CACHE> cache; + // std::optional data_; // deferred data update, not used in + // performance + std::shared_ptr tx_; + + void const* rt_ = nullptr; + + PerfHostFunctions( + test::jtx::Env& env, + Keylet const& k, + std::shared_ptr&& tx) + : TestHostFunctions(env), leKey(k), tx_(std::move(tx)) + { + } + + Expected + getLedgerSqn() override + { + auto seq = env_.current()->seq(); + if (seq > std::numeric_limits::max()) + return Unexpected(HostFunctionError::INTERNAL); // LCOV_EXCL_LINE + return static_cast(seq); + } + + Expected + getParentLedgerTime() override + { + auto time = + env_.current()->parentCloseTime().time_since_epoch().count(); + if (time > std::numeric_limits::max()) + return Unexpected(HostFunctionError::INTERNAL); + return static_cast(time); + } + + Expected + getParentLedgerHash() override + { + return env_.current()->header().parentHash; + } + + Expected + getBaseFee() override + { + auto fee = env_.current()->fees().base.drops(); + if (fee > std::numeric_limits::max()) + return Unexpected(HostFunctionError::INTERNAL); + return static_cast(fee); + } + + Expected + isAmendmentEnabled(uint256 const& amendmentId) override + { + return env_.current()->rules().enabled(amendmentId); + } + + Expected + isAmendmentEnabled(std::string_view const& amendmentName) override + { + auto const& table = env_.app().getAmendmentTable(); + auto const amendment = table.find(std::string(amendmentName)); + return env_.current()->rules().enabled(amendment); + } + + Expected, HostFunctionError> + getCurrentLedgerObj() + { + if (!isLedgerObjCached) + { + isLedgerObjCached = true; + currentLedgerObj = env_.le(leKey); + } + if (currentLedgerObj) + return currentLedgerObj; + return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + } + + Expected, HostFunctionError> + peekCurrentLedgerObj(int32_t cacheIdx) + { + --cacheIdx; + if (cacheIdx < 0 || cacheIdx >= MAX_CACHE) + return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + + if (!cache[cacheIdx]) + { // return Unexpected(HostFunctionError::INVALID_SLOT); + auto const r = getCurrentLedgerObj(); + if (!r) + return Unexpected(r.error()); + cache[cacheIdx] = *r; + } + + return cache[cacheIdx]; + } + + Expected + normalizeCacheIndex(int32_t cacheIdx) + { + --cacheIdx; + if (cacheIdx < 0 || cacheIdx >= MAX_CACHE) + return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + if (!cache[cacheIdx]) + return Unexpected(HostFunctionError::EMPTY_SLOT); + return cacheIdx; + } + + virtual Expected + cacheLedgerObj(uint256 const&, int32_t cacheIdx) override + { + // auto const& keylet = keylet::unchecked(objId); + + static int32_t intIdx = 0; + + if (cacheIdx < 0 || cacheIdx > MAX_CACHE) + return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + + if (!cacheIdx) + { + for (cacheIdx = 0; cacheIdx < MAX_CACHE; ++cacheIdx) + if (!cache[cacheIdx]) + break; + if (cacheIdx >= MAX_CACHE) + cacheIdx = intIdx++ % MAX_CACHE; + } + else + --cacheIdx; + + if (cacheIdx >= MAX_CACHE) + return Unexpected(HostFunctionError::SLOTS_FULL); + + cache[cacheIdx] = env_.le(leKey); + if (!cache[cacheIdx]) + return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + return cacheIdx + 1; + } + + static Expected + getAnyFieldData(STBase const* obj) + { + // auto const& fname = obj.getFName(); + if (!obj) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + auto const stype = obj->getSType(); + switch (stype) + { + // LCOV_EXCL_START + case STI_UNKNOWN: + case STI_NOTPRESENT: + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + break; + // LCOV_EXCL_STOP + case STI_OBJECT: + case STI_ARRAY: + return Unexpected(HostFunctionError::NOT_LEAF_FIELD); + break; + case STI_ACCOUNT: { + auto const* account(static_cast(obj)); + auto const& data = account->value(); + return Bytes{data.begin(), data.end()}; + } + break; + case STI_AMOUNT: + // will be processed by serializer + break; + case STI_ISSUE: { + auto const* issue(static_cast(obj)); + Asset const& asset(issue->value()); + // XRP and IOU will be processed by serializer + if (asset.holds()) + { + // MPT + auto const& mptIssue = asset.get(); + auto const& mptID = mptIssue.getMptID(); + return Bytes{mptID.cbegin(), mptID.cend()}; + } + } + break; + case STI_VL: { + auto const* vl(static_cast(obj)); + auto const& data = vl->value(); + return Bytes{data.begin(), data.end()}; + } + break; + case STI_UINT16: { + auto const& num( + static_cast const*>(obj)); + std::uint16_t const data = num->value(); + auto const* b = reinterpret_cast(&data); + auto const* e = reinterpret_cast(&data + 1); + return Bytes{b, e}; + } + case STI_UINT32: { + auto const* num( + static_cast const*>(obj)); + std::uint32_t const data = num->value(); + auto const* b = reinterpret_cast(&data); + auto const* e = reinterpret_cast(&data + 1); + return Bytes{b, e}; + } + break; + default: + break; // default to serializer + } + + Serializer msg; + obj->add(msg); + auto const data = msg.getData(); + + return data; + } + + Expected + getTxField(SField const& fname) override + { + return getAnyFieldData(tx_->peekAtPField(fname)); + } + + Expected + getCurrentLedgerObjField(SField const& fname) override + { + auto const sle = getCurrentLedgerObj(); + if (!sle) + return Unexpected(sle.error()); + return getAnyFieldData((*sle)->peekAtPField(fname)); + } + + Expected + getLedgerObjField(int32_t cacheIdx, SField const& fname) override + { + auto const sle = peekCurrentLedgerObj(cacheIdx); + if (!sle) + return Unexpected(sle.error()); + return getAnyFieldData((*sle)->peekAtPField(fname)); + } + + static inline bool + noField(STBase const* field) + { + return !field || (STI_NOTPRESENT == field->getSType()) || + (STI_UNKNOWN == field->getSType()); + } + + static Expected + locateField(STObject const& obj, Slice const& locator) + { + if (locator.empty() || (locator.size() & 3)) // must be multiple of 4 + return Unexpected(HostFunctionError::LOCATOR_MALFORMED); + + int32_t const* locPtr = + reinterpret_cast(locator.data()); + int32_t const locSize = locator.size() / 4; + STBase const* field = nullptr; + auto const& knownSFields = SField::getKnownCodeToField(); + + { + int32_t const sfieldCode = locPtr[0]; + auto const it = knownSFields.find(sfieldCode); + if (it == knownSFields.end()) + return Unexpected(HostFunctionError::INVALID_FIELD); + + auto const& fname(*it->second); + field = obj.peekAtPField(fname); + if (noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + + for (int i = 1; i < locSize; ++i) + { + int32_t const sfieldCode = locPtr[i]; + + if (STI_ARRAY == field->getSType()) + { + auto const* arr = static_cast(field); + if (sfieldCode >= arr->size()) + return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS); + field = &(arr->operator[](sfieldCode)); + } + else if (STI_OBJECT == field->getSType()) + { + auto const* o = static_cast(field); + + auto const it = knownSFields.find(sfieldCode); + if (it == knownSFields.end()) + return Unexpected(HostFunctionError::INVALID_FIELD); + + auto const& fname(*it->second); + field = o->peekAtPField(fname); + } + else // simple field must be the last one + { + return Unexpected(HostFunctionError::LOCATOR_MALFORMED); + } + + if (noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + + return field; + } + + Expected + getTxNestedField(Slice const& locator) override + { + // std::cout << tx_->getJson(JsonOptions::none).toStyledString() << + // std::endl; + auto const r = locateField(*tx_, locator); + if (!r) + return Unexpected(r.error()); + return getAnyFieldData(*r); + } + + Expected + getCurrentLedgerObjNestedField(Slice const& locator) override + { + auto const sle = getCurrentLedgerObj(); + if (!sle) + return Unexpected(sle.error()); + + auto const r = locateField(**sle, locator); + if (!r) + return Unexpected(r.error()); + + return getAnyFieldData(*r); + } + + Expected + getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) override + { + auto const sle = peekCurrentLedgerObj(cacheIdx); + if (!sle) + return Unexpected(sle.error()); + + auto const r = locateField(**sle, locator); + if (!r) + return Unexpected(r.error()); + + return getAnyFieldData(*r); + } + + Expected + getTxArrayLen(SField const& fname) override + { + if (fname.fieldType != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + + auto const* field = tx_->peekAtPField(fname); + if (noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + if (field->getSType() != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + int32_t const sz = static_cast(field)->size(); + + return sz; + } + + Expected + getCurrentLedgerObjArrayLen(SField const& fname) override + { + if (fname.fieldType != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + + auto const sle = getCurrentLedgerObj(); + if (!sle) + return Unexpected(sle.error()); + + auto const* field = (*sle)->peekAtPField(fname); + if (noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + if (field->getSType() != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + int32_t const sz = static_cast(field)->size(); + + return sz; + } + + Expected + getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) override + { + if (fname.fieldType != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + + auto const sle = peekCurrentLedgerObj(cacheIdx); + if (!sle) + return Unexpected(sle.error()); + + auto const* field = (*sle)->peekAtPField(fname); + if (noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + if (field->getSType() != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + int32_t const sz = static_cast(field)->size(); + + return sz; + } + + Expected + getTxNestedArrayLen(Slice const& locator) override + { + auto const r = locateField(*tx_, locator); + if (!r) + return Unexpected(r.error()); + auto const* field = r.value(); + + if (field->getSType() != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + int32_t const sz = static_cast(field)->size(); + + return sz; + } + + Expected + getCurrentLedgerObjNestedArrayLen(Slice const& locator) override + { + auto const sle = getCurrentLedgerObj(); + if (!sle) + return Unexpected(sle.error()); + + auto const r = locateField(**sle, locator); + if (!r) + return Unexpected(r.error()); + auto const* field = r.value(); + + if (field->getSType() != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + int32_t const sz = static_cast(field)->size(); + + return sz; + } + + Expected + getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) override + { + auto const sle = peekCurrentLedgerObj(cacheIdx); + if (!sle) + return Unexpected(sle.error()); + + auto const r = locateField(**sle, locator); + if (!r) + return Unexpected(r.error()); + + auto const* field = r.value(); + + if (field->getSType() != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + int32_t const sz = static_cast(field)->size(); + + return sz; + } + + Expected + updateData(Slice const& data) override + { + if (data.size() > maxWasmDataLength) + return Unexpected(HostFunctionError::DATA_FIELD_TOO_LARGE); + + xrpl::detail::ApplyViewBase v( + env_.app().openLedger().current().get(), tapNONE); + + auto sle = v.peek(leKey); + if (!sle) + return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + + sle->setFieldVL(sfData, data); + v.update(sle); + + return data.size(); + } + + Expected + checkSignature( + Slice const& message, + Slice const& signature, + Slice const& pubkey) override + { + if (!publicKeyType(pubkey)) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + PublicKey const pk(pubkey); + return verify(pk, message, signature); + } + + Expected + computeSha512HalfHash(Slice const& data) override + { + auto const hash = sha512Half(data); + return hash; + } + + Expected + accountKeylet(AccountID const& account) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::account(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + ammKeylet(Asset const& issue1, Asset const& issue2) override + { + if (issue1 == issue2) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + // note: this should be removed with the MPT DEX amendment + if (issue1.holds() || issue2.holds()) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + auto const keylet = keylet::amm(issue1, issue2); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + checkKeylet(AccountID const& account, std::uint32_t seq) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::check(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + credentialKeylet( + AccountID const& subject, + AccountID const& issuer, + Slice const& credentialType) override + { + if (!subject || !issuer) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + if (credentialType.empty() || + credentialType.size() > maxCredentialTypeLength) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + auto const keylet = keylet::credential(subject, issuer, credentialType); + + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + didKeylet(AccountID const& account) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::did(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + delegateKeylet(AccountID const& account, AccountID const& authorize) + override + { + if (!account || !authorize) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + if (account == authorize) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto const keylet = keylet::delegate(account, authorize); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + depositPreauthKeylet(AccountID const& account, AccountID const& authorize) + override + { + if (!account || !authorize) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + if (account == authorize) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto const keylet = keylet::depositPreauth(account, authorize); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + escrowKeylet(AccountID const& account, std::uint32_t seq) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::escrow(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + lineKeylet( + AccountID const& account1, + AccountID const& account2, + Currency const& currency) override + { + if (!account1 || !account2) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + if (account1 == account2) + return Unexpected(HostFunctionError::INVALID_PARAMS); + if (currency.isZero()) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + auto const keylet = keylet::line(account1, account2, currency); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) override + { + if (!issuer) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + auto const keylet = keylet::mptIssuance(seq, issuer); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + mptokenKeylet(MPTID const& mptid, AccountID const& holder) override + { + if (!mptid) + return Unexpected(HostFunctionError::INVALID_PARAMS); + if (!holder) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + auto const keylet = keylet::mptoken(mptid, holder); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + nftOfferKeylet(AccountID const& account, std::uint32_t seq) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::nftoffer(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + offerKeylet(AccountID const& account, std::uint32_t seq) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::offer(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + oracleKeylet(AccountID const& account, std::uint32_t documentId) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::oracle(account, documentId); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + paychanKeylet( + AccountID const& account, + AccountID const& destination, + std::uint32_t seq) override + { + if (!account || !destination) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + if (account == destination) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto const keylet = keylet::payChan(account, destination, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) + override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::permissionedDomain(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + signersKeylet(AccountID const& account) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::signers(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + ticketKeylet(AccountID const& account, std::uint32_t seq) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::ticket(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + vaultKeylet(AccountID const& account, std::uint32_t seq) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::vault(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + getNFT(AccountID const& account, uint256 const& nftId) override + { + if (!account || !nftId) + { + getJournal().trace() << "WASM getNFT: Invalid account or NFT ID"; + return Unexpected(HostFunctionError::INVALID_PARAMS); + } + + auto obj = nft::findToken(*env_.current(), account, nftId); + if (!obj) + { + getJournal().trace() << "WASM getNFT: NFT not found"; + return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + } + + auto ouri = obj->at(~sfURI); + if (!ouri) + return Bytes(); + + Slice const s = ouri->value(); + return Bytes(s.begin(), s.end()); + } + + Expected + getNFTIssuer(uint256 const& nftId) override + { + auto const issuer = nft::getIssuer(nftId); + if (!issuer) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + return Bytes{issuer.begin(), issuer.end()}; + } + + Expected + getNFTTaxon(uint256 const& nftId) override + { + return nft::toUInt32(nft::getTaxon(nftId)); + } + + Expected + getNFTFlags(uint256 const& nftId) override + { + return nft::getFlags(nftId); + } + + Expected + getNFTTransferFee(uint256 const& nftId) override + { + return nft::getTransferFee(nftId); + } + + Expected + getNFTSerial(uint256 const& nftId) override + { + return nft::getSerial(nftId); + } +}; + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp new file mode 100644 index 0000000000..045cfd20fe --- /dev/null +++ b/src/test/app/Wasm_test.cpp @@ -0,0 +1,746 @@ +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif + +#include + +#include + +namespace xrpl { +namespace test { + +bool +testGetDataIncrement(); + +using Add_proto = int32_t(int32_t, int32_t); +static wasm_trap_t* +Add(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + int32_t Val1 = params->data[0].of.i32; + int32_t Val2 = params->data[1].of.i32; + // printf("Host function \"Add\": %d + %d\n", Val1, Val2); + results->data[0] = WASM_I32_VAL(Val1 + Val2); + return nullptr; +} + +struct Wasm_test : public beast::unit_test::suite +{ + void + testGetDataHelperFunctions() + { + testcase("getData helper functions"); + BEAST_EXPECT(testGetDataIncrement()); + } + + void + testWasmLib() + { + testcase("wasmtime lib test"); + // clang-format off + /* The WASM module buffer. */ + Bytes const wasm = {/* WASM header */ + 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, + /* Type section */ + 0x01, 0x07, 0x01, + /* function type {i32, i32} -> {i32} */ + 0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F, + /* Import section */ + 0x02, 0x13, 0x01, + /* module name: "extern" */ + 0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E, + /* extern name: "func-add" */ + 0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64, + /* import desc: func 0 */ + 0x00, 0x00, + /* Function section */ + 0x03, 0x02, 0x01, 0x00, + /* Export section */ + 0x07, 0x0A, 0x01, + /* export name: "addTwo" */ + 0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F, + /* export desc: func 0 */ + 0x00, 0x01, + /* Code section */ + 0x0A, 0x0A, 0x01, + /* code body */ + 0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B}; + // clang-format on + auto& vm = WasmEngine::instance(); + + ImportVec imports; + WasmImpFunc( + imports, "func-add", reinterpret_cast(&Add)); + + auto re = vm.run(wasm, "addTwo", wasmParams(1234, 5678), imports); + + // if (res) printf("invokeAdd get the result: %d\n", res.value()); + + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == 6'912, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 3, std::to_string(re->cost)); + } + } + + void + testBadWasm() + { + testcase("bad wasm test"); + + using namespace test::jtx; + + Env env{*this}; + HostFunctions hfs(env.journal); + + { + auto wasmHex = "00000000"; + auto wasmStr = boost::algorithm::unhex(std::string(wasmHex)); + std::vector wasm(wasmStr.begin(), wasmStr.end()); + std::string funcName("mock_escrow"); + + auto re = runEscrowWasm(wasm, hfs, funcName, {}, 15); + BEAST_EXPECT(!re); + } + + { + auto wasmHex = "00112233445566778899AA"; + auto wasmStr = boost::algorithm::unhex(std::string(wasmHex)); + std::vector wasm(wasmStr.begin(), wasmStr.end()); + std::string funcName("mock_escrow"); + + auto const re = preflightEscrowWasm(wasm, hfs, funcName); + BEAST_EXPECT(!isTesSuccess(re)); + } + + { + // FinishFunction wrong function name + // pub fn bad() -> bool { + // unsafe { host_lib::getLedgerSqn() >= 5 } + // } + auto const badWasmHex = + "0061736d010000000105016000017f02190108686f73745f6c69620c6765" + "744c656467657253716e00000302010005030100100611027f00418080c0" + "000b7f00418080c0000b072b04066d656d6f727902000362616400010a5f" + "5f646174615f656e6403000b5f5f686561705f6261736503010a09010700" + "100041044a0b004d0970726f64756365727302086c616e67756167650104" + "52757374000c70726f6365737365642d6279010572757374631d312e3835" + "2e31202834656231363132353020323032352d30332d31352900490f7461" + "726765745f6665617475726573042b0f6d757461626c652d676c6f62616c" + "732b087369676e2d6578742b0f7265666572656e63652d74797065732b0a" + "6d756c746976616c7565"; + auto wasmStr = boost::algorithm::unhex(std::string(badWasmHex)); + std::vector wasm(wasmStr.begin(), wasmStr.end()); + + auto const re = + preflightEscrowWasm(wasm, hfs, ESCROW_FUNCTION_NAME); + BEAST_EXPECT(!isTesSuccess(re)); + } + } + + void + testWasmLedgerSqn() + { + testcase("Wasm get ledger sequence"); + + auto wasmStr = boost::algorithm::unhex(ledgerSqnWasmHex); + Bytes wasm(wasmStr.begin(), wasmStr.end()); + + using namespace test::jtx; + + Env env{*this}; + TestLedgerDataProvider hf(env); + + ImportVec imports; + WASM_IMPORT_FUNC2(imports, getLedgerSqn, "get_ledger_sqn", &hf, 33); + auto& engine = WasmEngine::instance(); + + auto re = engine.run( + wasm, + ESCROW_FUNCTION_NAME, + {}, + imports, + &hf, + 1'000'000, + env.journal); + + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == 0, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 38, std::to_string(re->cost)); + } + + env.close(); + env.close(); + + // empty module - run the same instance + re = engine.run( + {}, ESCROW_FUNCTION_NAME, {}, imports, &hf, 1'000'000, env.journal); + + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == 5, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 76, std::to_string(re->cost)); + } + } + + void + testWasmFib() + { + testcase("Wasm fibo"); + + auto const ws = boost::algorithm::unhex(fibWasmHex); + Bytes const wasm(ws.begin(), ws.end()); + auto& engine = WasmEngine::instance(); + + auto const re = engine.run(wasm, "fib", wasmParams(10)); + + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == 55, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 696, std::to_string(re->cost)); + } + } + + void + testWasmSha() + { + testcase("Wasm sha"); + + auto const ws = boost::algorithm::unhex(sha512PureWasmHex); + Bytes const wasm(ws.begin(), ws.end()); + auto& engine = WasmEngine::instance(); + + auto const re = + engine.run(wasm, "sha512_process", wasmParams(sha512PureWasmHex)); + + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == 34'432, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 145'573, std::to_string(re->cost)); + } + } + + void + testWasmB58() + { + testcase("Wasm base58"); + auto const ws = boost::algorithm::unhex(b58WasmHex); + Bytes const wasm(ws.begin(), ws.end()); + auto& engine = WasmEngine::instance(); + + Bytes outb; + outb.resize(1024); + + auto const minsz = std::min( + static_cast(512), + static_cast(b58WasmHex.size())); + auto const s = std::string_view(b58WasmHex.c_str(), minsz); + + auto const re = engine.run(wasm, "b58enco", wasmParams(outb, s)); + + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == 700, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 2'701'528, std::to_string(re->cost)); + } + } + + void + testHFCost() + { + testcase("wasm test host functions cost"); + + using namespace test::jtx; + + Env env(*this); + { + std::string const wasmHex = allHostFunctionsWasmHex; + std::string const wasmStr = boost::algorithm::unhex(wasmHex); + std::vector const wasm(wasmStr.begin(), wasmStr.end()); + + auto& engine = WasmEngine::instance(); + + TestHostFunctions hfs(env, 0); + ImportVec imp = createWasmImport(hfs); + for (auto& i : imp) + i.second.gas = 0; + + auto re = engine.run( + wasm, + ESCROW_FUNCTION_NAME, + {}, + imp, + &hfs, + 1'000'000, + env.journal); + + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == 1, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 842, std::to_string(re->cost)); + } + + env.close(); + } + + env.close(); + env.close(); + env.close(); + env.close(); + env.close(); + + { + std::string const wasmHex = allHostFunctionsWasmHex; + std::string const wasmStr = boost::algorithm::unhex(wasmHex); + std::vector const wasm(wasmStr.begin(), wasmStr.end()); + + auto& engine = WasmEngine::instance(); + + TestHostFunctions hfs(env, 0); + ImportVec const imp = createWasmImport(hfs); + + auto re = engine.run( + wasm, + ESCROW_FUNCTION_NAME, + {}, + imp, + &hfs, + 1'000'000, + env.journal); + + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == 1, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 39'602, std::to_string(re->cost)); + } + + env.close(); + } + + // not enough gas + { + std::string const wasmHex = allHostFunctionsWasmHex; + std::string const wasmStr = boost::algorithm::unhex(wasmHex); + std::vector const wasm(wasmStr.begin(), wasmStr.end()); + + auto& engine = WasmEngine::instance(); + + TestHostFunctions hfs(env, 0); + ImportVec const imp = createWasmImport(hfs); + + auto re = engine.run( + wasm, ESCROW_FUNCTION_NAME, {}, imp, &hfs, 200, env.journal); + + if (BEAST_EXPECT(!re)) + { + BEAST_EXPECTS( + re.error() == tecFAILED_PROCESSING, + std::to_string(TERtoInt(re.error()))); + } + + env.close(); + } + } + + void + testEscrowWasmDN() + { + testcase("escrow wasm devnet test"); + + std::string const wasmStr = + boost::algorithm::unhex(allHostFunctionsWasmHex); + std::vector wasm(wasmStr.begin(), wasmStr.end()); + + using namespace test::jtx; + Env env{*this}; + { + TestHostFunctions nfs(env, 0); + auto re = + runEscrowWasm(wasm, nfs, ESCROW_FUNCTION_NAME, {}, 100'000); + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == 1, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 39'602, std::to_string(re->cost)); + } + } + + { + // max() gas + TestHostFunctions nfs(env, 0); + auto re = runEscrowWasm(wasm, nfs, ESCROW_FUNCTION_NAME, {}, -1); + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == 1, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 39'602, std::to_string(re->cost)); + } + } + + { // fail because trying to access nonexistent field + struct BadTestHostFunctions : public TestHostFunctions + { + explicit BadTestHostFunctions(Env& env) : TestHostFunctions(env) + { + } + Expected + getTxField(SField const& fname) override + { + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + }; + BadTestHostFunctions nfs(env); + auto re = + runEscrowWasm(wasm, nfs, ESCROW_FUNCTION_NAME, {}, 100'000); + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == -201, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 5'012, std::to_string(re->cost)); + } + } + + { // fail because trying to allocate more than MAX_PAGES memory + struct BadTestHostFunctions : public TestHostFunctions + { + explicit BadTestHostFunctions(Env& env) : TestHostFunctions(env) + { + } + Expected + getTxField(SField const& fname) override + { + return Bytes((128 + 1) * 64 * 1024, 1); + } + }; + BadTestHostFunctions nfs(env); + auto re = + runEscrowWasm(wasm, nfs, ESCROW_FUNCTION_NAME, {}, 100'000); + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == -201, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 5'012, std::to_string(re->cost)); + } + } + + { // fail because recursion too deep + + auto const wasmStr = boost::algorithm::unhex(deepRecursionHex); + std::vector wasm(wasmStr.begin(), wasmStr.end()); + + TestHostFunctionsSink nfs(env); + std::string funcName("finish"); + auto re = runEscrowWasm(wasm, nfs, funcName, {}, 1'000'000'000); + BEAST_EXPECT(!re && re.error()); + // std::cout << "bad case (deep recursion) result " << re.error() + // << std::endl; + + auto const& sink = nfs.getSink(); + auto countSubstr = [](std::string const& str, + std::string const& substr) { + std::size_t pos = 0; + int occurrences = 0; + while ((pos = str.find(substr, pos)) != std::string::npos) + { + occurrences++; + pos += substr.length(); + } + return occurrences; + }; + + auto const s = sink.messages().str(); + BEAST_EXPECT( + countSubstr(s, "WASMI Error: failure to call func") == 1); + BEAST_EXPECT(countSubstr(s, "exception: failure") > 0); + } + + { + // expected import not provided + auto wasmStr = boost::algorithm::unhex(ledgerSqnWasmHex); + Bytes wasm(wasmStr.begin(), wasmStr.end()); + TestLedgerDataProvider ledgerDataProvider(env); + + ImportVec imports; + WASM_IMPORT_FUNC2( + imports, getLedgerSqn, "get_ledger_sqn2", &ledgerDataProvider); + + auto& engine = WasmEngine::instance(); + + auto re = engine.run( + wasm, + ESCROW_FUNCTION_NAME, + {}, + imports, + &ledgerDataProvider, + 1'000'000, + env.journal); + + BEAST_EXPECT(!re); + } + + { + // bad import format + auto wasmStr = boost::algorithm::unhex(ledgerSqnWasmHex); + Bytes wasm(wasmStr.begin(), wasmStr.end()); + TestLedgerDataProvider ledgerDataProvider(env); + + ImportVec imports; + WASM_IMPORT_FUNC2( + imports, getLedgerSqn, "get_ledger_sqn", &ledgerDataProvider); + imports[0].first = nullptr; + + auto& engine = WasmEngine::instance(); + + auto re = engine.run( + wasm, + ESCROW_FUNCTION_NAME, + {}, + imports, + &ledgerDataProvider, + 1'000'000, + env.journal); + + BEAST_EXPECT(!re); + } + + { + // bad function name + auto wasmStr = boost::algorithm::unhex(ledgerSqnWasmHex); + Bytes wasm(wasmStr.begin(), wasmStr.end()); + TestLedgerDataProvider ledgerDataProvider(env); + + ImportVec imports; + WASM_IMPORT_FUNC2( + imports, getLedgerSqn, "get_ledger_sqn", &ledgerDataProvider); + + auto& engine = WasmEngine::instance(); + auto re = engine.run( + wasm, + "func1", + {}, + imports, + &ledgerDataProvider, + 1'000'000, + env.journal); + + BEAST_EXPECT(!re); + } + } + + void + testFloat() + { + testcase("float point"); + + std::string const funcName("finish"); + + using namespace test::jtx; + + Env env(*this); + { + std::string const wasmHex = floatTestsWasmHex; + std::string const wasmStr = boost::algorithm::unhex(wasmHex); + std::vector const wasm(wasmStr.begin(), wasmStr.end()); + + TestHostFunctions hf(env, 0); + auto re = runEscrowWasm(wasm, hf, funcName, {}, 100'000); + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == 1, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 97'356, std::to_string(re->cost)); + } + env.close(); + } + + { + std::string const wasmHex = float0Hex; + std::string const wasmStr = boost::algorithm::unhex(wasmHex); + std::vector const wasm(wasmStr.begin(), wasmStr.end()); + + TestHostFunctions hf(env, 0); + auto re = runEscrowWasm(wasm, hf, funcName, {}, 100'000); + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECTS(re->result == 1, std::to_string(re->result)); + BEAST_EXPECTS(re->cost == 2'054, std::to_string(re->cost)); + } + env.close(); + } + } + + void + perfTest() + { + testcase("Perf test host functions"); + + using namespace jtx; + using namespace std::chrono; + + // std::string const funcName("test"); + auto const& wasmHex = hfPerfTest; + std::string const wasmStr = boost::algorithm::unhex(wasmHex); + std::vector const wasm(wasmStr.begin(), wasmStr.end()); + + // std::string const credType = "abcde"; + // std::string const credType2 = "fghijk"; + // std::string const credType3 = "0123456"; + // char const uri[] = "uri"; + + Account const alan{"alan"}; + Account const bob{"bob"}; + Account const issuer{"issuer"}; + + { + Env env(*this); + // Env env(*this, envconfig(), {}, nullptr, + // beast::severities::kTrace); + env.fund(XRP(5000), alan, bob, issuer); + env.close(); + + // // create escrow + // auto const seq = env.seq(alan); + // auto const k = keylet::escrow(alan, seq); + // // auto const allowance = 3'600; + // auto escrowCreate = escrow::create(alan, bob, XRP(1000)); + // XRPAmount txnFees = env.current()->fees().base + 1000; + // env(escrowCreate, + // escrow::finish_function(wasmHex), + // escrow::finish_time(env.now() + 11s), + // escrow::cancel_time(env.now() + 100s), + // escrow::data("1000000000"), // 1000 XRP in drops + // memodata("memo1234567"), + // memodata("2memo1234567"), + // fee(txnFees)); + + // // create depositPreauth + // auto const k = keylet::depositPreauth( + // bob, + // {{issuer.id(), makeSlice(credType)}, + // {issuer.id(), makeSlice(credType2)}, + // {issuer.id(), makeSlice(credType3)}}); + // env(deposit::authCredentials( + // bob, + // {{issuer, credType}, + // {issuer, credType2}, + // {issuer, credType3}})); + + // create nft + [[maybe_unused]] uint256 const nft0{ + token::getNextID(env, alan, 0u)}; + env(token::mint(alan, 0u)); + auto const k = keylet::nftoffer(alan, 0); + [[maybe_unused]] uint256 const nft1{ + token::getNextID(env, alan, 0u)}; + + env(token::mint(alan, 0u), + token::uri( + "https://github.com/XRPLF/XRPL-Standards/discussions/" + "279?id=github.com/XRPLF/XRPL-Standards/discussions/" + "279&ut=github.com/XRPLF/XRPL-Standards/discussions/" + "279&sid=github.com/XRPLF/XRPL-Standards/discussions/" + "279&aot=github.com/XRPLF/XRPL-Standards/disc")); + [[maybe_unused]] uint256 const nft2{ + token::getNextID(env, alan, 0u)}; + env(token::mint(alan, 0u)); + env.close(); + + PerfHostFunctions nfs(env, k, env.tx()); + + auto re = runEscrowWasm(wasm, nfs, ESCROW_FUNCTION_NAME); + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECT(re->result); + std::cout << "Res: " << re->result << " cost: " << re->cost + << std::endl; + } + + // env(escrow::finish(alan, alan, seq), + // escrow::comp_allowance(allowance), + // fee(txnFees), + // ter(tesSUCCESS)); + + env.close(); + } + } + + void + testCodecovWasm() + { + testcase("Codecov wasm test"); + + using namespace test::jtx; + + Env env{*this}; + + auto const wasmStr = boost::algorithm::unhex(codecovTestsWasmHex); + Bytes const wasm(wasmStr.begin(), wasmStr.end()); + TestHostFunctions hfs(env, 0); + + auto const allowance = 292'345; + auto re = runEscrowWasm(wasm, hfs, ESCROW_FUNCTION_NAME, {}, allowance); + + if (BEAST_EXPECT(re.has_value())) + { + BEAST_EXPECT(re->result); + BEAST_EXPECTS(re->cost == allowance, std::to_string(re->cost)); + } + } + + void + testDisabledFloat() + { + testcase("disabled float"); + + using namespace test::jtx; + Env env{*this}; + + auto const wasmStr = boost::algorithm::unhex(disabledFloatHex); + Bytes wasm(wasmStr.begin(), wasmStr.end()); + std::string const funcName("finish"); + TestHostFunctions hfs(env, 0); + + { + // f32 set constant, opcode disabled exception + auto const re = runEscrowWasm(wasm, hfs, funcName, {}, 1'000'000); + if (BEAST_EXPECT(!re.has_value())) + { + BEAST_EXPECT(re.error() == tecFAILED_PROCESSING); + } + } + + { + // f32 add, can't create module exception + wasm[0x117] = 0x92; + auto const re = runEscrowWasm(wasm, hfs, funcName, {}, 1'000'000); + if (BEAST_EXPECT(!re.has_value())) + { + BEAST_EXPECT(re.error() == tecFAILED_PROCESSING); + } + } + } + + void + run() override + { + using namespace test::jtx; + + testGetDataHelperFunctions(); + testWasmLib(); + testBadWasm(); + testWasmLedgerSqn(); + + testWasmFib(); + testWasmSha(); + testWasmB58(); + + testHFCost(); + testEscrowWasmDN(); + testFloat(); + + testCodecovWasm(); + testDisabledFloat(); + + // perfTest(); + } +}; + +BEAST_DEFINE_TESTSUITE(Wasm, app, xrpl); + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/wasm_fixtures/.gitignore b/src/test/app/wasm_fixtures/.gitignore new file mode 100644 index 0000000000..08b2e8a256 --- /dev/null +++ b/src/test/app/wasm_fixtures/.gitignore @@ -0,0 +1,3 @@ +**/target +**/debug +*.wasm diff --git a/src/test/app/wasm_fixtures/all_host_functions/Cargo.lock b/src/test/app/wasm_fixtures/all_host_functions/Cargo.lock new file mode 100644 index 0000000000..25ea64bbd2 --- /dev/null +++ b/src/test/app/wasm_fixtures/all_host_functions/Cargo.lock @@ -0,0 +1,171 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "all_host_functions" +version = "0.1.0" +dependencies = [ + "xrpl-wasm-stdlib", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "xrpl-address-macro" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e" +dependencies = [ + "bs58", + "quote", + "sha2", + "syn", +] + +[[package]] +name = "xrpl-wasm-stdlib" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e" +dependencies = [ + "xrpl-address-macro", +] diff --git a/src/test/app/wasm_fixtures/all_host_functions/Cargo.toml b/src/test/app/wasm_fixtures/all_host_functions/Cargo.toml new file mode 100644 index 0000000000..82f6ce05a3 --- /dev/null +++ b/src/test/app/wasm_fixtures/all_host_functions/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "all_host_functions" +version = "0.1.0" +edition = "2024" + +# This empty workspace definition keeps this project independent of the parent workspace +[workspace] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-wasm-stdlib" } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "z" +lto = true diff --git a/src/test/app/wasm_fixtures/all_host_functions/src/lib.rs b/src/test/app/wasm_fixtures/all_host_functions/src/lib.rs new file mode 100644 index 0000000000..942e1c67c6 --- /dev/null +++ b/src/test/app/wasm_fixtures/all_host_functions/src/lib.rs @@ -0,0 +1,772 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +// +// Host Functions Test +// Tests 26 host functions (across 7 categories) +// +// With craft you can run this test with: +// craft test --project host_functions_test --test-case host_functions_test +// +// Amount Format Update: +// - XRP amounts now return as 8-byte serialized rippled objects +// - IOU and MPT amounts return in variable-length serialized format +// - Format details: https://xrpl.org/docs/references/protocol/binary-format#amount-fields +// +// Error Code Ranges: +// -100 to -199: Ledger Header Functions (3 functions) +// -200 to -299: Transaction Data Functions (5 functions) +// -300 to -399: Current Ledger Object Functions (4 functions) +// -400 to -499: Any Ledger Object Functions (5 functions) +// -500 to -599: Keylet Generation Functions (4 functions) +// -600 to -699: Utility Functions (4 functions) +// -700 to -799: Data Update Functions (1 function) +// + +use xrpl_std::core::current_tx::escrow_finish::EscrowFinish; +use xrpl_std::core::current_tx::traits::TransactionCommonFields; +use xrpl_std::host; +use xrpl_std::host::trace::{trace, trace_account_buf, trace_data, trace_num, DataRepr}; +use xrpl_std::sfield; + +#[unsafe(no_mangle)] +pub extern "C" fn finish() -> i32 { + let _ = trace("=== HOST FUNCTIONS TEST ==="); + let _ = trace("Testing 26 host functions"); + + // Category 1: Ledger Header Data Functions (3 functions) + // Error range: -100 to -199 + match test_ledger_header_functions() { + 0 => (), + err => return err, + } + + // Category 2: Transaction Data Functions (5 functions) + // Error range: -200 to -299 + match test_transaction_data_functions() { + 0 => (), + err => return err, + } + + // Category 3: Current Ledger Object Functions (4 functions) + // Error range: -300 to -399 + match test_current_ledger_object_functions() { + 0 => (), + err => return err, + } + + // Category 4: Any Ledger Object Functions (5 functions) + // Error range: -400 to -499 + match test_any_ledger_object_functions() { + 0 => (), + err => return err, + } + + // Category 5: Keylet Generation Functions (4 functions) + // Error range: -500 to -599 + match test_keylet_generation_functions() { + 0 => (), + err => return err, + } + + // Category 6: Utility Functions (4 functions) + // Error range: -600 to -699 + match test_utility_functions() { + 0 => (), + err => return err, + } + + // Category 7: Data Update Functions (1 function) + // Error range: -700 to -799 + match test_data_update_functions() { + 0 => (), + err => return err, + } + + let _ = trace("SUCCESS: All host function tests passed!"); + 1 // Success return code for WASM finish function +} + +/// Test Category 1: Ledger Header Data Functions (3 functions) +/// - get_ledger_sqn() - Get ledger sequence number +/// - get_parent_ledger_time() - Get parent ledger timestamp +/// - get_parent_ledger_hash() - Get parent ledger hash +fn test_ledger_header_functions() -> i32 { + let _ = trace("--- Category 1: Ledger Header Functions ---"); + + // Test 1.1: get_ledger_sqn() - should return current ledger sequence number + let sqn_result = unsafe { host::get_ledger_sqn() }; + + if sqn_result <= 0 { + let _ = trace_num("ERROR: get_ledger_sqn failed:", sqn_result as i64); + return -101; // Ledger sequence number test failed + } + let _ = trace_num("Ledger sequence number:", sqn_result as i64); + + // Test 1.2: get_parent_ledger_time() - should return parent ledger timestamp + let time_result = unsafe { host::get_parent_ledger_time() }; + + if time_result <= 0 { + let _ = trace_num("ERROR: get_parent_ledger_time failed:", time_result as i64); + return -102; // Parent ledger time test failed + } + let _ = trace_num("Parent ledger time:", time_result as i64); + + // Test 1.3: get_parent_ledger_hash() - should return parent ledger hash (32 bytes) + let mut hash_buffer = [0u8; 32]; + let hash_result = + unsafe { host::get_parent_ledger_hash(hash_buffer.as_mut_ptr(), hash_buffer.len()) }; + + if hash_result != 32 { + let _ = trace_num( + "ERROR: get_parent_ledger_hash wrong length:", + hash_result as i64, + ); + return -103; // Parent ledger hash test failed - should be exactly 32 bytes + } + let _ = trace_data("Parent ledger hash:", &hash_buffer, DataRepr::AsHex); + + let _ = trace("SUCCESS: Ledger header functions"); + 0 +} + +/// Test Category 2: Transaction Data Functions (5 functions) +/// Tests all functions for accessing current transaction data +fn test_transaction_data_functions() -> i32 { + let _ = trace("--- Category 2: Transaction Data Functions ---"); + + // Test 2.1: get_tx_field() - Basic transaction field access + // Test with Account field (required, 20 bytes) + let mut account_buffer = [0u8; 20]; + let account_len = unsafe { + host::get_tx_field( + sfield::Account, + account_buffer.as_mut_ptr(), + account_buffer.len(), + ) + }; + + if account_len != 20 { + let _ = trace_num( + "ERROR: get_tx_field(Account) wrong length:", + account_len as i64, + ); + return -201; // Basic transaction field test failed + } + let _ = trace_account_buf("Transaction Account:", &account_buffer); + + // Test with Fee field (XRP amount - 8 bytes in new serialized format) + // New format: XRP amounts are always 8 bytes (positive: value | cPositive flag, negative: just value) + let mut fee_buffer = [0u8; 8]; + let fee_len = + unsafe { host::get_tx_field(sfield::Fee, fee_buffer.as_mut_ptr(), fee_buffer.len()) }; + + if fee_len != 8 { + let _ = trace_num( + "ERROR: get_tx_field(Fee) wrong length (expected 8 bytes for XRP):", + fee_len as i64, + ); + return -202; // Fee field test failed - XRP amounts should be exactly 8 bytes + } + let _ = trace_num("Transaction Fee length:", fee_len as i64); + let _ = trace_data( + "Transaction Fee (serialized XRP amount):", + &fee_buffer, + DataRepr::AsHex, + ); + + // Test with Sequence field (required, 4 bytes uint32) + let mut seq_buffer = [0u8; 4]; + let seq_len = + unsafe { host::get_tx_field(sfield::Sequence, seq_buffer.as_mut_ptr(), seq_buffer.len()) }; + + if seq_len != 4 { + let _ = trace_num( + "ERROR: get_tx_field(Sequence) wrong length:", + seq_len as i64, + ); + return -203; // Sequence field test failed + } + let _ = trace_data("Transaction Sequence:", &seq_buffer, DataRepr::AsHex); + + // NOTE: get_tx_field2() through get_tx_field6() have been deprecated. + // Use get_tx_field() with appropriate parameters for all transaction field access. + + // Test 2.2: get_tx_nested_field() - Nested field access with locator + let locator = [0x01, 0x00]; // Simple locator for first element + let mut nested_buffer = [0u8; 32]; + let nested_result = unsafe { + host::get_tx_nested_field( + locator.as_ptr(), + locator.len(), + nested_buffer.as_mut_ptr(), + nested_buffer.len(), + ) + }; + + if nested_result < 0 { + let _ = trace_num( + "INFO: get_tx_nested_field not applicable:", + nested_result as i64, + ); + // Expected - locator may not match transaction structure + } else { + let _ = trace_num("Nested field length:", nested_result as i64); + let _ = trace_data( + "Nested field:", + &nested_buffer[..nested_result as usize], + DataRepr::AsHex, + ); + } + + // Test 2.3: get_tx_array_len() - Get array length + let signers_len = unsafe { host::get_tx_array_len(sfield::Signers) }; + let _ = trace_num("Signers array length:", signers_len as i64); + + let memos_len = unsafe { host::get_tx_array_len(sfield::Memos) }; + let _ = trace_num("Memos array length:", memos_len as i64); + + // Test 2.4: get_tx_nested_array_len() - Get nested array length with locator + let nested_array_len = + unsafe { host::get_tx_nested_array_len(locator.as_ptr(), locator.len()) }; + + if nested_array_len < 0 { + let _ = trace_num( + "INFO: get_tx_nested_array_len not applicable:", + nested_array_len as i64, + ); + } else { + let _ = trace_num("Nested array length:", nested_array_len as i64); + } + + let _ = trace("SUCCESS: Transaction data functions"); + 0 +} + +/// Test Category 3: Current Ledger Object Functions (4 functions) +/// Tests functions that access the current ledger object being processed +fn test_current_ledger_object_functions() -> i32 { + let _ = trace("--- Category 3: Current Ledger Object Functions ---"); + + // Test 3.1: get_current_ledger_obj_field() - Access field from current ledger object + // Test with Balance field (XRP amount - 8 bytes in new serialized format) + let mut balance_buffer = [0u8; 8]; + let balance_result = unsafe { + host::get_current_ledger_obj_field( + sfield::Balance, + balance_buffer.as_mut_ptr(), + balance_buffer.len(), + ) + }; + + if balance_result <= 0 { + let _ = trace_num( + "INFO: get_current_ledger_obj_field(Balance) failed (may be expected):", + balance_result as i64, + ); + // This might fail if current ledger object doesn't have balance field + } else if balance_result == 8 { + let _ = trace_num( + "Current object balance length (XRP amount):", + balance_result as i64, + ); + let _ = trace_data( + "Current object balance (serialized XRP amount):", + &balance_buffer, + DataRepr::AsHex, + ); + } else { + let _ = trace_num( + "Current object balance length (non-XRP amount):", + balance_result as i64, + ); + let _ = trace_data( + "Current object balance:", + &balance_buffer[..balance_result as usize], + DataRepr::AsHex, + ); + } + + // Test with Account field + let mut current_account_buffer = [0u8; 20]; + let current_account_result = unsafe { + host::get_current_ledger_obj_field( + sfield::Account, + current_account_buffer.as_mut_ptr(), + current_account_buffer.len(), + ) + }; + + if current_account_result <= 0 { + let _ = trace_num( + "INFO: get_current_ledger_obj_field(Account) failed:", + current_account_result as i64, + ); + } else { + let _ = trace_account_buf("Current ledger object account:", ¤t_account_buffer); + } + + // Test 3.2: get_current_ledger_obj_nested_field() - Nested field access + let locator = [0x01, 0x00]; // Simple locator + let mut current_nested_buffer = [0u8; 32]; + let current_nested_result = unsafe { + host::get_current_ledger_obj_nested_field( + locator.as_ptr(), + locator.len(), + current_nested_buffer.as_mut_ptr(), + current_nested_buffer.len(), + ) + }; + + if current_nested_result < 0 { + let _ = trace_num( + "INFO: get_current_ledger_obj_nested_field not applicable:", + current_nested_result as i64, + ); + } else { + let _ = trace_num("Current nested field length:", current_nested_result as i64); + let _ = trace_data( + "Current nested field:", + ¤t_nested_buffer[..current_nested_result as usize], + DataRepr::AsHex, + ); + } + + // Test 3.3: get_current_ledger_obj_array_len() - Array length in current object + let current_array_len = unsafe { host::get_current_ledger_obj_array_len(sfield::Signers) }; + let _ = trace_num( + "Current object Signers array length:", + current_array_len as i64, + ); + + // Test 3.4: get_current_ledger_obj_nested_array_len() - Nested array length + let current_nested_array_len = + unsafe { host::get_current_ledger_obj_nested_array_len(locator.as_ptr(), locator.len()) }; + + if current_nested_array_len < 0 { + let _ = trace_num( + "INFO: get_current_ledger_obj_nested_array_len not applicable:", + current_nested_array_len as i64, + ); + } else { + let _ = trace_num( + "Current nested array length:", + current_nested_array_len as i64, + ); + } + + let _ = trace("SUCCESS: Current ledger object functions"); + 0 +} + +/// Test Category 4: Any Ledger Object Functions (5 functions) +/// Tests functions that work with cached ledger objects +fn test_any_ledger_object_functions() -> i32 { + let _ = trace("--- Category 4: Any Ledger Object Functions ---"); + + // First we need to cache a ledger object to test the other functions + // Get the account from transaction and generate its keylet + let escrow_finish = EscrowFinish; + let account_id = escrow_finish.get_account().unwrap(); + + // Test 4.1: cache_ledger_obj() - Cache a ledger object + let mut keylet_buffer = [0u8; 32]; + let keylet_result = unsafe { + host::account_keylet( + account_id.0.as_ptr(), + account_id.0.len(), + keylet_buffer.as_mut_ptr(), + keylet_buffer.len(), + ) + }; + + if keylet_result != 32 { + let _ = trace_num( + "ERROR: account_keylet failed for caching test:", + keylet_result as i64, + ); + return -401; // Keylet generation failed for caching test + } + + let cache_result = + unsafe { host::cache_ledger_obj(keylet_buffer.as_ptr(), keylet_result as usize, 0) }; + + if cache_result <= 0 { + let _ = trace_num( + "INFO: cache_ledger_obj failed (expected with test fixtures):", + cache_result as i64, + ); + // Test fixtures may not contain the account object - this is expected + // We'll test the interface but expect failures + + // Test 4.2-4.5 with invalid slot (should fail gracefully) + let mut test_buffer = [0u8; 32]; + + // Test get_ledger_obj_field with invalid slot + let field_result = unsafe { + host::get_ledger_obj_field( + 1, + sfield::Balance, + test_buffer.as_mut_ptr(), + test_buffer.len(), + ) + }; + if field_result < 0 { + let _ = trace_num( + "INFO: get_ledger_obj_field failed as expected (no cached object):", + field_result as i64, + ); + } + + // Test get_ledger_obj_nested_field with invalid slot + let locator = [0x01, 0x00]; + let nested_result = unsafe { + host::get_ledger_obj_nested_field( + 1, + locator.as_ptr(), + locator.len(), + test_buffer.as_mut_ptr(), + test_buffer.len(), + ) + }; + if nested_result < 0 { + let _ = trace_num( + "INFO: get_ledger_obj_nested_field failed as expected:", + nested_result as i64, + ); + } + + // Test get_ledger_obj_array_len with invalid slot + let array_result = unsafe { host::get_ledger_obj_array_len(1, sfield::Signers) }; + if array_result < 0 { + let _ = trace_num( + "INFO: get_ledger_obj_array_len failed as expected:", + array_result as i64, + ); + } + + // Test get_ledger_obj_nested_array_len with invalid slot + let nested_array_result = + unsafe { host::get_ledger_obj_nested_array_len(1, locator.as_ptr(), locator.len()) }; + if nested_array_result < 0 { + let _ = trace_num( + "INFO: get_ledger_obj_nested_array_len failed as expected:", + nested_array_result as i64, + ); + } + + let _ = trace("SUCCESS: Any ledger object functions (interface tested)"); + return 0; + } + + // If we successfully cached an object, test the access functions + let slot = cache_result; + let _ = trace_num("Successfully cached object in slot:", slot as i64); + + // Test 4.2: get_ledger_obj_field() - Access field from cached object + let mut cached_balance_buffer = [0u8; 8]; + let cached_balance_result = unsafe { + host::get_ledger_obj_field( + slot, + sfield::Balance, + cached_balance_buffer.as_mut_ptr(), + cached_balance_buffer.len(), + ) + }; + + if cached_balance_result <= 0 { + let _ = trace_num( + "INFO: get_ledger_obj_field(Balance) failed:", + cached_balance_result as i64, + ); + } else if cached_balance_result == 8 { + let _ = trace_num( + "Cached object balance length (XRP amount):", + cached_balance_result as i64, + ); + let _ = trace_data( + "Cached object balance (serialized XRP amount):", + &cached_balance_buffer, + DataRepr::AsHex, + ); + } else { + let _ = trace_num( + "Cached object balance length (non-XRP amount):", + cached_balance_result as i64, + ); + let _ = trace_data( + "Cached object balance:", + &cached_balance_buffer[..cached_balance_result as usize], + DataRepr::AsHex, + ); + } + + // Test 4.3: get_ledger_obj_nested_field() - Nested field from cached object + let locator = [0x01, 0x00]; + let mut cached_nested_buffer = [0u8; 32]; + let cached_nested_result = unsafe { + host::get_ledger_obj_nested_field( + slot, + locator.as_ptr(), + locator.len(), + cached_nested_buffer.as_mut_ptr(), + cached_nested_buffer.len(), + ) + }; + + if cached_nested_result < 0 { + let _ = trace_num( + "INFO: get_ledger_obj_nested_field not applicable:", + cached_nested_result as i64, + ); + } else { + let _ = trace_num("Cached nested field length:", cached_nested_result as i64); + let _ = trace_data( + "Cached nested field:", + &cached_nested_buffer[..cached_nested_result as usize], + DataRepr::AsHex, + ); + } + + // Test 4.4: get_ledger_obj_array_len() - Array length from cached object + let cached_array_len = unsafe { host::get_ledger_obj_array_len(slot, sfield::Signers) }; + let _ = trace_num( + "Cached object Signers array length:", + cached_array_len as i64, + ); + + // Test 4.5: get_ledger_obj_nested_array_len() - Nested array length from cached object + let cached_nested_array_len = + unsafe { host::get_ledger_obj_nested_array_len(slot, locator.as_ptr(), locator.len()) }; + + if cached_nested_array_len < 0 { + let _ = trace_num( + "INFO: get_ledger_obj_nested_array_len not applicable:", + cached_nested_array_len as i64, + ); + } else { + let _ = trace_num( + "Cached nested array length:", + cached_nested_array_len as i64, + ); + } + + let _ = trace("SUCCESS: Any ledger object functions"); + 0 +} + +/// Test Category 5: Keylet Generation Functions (4 functions) +/// Tests keylet generation functions for different ledger entry types +fn test_keylet_generation_functions() -> i32 { + let _ = trace("--- Category 5: Keylet Generation Functions ---"); + + let escrow_finish = EscrowFinish; + let account_id = escrow_finish.get_account().unwrap(); + + // Test 5.1: account_keylet() - Generate keylet for account + let mut account_keylet_buffer = [0u8; 32]; + let account_keylet_result = unsafe { + host::account_keylet( + account_id.0.as_ptr(), + account_id.0.len(), + account_keylet_buffer.as_mut_ptr(), + account_keylet_buffer.len(), + ) + }; + + if account_keylet_result != 32 { + let _ = trace_num( + "ERROR: account_keylet failed:", + account_keylet_result as i64, + ); + return -501; // Account keylet generation failed + } + let _ = trace_data("Account keylet:", &account_keylet_buffer, DataRepr::AsHex); + + // Test 5.2: credential_keylet() - Generate keylet for credential + let mut credential_keylet_buffer = [0u8; 32]; + let credential_keylet_result = unsafe { + host::credential_keylet( + account_id.0.as_ptr(), // Subject + account_id.0.len(), + account_id.0.as_ptr(), // Issuer - same account for test + account_id.0.len(), + b"TestType".as_ptr(), // Credential type + 9usize, // Length of "TestType" + credential_keylet_buffer.as_mut_ptr(), + credential_keylet_buffer.len(), + ) + }; + + if credential_keylet_result <= 0 { + let _ = trace_num( + "INFO: credential_keylet failed (expected - interface issue):", + credential_keylet_result as i64, + ); + // This is expected to fail due to unusual parameter types + } else { + let _ = trace_data( + "Credential keylet:", + &credential_keylet_buffer[..credential_keylet_result as usize], + DataRepr::AsHex, + ); + } + + // Test 5.3: escrow_keylet() - Generate keylet for escrow + let mut escrow_keylet_buffer = [0u8; 32]; + let escrow_keylet_result = unsafe { + host::escrow_keylet( + account_id.0.as_ptr(), + account_id.0.len(), + 1000, // Sequence number + escrow_keylet_buffer.as_mut_ptr(), + escrow_keylet_buffer.len(), + ) + }; + + if escrow_keylet_result != 32 { + let _ = trace_num("ERROR: escrow_keylet failed:", escrow_keylet_result as i64); + return -503; // Escrow keylet generation failed + } + let _ = trace_data("Escrow keylet:", &escrow_keylet_buffer, DataRepr::AsHex); + + // Test 5.4: oracle_keylet() - Generate keylet for oracle + let mut oracle_keylet_buffer = [0u8; 32]; + let oracle_keylet_result = unsafe { + host::oracle_keylet( + account_id.0.as_ptr(), + account_id.0.len(), + 42, // Document ID + oracle_keylet_buffer.as_mut_ptr(), + oracle_keylet_buffer.len(), + ) + }; + + if oracle_keylet_result != 32 { + let _ = trace_num("ERROR: oracle_keylet failed:", oracle_keylet_result as i64); + return -504; // Oracle keylet generation failed + } + let _ = trace_data("Oracle keylet:", &oracle_keylet_buffer, DataRepr::AsHex); + + let _ = trace("SUCCESS: Keylet generation functions"); + 0 +} + +/// Test Category 6: Utility Functions (4 functions) +/// Tests utility functions for hashing, NFT access, and tracing +fn test_utility_functions() -> i32 { + let _ = trace("--- Category 6: Utility Functions ---"); + + // Test 6.1: compute_sha512_half() - SHA512 hash computation (first 32 bytes) + let test_data = b"Hello, XRPL WASM world!"; + let mut hash_output = [0u8; 32]; + let hash_result = unsafe { + host::compute_sha512_half( + test_data.as_ptr(), + test_data.len(), + hash_output.as_mut_ptr(), + hash_output.len(), + ) + }; + + if hash_result != 32 { + let _ = trace_num("ERROR: compute_sha512_half failed:", hash_result as i64); + return -601; // SHA512 half computation failed + } + let _ = trace_data("Input data:", test_data, DataRepr::AsHex); + let _ = trace_data("SHA512 half hash:", &hash_output, DataRepr::AsHex); + + // Test 6.2: get_nft() - NFT data retrieval + let escrow_finish = EscrowFinish; + let account_id = escrow_finish.get_account().unwrap(); + let nft_id = [0u8; 32]; // Dummy NFT ID for testing + let mut nft_buffer = [0u8; 256]; + let nft_result = unsafe { + host::get_nft( + account_id.0.as_ptr(), + account_id.0.len(), + nft_id.as_ptr(), + nft_id.len(), + nft_buffer.as_mut_ptr(), + nft_buffer.len(), + ) + }; + + if nft_result <= 0 { + let _ = trace_num( + "INFO: get_nft failed (expected - no such NFT):", + nft_result as i64, + ); + // This is expected - test account likely doesn't own the dummy NFT + } else { + let _ = trace_num("NFT data length:", nft_result as i64); + let _ = trace_data( + "NFT data:", + &nft_buffer[..nft_result as usize], + DataRepr::AsHex, + ); + } + + // Test 6.3: trace() - Debug logging with data + let trace_message = b"Test trace message"; + let trace_data_payload = b"payload"; + let trace_result = unsafe { + host::trace( + trace_message.as_ptr(), + trace_message.len(), + trace_data_payload.as_ptr(), + trace_data_payload.len(), + 1, // as_hex = true + ) + }; + + if trace_result < 0 { + let _ = trace_num("ERROR: trace() failed:", trace_result as i64); + return -603; // Trace function failed + } + let _ = trace_num("Trace function bytes written:", trace_result as i64); + + // Test 6.4: trace_num() - Debug logging with number + let test_number = 42i64; + let trace_num_result = trace_num("Test number trace", test_number); + + use xrpl_std::host::Result; + match trace_num_result { + Result::Ok(_) => { + let _ = trace_num("Trace_num function succeeded", 0); + } + Result::Err(_) => { + let _ = trace_num("ERROR: trace_num() failed:", -604); + return -604; // Trace number function failed + } + } + + let _ = trace("SUCCESS: Utility functions"); + 0 +} + +/// Test Category 7: Data Update Functions (1 function) +/// Tests the function for modifying the current ledger entry +fn test_data_update_functions() -> i32 { + let _ = trace("--- Category 7: Data Update Functions ---"); + + // Test 7.1: update_data() - Update current ledger entry data + let update_payload = b"Updated ledger entry data from WASM test"; + + let update_result = unsafe { host::update_data(update_payload.as_ptr(), update_payload.len()) }; + + if update_result != update_payload.len() as i32 { + let _ = trace_num("ERROR: update_data failed:", update_result as i64); + return -701; // Data update failed + } + + let _ = trace_data( + "Successfully updated ledger entry with:", + update_payload, + DataRepr::AsHex, + ); + let _ = trace("SUCCESS: Data update functions"); + 0 +} diff --git a/src/test/app/wasm_fixtures/all_keylets/Cargo.lock b/src/test/app/wasm_fixtures/all_keylets/Cargo.lock new file mode 100644 index 0000000000..88a6e34459 --- /dev/null +++ b/src/test/app/wasm_fixtures/all_keylets/Cargo.lock @@ -0,0 +1,171 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "all_keylets" +version = "0.0.1" +dependencies = [ + "xrpl-wasm-stdlib", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "xrpl-address-macro" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e" +dependencies = [ + "bs58", + "quote", + "sha2", + "syn", +] + +[[package]] +name = "xrpl-wasm-stdlib" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e" +dependencies = [ + "xrpl-address-macro", +] diff --git a/src/test/app/wasm_fixtures/all_keylets/Cargo.toml b/src/test/app/wasm_fixtures/all_keylets/Cargo.toml new file mode 100644 index 0000000000..823de4127d --- /dev/null +++ b/src/test/app/wasm_fixtures/all_keylets/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2024" +name = "all_keylets" +version = "0.0.1" + +# This empty workspace definition keeps this project independent of the parent workspace +[workspace] + +[lib] +crate-type = ["cdylib"] + +[profile.release] +lto = true +opt-level = 's' +panic = "abort" + +[dependencies] +xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-wasm-stdlib" } + +[profile.dev] +panic = "abort" diff --git a/src/test/app/wasm_fixtures/all_keylets/src/lib.rs b/src/test/app/wasm_fixtures/all_keylets/src/lib.rs new file mode 100644 index 0000000000..e3d9ab1b19 --- /dev/null +++ b/src/test/app/wasm_fixtures/all_keylets/src/lib.rs @@ -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, + keylet_type: &str, + field: i32, +) -> Result { + 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::(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::(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. +} diff --git a/src/test/app/wasm_fixtures/b58.c b/src/test/app/wasm_fixtures/b58.c new file mode 100644 index 0000000000..00b6178ec0 --- /dev/null +++ b/src/test/app/wasm_fixtures/b58.c @@ -0,0 +1,73 @@ +#include + +static char const b58digits_ordered[] = + "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + +uint8_t e_data[32 * 1024]; + +void* +allocate(int sz) +{ + static int idx = 0; + if (idx >= 32) + return 0; + if (sz > 1024) + return 0; + return &e_data[idx++ << 10]; +} + +void +deallocate(void* p) +{ +} + +extern int32_t +b58enco(char* b58, int32_t b58sz, void const* data, int32_t binsz) +{ + uint8_t const* bin = data; + int32_t carry; + int32_t i, j, high, zcount = 0; + int32_t size; + + while (zcount < binsz && !bin[zcount]) + ++zcount; + + size = (binsz - zcount) * 138 / 100 + 1; + uint8_t* buf = allocate(size); + if (!buf) + return 0; + // memset(buf, 0, size); + for (i = 0; i < size; ++i) + buf[i] = 0; + + for (i = zcount, high = size - 1; i < binsz; ++i, high = j) + { + for (carry = bin[i], j = size - 1; (j > high) || carry; --j) + { + carry += 256 * buf[j]; + buf[j] = carry % 58; + carry /= 58; + if (!j) + break; + } + } + + for (j = 0; j < size && !buf[j]; ++j) + ; + + if (b58sz <= zcount + size - j) + return 0; + + if (zcount) + { + // memset(b58, '1', zcount); + for (i = 0; i < zcount; ++i) + b58[i] = '1'; + } + + for (i = zcount; j < size; ++i, ++j) + b58[i] = b58digits_ordered[buf[j]]; + b58[i] = '\0'; + + return i + 1; +} diff --git a/src/test/app/wasm_fixtures/codecov_tests/Cargo.lock b/src/test/app/wasm_fixtures/codecov_tests/Cargo.lock new file mode 100644 index 0000000000..e44fd14755 --- /dev/null +++ b/src/test/app/wasm_fixtures/codecov_tests/Cargo.lock @@ -0,0 +1,171 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "codecov_tests" +version = "0.0.1" +dependencies = [ + "xrpl-wasm-stdlib", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "xrpl-address-macro" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e" +dependencies = [ + "bs58", + "quote", + "sha2", + "syn", +] + +[[package]] +name = "xrpl-wasm-stdlib" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e" +dependencies = [ + "xrpl-address-macro", +] diff --git a/src/test/app/wasm_fixtures/codecov_tests/Cargo.toml b/src/test/app/wasm_fixtures/codecov_tests/Cargo.toml new file mode 100644 index 0000000000..5031bcb566 --- /dev/null +++ b/src/test/app/wasm_fixtures/codecov_tests/Cargo.toml @@ -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" } diff --git a/src/test/app/wasm_fixtures/codecov_tests/src/host_bindings_loose.rs b/src/test/app/wasm_fixtures/codecov_tests/src/host_bindings_loose.rs new file mode 100644 index 0000000000..c99a0047d5 --- /dev/null +++ b/src/test/app/wasm_fixtures/codecov_tests/src/host_bindings_loose.rs @@ -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; +} diff --git a/src/test/app/wasm_fixtures/codecov_tests/src/lib.rs b/src/test/app/wasm_fixtures/codecov_tests/src/lib.rs new file mode 100644 index 0000000000..4cbc7e1707 --- /dev/null +++ b/src/test/app/wasm_fixtures/codecov_tests/src/lib.rs @@ -0,0 +1,1547 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +use core::panic; +use xrpl_std::core::current_tx::escrow_finish::{get_current_escrow_finish, EscrowFinish}; +use xrpl_std::core::current_tx::traits::TransactionCommonFields; +use xrpl_std::core::locator::Locator; +use xrpl_std::core::types::issue::Issue; +use xrpl_std::core::types::issue::XrpIssue; +use xrpl_std::core::types::keylets; +use xrpl_std::core::types::mpt_id::MptId; +use xrpl_std::host; +use xrpl_std::host::error_codes; +use xrpl_std::host::trace::{trace, trace_num as trace_number}; +use xrpl_std::sfield; + +mod host_bindings_loose; +include!("host_bindings_loose.rs"); + +fn check_result(result: i32, expected: i32, test_name: &'static str) { + match result { + code if code == expected => { + let _ = trace_number(test_name, code.into()); + } + code if code >= 0 => { + let _ = trace(test_name); + let _ = trace_number("TEST FAILED", code.into()); + panic!("Unexpected success code: {}", code); + } + code => { + let _ = trace(test_name); + let _ = trace_number("TEST FAILED", code.into()); + panic!("Error code: {}", code); + } + } +} + +fn with_buffer(mut f: F) -> R +where + F: FnMut(*mut u8, usize) -> R, +{ + let mut buf = [0u8; N]; + f(buf.as_mut_ptr(), buf.len()) +} + +#[unsafe(no_mangle)] +pub extern "C" fn finish() -> i32 { + let _ = trace("$$$$$ STARTING WASM EXECUTION $$$$$"); + + // ######################################## + // Step #1: Test all host function happy paths + // Note: not testing all the keylet functions, + // that's in a separate test file (all_keylets). + // The float tests are also in a separate file (float_tests). + // ######################################## + check_result(unsafe { host::get_ledger_sqn() }, 12345, "get_ledger_sqn"); + check_result( + unsafe { host::get_parent_ledger_time() }, + 67890, + "get_parent_ledger_time", + ); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { host::get_parent_ledger_hash(ptr, len) }, + 32, + "get_parent_ledger_hash", + ); + }); + check_result(unsafe { host::get_base_fee() }, 10, "get_base_fee"); + let amendment_name: &[u8] = b"test_amendment"; + let amendment_id: [u8; 32] = [1; 32]; + check_result( + unsafe { host::amendment_enabled(amendment_name.as_ptr(), amendment_name.len()) }, + 1, + "amendment_enabled", + ); + check_result( + unsafe { host::amendment_enabled(amendment_id.as_ptr(), amendment_id.len()) }, + 1, + "amendment_enabled", + ); + let tx: EscrowFinish = get_current_escrow_finish(); + let account = tx.get_account().unwrap_or_panic(); // get_tx_field under the hood + let keylet = keylets::account_keylet(&account).unwrap_or_panic(); // account_keylet under the hood + check_result( + unsafe { host::cache_ledger_obj(keylet.as_ptr(), keylet.len(), 0) }, + 1, + "cache_ledger_obj", + ); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::get_current_ledger_obj_field(sfield::Account, ptr, len) }, + 20, + "get_current_ledger_obj_field", + ); + }); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::get_ledger_obj_field(1, sfield::Account, ptr, len) }, + 20, + "get_ledger_obj_field", + ); + }); + let mut locator = Locator::new(); + locator.pack(sfield::Account); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::get_tx_nested_field(locator.as_ptr(), locator.len(), ptr, len) }, + 20, + "get_tx_nested_field", + ); + }); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { + host::get_current_ledger_obj_nested_field(locator.as_ptr(), locator.len(), ptr, len) + }, + 20, + "get_current_ledger_obj_nested_field", + ); + }); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { + host::get_ledger_obj_nested_field(1, locator.as_ptr(), locator.len(), ptr, len) + }, + 20, + "get_ledger_obj_nested_field", + ); + }); + check_result( + unsafe { host::get_tx_array_len(sfield::Memos) }, + 32, + "get_tx_array_len", + ); + check_result( + unsafe { host::get_current_ledger_obj_array_len(sfield::Memos) }, + 32, + "get_current_ledger_obj_array_len", + ); + check_result( + unsafe { host::get_ledger_obj_array_len(1, sfield::Memos) }, + 32, + "get_ledger_obj_array_len", + ); + check_result( + unsafe { host::get_tx_nested_array_len(locator.as_ptr(), locator.len()) }, + 32, + "get_tx_nested_array_len", + ); + check_result( + unsafe { host::get_current_ledger_obj_nested_array_len(locator.as_ptr(), locator.len()) }, + 32, + "get_current_ledger_obj_nested_array_len", + ); + check_result( + unsafe { host::get_ledger_obj_nested_array_len(1, locator.as_ptr(), locator.len()) }, + 32, + "get_ledger_obj_nested_array_len", + ); + check_result( + unsafe { host::update_data(account.0.as_ptr(), account.0.len()) }, + 20, + "update_data", + ); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { host::compute_sha512_half(locator.as_ptr(), locator.len(), ptr, len) }, + 32, + "compute_sha512_half", + ); + }); + let message: &[u8] = b"test message"; + let pubkey: &[u8] = b"test pubkey"; //tx.get_public_key().unwrap_or_panic(); + let signature: &[u8] = b"test signature"; + check_result( + unsafe { + host::check_sig( + message.as_ptr(), + message.len(), + pubkey.as_ptr(), + pubkey.len(), + signature.as_ptr(), + signature.len(), + ) + }, + 1, + "check_sig", + ); + + let nft_id: [u8; 32] = amendment_id; + with_buffer::<18, _, _>(|ptr, len| { + check_result( + unsafe { + host::get_nft( + account.0.as_ptr(), + account.0.len(), + nft_id.as_ptr(), + nft_id.len(), + ptr, + len, + ) + }, + 18, + "get_nft", + ) + }); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::get_nft_issuer(nft_id.as_ptr(), nft_id.len(), ptr, len) }, + 20, + "get_nft_issuer", + ) + }); + with_buffer::<4, _, _>(|ptr, len| { + check_result( + unsafe { host::get_nft_taxon(nft_id.as_ptr(), nft_id.len(), ptr, len) }, + 4, + "get_nft_taxon", + ) + }); + check_result( + unsafe { host::get_nft_flags(nft_id.as_ptr(), nft_id.len()) }, + 8, + "get_nft_flags", + ); + check_result( + unsafe { host::get_nft_transfer_fee(nft_id.as_ptr(), nft_id.len()) }, + 10, + "get_nft_transfer_fee", + ); + with_buffer::<4, _, _>(|ptr, len| { + check_result( + unsafe { host::get_nft_serial(nft_id.as_ptr(), nft_id.len(), ptr, len) }, + 4, + "get_nft_serial", + ) + }); + let message = "testing trace"; + check_result( + unsafe { + host::trace_account( + message.as_ptr(), + message.len(), + account.0.as_ptr(), + account.0.len(), + ) + }, + 47, + "trace_account", + ); + let amount = &[0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F]; // 95 drops of XRP + check_result( + unsafe { + host::trace_amount( + message.as_ptr(), + message.len(), + amount.as_ptr(), + amount.len(), + ) + }, + 19, + "trace_amount", + ); + let amount = &[0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; // 0 drops of XRP + check_result( + unsafe { + host::trace_amount( + message.as_ptr(), + message.len(), + amount.as_ptr(), + amount.len(), + ) + }, + 18, + "trace_amount_zero", + ); + + // ######################################## + // Step #2: Test set_data edge cases + // ######################################## + check_result( + unsafe { host_bindings_loose::get_parent_ledger_hash(-1, 4) }, + error_codes::INVALID_PARAMS, + "get_parent_ledger_hash_neg_ptr", + ); + with_buffer::<4, _, _>(|ptr, _len| { + check_result( + unsafe { host_bindings_loose::get_parent_ledger_hash(ptr as i32, -1) }, + error_codes::INVALID_PARAMS, + "get_parent_ledger_hash_neg_len", + ) + }); + with_buffer::<3, _, _>(|ptr, len| { + check_result( + unsafe { host_bindings_loose::get_parent_ledger_hash(ptr as i32, len as i32) }, + error_codes::BUFFER_TOO_SMALL, + "get_parent_ledger_hash_buf_too_small", + ) + }); + with_buffer::<4, _, _>(|ptr, _len| { + check_result( + unsafe { host_bindings_loose::get_parent_ledger_hash(ptr as i32, 1_000_000_000) }, + error_codes::POINTER_OUT_OF_BOUNDS, + "get_parent_ledger_hash_len_too_long", + ) + }); + let message = "testing trace"; + check_result( + unsafe { + host::trace_account( + message.as_ptr(), + message.len(), + account.0.as_ptr(), + account.0.len(), + ) + }, + 47, + "trace_account", + ); + let amount = &[0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F]; // 95 drops of XRP + check_result( + unsafe { + host::trace_amount( + message.as_ptr(), + message.len(), + amount.as_ptr(), + amount.len(), + ) + }, + 19, + "trace_amount", + ); + + // ######################################## + // Step #3: Test getData[Type] edge cases + // ######################################## + + // uint64 + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_from_uint( + locator.as_ptr().wrapping_add(1_000_000_000), + 8, + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_from_uint_len_oob", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_from_uint( + locator.as_ptr(), + locator.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::INVALID_PARAMS, + "float_from_uint_wrong_len", + ) + }); + + // SField + check_result( + unsafe { host::get_tx_array_len(2) }, // not a valid SField value + error_codes::INVALID_FIELD, + "get_tx_array_len_invalid_sfield", + ); + + // Slice + check_result( + unsafe { host_bindings_loose::get_tx_nested_array_len(-1, locator.len() as i32) }, + error_codes::INVALID_PARAMS, + "get_tx_nested_array_len_neg_ptr", + ); + check_result( + unsafe { host_bindings_loose::get_tx_nested_array_len(locator.as_ptr() as i32, -1) }, + error_codes::INVALID_PARAMS, + "get_tx_nested_array_len_neg_len", + ); + let long_len = 4 * 1024 + 1; + check_result( + unsafe { + host_bindings_loose::get_tx_nested_array_len(locator.as_ptr() as i32, long_len as i32) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_tx_nested_array_len_too_long", + ); + check_result( + unsafe { + host_bindings_loose::get_tx_nested_array_len( + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "get_tx_nested_array_len_ptr_oob", + ); + + // uint256 + check_result( + unsafe { + host_bindings_loose::cache_ledger_obj( + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + 1, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "cache_ledger_obj_ptr_oob", + ); + check_result( + unsafe { + host_bindings_loose::cache_ledger_obj(locator.as_ptr() as i32, locator.len() as i32, 1) + }, + error_codes::INVALID_PARAMS, + "cache_ledger_obj_wrong_len", + ); + + // AccountID + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host_bindings_loose::account_keylet( + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + ptr, + len, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "account_keylet_len_oob", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host_bindings_loose::account_keylet( + locator.as_ptr() as i32, + locator.len() as i32, + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "account_keylet_wrong_len", + ) + }); + + // Currency + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host_bindings_loose::line_keylet( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + ptr, + len, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "line_keylet_len_oob_currency", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host_bindings_loose::line_keylet( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + locator.as_ptr() as i32, + locator.len() as i32, + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "line_keylet_wrong_len_currency", + ) + }); + + // Issue + let asset1_bytes = Issue::XRP(XrpIssue {}).as_bytes(); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_keylet( + asset1_bytes.as_ptr(), + asset1_bytes.len(), + locator.as_ptr().wrapping_add(1_000_000_000), + locator.len(), + ptr, + len, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "amm_keylet_len_oob_asset2", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_keylet( + asset1_bytes.as_ptr(), + asset1_bytes.len(), + locator.as_ptr(), + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "amm_keylet_len_wrong_len_asset2", + ) + }); + let currency: &[u8] = b"USD00000000000000000"; // 20 bytes + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_keylet( + asset1_bytes.as_ptr(), + asset1_bytes.len(), + currency.as_ptr(), + currency.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "amm_keylet_len_wrong_non_xrp_currency_len", + ) + }); + let xrpissue: &[u8] = &[0; 40]; // 40 bytes + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_keylet( + xrpissue.as_ptr(), + xrpissue.len(), + asset1_bytes.as_ptr(), + asset1_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "amm_keylet_len_wrong_xrp_currency_len", + ) + }); + let mptid = MptId::new(1, account); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_keylet( + mptid.as_ptr(), + mptid.len(), + asset1_bytes.as_ptr(), + asset1_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "amm_keylet_mpt", + ) + }); + + // string + check_result( + unsafe { + host_bindings_loose::trace_num( + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + 42, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_num_oob_str", + ); + + // ######################################## + // Step #4: Test other host function edge cases + // ######################################## + + // invalid SFields + + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::get_tx_field(2, ptr, len) }, + error_codes::INVALID_FIELD, + "get_tx_field_invalid_sfield", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::get_current_ledger_obj_field(2, ptr, len) }, + error_codes::INVALID_FIELD, + "get_current_ledger_obj_field_invalid_sfield", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::get_ledger_obj_field(1, 2, ptr, len) }, + error_codes::INVALID_FIELD, + "get_ledger_obj_field_invalid_sfield", + ); + }); + check_result( + unsafe { host::get_tx_array_len(2) }, + error_codes::INVALID_FIELD, + "get_tx_array_len_invalid_sfield", + ); + check_result( + unsafe { host::get_current_ledger_obj_array_len(2) }, + error_codes::INVALID_FIELD, + "get_current_ledger_obj_array_len_invalid_sfield", + ); + check_result( + unsafe { host::get_ledger_obj_array_len(1, 2) }, + error_codes::INVALID_FIELD, + "get_ledger_obj_array_len_invalid_sfield", + ); + + // invalid Slice + + check_result( + unsafe { host::amendment_enabled(amendment_name.as_ptr(), long_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "amendment_enabled_too_big_slice", + ); + check_result( + unsafe { host::amendment_enabled(amendment_name.as_ptr(), 65) }, + error_codes::DATA_FIELD_TOO_LARGE, + "amendment_enabled_too_long", + ); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::get_tx_nested_field(locator.as_ptr(), long_len, ptr, len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_tx_nested_field_too_big_slice", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::get_current_ledger_obj_nested_field(locator.as_ptr(), long_len, ptr, len) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_current_ledger_obj_nested_field_too_big_slice", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::get_ledger_obj_nested_field(1, locator.as_ptr(), long_len, ptr, len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_ledger_obj_nested_field_too_big_slice", + ); + }); + check_result( + unsafe { host::get_tx_nested_array_len(locator.as_ptr(), long_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_tx_nested_array_len_too_big_slice", + ); + check_result( + unsafe { host::get_current_ledger_obj_nested_array_len(locator.as_ptr(), long_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_current_ledger_obj_nested_array_len_too_big_slice", + ); + check_result( + unsafe { host::get_ledger_obj_nested_array_len(1, locator.as_ptr(), long_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_ledger_obj_nested_array_len_too_big_slice", + ); + check_result( + unsafe { host::update_data(locator.as_ptr(), long_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "update_data_too_big_slice", + ); + check_result( + unsafe { + host::check_sig( + message.as_ptr(), + long_len, + pubkey.as_ptr(), + pubkey.len(), + signature.as_ptr(), + signature.len(), + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "check_sig", + ); + check_result( + unsafe { + host::check_sig( + message.as_ptr(), + message.len(), + pubkey.as_ptr(), + long_len, + signature.as_ptr(), + signature.len(), + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "check_sig", + ); + check_result( + unsafe { + host::check_sig( + message.as_ptr(), + message.len(), + pubkey.as_ptr(), + pubkey.len(), + signature.as_ptr(), + long_len, + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "check_sig", + ); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::compute_sha512_half(locator.as_ptr(), long_len, ptr, len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "compute_sha512_half_too_big_slice", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_keylet( + asset1_bytes.as_ptr(), + long_len, + asset1_bytes.as_ptr(), + asset1_bytes.len(), + ptr, + len, + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "amm_keylet_too_big_slice", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::credential_keylet( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), + long_len, + ptr, + len, + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "credential_keylet_too_big_slice", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::mptoken_keylet( + mptid.as_ptr(), + long_len, + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "mptoken_keylet_too_big_slice_mptid", + ) + }); + check_result( + unsafe { + host::trace( + message.as_ptr(), + message.len(), + locator.as_ptr().wrapping_add(1_000_000_000), + locator.len(), + 0, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_oob_slice", + ); + let float: [u8; 8] = [0xD4, 0x83, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00]; + check_result( + unsafe { + host::trace_opaque_float( + message.as_ptr(), + message.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_opaque_float_oob_slice", + ); + check_result( + unsafe { + host::trace_amount( + message.as_ptr(), + message.len(), + locator.as_ptr().wrapping_add(1_000_000_000), + locator.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_amount_oob_slice", + ); + check_result( + unsafe { + host::float_compare( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + float.as_ptr(), + float.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_compare_oob_slice1", + ); + check_result( + unsafe { + host::float_compare( + float.as_ptr(), + float.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_compare_oob_slice2", + ); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_add( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + float.as_ptr(), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_add_oob_slice1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_add( + float.as_ptr(), + float.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_add_oob_slice2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_subtract( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + float.as_ptr(), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_subtract_oob_slice1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_subtract( + float.as_ptr(), + float.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_subtract_oob_slice2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_multiply( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + float.as_ptr(), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_multiply_oob_slice1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_multiply( + float.as_ptr(), + float.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_multiply_oob_slice2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_divide( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + float.as_ptr(), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_divide_oob_slice1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_divide( + float.as_ptr(), + float.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_divide_oob_slice2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_root( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + 3, + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_root_oob_slice", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_pow( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + 3, + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_pow_oob_slice", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_log( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_log_oob_slice", + ) + }); + + // invalid UInt256 + + check_result( + unsafe { host::cache_ledger_obj(locator.as_ptr(), locator.len(), 0) }, + error_codes::INVALID_PARAMS, + "cache_ledger_obj_wrong_size_uint256", + ); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::get_nft( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "get_nft_wrong_size_uint256", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::get_nft_issuer(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "get_nft_issuer_wrong_size_uint256", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::get_nft_taxon(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "get_nft_taxon_wrong_size_uint256", + ) + }); + check_result( + unsafe { host::get_nft_flags(locator.as_ptr(), locator.len()) }, + error_codes::INVALID_PARAMS, + "get_nft_flags_wrong_size_uint256", + ); + check_result( + unsafe { host::get_nft_transfer_fee(locator.as_ptr(), locator.len()) }, + error_codes::INVALID_PARAMS, + "get_nft_transfer_fee_wrong_size_uint256", + ); + with_buffer::<4, _, _>(|ptr, len| { + check_result( + unsafe { host::get_nft_serial(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "get_nft_serial_wrong_size_uint256", + ) + }); + + // invalid AccountID + + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::account_keylet(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "account_keylet_wrong_size_accountid", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::check_keylet(locator.as_ptr(), locator.len(), 1, ptr, len) }, + error_codes::INVALID_PARAMS, + "check_keylet_wrong_size_accountid", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::credential_keylet( + locator.as_ptr(), // invalid AccountID size + locator.len(), + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // valid slice size + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "credential_keylet_wrong_size_accountid1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::credential_keylet( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // invalid AccountID size + locator.len(), + locator.as_ptr(), // valid slice size + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "credential_keylet_wrong_size_accountid2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::delegate_keylet( + locator.as_ptr(), // invalid AccountID size + locator.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "delegate_keylet_wrong_size_accountid1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::delegate_keylet( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // invalid AccountID size + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "delegate_keylet_wrong_size_accountid2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::deposit_preauth_keylet( + locator.as_ptr(), // invalid AccountID size + locator.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "deposit_preauth_keylet_wrong_size_accountid1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::deposit_preauth_keylet( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // invalid AccountID size + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "deposit_preauth_keylet_wrong_size_accountid2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::did_keylet(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "did_keylet_wrong_size_accountid", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::escrow_keylet(locator.as_ptr(), locator.len(), 1, ptr, len) }, + error_codes::INVALID_PARAMS, + "escrow_keylet_wrong_size_accountid", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::line_keylet( + locator.as_ptr(), // invalid AccountID size + locator.len(), + account.0.as_ptr(), + account.0.len(), + currency.as_ptr(), + currency.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "line_keylet_wrong_size_accountid1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::line_keylet( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // invalid AccountID size + locator.len(), + currency.as_ptr(), + currency.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "line_keylet_wrong_size_accountid2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::mpt_issuance_keylet(locator.as_ptr(), locator.len(), 1, ptr, len) }, + error_codes::INVALID_PARAMS, + "mpt_issuance_keylet_wrong_size_accountid", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::mptoken_keylet( + mptid.as_ptr(), + mptid.len(), + locator.as_ptr(), + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "mptoken_keylet_wrong_size_accountid", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::nft_offer_keylet(locator.as_ptr(), locator.len(), 1, ptr, len) }, + error_codes::INVALID_PARAMS, + "nft_offer_keylet_wrong_size_accountid", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::offer_keylet(locator.as_ptr(), locator.len(), 1, ptr, len) }, + error_codes::INVALID_PARAMS, + "offer_keylet_wrong_size_accountid", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::oracle_keylet(locator.as_ptr(), locator.len(), 1, ptr, len) }, + error_codes::INVALID_PARAMS, + "oracle_keylet_wrong_size_accountid", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::paychan_keylet( + locator.as_ptr(), // invalid AccountID size + locator.len(), + account.0.as_ptr(), + account.0.len(), + 1, + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "paychan_keylet_wrong_size_accountid1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::paychan_keylet( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // invalid AccountID size + locator.len(), + 1, + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "paychan_keylet_wrong_size_accountid2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::permissioned_domain_keylet(locator.as_ptr(), locator.len(), 1, ptr, len) + }, + error_codes::INVALID_PARAMS, + "permissioned_domain_keylet_wrong_size_accountid", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::signers_keylet(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "signers_keylet_wrong_size_accountid", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::ticket_keylet(locator.as_ptr(), locator.len(), 1, ptr, len) }, + error_codes::INVALID_PARAMS, + "ticket_keylet_wrong_size_accountid", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::vault_keylet(locator.as_ptr(), locator.len(), 1, ptr, len) }, + error_codes::INVALID_PARAMS, + "vault_keylet_wrong_size_accountid", + ) + }); + let uint256: &[u8] = b"00000000000000000000000000000001"; + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::get_nft( + locator.as_ptr(), + locator.len(), + uint256.as_ptr(), + uint256.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "get_nft_wrong_size_accountid", + ) + }); + check_result( + unsafe { + host::trace_account( + message.as_ptr(), + message.len(), + locator.as_ptr(), + locator.len(), + ) + }, + error_codes::INVALID_PARAMS, + "trace_account_wrong_size_accountid", + ); + + // invalid Currency was already tested above + // invalid string + + check_result( + unsafe { + host::trace( + message.as_ptr().wrapping_add(1_000_000_000), + message.len(), + uint256.as_ptr(), + uint256.len(), + 0, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_oob_string", + ); + check_result( + unsafe { + host::trace_opaque_float( + message.as_ptr().wrapping_add(1_000_000_000), + message.len(), + float.as_ptr(), + float.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_opaque_float_oob_string", + ); + check_result( + unsafe { + host::trace_account( + message.as_ptr().wrapping_add(1_000_000_000), + message.len(), + account.0.as_ptr(), + account.0.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_account_oob_string", + ); + check_result( + unsafe { + host::trace_amount( + message.as_ptr().wrapping_add(1_000_000_000), + message.len(), + amount.as_ptr(), + amount.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_amount_oob_string", + ); + + // trace too large + + check_result( + unsafe { + host::trace( + locator.as_ptr(), + locator.len(), + locator.as_ptr(), + long_len, + 0, + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_too_long", + ); + check_result( + unsafe { host::trace_num(locator.as_ptr(), long_len, 1) }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_num_too_long", + ); + check_result( + unsafe { + host::trace_opaque_float(message.as_ptr(), long_len, float.as_ptr(), float.len()) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_opaque_float_too_long", + ); + check_result( + unsafe { + host::trace_account( + message.as_ptr(), + long_len, + account.0.as_ptr(), + account.0.len(), + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_account_too_long", + ); + check_result( + unsafe { host::trace_amount(message.as_ptr(), long_len, amount.as_ptr(), amount.len()) }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_amount_too_long", + ); + + // trace amount errors + + check_result( + unsafe { + host::trace_amount( + message.as_ptr(), + message.len(), + locator.as_ptr(), + locator.len(), + ) + }, + error_codes::INVALID_PARAMS, + "trace_amount_wrong_length", + ); + + // other misc errors + + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::mptoken_keylet( + locator.as_ptr(), + locator.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "mptoken_keylet_mptid_wrong_length", + ) + }); + + // ensure that the Slice index desync issue is fixed + let empty: &[u8] = b""; + check_result( + unsafe { + host::trace_account( + empty.as_ptr(), + empty.len(), + account.0.as_ptr(), + account.0.len(), + ) + }, + 34, + "trace_account_check_desync", + ); + + 1 // <-- If we get here, finish the escrow. +} diff --git a/src/test/app/wasm_fixtures/contract_data/Cargo.lock b/src/test/app/wasm_fixtures/contract_data/Cargo.lock new file mode 100644 index 0000000000..5b87de721f --- /dev/null +++ b/src/test/app/wasm_fixtures/contract_data/Cargo.lock @@ -0,0 +1,15 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "contract_data" +version = "0.0.1" +dependencies = [ + "xrpl-wasm-std", +] + +[[package]] +name = "xrpl-wasm-std" +version = "0.5.1-devnet5" +source = "git+https://github.com/Transia-RnD/craft.git?branch=dangell%2Fsmart-contracts#3c8191ae9832ea25f7d8f3e5eeb33b65181d31b5" diff --git a/src/test/app/wasm_fixtures/contract_data/Cargo.toml b/src/test/app/wasm_fixtures/contract_data/Cargo.toml new file mode 100644 index 0000000000..5638081ca0 --- /dev/null +++ b/src/test/app/wasm_fixtures/contract_data/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "contract_data" +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-wasm-std = { git = "https://github.com/Transia-RnD/craft.git", branch = "dangell/smart-contracts", package = "xrpl-wasm-std" } + +[profile.dev] +panic = "abort" diff --git a/src/test/app/wasm_fixtures/contract_data/src/lib.rs b/src/test/app/wasm_fixtures/contract_data/src/lib.rs new file mode 100644 index 0000000000..aad35252ac --- /dev/null +++ b/src/test/app/wasm_fixtures/contract_data/src/lib.rs @@ -0,0 +1,532 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +use xrpl_wasm_std::host::trace::{trace, trace_num}; +use xrpl_wasm_std::core::types::account_id::AccountID; +use xrpl_wasm_std::core::data::codec::{ + get_data, + set_data, + get_nested_data, + set_nested_data, + get_array_element, + set_array_element, + get_nested_array_element, + set_nested_array_element +}; + +// Different accounts for different test patterns +const ACCOUNT: [u8; 20] = [ + 0xAE, 0x12, 0x3A, 0x85, 0x56, 0xF3, 0xCF, 0x91, 0x15, 0x47, + 0x11, 0x37, 0x6A, 0xFB, 0x0F, 0x89, 0x4F, 0x83, 0x2B, 0x3D +]; + +// ============================================================================ +// TEST 1: Simple Object - Only top-level key-value pairs +// Creates: { "value_u8": 42, "value_u16": 1234, "count": 3, ... } +// ============================================================================ + +#[unsafe(no_mangle)] +pub extern "C" fn object_simple_create() -> i32 { + let _ = trace("=== TEST 1: Simple Object Create ==="); + let account = AccountID(ACCOUNT); + + // Test u8 + let _ = trace("Testing u8..."); + if let Err(e) = set_data::(&account, "value_u8", 42) { + return e; + } + if let Some(val) = get_data::(&account, "value_u8") { + let _ = trace_num("Read back u8:", val.into()); + } else { + let _ = trace("Failed to read back u8"); + return -1; + } + + // Test u16 + let _ = trace("Testing u16..."); + if let Err(e) = set_data::(&account, "value_u16", 1234) { + return e; + } + if let Some(val) = get_data::(&account, "value_u16") { + let _ = trace_num("Read back u16:", val.into()); + } else { + let _ = trace("Failed to read back u16"); + return -1; + } + + // Test u32 + let _ = trace("Testing u32..."); + if let Err(e) = set_data::(&account, "count", 3) { + return e; + } + if let Err(e) = set_data::(&account, "total", 12) { + return e; + } + if let Some(count_val) = get_data::(&account, "count") { + let _ = trace_num("Read back count:", count_val.into()); + } else { + let _ = trace("Failed to read back count"); + return -1; + } + + // Test u64 + let _ = trace("Testing u64..."); + if let Err(e) = set_data::(&account, "value_u64", 9876543210) { + return e; + } + if let Some(val) = get_data::(&account, "value_u64") { + let _ = trace_num("Read back u64:", val as i64); + } else { + let _ = trace("Failed to read back u64"); + return -1; + } + + // Test AccountID + let _ = trace("Testing AccountID..."); + const DESTINATION: [u8; 20] = [ + 0x05, 0x96, 0x91, 0x5C, 0xFD, 0xEE, 0xE3, 0xA6, 0x95, 0xB3, + 0xEF, 0xD6, 0xBD, 0xA9, 0xAC, 0x78, 0x8A, 0x36, 0x8B, 0x7B + ]; + let destination = AccountID(DESTINATION); + if let Err(e) = set_data(&account, "destination", destination) { + return e; + } + if let Some(_dest) = get_data::(&account, "destination") { + let _ = trace("Read back AccountID successfully"); + } else { + let _ = trace("Failed to read back AccountID"); + return -1; + } + + // Test reading non-existent key + let _ = trace("Testing non-existent key..."); + if let Some(_) = get_data::(&account, "nonexistent") { + let _ = trace("ERROR: Should not have found nonexistent key"); + return -1; + } else { + let _ = trace("Correctly returned None for nonexistent key"); + } + + let _ = trace("Simple object create tests passed!"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn object_simple_update() -> i32 { + let _ = trace("=== TEST 1: Simple Object Update ==="); + let account = AccountID(ACCOUNT); + + // Update u8 + let _ = trace("Updating u8 to 99..."); + if let Err(e) = set_data::(&account, "value_u8", 99) { + return e; + } + if let Some(val) = get_data::(&account, "value_u8") { + let _ = trace_num("Read back updated u8:", val.into()); + } else { + let _ = trace("Failed to read back u8"); + return -1; + } + + // Update u32 + let _ = trace("Updating count to 4..."); + if let Err(e) = set_data::(&account, "count", 4) { + return e; + } + if let Some(count_val) = get_data::(&account, "count") { + let _ = trace_num("Read back updated count:", count_val.into()); + } else { + let _ = trace("Failed to read back count"); + return -1; + } + + // Add new field + let _ = trace("Adding new field 'status'..."); + if let Err(e) = set_data::(&account, "status", 100) { + return e; + } + if let Some(val) = get_data::(&account, "status") { + let _ = trace_num("Read back new status:", val.into()); + } else { + let _ = trace("Failed to read back status"); + return -1; + } + + let _ = trace("Simple object update tests passed!"); + 0 +} + +// ============================================================================ +// TEST 2: Nested Object - Objects containing objects (depth 1) +// Creates: { "stats": {"score": 9999, "level": 5}, "key": {"subkey": 12} } +// ============================================================================ + +#[unsafe(no_mangle)] +pub extern "C" fn object_nested_create() -> i32 { + let _ = trace("=== TEST 2: Nested Object Create ==="); + let account = AccountID(ACCOUNT); + + // Test nested u8 + let _ = trace("Testing nested u8..."); + if let Err(e) = set_nested_data::(&account, "key", "subkey", 12) { + return e; + } + if let Some(nested_val) = get_nested_data::(&account, "key", "subkey") { + let _ = trace_num("Read back nested value:", nested_val.into()); + } else { + let _ = trace("Failed to read back nested value"); + return -1; + } + + // Test nested u32 + let _ = trace("Testing nested u32..."); + if let Err(e) = set_nested_data::(&account, "stats", "score", 9999) { + return e; + } + if let Some(val) = get_nested_data::(&account, "stats", "score") { + let _ = trace_num("Read back nested u32:", val.into()); + } else { + let _ = trace("Failed to read back nested u32"); + return -1; + } + + // Test multiple fields in same nested object + let _ = trace("Adding multiple fields to nested object..."); + if let Err(e) = set_nested_data::(&account, "stats", "level", 5) { + return e; + } + if let Err(e) = set_nested_data::(&account, "stats", "coins", 1000) { + return e; + } + if let Some(val) = get_nested_data::(&account, "stats", "level") { + let _ = trace_num("Read back stats.level:", val.into()); + } else { + let _ = trace("Failed to read back stats.level"); + return -1; + } + + // Test nested u64 + let _ = trace("Testing nested u64..."); + if let Err(e) = set_nested_data::(&account, "data", "timestamp", 1234567890) { + return e; + } + if let Some(val) = get_nested_data::(&account, "data", "timestamp") { + let _ = trace_num("Read back nested u64:", val as i64); + } else { + let _ = trace("Failed to read back nested u64"); + return -1; + } + + let _ = trace("Nested object create tests passed!"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn object_nested_update() -> i32 { + let _ = trace("=== TEST 2: Nested Object Update ==="); + let account = AccountID(ACCOUNT); + + // Update nested value + let _ = trace("Updating nested score to 12345..."); + if let Err(e) = set_nested_data::(&account, "stats", "score", 12345) { + return e; + } + if let Some(val) = get_nested_data::(&account, "stats", "score") { + let _ = trace_num("Read back updated nested score:", val.into()); + } else { + let _ = trace("Failed to read back nested score"); + return -1; + } + + // Update another nested field + let _ = trace("Updating nested level to 10..."); + if let Err(e) = set_nested_data::(&account, "stats", "level", 10) { + return e; + } + if let Some(val) = get_nested_data::(&account, "stats", "level") { + let _ = trace_num("Read back updated level:", val.into()); + } else { + let _ = trace("Failed to read back level"); + return -1; + } + + // Add new nested field + let _ = trace("Adding new nested field..."); + if let Err(e) = set_nested_data::(&account, "config", "timeout", 30) { + return e; + } + if let Some(val) = get_nested_data::(&account, "config", "timeout") { + let _ = trace_num("Read back new config.timeout:", val.into()); + } else { + let _ = trace("Failed to read back config.timeout"); + return -1; + } + + let _ = trace("Nested object update tests passed!"); + 0 +} + +// ============================================================================ +// TEST 3: Object with Arrays - Objects containing arrays of simple values +// Creates: { "items": [10, 20, 30], "values": [100, 200] } +// Note: This uses set_array_element which creates an object with array fields +// ============================================================================ + +#[unsafe(no_mangle)] +pub extern "C" fn object_with_arrays_create() -> i32 { + let _ = trace("=== TEST 3: Object with Arrays Create ==="); + let account = AccountID(ACCOUNT); + + // Test u8 array + let _ = trace("Testing u8 array..."); + if let Err(e) = set_array_element::(&account, "array_u8", 0, 10) { + return e; + } + if let Err(e) = set_array_element::(&account, "array_u8", 1, 20) { + return e; + } + if let Err(e) = set_array_element::(&account, "array_u8", 2, 30) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u8", 0) { + let _ = trace_num("Read array_u8[0]:", val.into()); + } else { + let _ = trace("Failed to read array_u8[0]"); + return -1; + } + if let Some(val) = get_array_element::(&account, "array_u8", 1) { + let _ = trace_num("Read array_u8[1]:", val.into()); + } else { + let _ = trace("Failed to read array_u8[1]"); + return -1; + } + + // Test u16 array + let _ = trace("Testing u16 array..."); + if let Err(e) = set_array_element::(&account, "array_u16", 0, 100) { + return e; + } + if let Err(e) = set_array_element::(&account, "array_u16", 1, 200) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u16", 0) { + let _ = trace_num("Read array_u16[0]:", val.into()); + } else { + let _ = trace("Failed to read array_u16[0]"); + return -1; + } + + // Test u32 array + let _ = trace("Testing u32 array..."); + if let Err(e) = set_array_element::(&account, "array_u32", 0, 1000) { + return e; + } + if let Err(e) = set_array_element::(&account, "array_u32", 1, 2000) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u32", 0) { + let _ = trace_num("Read array_u32[0]:", val.into()); + } else { + let _ = trace("Failed to read array_u32[0]"); + return -1; + } + + // Test u64 array + let _ = trace("Testing u64 array..."); + if let Err(e) = set_array_element::(&account, "array_u64", 0, 10000) { + return e; + } + if let Err(e) = set_array_element::(&account, "array_u64", 1, 20000) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u64", 0) { + let _ = trace_num("Read array_u64[0]:", val as i64); + } else { + let _ = trace("Failed to read array_u64[0]"); + return -1; + } + + let _ = trace("Object with arrays create tests passed!"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn object_with_arrays_update() -> i32 { + let _ = trace("=== TEST 3: Object with Arrays Update ==="); + let account = AccountID(ACCOUNT); + + // Update array element + let _ = trace("Updating array_u32[0] to 7777..."); + if let Err(e) = set_array_element::(&account, "array_u32", 0, 7777) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u32", 0) { + let _ = trace_num("Read back updated array_u32[0]:", val.into()); + } else { + let _ = trace("Failed to read back array_u32[0]"); + return -1; + } + + // Add new array element + let _ = trace("Adding new array element array_u16[2]..."); + if let Err(e) = set_array_element::(&account, "array_u16", 2, 300) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u16", 2) { + let _ = trace_num("Read back new array_u16[2]:", val.into()); + } else { + let _ = trace("Failed to read back array_u16[2]"); + return -1; + } + + // Add element with gap (should auto-fill with nulls) + let _ = trace("Adding array_u8[5] (skipping indices 3-4)..."); + if let Err(e) = set_array_element::(&account, "array_u8", 5, 50) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u8", 5) { + let _ = trace_num("Read back array_u8[5]:", val.into()); + } else { + let _ = trace("Failed to read back array_u8[5]"); + return -1; + } + + let _ = trace("Object with arrays update tests passed!"); + 0 +} + +// ============================================================================ +// TEST 4: Object with Nested Arrays - Objects containing arrays of objects +// Creates: { "nested_array": [{"field1": 55, "field2": 66}, {"field1": 77}] } +// This is the most complex structure allowed (depth 1) +// DA: I wouldnt use this. If you are doing this, consider redesigning your data model +// ============================================================================ + +#[unsafe(no_mangle)] +pub extern "C" fn object_with_nested_arrays_create() -> i32 { + let _ = trace("=== TEST 4: Object with Nested Arrays Create ==="); + let account = AccountID(ACCOUNT); + + // Test nested u8 array with multiple fields + let _ = trace("Testing nested u8 array..."); + if let Err(e) = set_nested_array_element::(&account, "nested_array", 0, "field1", 55) { + return e; + } + if let Err(e) = set_nested_array_element::(&account, "nested_array", 0, "field2", 66) { + return e; + } + if let Err(e) = set_nested_array_element::(&account, "nested_array", 1, "field1", 77) { + return e; + } + + if let Some(val) = get_nested_array_element::(&account, "nested_array", 0, "field1") { + let _ = trace_num("Read nested_array[0].field1:", val.into()); + } else { + let _ = trace("Failed to read nested_array[0].field1"); + return -1; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array", 0, "field2") { + let _ = trace_num("Read nested_array[0].field2:", val.into()); + } else { + let _ = trace("Failed to read nested_array[0].field2"); + return -1; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array", 1, "field1") { + let _ = trace_num("Read nested_array[1].field1:", val.into()); + } else { + let _ = trace("Failed to read nested_array[1].field1"); + return -1; + } + + // Test nested u32 array + let _ = trace("Testing nested u32 array..."); + if let Err(e) = set_nested_array_element::(&account, "nested_array_u32", 0, "value", 5555) { + return e; + } + if let Err(e) = set_nested_array_element::(&account, "nested_array_u32", 1, "value", 6666) { + return e; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array_u32", 0, "value") { + let _ = trace_num("Read nested_array_u32[0].value:", val.into()); + } else { + let _ = trace("Failed to read nested_array_u32[0].value"); + return -1; + } + + // Test nested u64 array + let _ = trace("Testing nested u64 array..."); + if let Err(e) = set_nested_array_element::(&account, "items", 0, "id", 99999) { + return e; + } + if let Err(e) = set_nested_array_element::(&account, "items", 0, "price", 123456) { + return e; + } + if let Some(val) = get_nested_array_element::(&account, "items", 0, "id") { + let _ = trace_num("Read items[0].id:", val as i64); + } else { + let _ = trace("Failed to read items[0].id"); + return -1; + } + + let _ = trace("Object with nested arrays create tests passed!"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn object_with_nested_arrays_update() -> i32 { + let _ = trace("=== TEST 4: Object with Nested Arrays Update ==="); + let account = AccountID(ACCOUNT); + + // Update nested array element + let _ = trace("Updating nested_array[0].field1 to 88..."); + if let Err(e) = set_nested_array_element::(&account, "nested_array", 0, "field1", 88) { + return e; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array", 0, "field1") { + let _ = trace_num("Read back updated nested_array[0].field1:", val.into()); + } else { + let _ = trace("Failed to read back nested_array[0].field1"); + return -1; + } + + // Add new field to existing array element + let _ = trace("Adding field3 to nested_array[0]..."); + if let Err(e) = set_nested_array_element::(&account, "nested_array", 0, "field3", 111) { + return e; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array", 0, "field3") { + let _ = trace_num("Read back nested_array[0].field3:", val.into()); + } else { + let _ = trace("Failed to read back nested_array[0].field3"); + return -1; + } + + // Add new array element + let _ = trace("Adding nested_array[2]..."); + if let Err(e) = set_nested_array_element::(&account, "nested_array", 2, "field1", 99) { + return e; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array", 2, "field1") { + let _ = trace_num("Read back nested_array[2].field1:", val.into()); + } else { + let _ = trace("Failed to read back nested_array[2].field1"); + return -1; + } + + // Update u32 nested array + let _ = trace("Updating nested_array_u32[1].value to 8888..."); + if let Err(e) = set_nested_array_element::(&account, "nested_array_u32", 1, "value", 8888) { + return e; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array_u32", 1, "value") { + let _ = trace_num("Read back updated nested_array_u32[1].value:", val.into()); + } else { + let _ = trace("Failed to read back nested_array_u32[1].value"); + return -1; + } + + let _ = trace("Object with nested arrays update tests passed!"); + 0 +} diff --git a/src/test/app/wasm_fixtures/copyFixtures.py b/src/test/app/wasm_fixtures/copyFixtures.py new file mode 100644 index 0000000000..cd53184b5e --- /dev/null +++ b/src/test/app/wasm_fixtures/copyFixtures.py @@ -0,0 +1,134 @@ +import os +import sys +import subprocess +import re + +OPT = "-Oz" + + +def update_fixture(project_name, wasm): + fixture_name = ( + re.sub(r"_([a-z])", lambda m: m.group(1).upper(), project_name) + "WasmHex" + ) + print(f"Updating fixture: {fixture_name}") + + cpp_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "fixtures.cpp")) + h_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "fixtures.h")) + with open(cpp_path, "r", encoding="utf8") as f: + cpp_content = f.read() + + pattern = rf'extern std::string const {fixture_name} =[ \n]+"[^;]*;' + if re.search(pattern, cpp_content, flags=re.MULTILINE): + updated_cpp_content = re.sub( + pattern, + f'extern std::string const {fixture_name} = "{wasm}";', + cpp_content, + flags=re.MULTILINE, + ) + else: + with open(h_path, "r", encoding="utf8") as f: + h_content = f.read() + updated_h_content = ( + h_content.rstrip() + f"\n\n extern std::string const {fixture_name};\n" + ) + with open(h_path, "w", encoding="utf8") as f: + f.write(updated_h_content) + updated_cpp_content = ( + cpp_content.rstrip() + + f'\n\nextern std::string const {fixture_name} = "{wasm}";\n' + ) + + with open(cpp_path, "w", encoding="utf8") as f: + f.write(updated_cpp_content) + + +def process_rust(project_name): + project_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), project_name) + ) + wasm_location = f"target/wasm32v1-none/release/{project_name}.wasm" + build_cmd = ( + f"(cd {project_path} " + f"&& cargo build --target wasm32v1-none --release " + f"&& wasm-opt {wasm_location} {OPT} -o {wasm_location}" + ")" + ) + try: + result = subprocess.run( + build_cmd, shell=True, check=True, capture_output=True, text=True + ) + print(f"stdout: {result.stdout}") + if result.stderr: + print(f"stderr: {result.stderr}") + print(f"WASM file for {project_name} has been built and optimized.") + except subprocess.CalledProcessError as e: + print(f"exec error: {e}") + sys.exit(1) + + src_path = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + f"{project_name}/target/wasm32v1-none/release/{project_name}.wasm", + ) + ) + with open(src_path, "rb") as f: + data = f.read() + wasm = data.hex() + update_fixture(project_name, wasm) + + +def process_c(project_name): + project_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), f"{project_name}.c") + ) + wasm_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), f"{project_name}.wasm") + ) + build_cmd = ( + f"$CC --sysroot=$SYSROOT " + f"-O3 -ffast-math --target=wasm32 -fno-exceptions -fno-threadsafe-statics -fvisibility=default -Wl,--export-all -Wl,--no-entry -Wl,--allow-undefined -DNDEBUG --no-standard-libraries -fno-builtin-memset " + f"-o {wasm_path} {project_path}" + f"&& wasm-opt {wasm_path} {OPT} -o {wasm_path}" + ) + try: + result = subprocess.run( + build_cmd, shell=True, check=True, capture_output=True, text=True + ) + print(f"stdout: {result.stdout}") + if result.stderr: + print(f"stderr: {result.stderr}") + print( + f"WASM file for {project_name} has been built with WASI support using clang." + ) + except subprocess.CalledProcessError as e: + print(f"exec error: {e}") + sys.exit(1) + + with open(wasm_path, "rb") as f: + data = f.read() + wasm = data.hex() + update_fixture(project_name, wasm) + + +if __name__ == "__main__": + if len(sys.argv) > 2: + print("Usage: python copyFixtures.py []") + 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.") diff --git a/src/test/app/wasm_fixtures/disableFloat.wat b/src/test/app/wasm_fixtures/disableFloat.wat new file mode 100644 index 0000000000..035a849e30 --- /dev/null +++ b/src/test/app/wasm_fixtures/disableFloat.wat @@ -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))) diff --git a/src/test/app/wasm_fixtures/emit_txn/Cargo.toml b/src/test/app/wasm_fixtures/emit_txn/Cargo.toml new file mode 100644 index 0000000000..6ae66a5914 --- /dev/null +++ b/src/test/app/wasm_fixtures/emit_txn/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "emit_txn" +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-wasm-std = { git = "https://github.com/Transia-RnD/craft.git", branch = "dangell/smart-contracts", package = "xrpl-wasm-std" } + +[profile.dev] +panic = "abort" diff --git a/src/test/app/wasm_fixtures/emit_txn/src/lib.rs b/src/test/app/wasm_fixtures/emit_txn/src/lib.rs new file mode 100644 index 0000000000..a50df3e783 --- /dev/null +++ b/src/test/app/wasm_fixtures/emit_txn/src/lib.rs @@ -0,0 +1,218 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +use xrpl_wasm_std::core::current_tx::contract_call::{ContractCall, get_current_contract_call}; +use xrpl_wasm_std::core::current_tx::traits::TransactionCommonFields; +use xrpl_wasm_std::core::submit::inner_objects::build_memo; +use xrpl_wasm_std::core::transaction_types::TT_PAYMENT; +use xrpl_wasm_std::core::types::account_id::AccountID; +use xrpl_wasm_std::host::{add_txn_field, build_txn, emit_built_txn}; +use xrpl_wasm_std::sfield; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Custom error code for transaction failures +const CUSTOM_ERROR_CODE: i32 = -18; + +/// XRPL encoding markers +mod markers { + pub const ARRAY_END: u8 = 0xF1; + pub const OBJECT_END: u8 = 0xE1; +} + +/// Buffer sizes +mod buffer_sizes { + pub const MEMO_BUFFER: usize = 256; + pub const MEMOS_ARRAY: usize = 1024; + pub const DESTINATION: usize = 21; +} + +// ============================================================================ +// Helper Functions +// ============================================================================ + +/// Builds a complete memos array from individual memo buffers +/// +/// # Arguments +/// * `buffer` - Output buffer for the complete memos array +/// * `memo_buffers` - Slice of memo data and their lengths +/// +/// # Returns +/// Total length of the memos array including the end marker +fn build_memos_array( + buffer: &mut [u8; buffer_sizes::MEMOS_ARRAY], + memo_buffers: &[(&[u8], usize)] +) -> usize { + let mut position = 0; + + // Copy each memo into the array + for (memo_data, memo_length) in memo_buffers { + buffer[position..position + memo_length].copy_from_slice(&memo_data[..*memo_length]); + position += memo_length; + } + + // Terminate the array + buffer[position] = markers::ARRAY_END; + position + 1 +} + +/// Adds the amount field to the transaction +/// +/// # Arguments +/// * `txn_index` - Transaction builder index +/// * `amount_drops` - Amount in drops (192 in this example) +/// +/// # Returns +/// Result code from add_txn_field +unsafe fn add_amount_field(txn_index: i32) -> i32 { + // 192 drops encoded as XRPL Amount + const AMOUNT_BYTES: [u8; 8] = [ + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 + ]; + + add_txn_field( + txn_index, + sfield::Amount, + AMOUNT_BYTES.as_ptr(), + AMOUNT_BYTES.len() + ) +} + +/// Adds the destination field to the transaction +/// +/// # Arguments +/// * `txn_index` - Transaction builder index +/// * `destination` - Destination account ID +/// +/// # Returns +/// Result code from add_txn_field +unsafe fn add_destination_field(txn_index: i32, destination: &AccountID) -> i32 { + let mut dest_buffer = [0u8; buffer_sizes::DESTINATION]; + dest_buffer[0] = 0x14; // Length prefix for 20-byte account + dest_buffer[1..21].copy_from_slice(&destination.0); + + add_txn_field( + txn_index, + sfield::Destination, + dest_buffer.as_ptr(), + dest_buffer.len() + ) +} + +/// Adds the memos field to the transaction +/// +/// # Arguments +/// * `txn_index` - Transaction builder index +/// +/// # Returns +/// Result code from add_txn_field +unsafe fn add_memos_field(txn_index: i32) -> i32 { + use core::mem::MaybeUninit; + + // Uninitialized backing buffer (no zeroing => no memory.fill) + let mut memos_uninit: MaybeUninit<[u8; buffer_sizes::MEMOS_ARRAY]> = MaybeUninit::uninit(); + let base = memos_uninit.as_mut_ptr() as *mut u8; + + // Helper: get a 256-byte window at current position + #[inline(always)] + unsafe fn at<'a>(base: *mut u8, pos: usize) -> &'a mut [u8; buffer_sizes::MEMO_BUFFER] { + &mut *(base.add(pos) as *mut [u8; buffer_sizes::MEMO_BUFFER]) + } + + let mut pos = 0usize; + + // Write each Memo directly into the big buffer + let len1 = build_memo( + at(base, pos), + Some(b"invoice"), + Some(b"INV-2024-001"), + Some(b"text/plain") + ); + pos += len1; + + let len2 = build_memo( + at(base, pos), + Some(b"note"), + Some(b"Payment for consulting services"), + Some(b"text/plain") + ); + pos += len2; + + let len3 = build_memo( + at(base, pos), + None, + Some(b"Additional reference: Project Alpha"), + None + ); + pos += len3; + + // Terminate the array + *base.add(pos) = markers::ARRAY_END; + pos += 1; + + add_txn_field( + txn_index, + sfield::Memos, + base, + pos + ) +} + +// ============================================================================ +// Main Entry Point +// ============================================================================ + +/// Main hook function that builds and emits a payment transaction with memos +/// +/// This function: +/// 1. Retrieves the current contract call context +/// 2. Builds a payment transaction +/// 3. Adds amount, destination, and memos fields +/// 4. Emits the completed transaction +/// +/// # Returns +/// - 0 on success +/// - Negative error code on failure +#[unsafe(no_mangle)] +pub extern "C" fn emit() -> i32 { + // Get contract context + let contract_call: ContractCall = get_current_contract_call(); + let account = contract_call.get_account().unwrap(); + + // Initialize payment transaction + let txn_index = 0; + let build_result = unsafe { build_txn(TT_PAYMENT) }; + if build_result < 0 { + return CUSTOM_ERROR_CODE; + } + + // Build transaction fields + unsafe { + // Add amount field + if add_amount_field(txn_index) < 0 { + return CUSTOM_ERROR_CODE; + } + + // Add destination field + if add_destination_field(txn_index, &account) < 0 { + return CUSTOM_ERROR_CODE; + } + + // Add memos field + if add_memos_field(txn_index) < 0 { + return CUSTOM_ERROR_CODE; + } + + // Emit the completed transaction + let emission_result = emit_built_txn(txn_index); + if emission_result < 0 { + return emission_result; + } + } + + 0 // Success +} diff --git a/src/test/app/wasm_fixtures/events/Cargo.toml b/src/test/app/wasm_fixtures/events/Cargo.toml new file mode 100644 index 0000000000..ce249ac9b0 --- /dev/null +++ b/src/test/app/wasm_fixtures/events/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "events" +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-wasm-std = { git = "https://github.com/Transia-RnD/craft.git", branch = "dangell/smart-contracts", package = "xrpl-wasm-std" } + +[profile.dev] +panic = "abort" diff --git a/src/test/app/wasm_fixtures/events/src/lib.rs b/src/test/app/wasm_fixtures/events/src/lib.rs new file mode 100644 index 0000000000..7cc8d0537c --- /dev/null +++ b/src/test/app/wasm_fixtures/events/src/lib.rs @@ -0,0 +1,99 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +use xrpl_wasm_std::core::types::account_id::AccountID; +use xrpl_wasm_std::core::event::codec_v2::{ + EventBuffer, event_add_u8, event_add_u16, event_add_u32, event_add_u64, + event_add_u128, event_add_u160, event_add_u192, event_add_u256, event_add_amount, event_add_account, + event_add_currency, event_add_str +}; + +#[unsafe(no_mangle)] +pub extern "C" fn events() -> i32 { + let mut buf = EventBuffer::new(); + + // STI_AMOUNT + const AMOUNT: [u8; 8] = [ + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 + ]; + if event_add_amount(&mut buf, "amount", &AMOUNT).is_err() { + return -1; + } + + // STI_CURRENCY + const CURRENCY: [u8; 20] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x00, + 0x00, 0x00, 0x00, 0x00 + ]; + if event_add_currency(&mut buf, "currency", &CURRENCY).is_err() { + return -1; + } + + // STI_ACCOUNT + const ACCOUNT: [u8; 20] = [ + 0x59, 0x69, 0x15, 0xCF, 0xDE, 0xEE, 0x3A, 0x69, + 0x5B, 0x3E, 0xFD, 0x6B, 0xDA, 0x9A, 0xC7, 0x88, + 0xA3, 0x68, 0xB7, 0xB + ]; + let account = AccountID(ACCOUNT); + if event_add_account(&mut buf, "destination", &account.0).is_err() { + return -1; + } + + // STI_UINT128 + if event_add_u128(&mut buf, "uint128", &[0u8; 16]).is_err() { + return -1; + } + + // STI_UINT16 + if event_add_u16(&mut buf, "uint16", 16).is_err() { + return -1; + } + + // STI_UINT160 + if event_add_u160(&mut buf, "uint160", &[0u8; 20]).is_err() { + return -1; + } + + // STI_UINT192 + if event_add_u192(&mut buf, "uint192", &[0u8; 24]).is_err() { + return -1; + } + + // STI_UINT256 + if event_add_u256(&mut buf, "uint256", &[0u8; 32]).is_err() { + return -1; + } + + // STI_UINT32 + if event_add_u32(&mut buf, "uint32", 32).is_err() { + return -1; + } + + // STI_UINT64 + if event_add_u64(&mut buf, "uint64", 64).is_err() { + return -1; + } + + // STI_UINT8 + if event_add_u8(&mut buf, "uint8", 8).is_err() { + return -1; + } + + // STI_VL + if event_add_str(&mut buf, "vl", "Hello, World!").is_err() { + return -1; + } + + // STI_ISSUE (XRP) + // STI_ISSUE (IOU) + // STI_ISSUE (MPT) + + if buf.emit("event1").is_err() { + return -1; + } + 0 +} diff --git a/src/test/app/wasm_fixtures/fib.c b/src/test/app/wasm_fixtures/fib.c new file mode 100644 index 0000000000..4410923054 --- /dev/null +++ b/src/test/app/wasm_fixtures/fib.c @@ -0,0 +1,12 @@ +// typedef long long mint; +typedef int mint; + +mint +fib(mint n) +{ + if (!n) + return 0; + if (n <= 2) + return 1; + return fib(n - 1) + fib(n - 2); +} diff --git a/src/test/app/wasm_fixtures/fixtures.cpp b/src/test/app/wasm_fixtures/fixtures.cpp new file mode 100644 index 0000000000..f3b54a6d05 --- /dev/null +++ b/src/test/app/wasm_fixtures/fixtures.cpp @@ -0,0 +1,1986 @@ +// TODO: consider moving these to separate files (and figure out the build) + +#include + +extern std::string const fibWasmHex = + "0061736d0100000001090260000060017f017f0303020001071b02115f5f" + "7761736d5f63616c6c5f63746f727300000366696200010a440202000b3f" + "01017f200045044041000f0b2000410348044041010f0b200041026a2100" + "0340200041036b100120016a2101200041026b220041044a0d000b200141" + "016a0b"; + +extern std::string const b58WasmHex = + "0061736d0100000001150460000060017f017f60017f0060047f7f7f7f01" + "7f0305040001020305030100020607017f0041d0080b074906066d656d6f" + "72790200115f5f7761736d5f63616c6c5f63746f7273000008616c6c6f63" + "617465000106655f6461746103000a6465616c6c6f636174650002076235" + "38656e636f00030ae8050402000b3401017f024020004180084a0d0041c0" + "082802002200411f4a0d0041c008200041016a3602002000410a7441d008" + "6a21010b20010b02000baa0501097f41d088060240200341004c0d000340" + "200220066a2d00000d012003200641016a2206470d000b200321060b2003" + "20066b220c418a016c41e4006d220741106a4170716b21090240200c4100" + "480d00200741016a220a410771210541002104200741074f0440200a41f8" + "ffff1f71210a0340200420096a4200370300200a200441086a2204470d00" + "0b0b2005450d00200420096a21040340200441003a0000200441016a2104" + "200541016b22050d000b0b200320064a0440200621052007210403402002" + "20056a2d0000210802402004220a20074e0440200721042008450d010b20" + "0721040340200420096a220b200b2d000041087420086a220b200b413a6d" + "2208413a6c6b3a00002004450440410021040c020b200441016b2204200a" + "4a0d00200b413a6b418d7f490d000b0b200541016a22052003470d000b0b" + "200741016a22022103410021040240200c4100480d000340200420096a2d" + "00000d012002200441016a2204470d000b200221040b2001200320066a20" + "046b4a047f02402006450d002006410771210841002105200641084f0440" + "200641787121010340200020056a42b1e2c48993a6cc9831370000200120" + "0541086a2205470d000b0b2008450d00200020056a21050340200541313a" + "0000200541016a2105200841016b22080d000b0b0240200420074a0d0020" + "0420076a410171047f200405200020066a200420096a2d00004180086a2d" + "00003a0000200641016a2106200441016a0b210520042007460d00200520" + "096a2101200720056b2102200020066a2103417f21040340200320046a22" + "0741016a200120046a220541016a2d00004180086a2d00003a0000200741" + "026a200541026a2d00004180086a2d00003a00002002200441026a220447" + "0d000b200420066a41016a21060b200020066a41003a0000200641016a05" + "41000b0b0b4101004180080b3a3132333435363738394142434445464748" + "4a4b4c4d4e505152535455565758595a6162636465666768696a6b6d6e6f" + "707172737475767778797a00490f7461726765745f666561747572657304" + "2b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f72" + "65666572656e63652d74797065732b0a6d756c746976616c7565"; + +extern std::string const sha512PureWasmHex = + "0061736d0100000001130460000060017f017f60017f0060027f7f017f03" + "05040001020305030100020607017f0041800d0b075006066d656d6f7279" + "0200115f5f7761736d5f63616c6c5f63746f7273000008616c6c6f636174" + "65000106655f6461746103000a6465616c6c6f6361746500020e73686135" + "31325f70726f6365737300030aa4060402000b0f0041800d410020004180" + "80024d1b0b02000b8b0602147e037f200141ff006a41ff014f044041c08c" + "06211620014180016d41016bac210b41808d02290300210c41888d022903" + "00210d41908d02290300210e41988d02290300210f41a08d022903002110" + "41a88d02290300211141b08d02290300211241b88d022903002113034041" + "002101200c21092013210a201221082011210520102103200f2115200e21" + "06200d2102034020022107200120166a200020016a290000220242388620" + "024280fe0383422886842002428080fc0783421886200242808080f80f83" + "4208868484200242088842808080f80f832002421888428080fc07838420" + "024228884280fe03832002423888848484220237030020014180086a2903" + "00200a200322044232892004422e89852004421789857c2008220a200442" + "7f858320052208200483847c7c20027c2203200922024224892002421e89" + "85200242198985200220072006221485832006200783857c7c2109200320" + "157c2103200421052006211520072106200141086a2201418001470d000b" + "200020016a21004180092117411e210103402002210620162001410e6b41" + "0f714103746a221820182903002016200141056b410f714103746a290300" + "20162001410d6b410f714103746a2903002202423f892002423889852002" + "420788857c7c20162001410f714103746a2903002202422d892002420389" + "852002420688857c22023703002017290300200a20032205423289200542" + "2e89852005421789857c2005427f852008832004200583847c7c20027c22" + "03200922024224892002421e898520024219898520022006200785832006" + "200783857c7c2109200320147c2103201741086a21172008210a20042108" + "200521042007211420062107200141016a220141de00470d000b41b88d02" + "200a20137c221337030041b08d02200820127c221237030041a88d022004" + "20117c221137030041a08d02200320107c221037030041988d02200f2014" + "7c220f37030041908d022007200e7c220e37030041888d022002200d7c22" + "0d37030041808d022009200c7c220c370300200b420052200b42017d210b" + "0d000b0b41808d020b0b880501004180080b800522ae28d7982f8a42cd65" + "ef23914437712f3b4deccffbc0b5bcdb8981a5dbb5e938b548f35bc25639" + "19d005b6f111f1599b4f19afa4823f9218816ddad55e1cab420203a398aa" + "07d8be6f7045015b83128cb2e44ebe853124e2b4ffd5c37d0c556f897bf2" + "745dbe72b196163bfeb1de803512c725a706dc9b942669cf74f19bc1d24a" + "f19ec1699be4e3254f388647beefb5d58c8bc69dc10f659cac77cca10c24" + "75022b596f2ce92d83e4a66eaa84744ad4fb41bddca9b05cb5531183da88" + "f976abdf66ee52513e981032b42d6dc631a83f21fb98c82703b0e40eefbe" + "c77f59bfc28fa83df30be0c625a70a934791a7d56f8203e05163ca06706e" + "0e0a67292914fc2fd246850ab72726c9265c38211b2eed2ac45afc6d2c4d" + "dfb3959d130d3853de63af8b54730a65a8b2773cbb0a6a76e6aeed472ec9" + "c2813b358214852c72926403f14ca1e8bfa2013042bc4b661aa89197f8d0" + "708b4bc230be5406a3516cc71852efd619e892d110a96555240699d62a20" + "715785350ef4b8d1bb3270a06a10c8d0d2b816c1a41953ab4151086c371e" + "99eb8edf4c774827a8489be1b5bcb034635ac9c5b30c1c39cb8a41e34aaa" + "d84e73e363774fca9c5ba3b8b2d6f36f2e68fcb2ef5dee828f74602f1743" + "6f63a57872abf0a11478c884ec39641a0802c78c281e6323faffbe90e9bd" + "82deeb6c50a41579c6b2f7a3f9be2b5372e3f27871c69c6126eace3e27ca" + "07c2c021c7b886d11eebe0cdd67ddaea78d16eee7f4f7df5ba6f1772aa67" + "f006a698c8a2c57d630aae0df9be04983f111b471c13350b711b847d0423" + "f577db289324c7407babca32bcbec9150abe9e3c4c0d109cc4671d43b642" + "3ecbbed4c54c2a7e65fc9c297f59ecfad63aab6fcb5f1758474a8c19446c" + "00490f7461726765745f6665617475726573042b0f6d757461626c652d67" + "6c6f62616c732b087369676e2d6578742b0f7265666572656e63652d7479" + "7065732b0a6d756c746976616c7565"; + +extern std::string const ledgerSqnWasmHex = + "0061736d010000000108026000017f60000002160103656e760e6765745f6c65646765725f" + "73716e000003030201000503010002063e0a7f004180080b7f004180080b7f004180100b7f" + "004180100b7f00418090040b7f004180080b7f00418090040b7f00418080080b7f0041000b" + "7f0041010b07b0010d066d656d6f72790200115f5f7761736d5f63616c6c5f63746f727300" + "010666696e69736800020362756603000c5f5f64736f5f68616e646c6503010a5f5f646174" + "615f656e6403020b5f5f737461636b5f6c6f7703030c5f5f737461636b5f6869676803040d" + "5f5f676c6f62616c5f6261736503050b5f5f686561705f6261736503060a5f5f686561705f" + "656e6403070d5f5f6d656d6f72795f6261736503080c5f5f7461626c655f6261736503090a" + "150202000b1001017f100022004100200041044b1b0b007f0970726f647563657273010c70" + "726f6365737365642d62790105636c616e675f31392e312e352d776173692d73646b202868" + "747470733a2f2f6769746875622e636f6d2f6c6c766d2f6c6c766d2d70726f6a6563742061" + "62346235613264623538323935386166316565333038613739306366646234326264323437" + "32302900490f7461726765745f6665617475726573042b0f6d757461626c652d676c6f6261" + "6c732b087369676e2d6578742b0f7265666572656e63652d74797065732b0a6d756c746976" + "616c7565"; + +extern std::string const allHostFunctionsWasmHex = + "0061736d0100000001540c60047f7f7f7f017f60027f7f017f60037f7f7f017f60057f7f7f" + "7f7f017f6000017f60017f017f60037f7f7f0060037f7f7e017f60087f7f7f7f7f7f7f7f01" + "7f60067f7f7f7f7f7f017f60017f0060027f7f0002ae061a08686f73745f6c69620c676574" + "5f74785f6669656c64000208686f73745f6c69620974726163655f6e756d000708686f7374" + "5f6c6962057472616365000308686f73745f6c69620e6765745f6c65646765725f73716e00" + "0408686f73745f6c6962166765745f706172656e745f6c65646765725f74696d6500040868" + "6f73745f6c6962166765745f706172656e745f6c65646765725f68617368000108686f7374" + "5f6c69620d74726163655f6163636f756e74000008686f73745f6c6962136765745f74785f" + "6e65737465645f6669656c64000008686f73745f6c6962106765745f74785f61727261795f" + "6c656e000508686f73745f6c6962176765745f74785f6e65737465645f61727261795f6c65" + "6e000108686f73745f6c69621c6765745f63757272656e745f6c65646765725f6f626a5f66" + "69656c64000208686f73745f6c6962236765745f63757272656e745f6c65646765725f6f62" + "6a5f6e65737465645f6669656c64000008686f73745f6c6962206765745f63757272656e74" + "5f6c65646765725f6f626a5f61727261795f6c656e000508686f73745f6c6962276765745f" + "63757272656e745f6c65646765725f6f626a5f6e65737465645f61727261795f6c656e0001" + "08686f73745f6c69620e6163636f756e745f6b65796c6574000008686f73745f6c69621063" + "616368655f6c65646765725f6f626a000208686f73745f6c6962146765745f6c6564676572" + "5f6f626a5f6669656c64000008686f73745f6c69621b6765745f6c65646765725f6f626a5f" + "6e65737465645f6669656c64000308686f73745f6c6962186765745f6c65646765725f6f62" + "6a5f61727261795f6c656e000108686f73745f6c69621f6765745f6c65646765725f6f626a" + "5f6e65737465645f61727261795f6c656e000208686f73745f6c69621163726564656e7469" + "616c5f6b65796c6574000808686f73745f6c69620d657363726f775f6b65796c6574000308" + "686f73745f6c69620d6f7261636c655f6b65796c6574000308686f73745f6c696213636f6d" + "707574655f7368613531325f68616c66000008686f73745f6c6962076765745f6e66740009" + "08686f73745f6c69620b7570646174655f6461746100010306050a0b060604050301001106" + "19037f01418080c0000b7f0041af99c0000b7f0041b099c0000b072e04066d656d6f727902" + "000666696e697368001e0a5f5f646174615f656e6403010b5f5f686561705f626173650302" + "0ae61b057c01027f230041206b220124002000027f418180202001410c6a41141000220241" + "14470440024020024100480440200020023602040c010b2000417f3602040b41010c010b20" + "00200129000c370001200041116a2001411c6a280000360000200041096a200141146a2900" + "0037000041000b3a0000200141206a24000b450020012d0000450440200020012900013700" + "00200041106a200141116a280000360000200041086a200141096a2900003700000f0b4180" + "80c000410b20013402041001000b1900200241214f0440000b200020023602042000200136" + "02000b1900200241094f0440000b20002002360204200020013602000bec1901097f230041" + "b0036b22002400418b80c000411b41004100410010021a41a680c000411941004100410010" + "021a41e780c000412b41004100410010021a027f0240024002400240024002400240100322" + "0141004a0440419281c00041172001ad10011a1004220141004c0d0141a981c00041132001" + "ad10011a200041c8016a22034200370300200041c0016a22054200370300200041b8016a22" + "044200370300200042003703b001200041b0016a22064120100522014120470d0241bc81c0" + "00411320064120410110021a41cf81c000412041004100410010021a41dc82c000412e4100" + "4100410010021a200041a0016a410036020020004198016a42003703002000420037039001" + "4181802020004190016a22024114100022014114470d03418a83c00041142002411410061a" + "2000420037037041888018200041f0006a22024108100022014108470d04419e83c0004117" + "420810011a41b583c000412820024108410110021a2000410036025041848008200041d000" + "6a22024104100022014104470d0541dd83c000411520024104410110021a200041013b003c" + "200342003703002005420037030020044200370300200042003703b00102402000413c6a41" + "02200641201007220141004e044041f283c00041142001ad10011a200041306a2006200110" + "1c418684c000410d20002802302000280234410110021a0c010b419384c00041292001ac10" + "011a0b41bc84c00041154183803c1008ac10011a41d184c00041134189803c1008ac10011a" + "02402000413c6a41021009220141004e044041e484c00041142001ad10011a0c010b41f884" + "c000412d2001ac10011a0b41a585c000412341004100410010021a41de86c0004133410041" + "00410010021a2000420037037041828018200041f0006a22014108100a220241004c0d0620" + "024108460440419187c000412b420810011a41bc87c000412f20014108410110021a0c080b" + "41eb87c000412f2002ad10011a200041286a200041f0006a2002101d419a88c00041172000" + "280228200028022c410110021a0c070b41bf82c000411d2001ac10011a419b7f0c070b419a" + "82c00041252001ac10011a419a7f0c060b41ef81c000412b2001ac10011a41997f0c050b41" + "b486c000412a2001ac10011a41b77e0c040b41f385c00041c1002001ac10011a41b67e0c03" + "0b41c885c000412b2001ac10011a41b57e0c020b41b188c00041c5002002ac10011a0b2000" + "41a0016a410036020020004198016a42003703002000420037039001024041818020200041" + "90016a22024114100a220141004a044041f688c000411e2002411410061a0c010b419489c0" + "0041332001ac10011a0b200041013b0050200041c8016a4200370300200041c0016a420037" + "0300200041b8016a4200370300200042003703b0010240200041d0006a4102200041b0016a" + "22014120100b220241004e044041c789c000411c2002ad10011a200041206a20012002101c" + "41e389c000411520002802202000280224410110021a0c010b41f889c00041392002ac1001" + "1a0b41b18ac00041244183803c100cac10011a0240200041d0006a4102100d220141004e04" + "4041d58ac000411c2001ad10011a0c010b41f18ac000413d2001ac10011a0b41ae8bc00041" + "2841004100410010021a41d68bc000412f41004100410010021a200041b0016a2202101a20" + "0041f0006a22012002101b200041a8016a4200370300200041a0016a420037030020004198" + "016a42003703002000420037039001024002400240024002402001411420004190016a2202" + "4120100e22014120460440200241204100100f220441004a044041858cc00041232004ad10" + "011a20004200370350200441828018200041d0006a220141081010220241004c0d02200241" + "0846044041a88cc000412a420810011a41d28cc000412e20014108410110021a0c060b4180" + "8dc000412e2002ad10011a200041186a200041d0006a2002101d41ae8dc000411620002802" + "18200028021c410110021a0c050b41e68fc000413c2004ac10011a200041c8016a42003703" + "00200041c0016a4200370300200041b8016a4200370300200042003703b001410141828018" + "200041b0016a4120101022014100480d020c030b41ba92c000412e2001ac10011a41ef7c0c" + "050b41c48dc000412b2002ac10011a0c020b41a290c00041c1002001ac10011a0b20004101" + "3b00504101200041d0006a4102200041b0016a412010112201410048044041e390c0004135" + "2001ac10011a0b41014183803c101222014100480440419891c00041322001ac10011a0b41" + "01200041d0006a410210132201410048044041ca91c00041392001ac10011a0b418392c000" + "413741004100410010021a0c010b200041013b003c200041c8016a4200370300200041c001" + "6a4200370300200041b8016a4200370300200042003703b001024020042000413c6a410220" + "0041b0016a220141201011220241004e044041ef8dc000411b2002ad10011a200041106a20" + "012002101c418a8ec000411420002802102000280214410110021a0c010b419e8ec0004131" + "2002ac10011a0b41cf8ec000412320044183803c1012ac10011a024020042000413c6a4102" + "1013220141004e044041f28ec000411b2001ad10011a0c010b418d8fc00041352001ac1001" + "1a0b41c28fc000412441004100410010021a0b41e892c000412f41004100410010021a2000" + "41b0016a2201101a2000413c6a22042001101b200041e8006a4200370300200041e0006a42" + "00370300200041d8006a420037030020004200370350024002400240024002400240200441" + "14200041d0006a22024120100e22014120460440419793c000410f20024120410110021a20" + "004188016a420037030020004180016a4200370300200041f8006a42003703002000420037" + "03700240200441142004411441a693c0004109200041f0006a220141201014220241004a04" + "40200041086a20012002101c41ae93c00041122000280208200028020c410110021a0c010b" + "41c093c000413c2002ac10011a0b200041a8016a22084200370300200041a0016a22034200" + "37030020004198016a2205420037030020004200370390012000413c6a2202411441e80720" + "004190016a22074120101522014120470d0141fc93c000410e20074120410110021a200041" + "c8016a4200370300200041c0016a4200370300200041b8016a4200370300200042003703b0" + "0120024114412a200041b0016a22044120101622014120470d02418a94c000410e20044120" + "410110021a419894c000412441004100410010021a419195c000412541004100410010021a" + "20004188016a420037030020004180016a4200370300200041f8006a420037030020004200" + "37037041b695c0004117200041f0006a22024120101722014120470d0341cd95c000410b41" + "b695c0004117410110021a41d895c000411120024120410110021a2004101a200041d0006a" + "22062004101b20084200370300200342003703002005420037030020004200370390010240" + "200422032003410020036b41037122026a22054f0d0020020440200221010340200341003a" + "0000200341016a2103200141016b22010d000b0b200241016b4107490d000340200341003a" + "0000200341076a41003a0000200341066a41003a0000200341056a41003a0000200341046a" + "41003a0000200341036a41003a0000200341026a41003a0000200341016a41003a00002003" + "41086a22032005470d000b0b200541800220026b2201417c716a220320054b044003402005" + "4100360200200541046a22052003490d000b0b024020032001410371220120036a22024f0d" + "002001220504400340200341003a0000200341016a2103200541016b22050d000b0b200141" + "016b4107490d000340200341003a0000200341076a41003a0000200341066a41003a000020" + "0341056a41003a0000200341046a41003a0000200341036a41003a0000200341026a41003a" + "0000200341016a41003a0000200341086a22032002470d000b0b0240200641142007412020" + "044180021018220141004a044041e995c00041102001ad10011a20014181024f0d0641f995" + "c000410920042001410110021a0c010b418296c000412e2001ac10011a0b41b096c0004112" + "41c296c00041074101100222014100480d0541c996c000411d2001ad10011a41e696c00041" + "11422a10014100480d0641f796c000411c420010011a419397c000411a4100410041001002" + "1a41ff97c000412941004100410010021a41a898c000412810192201412846044041d098c0" + "00412741a898c0004128410110021a41f798c000411e41004100410010021a41bf80c00041" + "2841004100410010021a41010c080b419599c000411a2001ac10011a41c37a0c070b41f494" + "c000411d2001ac10011a418b7c0c060b41d894c000411c2001ac10011a41897c0c050b41bc" + "94c000411c2001ac10011a41887c0c040b41dd97c00041222001ac10011a41a77b0c030b00" + "0b41c797c00041162001ac10011a41a57b0c010b41ad97c000411a42a47b10011a41a47b0b" + "200041b0036a24000b0bb9190100418080c0000baf196572726f725f636f64653d3d3d3d20" + "484f53542046554e4354494f4e532054455354203d3d3d54657374696e6720323620686f73" + "742066756e6374696f6e73535543434553533a20416c6c20686f73742066756e6374696f6e" + "20746573747320706173736564212d2d2d2043617465676f727920313a204c656467657220" + "4865616465722046756e6374696f6e73202d2d2d4c65646765722073657175656e6365206e" + "756d6265723a506172656e74206c65646765722074696d653a506172656e74206c65646765" + "7220686173683a535543434553533a204c6564676572206865616465722066756e6374696f" + "6e734552524f523a206765745f706172656e745f6c65646765725f686173682077726f6e67" + "206c656e6774683a4552524f523a206765745f706172656e745f6c65646765725f74696d65" + "206661696c65643a4552524f523a206765745f6c65646765725f73716e206661696c65643a" + "2d2d2d2043617465676f727920323a205472616e73616374696f6e20446174612046756e63" + "74696f6e73202d2d2d5472616e73616374696f6e204163636f756e743a5472616e73616374" + "696f6e20466565206c656e6774683a5472616e73616374696f6e2046656520287365726961" + "6c697a65642058525020616d6f756e74293a5472616e73616374696f6e2053657175656e63" + "653a4e6573746564206669656c64206c656e6774683a4e6573746564206669656c643a494e" + "464f3a206765745f74785f6e65737465645f6669656c64206e6f74206170706c696361626c" + "653a5369676e657273206172726179206c656e6774683a4d656d6f73206172726179206c65" + "6e6774683a4e6573746564206172726179206c656e6774683a494e464f3a206765745f7478" + "5f6e65737465645f61727261795f6c656e206e6f74206170706c696361626c653a53554343" + "4553533a205472616e73616374696f6e20646174612066756e6374696f6e734552524f523a" + "206765745f74785f6669656c642853657175656e6365292077726f6e67206c656e6774683a" + "4552524f523a206765745f74785f6669656c6428466565292077726f6e67206c656e677468" + "20286578706563746564203820627974657320666f7220585250293a4552524f523a206765" + "745f74785f6669656c64284163636f756e74292077726f6e67206c656e6774683a2d2d2d20" + "43617465676f727920333a2043757272656e74204c6564676572204f626a6563742046756e" + "6374696f6e73202d2d2d43757272656e74206f626a6563742062616c616e6365206c656e67" + "7468202858525020616d6f756e74293a43757272656e74206f626a6563742062616c616e63" + "65202873657269616c697a65642058525020616d6f756e74293a43757272656e74206f626a" + "6563742062616c616e6365206c656e67746820286e6f6e2d58525020616d6f756e74293a43" + "757272656e74206f626a6563742062616c616e63653a494e464f3a206765745f6375727265" + "6e745f6c65646765725f6f626a5f6669656c642842616c616e636529206661696c65642028" + "6d6179206265206578706563746564293a43757272656e74206c6564676572206f626a6563" + "74206163636f756e743a494e464f3a206765745f63757272656e745f6c65646765725f6f62" + "6a5f6669656c64284163636f756e7429206661696c65643a43757272656e74206e65737465" + "64206669656c64206c656e6774683a43757272656e74206e6573746564206669656c643a49" + "4e464f3a206765745f63757272656e745f6c65646765725f6f626a5f6e65737465645f6669" + "656c64206e6f74206170706c696361626c653a43757272656e74206f626a65637420536967" + "6e657273206172726179206c656e6774683a43757272656e74206e65737465642061727261" + "79206c656e6774683a494e464f3a206765745f63757272656e745f6c65646765725f6f626a" + "5f6e65737465645f61727261795f6c656e206e6f74206170706c696361626c653a53554343" + "4553533a2043757272656e74206c6564676572206f626a6563742066756e6374696f6e732d" + "2d2d2043617465676f727920343a20416e79204c6564676572204f626a6563742046756e63" + "74696f6e73202d2d2d5375636365737366756c6c7920636163686564206f626a6563742069" + "6e20736c6f743a436163686564206f626a6563742062616c616e6365206c656e6774682028" + "58525020616d6f756e74293a436163686564206f626a6563742062616c616e636520287365" + "7269616c697a65642058525020616d6f756e74293a436163686564206f626a656374206261" + "6c616e6365206c656e67746820286e6f6e2d58525020616d6f756e74293a43616368656420" + "6f626a6563742062616c616e63653a494e464f3a206765745f6c65646765725f6f626a5f66" + "69656c642842616c616e636529206661696c65643a436163686564206e6573746564206669" + "656c64206c656e6774683a436163686564206e6573746564206669656c643a494e464f3a20" + "6765745f6c65646765725f6f626a5f6e65737465645f6669656c64206e6f74206170706c69" + "6361626c653a436163686564206f626a656374205369676e657273206172726179206c656e" + "6774683a436163686564206e6573746564206172726179206c656e6774683a494e464f3a20" + "6765745f6c65646765725f6f626a5f6e65737465645f61727261795f6c656e206e6f742061" + "70706c696361626c653a535543434553533a20416e79206c6564676572206f626a65637420" + "66756e6374696f6e73494e464f3a2063616368655f6c65646765725f6f626a206661696c65" + "642028657870656374656420776974682074657374206669787475726573293a494e464f3a" + "206765745f6c65646765725f6f626a5f6669656c64206661696c6564206173206578706563" + "74656420286e6f20636163686564206f626a656374293a494e464f3a206765745f6c656467" + "65725f6f626a5f6e65737465645f6669656c64206661696c65642061732065787065637465" + "643a494e464f3a206765745f6c65646765725f6f626a5f61727261795f6c656e206661696c" + "65642061732065787065637465643a494e464f3a206765745f6c65646765725f6f626a5f6e" + "65737465645f61727261795f6c656e206661696c65642061732065787065637465643a5355" + "43434553533a20416e79206c6564676572206f626a6563742066756e6374696f6e73202869" + "6e7465726661636520746573746564294552524f523a206163636f756e745f6b65796c6574" + "206661696c656420666f722063616368696e6720746573743a2d2d2d2043617465676f7279" + "20353a204b65796c65742047656e65726174696f6e2046756e6374696f6e73202d2d2d4163" + "636f756e74206b65796c65743a546573745479706543726564656e7469616c206b65796c65" + "743a494e464f3a2063726564656e7469616c5f6b65796c6574206661696c65642028657870" + "6563746564202d20696e74657266616365206973737565293a457363726f77206b65796c65" + "743a4f7261636c65206b65796c65743a535543434553533a204b65796c65742067656e6572" + "6174696f6e2066756e6374696f6e734552524f523a206f7261636c655f6b65796c65742066" + "61696c65643a4552524f523a20657363726f775f6b65796c6574206661696c65643a455252" + "4f523a206163636f756e745f6b65796c6574206661696c65643a2d2d2d2043617465676f72" + "7920363a205574696c6974792046756e6374696f6e73202d2d2d48656c6c6f2c205852504c" + "205741534d20776f726c6421496e70757420646174613a5348413531322068616c66206861" + "73683a4e46542064617461206c656e6774683a4e465420646174613a494e464f3a20676574" + "5f6e6674206661696c656420286578706563746564202d206e6f2073756368204e4654293a" + "54657374207472616365206d6573736167657061796c6f616454726163652066756e637469" + "6f6e206279746573207772697474656e3a54657374206e756d626572207472616365547261" + "63655f6e756d2066756e6374696f6e20737563636565646564535543434553533a20557469" + "6c6974792066756e6374696f6e734552524f523a2074726163655f6e756d2829206661696c" + "65643a4552524f523a2074726163652829206661696c65643a4552524f523a20636f6d7075" + "74655f7368613531325f68616c66206661696c65643a2d2d2d2043617465676f727920373a" + "2044617461205570646174652046756e6374696f6e73202d2d2d55706461746564206c6564" + "67657220656e74727920646174612066726f6d205741534d20746573745375636365737366" + "756c6c792075706461746564206c656467657220656e74727920776974683a535543434553" + "533a2044617461207570646174652066756e6374696f6e734552524f523a20757064617465" + "5f64617461206661696c65643a004d0970726f64756365727302086c616e67756167650104" + "52757374000c70726f6365737365642d6279010572757374631d312e38352e312028346562" + "31363132353020323032352d30332d313529002c0f7461726765745f666561747572657302" + "2b0f6d757461626c652d676c6f62616c732b087369676e2d657874"; + +extern std::string const deepRecursionHex = + "0061736d010000000105016000017f030201000608017f0141c0843d0b070a010666696e69" + "736800000a16011400230045044041010f0b230041016b240010000b"; + +extern std::string const hfPerfTest = + "0061736d0100000001190460057f7f7f7f7f017f60047f7f7f7f017f6000006000017f0236" + "0303656e7609666c6f61745f6c6f67000003656e76057472616365000003656e7612747261" + "63655f6f70617175655f666c6f617400010303020203050301000206aa011c7f0041b0090b" + "7f004193090b7f0041b0080b7f0041c0080b7f0041e0080b7f004180090b7f004180080b7f" + "004184080b7f004188080b7f00418c080b7f004190080b7f004194080b7f004198080b7f00" + "419c080b7f0041a0080b7f0041a4080b7f0041a8080b7f0041ac080b7f00419b090b7f0041" + "80080b7f0041b0110b7f0041b0110b7f0041b091040b7f004180080b7f0041b091040b7f00" + "418080080b7f0041000b7f0041010b0785031f066d656d6f72790200115f5f7761736d5f63" + "616c6c5f63746f727300030666696e697368000403627566030001610301086572725f6865" + "61640302096572725f6461746131030305696e707574030406726573756c74030508495445" + "525f4d4158030609484153485f53495a450307084143435f53495a4503080d43555252454e" + "43595f53495a4503090b4b45594c45545f53495a45030a0a53465f4163636f756e74030b0e" + "53465f44657374696e6174696f6e030c0853465f4d656d6f73030d0753465f4d656d6f030e" + "0b53465f4d656d6f44617461030f0753465f4461746103101753465f417574686f72697a65" + "43726564656e7469616c730311016203120c5f5f64736f5f68616e646c6503130a5f5f6461" + "74615f656e6403140b5f5f737461636b5f6c6f7703150c5f5f737461636b5f686967680316" + "0d5f5f676c6f62616c5f6261736503170b5f5f686561705f6261736503180a5f5f68656170" + "5f656e6403190d5f5f6d656d6f72795f62617365031a0c5f5f7461626c655f62617365031b" + "0a7c0202000b7701017f41807821000340200041b0116a4200370300200041086a22000d00" + "0b41c0843d210002400340419309410841b009418008410010004108460440200041016b22" + "000d010c020b0b41b008410f41c0084113410010011a0b41e0084111419309410810021a41" + "8009411241b009410810021a41010b0b9f0104004180080b53809698002000000014000000" + "1400000020000000010008000300080009000f000a000e000d0007001b0007001a000f0066" + "6c6f61745f6c6f67206572726f7200696e76616c69642072657475726e2073697a650041e0" + "080b11666c6f61745f6c6f6720696e7075743a20004180090b12666c6f61745f6c6f672072" + "6573756c743a20004193090b10d48b29430a256d21d920c49ba5e353f8007f0970726f6475" + "63657273010c70726f6365737365642d62790105636c616e675f31392e312e352d77617369" + "2d73646b202868747470733a2f2f6769746875622e636f6d2f6c6c766d2f6c6c766d2d7072" + "6f6a6563742061623462356132646235383239353861663165653330386137393063666462" + "3432626432343732302900490f7461726765745f6665617475726573042b0f6d757461626c" + "652d676c6f62616c732b087369676e2d6578742b0f7265666572656e63652d74797065732b" + "0a6d756c746976616c7565"; + +extern std::string const allKeyletsWasmHex = + "0061736d0100000001530a60057f7f7f7f7f017f60047f7f7f7f017f60067f7f7f7f7f7f01" + "7f60037f7f7f017f60087f7f7f7f7f7f7f7f017f60037f7f7e017f60077f7f7f7f7f7f7f01" + "7f60057f7f7f7f7f006000017f60037f7f7f000284051808686f73745f6c69620574726163" + "65000008686f73745f6c69621063616368655f6c65646765725f6f626a000308686f73745f" + "6c69620974726163655f6e756d000508686f73745f6c6962146765745f6c65646765725f6f" + "626a5f6669656c64000108686f73745f6c69621c6765745f63757272656e745f6c65646765" + "725f6f626a5f6669656c64000308686f73745f6c69620d74726163655f6163636f756e7400" + "0108686f73745f6c69620e6163636f756e745f6b65796c6574000108686f73745f6c69620b" + "6c696e655f6b65796c6574000408686f73745f6c69620a616d6d5f6b65796c657400020868" + "6f73745f6c69620c636865636b5f6b65796c6574000008686f73745f6c6962116372656465" + "6e7469616c5f6b65796c6574000408686f73745f6c69620f64656c65676174655f6b65796c" + "6574000208686f73745f6c6962166465706f7369745f707265617574685f6b65796c657400" + "0208686f73745f6c69620a6469645f6b65796c6574000108686f73745f6c69620d65736372" + "6f775f6b65796c6574000008686f73745f6c6962136d70745f69737375616e63655f6b6579" + "6c6574000008686f73745f6c69620e6d70746f6b656e5f6b65796c6574000208686f73745f" + "6c6962106e66745f6f666665725f6b65796c6574000008686f73745f6c69620c6f66666572" + "5f6b65796c6574000008686f73745f6c69620e7061796368616e5f6b65796c657400060868" + "6f73745f6c69621a7065726d697373696f6e65645f646f6d61696e5f6b65796c6574000008" + "686f73745f6c69620e7369676e6572735f6b65796c6574000108686f73745f6c69620d7469" + "636b65745f6b65796c6574000008686f73745f6c69620c7661756c745f6b65796c65740000" + "03040307080905030100110619037f01418080c0000b7f0041ad8ac0000b7f0041b08ac000" + "0b073e05066d656d6f727902000d6f626a6563745f65786973747300180666696e69736800" + "190a5f5f646174615f656e6403010b5f5f686561705f6261736503020ad53103fb0402017f" + "027e23004180016b2205240002400240024020012d0000450440200541186a200141196a29" + "0000370300200541106a200141116a290000370300200541086a200141096a290000370300" + "200520012900013703002002200320054120410110001a2005412041001001220141004c0d" + "01024002402004450440418b80c000410f4285801410021a200141858014200541e0006a41" + "20100322014120460d0141a680c0004115417f20012001417f4e1b2201ac10021a20004101" + "3a0000200020013602040c060b418b80c000410f2004ac10021a20012004200541e0006a41" + "14100322014114460d0141a680c0004115417f20012001417f4e1b2201ac10021a20004101" + "3a0000200020013602040c050b200541c2006a200541e2006a2d00003a0000200541286a20" + "0541ef006a2900002206370300200541306a200541f7006a2900002207370300200541386a" + "200541ff006a2d000022013a0000200541cf006a2006370000200541d7006a200737000020" + "0541df006a20013a0000200520052f00603b01402005200529006722063703202005200528" + "006336004320052006370047419a80c000410c200541406b4120410110001a0c030b200541" + "c2006a200541e2006a2d00003a0000200520052900673703202005200541ec006a29000037" + "0025200541cc006a2005290025370000200520052f00603b01402005200528006336004320" + "052005290320370047419a80c000410c200541406b4114410110001a0c020b41c280c00041" + "1620012802042201ac10021a200041013a0000200020013602040c020b41bb80c000410720" + "01ac10021a200041013a0000200020013602040c010b20004180023b01000b20054180016a" + "24000bd627020a7f027e23004180076b2200240041d880c000412341004100410010001a02" + "402000027f02404181802020004188016a22034114100422044114460440200041066a2000" + "418a016a22012d00003a00002000200029008f013703e001200020004194016a2202290000" + "3700e501200041106a20002900e501370000200020002f0088013b01042000200028008b01" + "360007200020002903e00137000b41fb80c0004108200041046a2205411410051a41838020" + "20034114100422044114470d032000411a6a20012d00003a00002000200029008f013703e0" + "01200020022900003700e501200041246a20002900e501370000200020002f0088013b0118" + "2000200028008b0136001b200020002903e00137001f418381c000410c200041186a411410" + "051a200041a0016a2201420037030020004198016a2202420037030020004190016a420037" + "030020004200370388012005411420034120100622044120460d0102402004410048044020" + "0020043602300c010b2000417f3602300b41010c020b0c020b200041c5006a200129030037" + "00002000413d6a2002290300370000200041356a20004190016a2903003700002000200029" + "03880137002d41000b3a002c20004188016a22032000412c6a418f81c00041074181802010" + "180240024020002d00880145044041002104419681c000413541004100410010001a200041" + "de006a41c4003a0000200041d8006a4100360200200041e3006a41003a0000200041d5a601" + "3b015c200042003703502000410036005f200041a0016a2201420037030020004198016a22" + "02420037030020004190016a220542003703002000420037038801200041046a4114200041" + "186a4114200041d0006a411420034120100722034120470440024020034100480440200020" + "033602680c010b2000417f3602680b410121040c020b200041fd006a200129030037000020" + "0041f5006a2002290300370000200041ed006a200529030037000020002000290388013700" + "650c010b200028028c01210441878ac0004112420510021a0c010b200020043a0064200041" + "88016a200041e4006a41cb81c000410941001018024020002d00880145044041d481c00041" + "3741004100410010001a200041f0016a200041286a2201280100360200200041e8016a2000" + "41206a2202290100370300200041fc016a200041d8006a290300220a37020020004184026a" + "200041e0006a2802002203360200200020002901183703e00120002000290350220b3702f4" + "01200041e8066a22042003360200200041e0066a2203200a3703002000200b3703d8062000" + "41f4066a2002290100370200200041fc066a2001280100360200200020002901183702ec06" + "20004188026a200041d8066a22014128101a2000418c016a200041e0016a41d000101a2000" + "410136028801200041f0066a22024200370300200442003703002003420037030020004200" + "3703d80641998ac0004114200041b4016a4128200141201008220141204704400240200141" + "00480440200020013602e4010c010b2000417f3602e4010b410121040c020b200041f9016a" + "2002290300370000200041f1016a2004290300370000200041e9016a200329030037000020" + "0020002903d8063700e101410021040c010b200028028c01210441878ac000411242051002" + "1a0c010b200020043a00e001200041b4026a200041e0016a2203418b82c000410341818020" + "1018024020002d00b40245044041002104418e82c000413141004100410010001a200041f8" + "016a22014200370300200041f0016a22024200370300200041e8016a220542003703002000" + "42003703e001200041046a4114410620034120100922034120470440024020034100480440" + "200020033602c0020c010b2000417f3602c0020b410121040c020b200041d5026a20012903" + "00370000200041cd026a2002290300370000200041c5026a20052903003700002000200029" + "03e0013700bd020c010b20002802b802210441878ac0004112420610021a0c010b20002004" + "3a00bc02200041e0016a2203200041bc026a41bf82c0004105418180201018024020002d00" + "e0014504404100210441c482c000413341004100410010001a200041f8016a220142003703" + "00200041f0016a22024200370300200041e8016a22054200370300200042003703e0012000" + "41046a220941142009411441f782c000411220034120100a22034120470440024020034100" + "480440200020033602e4020c010b2000417f3602e4020b410121040c020b200041f9026a20" + "01290300370000200041f1026a2002290300370000200041e9026a20052903003700002000" + "20002903e0013700e1020c010b20002802e401210441878ac0004112420610021a0c010b20" + "0020043a00e002200041e0016a2203200041e0026a418983c000410a419880201018024020" + "002d00e00145044041002104419383c000413841004100410010001a200041f8016a220142" + "00370300200041f0016a22024200370300200041e8016a22054200370300200042003703e0" + "01200041046a4114200041186a411420034120100b22034120470440024020034100480440" + "20002003360288030c010b2000417f360288030b410121040c020b2000419d036a20012903" + "0037000020004195036a20022903003700002000418d036a20052903003700002000200029" + "03e001370085030c010b20002802e401210441878ac0004112420710021a0c010b20002004" + "3a008403200041e0016a220520004184036a41cb83c0004108418180201018024002400240" + "0240024002400240024002400240024020002d00e00145044041d383c00041364100410041" + "0010001a230041206b22032400200341186a22064200370300200341106a22074200370300" + "200341086a2208420037030020034200370300200041a8036a2201027f200041046a220441" + "14200041186a2209411420034120100c220241204704400240200241004804402001200236" + "02040c010b2001417f3602040b41010c010b20012003290300370001200141196a20062903" + "00370000200141116a2007290300370000200141096a200829030037000041000b3a000020" + "0341206a240020052001418984c000410e41818020101820002d00e0010d01419784c00041" + "3c41004100410010001a230041206b22032400200341186a22064200370300200341106a22" + "074200370300200341086a2208420037030020034200370300200041cc036a2201027f2004" + "411420034120100d22024120470440024020024100480440200120023602040c010b200141" + "7f3602040b41010c010b20012003290300370001200141196a200629030037000020014111" + "6a2007290300370000200141096a200829030037000041000b3a0000200341206a24002005" + "200141d384c000410341818020101820002d00e0010d0241d684c000413141004100410010" + "001a230041206b22032400200341186a22064200370300200341106a220742003703002003" + "41086a2208420037030020034200370300200041f0036a2201027f20044114410b20034120" + "100e22024120470440024020024100480440200120023602040c010b2001417f3602040b41" + "010c010b20012003290300370001200141196a2006290300370000200141116a2007290300" + "370000200141096a200829030037000041000b3a0000200341206a240020052001418785c0" + "00410641818020101820002d00e0010d03418d85c000413441004100410010001a23004120" + "6b22032400200341186a22064200370300200341106a22074200370300200341086a220842" + "003703002003420037030020004194046a2201027f20044114410c20034120100f22024120" + "470440024020024100480440200120023602040c010b2001417f3602040b41010c010b2001" + "2003290300370001200141196a2006290300370000200141116a2007290300370000200141" + "096a200829030037000041000b3a0000200341206a2400200041f4016a200041146a280100" + "360200200041ec016a2000410c6a290100370200200020002901043702e401200041808080" + "e0003602e001200041d8066a2203200141c185c000410b41848020101820002d00d8060d04" + "41cc85c000413941004100410010001a230041206b22012400200141186a22064200370300" + "200141106a22074200370300200141086a2208420037030020014200370300200041b8046a" + "2202027f200541182009411420014120101022054120470440024020054100480440200220" + "053602040c010b2002417f3602040b41010c010b20022001290300370001200241196a2006" + "290300370000200241116a2007290300370000200241096a200829030037000041000b3a00" + "00200141206a240020032002418586c000410741818020101820002d00d8060d05418c86c0" + "00413541004100410010001a230041206b22012400200141186a2206420037030020014110" + "6a22074200370300200141086a2208420037030020014200370300200041dc046a2202027f" + "20094114410620014120101122054120470440024020054100480440200220053602040c01" + "0b2002417f3602040b41010c010b20022001290300370001200241196a2006290300370000" + "200241116a2007290300370000200241096a200829030037000041000b3a0000200141206a" + "24002003200241c186c000410c41828020101820002d00d8060d0641cd86c000413a410041" + "00410010001a230041206b22012400200141186a22064200370300200141106a2207420037" + "0300200141086a220842003703002001420037030020004180056a2202027f20044114410d" + "20014120101222054120470440024020054100480440200220053602040c010b2002417f36" + "02040b41010c010b20022001290300370001200241196a2006290300370000200241116a20" + "07290300370000200241096a200829030037000041000b3a0000200141206a240020032002" + "418787c000410541818020101820002d00d8060d07418c87c000413341004100410010001a" + "230041206b22012400200141186a22064200370300200141106a2207420037030020014108" + "6a2208420037030020014200370300200041a4056a2202027f2004411420094114410e2001" + "4120101322054120470440024020054100480440200220053602040c010b2002417f360204" + "0b41010c010b20022001290300370001200241196a2006290300370000200241116a200729" + "0300370000200241096a200829030037000041000b3a0000200141206a24002003200241bf" + "87c000410a41818020101820002d00d8060d0841c987c000413841004100410010001a2300" + "41206b22012400200141186a22094200370300200141106a22064200370300200141086a22" + "07420037030020014200370300200041c8056a2202027f20044114410f2001412010142205" + "4120470440024020054100480440200220053602040c010b2002417f3602040b41010c010b" + "20022001290300370001200241196a2009290300370000200241116a200629030037000020" + "0241096a200729030037000041000b3a0000200141206a240020032002418188c000411241" + "828020101820002d00d8060d09419388c00041c00041004100410010001a230041206b2201" + "2400200141186a22094200370300200141106a22064200370300200141086a220742003703" + "0020014200370300200041ec056a2202027f20044114200141201015220541204704400240" + "20054100480440200220053602040c010b2002417f3602040b41010c010b20022001290300" + "370001200241196a2009290300370000200241116a2006290300370000200241096a200729" + "030037000041000b3a0000200141206a24002003200241d388c000410a4100101820002d00" + "d8060d0a41dd88c000413841004100410010001a230041206b22012400200141186a220942" + "00370300200141106a22064200370300200141086a22074200370300200142003703002000" + "4190066a2202027f2004411441122001412010162205412047044002402005410048044020" + "0220053602040c010b2002417f3602040b41010c010b20022001290300370001200241196a" + "2009290300370000200241116a2006290300370000200241096a200729030037000041000b" + "3a0000200141206a240020032002419589c000410641818020101820002d00d8060d0b419b" + "89c000413441004100410010001a230041206b22012400200141186a220542003703002001" + "41106a22094200370300200141086a2206420037030020014200370300200041b4066a2202" + "027f2004411441132001412010172204412047044002402004410048044020022004360204" + "0c010b2002417f3602040b41010c010b20022001290300370001200241196a200529030037" + "0000200241116a2009290300370000200241096a200629030037000041000b3a0000200141" + "206a24002003200241cf89c000410541818020101820002d00d80645044041d489c0004133" + "41004100410010001a410121040c0d0b20002802dc06210441878ac0004112421310021a0c" + "0c0b20002802e401210441878ac0004112420810021a0c0b0b20002802e401210441878ac0" + "004112420910021a0c0a0b20002802e401210441878ac0004112420a10021a0c090b200028" + "02e401210441878ac0004112420b10021a0c080b20002802dc06210441878ac0004112420c" + "10021a0c070b20002802dc06210441878ac0004112420d10021a0c060b20002802dc062104" + "41878ac0004112420d10021a0c050b20002802dc06210441878ac0004112420d10021a0c04" + "0b20002802dc06210441878ac0004112420e10021a0c030b20002802dc06210441878ac000" + "4112420f10021a0c020b20002802dc06210441878ac0004112421010021a0c010b20002802" + "dc06210441878ac0004112421210021a0b20004180076a240020040f0b418080c000410b41" + "7f20042004417f4e1bac1002000bfd0401067f200241104f044002402000410020006b4103" + "7122056a220420004d0d002001210320050440200521060340200020032d00003a00002003" + "41016a2103200041016a2100200641016b22060d000b0b200541016b4107490d0003402000" + "20032d00003a0000200041016a200341016a2d00003a0000200041026a200341026a2d0000" + "3a0000200041036a200341036a2d00003a0000200041046a200341046a2d00003a00002000" + "41056a200341056a2d00003a0000200041066a200341066a2d00003a0000200041076a2003" + "41076a2d00003a0000200341086a2103200041086a22002004470d000b0b2004200220056b" + "2207417c7122086a21000240200120056a2206410371450440200020044d0d012006210103" + "4020042001280200360200200141046a2101200441046a22042000490d000b0c010b200020" + "044d0d002006410374220541187121032006417c71220241046a2101410020056b41187121" + "05200228020021020340200420022003762001280200220220057472360200200141046a21" + "01200441046a22042000490d000b0b20074103712102200620086a21010b02402000200020" + "026a22064f0d002002410771220304400340200020012d00003a0000200141016a21012000" + "41016a2100200341016b22030d000b0b200241016b4107490d000340200020012d00003a00" + "00200041016a200141016a2d00003a0000200041026a200141026a2d00003a000020004103" + "6a200141036a2d00003a0000200041046a200141046a2d00003a0000200041056a20014105" + "6a2d00003a0000200041066a200141066a2d00003a0000200041076a200141076a2d00003a" + "0000200141086a2101200041086a22002006470d000b0b0b0ba30a0100418080c0000b990a" + "6572726f725f636f64653d47657474696e67206669656c643a204669656c6420646174613a" + "204572726f722067657474696e67206669656c643a204572726f723a204572726f72206765" + "7474696e67206b65796c65743a202424242424205354415254494e47205741534d20455845" + "435554494f4e2024242424244163636f756e743a44657374696e6174696f6e3a4163636f75" + "6e744163636f756e74206f626a656374206578697374732c2070726f63656564696e672077" + "69746820657363726f772066696e6973682e54727573746c696e6554727573746c696e6520" + "6f626a656374206578697374732c2070726f63656564696e67207769746820657363726f77" + "2066696e6973682e414d4d414d4d206f626a656374206578697374732c2070726f63656564" + "696e67207769746820657363726f772066696e6973682e436865636b436865636b206f626a" + "656374206578697374732c2070726f63656564696e67207769746820657363726f77206669" + "6e6973682e7465726d73616e64636f6e646974696f6e7343726564656e7469616c43726564" + "656e7469616c206f626a656374206578697374732c2070726f63656564696e672077697468" + "20657363726f772066696e6973682e44656c656761746544656c6567617465206f626a6563" + "74206578697374732c2070726f63656564696e67207769746820657363726f772066696e69" + "73682e4465706f736974507265617574684465706f73697450726561757468206f626a6563" + "74206578697374732c2070726f63656564696e67207769746820657363726f772066696e69" + "73682e444944444944206f626a656374206578697374732c2070726f63656564696e672077" + "69746820657363726f772066696e6973682e457363726f77457363726f77206f626a656374" + "206578697374732c2070726f63656564696e67207769746820657363726f772066696e6973" + "682e4d505449737375616e63654d505449737375616e6365206f626a656374206578697374" + "732c2070726f63656564696e67207769746820657363726f772066696e6973682e4d50546f" + "6b656e4d50546f6b656e206f626a656374206578697374732c2070726f63656564696e6720" + "7769746820657363726f772066696e6973682e4e46546f6b656e4f666665724e46546f6b65" + "6e4f66666572206f626a656374206578697374732c2070726f63656564696e672077697468" + "20657363726f772066696e6973682e4f666665724f66666572206f626a6563742065786973" + "74732c2070726f63656564696e67207769746820657363726f772066696e6973682e506179" + "4368616e6e656c5061794368616e6e656c206f626a656374206578697374732c2070726f63" + "656564696e67207769746820657363726f772066696e6973682e5065726d697373696f6e65" + "64446f6d61696e5065726d697373696f6e6564446f6d61696e206f626a6563742065786973" + "74732c2070726f63656564696e67207769746820657363726f772066696e6973682e536967" + "6e65724c6973745369676e65724c697374206f626a656374206578697374732c2070726f63" + "656564696e67207769746820657363726f772066696e6973682e5469636b65745469636b65" + "74206f626a656374206578697374732c2070726f63656564696e6720776974682065736372" + "6f772066696e6973682e5661756c745661756c74206f626a656374206578697374732c2070" + "726f63656564696e67207769746820657363726f772066696e6973682e43757272656e7420" + "7365712076616c75653a004d0970726f64756365727302086c616e67756167650104527573" + "74000c70726f6365737365642d6279010572757374631d312e38352e312028346562313631" + "32353020323032352d30332d313529002c0f7461726765745f6665617475726573022b0f6d" + "757461626c652d676c6f62616c732b087369676e2d657874"; + +extern std::string const codecovTestsWasmHex = + "0061736d0100000001570b60047f7f7f7f017f60057f7f7f7f7f017f60027f7f017f60067f" + "7f7f7f7f7f017f60077f7f7f7f7f7f7f017f6000017f60037f7f7f017f60017f017f60087f" + "7f7f7f7f7f7f7f017f60037f7f7e017f60047f7f7f7f0002990d3c08686f73745f6c696205" + "7472616365000108686f73745f6c69620974726163655f6e756d000908686f73745f6c6962" + "0e6765745f6c65646765725f73716e000508686f73745f6c6962166765745f706172656e74" + "5f6c65646765725f74696d65000508686f73745f6c6962166765745f706172656e745f6c65" + "646765725f68617368000208686f73745f6c69620c6765745f626173655f66656500050868" + "6f73745f6c696211616d656e646d656e745f656e61626c6564000208686f73745f6c69620c" + "6765745f74785f6669656c64000608686f73745f6c69620e6163636f756e745f6b65796c65" + "74000008686f73745f6c69621063616368655f6c65646765725f6f626a000608686f73745f" + "6c69621c6765745f63757272656e745f6c65646765725f6f626a5f6669656c64000608686f" + "73745f6c6962146765745f6c65646765725f6f626a5f6669656c64000008686f73745f6c69" + "62136765745f74785f6e65737465645f6669656c64000008686f73745f6c6962236765745f" + "63757272656e745f6c65646765725f6f626a5f6e65737465645f6669656c64000008686f73" + "745f6c69621b6765745f6c65646765725f6f626a5f6e65737465645f6669656c6400010868" + "6f73745f6c6962106765745f74785f61727261795f6c656e000708686f73745f6c69622067" + "65745f63757272656e745f6c65646765725f6f626a5f61727261795f6c656e000708686f73" + "745f6c6962186765745f6c65646765725f6f626a5f61727261795f6c656e000208686f7374" + "5f6c6962176765745f74785f6e65737465645f61727261795f6c656e000208686f73745f6c" + "6962276765745f63757272656e745f6c65646765725f6f626a5f6e65737465645f61727261" + "795f6c656e000208686f73745f6c69621f6765745f6c65646765725f6f626a5f6e65737465" + "645f61727261795f6c656e000608686f73745f6c69620b7570646174655f64617461000208" + "686f73745f6c696213636f6d707574655f7368613531325f68616c66000008686f73745f6c" + "696209636865636b5f736967000308686f73745f6c6962076765745f6e6674000308686f73" + "745f6c69620e6765745f6e66745f697373756572000008686f73745f6c69620d6765745f6e" + "66745f7461786f6e000008686f73745f6c69620d6765745f6e66745f666c61677300020868" + "6f73745f6c6962146765745f6e66745f7472616e736665725f666565000208686f73745f6c" + "69620e6765745f6e66745f73657269616c000008686f73745f6c69620d74726163655f6163" + "636f756e74000008686f73745f6c69620c74726163655f616d6f756e74000008686f73745f" + "6c69620f666c6f61745f66726f6d5f75696e74000108686f73745f6c69620b6c696e655f6b" + "65796c6574000808686f73745f6c69620a616d6d5f6b65796c6574000308686f73745f6c69" + "621163726564656e7469616c5f6b65796c6574000808686f73745f6c69620e6d70746f6b65" + "6e5f6b65796c6574000308686f73745f6c69621274726163655f6f70617175655f666c6f61" + "74000008686f73745f6c69620d666c6f61745f636f6d70617265000008686f73745f6c6962" + "09666c6f61745f616464000408686f73745f6c69620e666c6f61745f737562747261637400" + "0408686f73745f6c69620e666c6f61745f6d756c7469706c79000408686f73745f6c69620c" + "666c6f61745f646976696465000408686f73745f6c69620a666c6f61745f726f6f74000308" + "686f73745f6c696209666c6f61745f706f77000308686f73745f6c696209666c6f61745f6c" + "6f67000108686f73745f6c69620c636865636b5f6b65796c6574000108686f73745f6c6962" + "0f64656c65676174655f6b65796c6574000308686f73745f6c6962166465706f7369745f70" + "7265617574685f6b65796c6574000308686f73745f6c69620a6469645f6b65796c65740000" + "08686f73745f6c69620d657363726f775f6b65796c6574000108686f73745f6c6962136d70" + "745f69737375616e63655f6b65796c6574000108686f73745f6c6962106e66745f6f666665" + "725f6b65796c6574000108686f73745f6c69620c6f666665725f6b65796c6574000108686f" + "73745f6c69620d6f7261636c655f6b65796c6574000108686f73745f6c69620e7061796368" + "616e5f6b65796c6574000408686f73745f6c69621a7065726d697373696f6e65645f646f6d" + "61696e5f6b65796c6574000108686f73745f6c69620e7369676e6572735f6b65796c657400" + "0008686f73745f6c69620d7469636b65745f6b65796c6574000108686f73745f6c69620c76" + "61756c745f6b65796c657400010303020a0505030100110619037f01418080c0000b7f0041" + "a99ec0000b7f0041b09ec0000b072e04066d656d6f727902000666696e697368003d0a5f5f" + "646174615f656e6403010b5f5f686561705f6261736503020a9e2702460002402000200147" + "04402002200341004100410010001a20004100480d01418b80c000410b2000ad1001000b20" + "0220032000ac10011a0f0b418b80c000410b2000ac1001000bd426020a7f017e230041f001" + "6b22002400419680c000412341004100410010001a100241b9e00041b980c000410e103c10" + "0341b2920441c780c0004116103c200041f0006a22044200370300200041e8006a22064200" + "370300200041e0006a2203420037030020004200370358200041d8006a2201412010044120" + "41d88cc0004116103c1005410a41dd80c000410c103c200041186a2207428182848890a0c0" + "8001370300200041106a2208428182848890a0c08001370300200041086a22094281828488" + "90a0c080013703002000428182848890a0c0800137030041e980c000410e1006410141f780" + "c0004111103c200041201006410141f780c0004111103c4181802020014114100722024114" + "4604400240200041266a200041da006a2d00003a00002000200029005f3703c80120002000" + "41e4006a2900003700cd01200041306a20002900cd01370000200020002f00583b01242000" + "200028005b360027200020002903c80137002b200442003703002006420037030020034200" + "37030020004200370358200041246a2204411420014120100822024120470d002000413a6a" + "20002d005a3a0000200041d0016a2202200041e7006a290000220a370300200041c7006a20" + "0a370000200041cf006a200041ef006a290000370000200041d7006a200041f7006a2d0000" + "3a0000200020002f01583b01382000200028005b36003b2000200029005f37003f20004138" + "6a4120410010094101418881c0004110103c20064100360200200342003703002000420037" + "03584181802020014114100a411441ee8cc000411c103c2006410036020020034200370300" + "2000420037035841014181802020014114100b4114418a8dc0004114103c20004104360298" + "01200041818020360258200041d8016a2203410036020020024200370300200042003703c8" + "0120014104200041c8016a22064114100c4114419e8dc0004113103c200341003602002002" + "4200370300200042003703c801200120002802980120064114100d411441b18dc000412310" + "3c2003410036020020024200370300200042003703c8014101200120002802980120064114" + "100e411441d48dc000411b103c4189803c100f4120419881c0004110103c4189803c101041" + "2041a881c0004120103c41014189803c1011412041c881c0004118103c2001200028029801" + "1012412041e081c0004117103c20012000280298011013412041f781c0004127103c410120" + "0120002802980110144120419e82c000411f103c200441141015411441bd82c000410b103c" + "200041e0016a220542003703002003420037030020024200370300200042003703c8012001" + "200028029801200641201016412041ef8dc0004113103c41c882c000410c41d482c000410b" + "41df82c000410e1017410141ed82c0004109103c200041b8016a2007290300370300200041" + "b0016a2008290300370300200041a8016a2009290300370300200020002903003703a00120" + "0341003b010020024200370300200042003703c80120044114200041a0016a220741202006" + "41121018411241828ec0004107103c2003410036020020024200370300200042003703c801" + "20074120200641141019411441898ec000410e103c200041003602c8012007412020064104" + "101a410441978ec000410d103c20074120101b410841f682c000410d103c20074120101c41" + "0a418383c0004114103c200041003602c8012007412020064104101d410441a48ec000410e" + "103c419783c000410d20044114101e412f41a483c000410d103c419783c000410d41b183c0" + "004108101f411341b983c000410c103c419783c000410d41c583c0004108101f411241cd83" + "c0004111103c417f41041004417141de83c000411e103c200041003602c8012006417f1004" + "417141b28ec000411e103c200041ca016a41003a0000200041003b01c80120064103100441" + "7d41d08ec0004124103c200041003602c8012006418094ebdc031004417341f48ec0004123" + "103c419783c000410d20044114101e412f41a483c000410d103c419783c000410d41b183c0" + "004108101f411341b983c000410c103c200542003703002003420037030020024200370300" + "200042003703c801200041d894ebdc036a220741082006412041001020417341978fc00041" + "17103c200542003703002003420037030020024200370300200042003703c8012001200028" + "0298012006412041001020417141ae8fc0004119103c4102100f416f41fc83c000411f103c" + "417f20002802980110124171419b84c000411f103c2001417f1012417141ba84c000411f10" + "3c20014181201012417441d984c0004120103c20072000280298011012417341f984c00041" + "1f103c2007200028029801410110094173419885c0004118103c2001200028029801410110" + "09417141b085c000411a103c20054200370300200342003703002002420037030020004200" + "3703c8012007200028029801200641201008417341c78fc0004116103c2005420037030020" + "03420037030020024200370300200042003703c80120012000280298012006412010084171" + "41dd8fc0004118103c200542003703002003420037030020024200370300200042003703c8" + "0120044114200441142007200028029801200641201021417341f58fc000411c103c200542" + "003703002003420037030020024200370300200042003703c8012004411420044114200120" + "00280298012006412010214171419190c000411e103c200542003703002003420037030020" + "024200370300200042003703c80141959ec000411420072000280298012006412010224173" + "41af90c0004119103c200542003703002003420037030020024200370300200042003703c8" + "0141959ec00041142001200028029801200641201022417141c890c000411f103c20054200" + "3703002003420037030020024200370300200042003703c80141959ec000411441ca85c000" + "4114200641201022417141e790c0004129103c200542003703002003420037030020024200" + "370300200042003703c80141de85c000412841959ec00041142006412010224171419091c0" + "004125103c200041dc016a200041346a280100360200200041d4016a2000412c6a29010037" + "0200200020002901243702cc01200041808080083602c801200041003b01c0012006411841" + "959ec0004114200041c0016a220341021022417141b591c000410e103c2007200028029801" + "422a10014173418686c0004111103c200041003b01c0014102200341021007416f41c391c0" + "00411b103c200041003b01c001410220034102100a416f41de91c000412b103c200041003b" + "01c0014101410220034102100b416f418992c0004123103c4102100f416f41fc83c000411f" + "103c41021010416f419786c000412f103c410141021011416f41c686c0004127103c41e980" + "c0004181201006417441ed86c000411f103c41e980c00041c10010064174418c87c000411a" + "103c200041003b01c001200141812020034102100c417441ac92c0004121103c200041003b" + "01c001200141812020034102100d417441cd92c0004131103c200041003b01c00141012001" + "41812020034102100e417441fe92c0004129103c20014181201012417441a687c000412510" + "3c20014181201013417441cb87c0004135103c4101200141812010144174418088c000412d" + "103c20014181201015417441ad88c0004119103c419783c00041812041d482c000410b41df" + "82c000410e1017417441ed82c0004109103c419783c000410d41d482c00041812041df82c0" + "00410e1017417441ed82c0004109103c419783c000410d41d482c000410b41df82c0004181" + "201017417441ed82c0004109103c200041003b01c0012001418120200341021016417441a7" + "93c0004121103c200041003b01c00141959ec00041812041959ec000411420034102102241" + "7441c893c0004118103c200041003b01c00120044114200441142001418120200341021023" + "417441e093c000411f103c200041003b01c001200641812020044114200341021024417441" + "ff93c0004122103c419783c000410d200720002802980141001000417341c688c000410f10" + "3c200042d487b6f4c7d4b1c0003700c001419783c000410d200041c095ebdc036a22054108" + "1025417341d588c000411c103c419783c000410d2007200028029801101f417341f188c000" + "4116103c200541082003410810264173418789c0004118103c200341082005410810264173" + "419f89c0004118103c200041003b01ec012005410820034108200041ec016a220241024100" + "1027417341a194c0004114103c200041003b01ec0120034108200541082002410241001027" + "417341b594c0004114103c200041003b01ec01200541082003410820024102410010284173" + "41c994c0004119103c200041003b01ec0120034108200541082002410241001028417341e2" + "94c0004119103c200041003b01ec0120054108200341082002410241001029417341fb94c0" + "004119103c200041003b01ec01200341082005410820024102410010294173419495c00041" + "19103c200041003b01ec012005410820034108200241024100102a417341ad95c000411710" + "3c200041003b01ec012003410820054108200241024100102a417341c495c0004117103c20" + "0041003b01ec01200541084103200241024100102b417341db95c0004114103c200041003b" + "01ec01200541084103200241024100102c417341ef95c0004113103c200041003b01ec0120" + "054108200241024100102d4173418296c0004113103c200120002802980141001009417141" + "b789c0004123103c200041003b01ec01200441142001200028029801200241021018417141" + "9596c000411a103c200041003b01ec012001200028029801200241021019417141af96c000" + "4121103c200041003b01ec01200120002802980120024102101a417141d096c0004120103c" + "2001200028029801101b417141da89c0004120103c2001200028029801101c417141fa89c0" + "004127103c200041003602ec01200120002802980120024104101d417141f096c000412110" + "3c200041003b01ec0120012000280298012002410210084171419197c0004123103c200041" + "003b01ec012001200028029801410120024102102e417141b497c0004121103c200041003b" + "01ec01200120002802980122052004411420012005200241021023417141d597c000412710" + "3c200041003b01ec01200441142001200028029801220520012005200241021023417141fc" + "97c0004127103c200041003b01ec0120012000280298012004411420024102102f417141a3" + "98c0004125103c200041003b01ec0120044114200120002802980120024102102f417141c8" + "98c0004125103c200041003b01ec01200120002802980120044114200241021030417141ed" + "98c000412c103c200041003b01ec0120044114200120002802980120024102103041714199" + "99c000412c103c200041003b01ec012001200028029801200241021031417141c599c00041" + "1f103c200041003b01ec0120012000280298014101200241021032417141e499c000412210" + "3c200041003b01ec0120012000280298012004411441ca85c0004114200241021021417141" + "869ac0004121103c200041003b01ec0120044114200120002802980141ca85c00041142002" + "41021021417141a79ac0004121103c200041003b01ec012001200028029801410120024102" + "1033417141c89ac0004128103c200041003b01ec0120064118200120002802980120024102" + "1024417141f09ac0004123103c200041003b01ec0120012000280298014101200241021034" + "417141939bc0004125103c200041003b01ec01200120002802980141012002410210354171" + "41b89bc0004121103c200041003b01ec0120012000280298014101200241021036417141d9" + "9bc0004122103c200041003b01ec0120012000280298012004411441012002410210374171" + "41fb9bc0004124103c200041003b01ec012004411420012000280298014101200241021037" + "4171419f9cc0004124103c200041003b01ec01200120002802980141012002410210384171" + "41c39cc000412f103c200041003b01ec012001200028029801200241021039417141f29cc0" + "004123103c200041003b01ec012001200028029801410120024102103a417141959dc00041" + "22103c200041003b01ec012001200028029801410120024102103b417141b79dc000412110" + "3c200041003b01ec01200120002802980141a18ac0004120200241021018417141d89dc000" + "411c103c419783c000410d2001200028029801101e417141c18ac0004122103c419797abdd" + "03410d41a18ac000412041001000417341e38ac0004110103c419797abdd03410d20034108" + "1025417341f38ac000411d103c419797abdd03410d20044114101e417341908bc000411810" + "3c419797abdd03410d41b183c0004108101f417341a88bc0004117103c2001200028029801" + "200141812041001000417441bf8bc000410e103c200141812042011001417441cd8bc00041" + "12103c419783c000418120200341081025417441df8bc000411b103c419783c00041812020" + "044114101e417441fa8bc0004116103c419783c00041812041b183c0004108101f41744190" + "8cc0004115103c419783c000410d2001200028029801101f417141a58cc0004119103c2000" + "41003b01ec01200120002802980120044114200241021024417141f49dc0004121103c4101" + "410020044114101e412241be8cc000411a103c200041f0016a240041010f0b0b418080c000" + "410b417f20022002417f4e1bac1001000b0b801e0200418080c0000bde056572726f725f63" + "6f64653d54455354204641494c45442424242424205354415254494e47205741534d204558" + "45435554494f4e2024242424246765745f6c65646765725f73716e6765745f706172656e74" + "5f6c65646765725f74696d656765745f626173655f666565746573745f616d656e646d656e" + "74616d656e646d656e745f656e61626c656463616368655f6c65646765725f6f626a676574" + "5f74785f61727261795f6c656e6765745f63757272656e745f6c65646765725f6f626a5f61" + "727261795f6c656e6765745f6c65646765725f6f626a5f61727261795f6c656e6765745f74" + "785f6e65737465645f61727261795f6c656e6765745f63757272656e745f6c65646765725f" + "6f626a5f6e65737465645f61727261795f6c656e6765745f6c65646765725f6f626a5f6e65" + "737465645f61727261795f6c656e7570646174655f6461746174657374206d657373616765" + "74657374207075626b657974657374207369676e6174757265636865636b5f736967676574" + "5f6e66745f666c6167736765745f6e66745f7472616e736665725f66656574657374696e67" + "20747261636574726163655f6163636f756e74400000000000005f74726163655f616d6f75" + "6e74400000000000000074726163655f616d6f756e745f7a65726f6765745f706172656e74" + "5f6c65646765725f686173685f6e65675f7074726765745f74785f61727261795f6c656e5f" + "696e76616c69645f736669656c646765745f74785f6e65737465645f61727261795f6c656e" + "5f6e65675f7074726765745f74785f6e65737465645f61727261795f6c656e5f6e65675f6c" + "656e6765745f74785f6e65737465645f61727261795f6c656e5f746f6f5f6c6f6e67676574" + "5f74785f6e65737465645f61727261795f6c656e5f7074725f6f6f6263616368655f6c6564" + "6765725f6f626a5f7074725f6f6f6263616368655f6c65646765725f6f626a5f77726f6e67" + "5f6c656e555344303030303030303030303030303030303000418686c0000b8f1874726163" + "655f6e756d5f6f6f625f7374726765745f63757272656e745f6c65646765725f6f626a5f61" + "727261795f6c656e5f696e76616c69645f736669656c646765745f6c65646765725f6f626a" + "5f61727261795f6c656e5f696e76616c69645f736669656c64616d656e646d656e745f656e" + "61626c65645f746f6f5f6269675f736c696365616d656e646d656e745f656e61626c65645f" + "746f6f5f6c6f6e676765745f74785f6e65737465645f61727261795f6c656e5f746f6f5f62" + "69675f736c6963656765745f63757272656e745f6c65646765725f6f626a5f6e6573746564" + "5f61727261795f6c656e5f746f6f5f6269675f736c6963656765745f6c65646765725f6f62" + "6a5f6e65737465645f61727261795f6c656e5f746f6f5f6269675f736c6963657570646174" + "655f646174615f746f6f5f6269675f736c69636574726163655f6f6f625f736c6963657472" + "6163655f6f70617175655f666c6f61745f6f6f625f736c69636574726163655f616d6f756e" + "745f6f6f625f736c696365666c6f61745f636f6d706172655f6f6f625f736c69636531666c" + "6f61745f636f6d706172655f6f6f625f736c6963653263616368655f6c65646765725f6f62" + "6a5f77726f6e675f73697a655f75696e743235366765745f6e66745f666c6167735f77726f" + "6e675f73697a655f75696e743235366765745f6e66745f7472616e736665725f6665655f77" + "726f6e675f73697a655f75696e743235363030303030303030303030303030303030303030" + "30303030303030303030303174726163655f6163636f756e745f77726f6e675f73697a655f" + "6163636f756e74696474726163655f6f6f625f737472696e6774726163655f6f7061717565" + "5f666c6f61745f6f6f625f737472696e6774726163655f6163636f756e745f6f6f625f7374" + "72696e6774726163655f616d6f756e745f6f6f625f737472696e6774726163655f746f6f5f" + "6c6f6e6774726163655f6e756d5f746f6f5f6c6f6e6774726163655f6f70617175655f666c" + "6f61745f746f6f5f6c6f6e6774726163655f6163636f756e745f746f6f5f6c6f6e67747261" + "63655f616d6f756e745f746f6f5f6c6f6e6774726163655f616d6f756e745f77726f6e675f" + "6c656e67746874726163655f6163636f756e745f636865636b5f646573796e636765745f70" + "6172656e745f6c65646765725f686173686765745f63757272656e745f6c65646765725f6f" + "626a5f6669656c646765745f6c65646765725f6f626a5f6669656c646765745f74785f6e65" + "737465645f6669656c646765745f63757272656e745f6c65646765725f6f626a5f6e657374" + "65645f6669656c646765745f6c65646765725f6f626a5f6e65737465645f6669656c64636f" + "6d707574655f7368613531325f68616c666765745f6e66746765745f6e66745f6973737565" + "726765745f6e66745f7461786f6e6765745f6e66745f73657269616c6765745f706172656e" + "745f6c65646765725f686173685f6e65675f6c656e6765745f706172656e745f6c65646765" + "725f686173685f6275665f746f6f5f736d616c6c6765745f706172656e745f6c6564676572" + "5f686173685f6c656e5f746f6f5f6c6f6e67666c6f61745f66726f6d5f75696e745f6c656e" + "5f6f6f62666c6f61745f66726f6d5f75696e745f77726f6e675f6c656e6163636f756e745f" + "6b65796c65745f6c656e5f6f6f626163636f756e745f6b65796c65745f77726f6e675f6c65" + "6e6c696e655f6b65796c65745f6c656e5f6f6f625f63757272656e63796c696e655f6b6579" + "6c65745f77726f6e675f6c656e5f63757272656e6379616d6d5f6b65796c65745f6c656e5f" + "6f6f625f617373657432616d6d5f6b65796c65745f6c656e5f77726f6e675f6c656e5f6173" + "73657432616d6d5f6b65796c65745f6c656e5f77726f6e675f6e6f6e5f7872705f63757272" + "656e63795f6c656e616d6d5f6b65796c65745f6c656e5f77726f6e675f7872705f63757272" + "656e63795f6c656e616d6d5f6b65796c65745f6d70746765745f74785f6669656c645f696e" + "76616c69645f736669656c646765745f63757272656e745f6c65646765725f6f626a5f6669" + "656c645f696e76616c69645f736669656c646765745f6c65646765725f6f626a5f6669656c" + "645f696e76616c69645f736669656c646765745f74785f6e65737465645f6669656c645f74" + "6f6f5f6269675f736c6963656765745f63757272656e745f6c65646765725f6f626a5f6e65" + "737465645f6669656c645f746f6f5f6269675f736c6963656765745f6c65646765725f6f62" + "6a5f6e65737465645f6669656c645f746f6f5f6269675f736c696365636f6d707574655f73" + "68613531325f68616c665f746f6f5f6269675f736c696365616d6d5f6b65796c65745f746f" + "6f5f6269675f736c69636563726564656e7469616c5f6b65796c65745f746f6f5f6269675f" + "736c6963656d70746f6b656e5f6b65796c65745f746f6f5f6269675f736c6963655f6d7074" + "6964666c6f61745f6164645f6f6f625f736c69636531666c6f61745f6164645f6f6f625f73" + "6c69636532666c6f61745f73756274726163745f6f6f625f736c69636531666c6f61745f73" + "756274726163745f6f6f625f736c69636532666c6f61745f6d756c7469706c795f6f6f625f" + "736c69636531666c6f61745f6d756c7469706c795f6f6f625f736c69636532666c6f61745f" + "6469766964655f6f6f625f736c69636531666c6f61745f6469766964655f6f6f625f736c69" + "636532666c6f61745f726f6f745f6f6f625f736c696365666c6f61745f706f775f6f6f625f" + "736c696365666c6f61745f6c6f675f6f6f625f736c6963656765745f6e66745f77726f6e67" + "5f73697a655f75696e743235366765745f6e66745f6973737565725f77726f6e675f73697a" + "655f75696e743235366765745f6e66745f7461786f6e5f77726f6e675f73697a655f75696e" + "743235366765745f6e66745f73657269616c5f77726f6e675f73697a655f75696e74323536" + "6163636f756e745f6b65796c65745f77726f6e675f73697a655f6163636f756e7469646368" + "65636b5f6b65796c65745f77726f6e675f73697a655f6163636f756e74696463726564656e" + "7469616c5f6b65796c65745f77726f6e675f73697a655f6163636f756e7469643163726564" + "656e7469616c5f6b65796c65745f77726f6e675f73697a655f6163636f756e746964326465" + "6c65676174655f6b65796c65745f77726f6e675f73697a655f6163636f756e746964316465" + "6c65676174655f6b65796c65745f77726f6e675f73697a655f6163636f756e746964326465" + "706f7369745f707265617574685f6b65796c65745f77726f6e675f73697a655f6163636f75" + "6e746964316465706f7369745f707265617574685f6b65796c65745f77726f6e675f73697a" + "655f6163636f756e746964326469645f6b65796c65745f77726f6e675f73697a655f616363" + "6f756e746964657363726f775f6b65796c65745f77726f6e675f73697a655f6163636f756e" + "7469646c696e655f6b65796c65745f77726f6e675f73697a655f6163636f756e746964316c" + "696e655f6b65796c65745f77726f6e675f73697a655f6163636f756e746964326d70745f69" + "737375616e63655f6b65796c65745f77726f6e675f73697a655f6163636f756e7469646d70" + "746f6b656e5f6b65796c65745f77726f6e675f73697a655f6163636f756e7469646e66745f" + "6f666665725f6b65796c65745f77726f6e675f73697a655f6163636f756e7469646f666665" + "725f6b65796c65745f77726f6e675f73697a655f6163636f756e7469646f7261636c655f6b" + "65796c65745f77726f6e675f73697a655f6163636f756e7469647061796368616e5f6b6579" + "6c65745f77726f6e675f73697a655f6163636f756e746964317061796368616e5f6b65796c" + "65745f77726f6e675f73697a655f6163636f756e746964327065726d697373696f6e65645f" + "646f6d61696e5f6b65796c65745f77726f6e675f73697a655f6163636f756e746964736967" + "6e6572735f6b65796c65745f77726f6e675f73697a655f6163636f756e7469647469636b65" + "745f6b65796c65745f77726f6e675f73697a655f6163636f756e7469647661756c745f6b65" + "796c65745f77726f6e675f73697a655f6163636f756e7469646765745f6e66745f77726f6e" + "675f73697a655f6163636f756e7469646d70746f6b656e5f6b65796c65745f6d707469645f" + "77726f6e675f6c656e677468004d0970726f64756365727302086c616e6775616765010452" + "757374000c70726f6365737365642d6279010572757374631d312e38352e31202834656231" + "363132353020323032352d30332d313529002c0f7461726765745f6665617475726573022b" + "0f6d757461626c652d676c6f62616c732b087369676e2d657874"; + +extern std::string const floatTestsWasmHex = + "0061736d0100000001430860077f7f7f7f7f7f7f017f60057f7f7f7f7f017f60047f7f7f7f" + "017f60067f7f7f7f7f7f017f60047e7f7f7f017f60057f7e7f7f7f017f60037f7f7e017f60" + "00017f02c9020e08686f73745f6c6962057472616365000108686f73745f6c69620e666c6f" + "61745f66726f6d5f696e74000408686f73745f6c69621274726163655f6f70617175655f66" + "6c6f6174000208686f73745f6c69620f666c6f61745f66726f6d5f75696e74000108686f73" + "745f6c696209666c6f61745f736574000508686f73745f6c69620d666c6f61745f636f6d70" + "617265000208686f73745f6c696209666c6f61745f616464000008686f73745f6c69620e66" + "6c6f61745f7375627472616374000008686f73745f6c69620e666c6f61745f6d756c746970" + "6c79000008686f73745f6c69620c666c6f61745f646976696465000008686f73745f6c6962" + "09666c6f61745f706f77000308686f73745f6c69620974726163655f6e756d000608686f73" + "745f6c69620a666c6f61745f726f6f74000308686f73745f6c696209666c6f61745f6c6f67" + "00010302010705030100110619037f01418080c0000b7f0041d28ac0000b7f0041e08ac000" + "0b072e04066d656d6f727902000666696e697368000e0a5f5f646174615f656e6403010b5f" + "5f686561705f6261736503020af20e01ef0e01047f230041206b22012400418080c000411d" + "41004100410010001a200142003703100240428ce000200141106a22004108410010014108" + "460440419d80c00041172000410810021a41b480c000411e20004108410110001a0c010b41" + "d280c000411e41004100410010001a0b2001428ce0003703180240200141186a4108200141" + "106a2200410841001003410846044041f080c00041172000410810021a0c010b418781c000" + "411e41004100410010001a0b0240410242fb00200141106a22004108410010044108460440" + "41a581c00041212000410810021a0c010b41c681c000412641004100410010001a0b41ec81" + "c0004115418182c000410810021a418982c0004116419f82c000410810021a41a782c00041" + "1b41004100410010001a2001420037031802404201200141186a2200410841001001410846" + "044041c282c000410f2000410810021a0c010b41d182c000411641004100410010001a0b02" + "40200141186a4108418182c0004108100545044041e782c000411b41004100410010001a0c" + "010b418283c000411b41004100410010001a0b0240200141186a4108419f82c00041081005" + "4101460440419d83c000412341004100410010001a0c010b41c083c0004124410041004100" + "10001a0b0240419f82c0004108200141186a41081005410246044041e483c0004123410041" + "00410010001a0c010b418784c000412441004100410010001a0b41ab84c000412041004100" + "410010001a200142d487b6f4c7d4b1c000370310410921000340200141106a220241084181" + "82c000410820024108410010061a200041016b22000d000b20014200370318420a20014118" + "6a22004108410010011a02402000410820024108100545044041cb84c00041144100410041" + "0010001a0c010b41df84c000411341004100410010001a0b410b21000340200141106a2202" + "4108418182c000410820024108410010071a200041016b22000d000b024020024108419f82" + "c0004108100545044041f284c000411941004100410010001a0c010b418b85c00041184100" + "4100410010001a0b41a385c000412341004100410010001a20014200370300420a20014108" + "410010011a200142d487b6f4c7d4b1c000370308410621000340200141086a220241082001" + "410820024108410010081a200041016b22000d000b2001420037031042c0843d200141106a" + "22004108410010011a02402000410820024108100545044041c685c0004119410041004100" + "10001a0c010b41df85c000411841004100410010001a0b410721000340200141086a220241" + "082001410820024108410010091a200041016b22000d000b20014200370318417f42012001" + "41186a22004108410010041a02402002410820004108100545044041f785c0004117410041" + "00410010001a0c010b418e86c000411641004100410010001a0b41a486c000411741004100" + "410010001a20014200370308418182c00041084103200141086a220041084100100a1a41bb" + "86c00041122000410810021a419f82c00041084106200041084100100a1a41cd86c0004118" + "2000410810021a200142003703104209200141106a22024108410010011a20024108410220" + "0041084100100a1a41e586c00041142000410810021a200241084100200041084100100a1a" + "41f986c00041172000410810021a200142003703184200200141186a22034108410010011a" + "200341084102200041084100100a1a419087c00041142000410810021a41a487c000413820" + "0341084100200041084100100aac100b1a41dc87c000411841004100410010001a20014200" + "370308420920004108410010011a20014200370310200041084102200241084100100c1a41" + "f487c00041122002410810021a200041084103200241084100100c1a418688c00041122002" + "410810021a2001420037031842c0843d20034108410010011a200341084103200241084100" + "100c1a419888c00041182002410810021a200341084106200241084100100c1a41b088c000" + "411c2002410810021a41cc88c000411741004100410010001a2001420037031042c0843d20" + "024108410010011a2001420037031820024108200341084100100d1a41e388c00041142003" + "410810021a41f788c000411a41004100410010001a20014200370318418182c0004108419f" + "82c000410820034108410010081a0240419f82c0004108200341081005450440419189c000" + "411641004100410010001a0c010b41a789c000411541004100410010001a0b419f82c00041" + "08419f82c0004108200141186a22004108410010081a0240418182c0004108200041081005" + "45044041bc89c000411741004100410010001a0c010b41d389c00041164100410041001000" + "1a0b41e989c000411a41004100410010001a2001420037031020014200370318420a200141" + "186a22024108410010011a418182c000410820024108200141106a22004108410010091a41" + "838ac00041192000410810021a418182c00041082000410820004108410010091a419c8ac0" + "00410f2000410810021a02402002410820004108100545044041ab8ac00041144100410041" + "0010001a0c010b41bf8ac000411341004100410010001a0b200141206a240041010b0bdc0a" + "0100418080c0000bd20a0a24242420746573745f666c6f61745f66726f6d5f7761736d2024" + "24242020666c6f61742066726f6d206936342031323330303a2020666c6f61742066726f6d" + "20693634203132333030206173204845583a2020666c6f61742066726f6d20693634203132" + "3330303a206661696c65642020666c6f61742066726f6d207536342031323330303a202066" + "6c6f61742066726f6d207536342031323330303a206661696c65642020666c6f6174206672" + "6f6d2065787020322c206d616e7469737361203132333a2020666c6f61742066726f6d2065" + "787020322c206d616e746973736120333a206661696c65642020666c6f61742066726f6d20" + "636f6e737420313ad4838d7ea4c680002020666c6f61742066726f6d20636f6e7374202d31" + "3a94838d7ea4c680000a24242420746573745f666c6f61745f636f6d706172652024242420" + "20666c6f61742066726f6d20313a2020666c6f61742066726f6d20313a206661696c656420" + "20666c6f61742066726f6d2031203d3d20464c4f41545f4f4e452020666c6f61742066726f" + "6d203120213d20464c4f41545f4f4e452020666c6f61742066726f6d2031203e20464c4f41" + "545f4e454741544956455f4f4e452020666c6f61742066726f6d203120213e20464c4f4154" + "5f4e454741544956455f4f4e452020464c4f41545f4e454741544956455f4f4e45203c2066" + "6c6f61742066726f6d20312020464c4f41545f4e454741544956455f4f4e4520213c20666c" + "6f61742066726f6d20310a24242420746573745f666c6f61745f6164645f73756274726163" + "742024242420207265706561746564206164643a20676f6f64202072657065617465642061" + "64643a20626164202072657065617465642073756274726163743a20676f6f642020726570" + "65617465642073756274726163743a206261640a24242420746573745f666c6f61745f6d75" + "6c7469706c795f6469766964652024242420207265706561746564206d756c7469706c793a" + "20676f6f6420207265706561746564206d756c7469706c793a206261642020726570656174" + "6564206469766964653a20676f6f6420207265706561746564206469766964653a20626164" + "0a24242420746573745f666c6f61745f706f77202424242020666c6f61742063756265206f" + "6620313a2020666c6f61742036746820706f776572206f66202d313a2020666c6f61742073" + "7175617265206f6620393a2020666c6f61742030746820706f776572206f6620393a202066" + "6c6f617420737175617265206f6620303a2020666c6f61742030746820706f776572206f66" + "20302028657870656374696e6720494e56414c49445f504152414d53206572726f72293a0a" + "24242420746573745f666c6f61745f726f6f74202424242020666c6f61742073717274206f" + "6620393a2020666c6f61742063627274206f6620393a2020666c6f61742063627274206f66" + "20313030303030303a2020666c6f61742036746820726f6f74206f6620313030303030303a" + "0a24242420746573745f666c6f61745f6c6f672024242420206c6f675f3130206f66203130" + "30303030303a0a24242420746573745f666c6f61745f6e65676174652024242420206e6567" + "61746520636f6e737420313a20676f6f6420206e656761746520636f6e737420313a206261" + "6420206e656761746520636f6e7374202d313a20676f6f6420206e656761746520636f6e73" + "74202d313a206261640a24242420746573745f666c6f61745f696e76657274202424242020" + "696e76657274206120666c6f61742066726f6d2031303a2020696e7665727420616761696e" + "3a2020696e766572742074776963653a20676f6f642020696e766572742074776963653a20" + "626164004d0970726f64756365727302086c616e6775616765010452757374000c70726f63" + "65737365642d6279010572757374631d312e38352e31202834656231363132353020323032" + "352d30332d313529002c0f7461726765745f6665617475726573022b0f6d757461626c652d" + "676c6f62616c732b087369676e2d657874"; + +extern std::string const float0Hex = + "0061736d0100000001290560057f7f7f7f7f017f60047e7f7f7f017f6007" + "7f7f7f7f7f7f7f017f60047f7f7f7f017f6000017f025f0408686f73745f" + "6c6962057472616365000008686f73745f6c69620e666c6f61745f66726f" + "6d5f696e74000108686f73745f6c69620e666c6f61745f73756274726163" + "74000208686f73745f6c69620d666c6f61745f636f6d7061726500030302" + "010405030100110619037f01418080c0000b7f00419281c0000b7f0041a0" + "81c0000b072e04066d656d6f727902000666696e69736800040a5f5f6461" + "74615f656e6403010b5f5f686561705f6261736503020abe0201bb020101" + "7f23808080800041206b2200248080808000418080c08000411541004100" + "41001080808080001a200042003703084200200041086a41084100108180" + "8080001a20004200370310420a200041106a410841001081808080001a20" + "0042003703180240200041106a4108200041106a4108200041186a410841" + "001082808080004108460d00419580c08000411541004100410010808080" + "80001a0b02400240200041086a4108200041186a41081083808080000d00" + "41aa80c0800041174100410041001080808080001a0c010b41c180c08000" + "41164100410041001080808080001a0b02400240200041086a410841d780" + "c0800041081083808080000d0041df80c08000411a410041004100108080" + "8080001a0c010b41f980c0800041194100410041001080808080001a0b20" + "0041206a24808080800041010b0b9c010100418080c0000b92010a242424" + "20746573745f666c6f61745f30202424242020666c6f61742031302d3130" + "3a206661696c65642020666c6f6174203020636f6d706172653a20676f6f" + "642020666c6f6174203020636f6d706172653a2062616480000000000000" + "002020464c4f41545f5a45524f20636f6d706172653a20676f6f64202046" + "4c4f41545f5a45524f20636f6d706172653a20626164009502046e616d65" + "001110666c6f61745f74657374732e7761736d01da0105002b5f5a4e3878" + "72706c5f73746434686f7374357472616365313768616338383262323664" + "656162656436364501355f5a4e387872706c5f73746434686f7374313466" + "6c6f61745f66726f6d5f696e743137683032343066386533613839643139" + "39654502355f5a4e387872706c5f73746434686f73743134666c6f61745f" + "737562747261637431376864363430633135323334353432393563450334" + "5f5a4e387872706c5f73746434686f73743133666c6f61745f636f6d7061" + "72653137683663386465656231323864393638386645040666696e697368" + "071201000f5f5f737461636b5f706f696e746572090a0100072e726f6461" + "7461004d0970726f64756365727302086c616e6775616765010452757374" + "000c70726f6365737365642d6279010572757374631d312e38392e302028" + "32393438333838336520323032352d30382d3034290094010f7461726765" + "745f6665617475726573082b0b62756c6b2d6d656d6f72792b0f62756c6b" + "2d6d656d6f72792d6f70742b1663616c6c2d696e6469726563742d6f7665" + "726c6f6e672b0a6d756c746976616c75652b0f6d757461626c652d676c6f" + "62616c732b136e6f6e7472617070696e672d6670746f696e742b0f726566" + "6572656e63652d74797065732b087369676e2d657874"; + +extern std::string const disabledFloatHex = + "0061736d010000000108026000006000017f03030200010503010002063e" + "0a7f004180080b7f004180080b7f004180100b7f004180100b7f00418090" + "040b7f004180080b7f00418090040b7f00418080080b7f0041000b7f0041" + "010b07b0010d066d656d6f72790200115f5f7761736d5f63616c6c5f6374" + "6f727300000666696e69736800010362756603000c5f5f64736f5f68616e" + "646c6503010a5f5f646174615f656e6403020b5f5f737461636b5f6c6f77" + "03030c5f5f737461636b5f6869676803040d5f5f676c6f62616c5f626173" + "6503050b5f5f686561705f6261736503060a5f5f686561705f656e640307" + "0d5f5f6d656d6f72795f6261736503080c5f5f7461626c655f6261736503" + "090a150202000b100043000000c54300200045921a41010b"; + +extern std::string const parametersWasmHex = + "0061736D01000000015E0E60047F7F7F7F017F60037F7F7E017F60057F7F7F7F7F017F6007" + "7F7F7F7F7F7F7F017F60057F7E7F7F7F017F60027F7F0060037F7F7F0060027F7F017F6001" + "7F017F6000017F60037F7E7F0060000060047F7F7F7F0060037F7F7F017F02A3010708686F" + "73745F6C69620E66756E6374696F6E5F706172616D000008686F73745F6C69620E696E7374" + "616E63655F706172616D000008686F73745F6C69620974726163655F6E756D000108686F73" + "745F6C6962057472616365000208686F73745F6C69621274726163655F6F70617175655F66" + "6C6F6174000008686F73745F6C696209666C6F61745F616464000308686F73745F6C696209" + "666C6F61745F736574000403171605060605050707070708090A090B050C0D0D0D0D0D0D04" + "05017001010105030100110619037F01418080C0000B7F0041978AC0000B7F0041A08AC000" + "0B074905066D656D6F727902000F66756E6374696F6E5F706172616D7300110F696E737461" + "6E63655F706172616D7300130A5F5F646174615F656E6403010B5F5F686561705F62617365" + "03020AAD5D16B30101017F23808080800041F0006B2202248080808000200241086A410041" + "30109B808080001A0240024020014106200241086A4130108080808000220141004A0D0020" + "004103360200200041FF013A00040C010B2002200241086A2001108880808000200241386A" + "20022802002002280204108980808000024020022802384103470D00200041033602002000" + "41FC013A00040C010B2000200241386A4138109A808080001A0B200241F0006A2480808080" + "000B2500024020024131490D0020024130108B80808000000B200020023602042000200136" + "02000BE20302027F017E024020012D000022034118744118752204417F4A0D000240200241" + "30470D0020004101360200200020012900083700242000200129001C370010200020012900" + "003703082000412C6A200141106A290000370000200041346A200141186A28000036000020" + "0041186A200141246A290000370000200041206A2001412C6A2800003600000F0B20004283" + "808080703703000F0B02402004412071450D00024020024121470D00200041023602002000" + "2001290009370011200041196A200141116A290000370000200041216A200141196A290000" + "3700002000200441C001714106763A001020002001290001220542388620054280FE038342" + "2886842005428080FC0783421886200542808080F80F834208868484200542088842808080" + "F80F832005421888428080FC07838420054228884280FE038320054238888484843703080F" + "0B20004283808080703703000F0B024020024108460D0020004283808080703703000F0B20" + "00410036020020002001290000220542018342388620054280FE0383422886842005428080" + "FC0783421886200542808080F80F834208868484200542088842808080F80F832005421888" + "428080FC07838420054228884280FE038320054238888484842205420020057D200341C000" + "711B3703080BB30101017F23808080800041F0006B2202248080808000200241086A410041" + "30109B808080001A0240024020014106200241086A4130108180808000220141004A0D0020" + "004103360200200041FF013A00040C010B2002200241086A2001108880808000200241386A" + "20022802002002280204108980808000024020022802384103470D00200041033602002000" + "41FC013A00040C010B2000200241386A4138109A808080001A0B200241F0006A2480808080" + "000B0900109480808000000B11002000200141201097808080004100470B11002000200141" + "141097808080004100470B11002000200141181097808080004100470B110020002001410C" + "1097808080004100470B1500200041DD80C0800041101097808080004100470BDE1C05027F" + "017E017F017E017F23808080800041D0036B2200248080808000200041F8026A4100413010" + "9B808080001A02400240024041004110200041F8026A4101108080808000220141004A0D00" + "41FC89C08000411B427F1082808080001A0C010B200041C8006A200041F8026A2001108880" + "808000418080C08000410C20002802482D00002201AD1082808080001A200020013A00F802" + "418C80C08000410A200041F8026A410141011083808080001A02400240200141FF01470D00" + "200041F8026A41004130109B808080001A02400240024041014101200041F8026A41021080" + "80808000220141014E0D00427F21020C010B200041C0006A200041F8026A20011088808080" + "00200028024441024F0D01427C21020B41BC89C08000411C20021082808080001A0C030B41" + "9680C08000410D20002802402F00002201AD1082808080001A200020013B015241A380C080" + "00410B200041D2006A410241011083808080001A200141FFFF03460D01419789C080004125" + "4100410041001083808080001A0C020B41D889C0800041244100410041001083808080001A" + "0C010B200041F8026A41004130109B808080001A0240024041024102200041F8026A410410" + "8080808000220141014E0D00427F21020C010B200041386A200041F8026A20011088808080" + "000240200028023C41034B0D00427C21020C010B20002000280238280000220336025441AE" + "80C08000410B200041D4006A410441011083808080001A417F210102400240024002400240" + "024002400240024002402003417F470D00200041F8026A41004130109B808080001A024002" + "40024041034103200041F8026A4108108080808000220341014E0D00427F21020C010B2000" + "41306A200041F8026A2003108880808000200028023441074B0D01427C21020B41BA88C080" + "00411C20021082808080001A0C0D0B41B980C08000410D2000280230290000220210828080" + "80001A2000200237035841C680C08000410B200041D8006A410841011083808080001A427F" + "21022000290358427F520D01200041F8026A41004130109B808080001A0240024041044104" + "200041F8026A411010808080800022014101480D00200041286A200041F8026A2001108880" + "808000427C2102200028022C410F4B0D010B41F887C08000411D20021082808080001A0C0C" + "0B200028022822012900002102200020012900083703682000200237036041D180C0800041" + "0C200041E0006A411041011083808080001A200041E0006A1090808080000D02200041F802" + "6A41004130109B808080001A02400240024041054111200041F8026A411410808080800022" + "0141014E0D00427F21020C010B200041206A200041F8026A20011088808080002000280224" + "41134B0D01427C21020B41B587C08000411D20021082808080001A0C0C0B20002802202201" + "41116A2F00002103200141096A29000021022001290001210420012D00002105200041D202" + "6A200141136A2D00003A0000200041C0026A41086A2002370300200041C0026A41106A2003" + "3B0100200041F4006A41096A2002370000200041F4006A41106A200041C0026A410F6A2800" + "00360000200020053A00742000200437007541ED80C08000410C200041F4006A4114410110" + "83808080001A20004188016A410F6A410036000020004188016A41086A4200370300200042" + "0037038801200041013A009B0102400240200041F4006A20004188016A108D808080000D00" + "200041F8026A41004130109B808080001A024041064115200041F8026A4118108080808000" + "220141004A0D0041FF0121010C0C0B200041186A200041F8026A2001108880808000024020" + "0028021C220141134B0D0041FC0121010C0C0B200141174B0D0141182001108B8080800000" + "0B418F87C0800041264100410041001083808080001A0C0C0B200028021822012D00002103" + "200041C0026A200141016A4117109C808080001A200041A9016A200041C0026A41086A2900" + "00370000200041B0016A200041C0026A410F6A290000370000200020033A00A00120002000" + "2900C0023700A10141F980C08000410C200041A0016A411841011083808080001A200041B8" + "016A410F6A4200370000200041B8016A41086A4200370300200042003703B801200041013A" + "00CF01200041A0016A200041B8016A108E808080000D03200041F8026A41004130109B8080" + "80001A02400240024041074105200041F8026A4120108080808000220141014E0D00427F21" + "020C010B200041106A200041F8026A20011088808080002000280214411F4B0D01427C2102" + "0B41AF86C08000411D20021082808080001A0C0C0B200028021022012D00002103200041C0" + "026A200141016A411F109C808080001A200041D0016A41096A200041C0026A41086A290000" + "370000200041E1016A200041D0026A290000370000200041E8016A200041D7026A29000037" + "0000200020033A00D001200020002900C0023700D101418581C08000410C200041D0016A41" + "2041011083808080001A200042EFB4A8A69492A496253700880220004292BECDED89EBFFCF" + "1037008002200042DAA2C7AABABAF2CF483700F801200042D9ABE996FCBCDD8C703700F001" + "200041D0016A200041F0016A108C808080000D04200041F8026A41004130109B808080001A" + "02400240024041094108200041F8026A4114108080808000220141014E0D00427F21020C01" + "0B200041086A200041F8026A2001108880808000200028020C41134B0D01427C21020B41EC" + "85C08000411D20021082808080001A0C0C0B2000280208220141116A2F0000210320014109" + "6A29000021022001290001210420012D00002105200041D2026A200141136A2D00003A0000" + "200041C8026A2002370300200041C0026A41106A20033B010020004190026A41096A200237" + "000020004190026A41106A200041CF026A280000360000200020053A009002200020043700" + "9102419181C08000410E20004190026A411441011083808080001A200041CF86AEE9033600" + "B402200042958EC5B8A3EDFE87897F3700AC02200042AEA5E8A9E8EAFCE7917F3700A40220" + "004190026A200041A4026A108D808080000D05200041F8026A410A10878080800002402000" + "2802F80222014103470D0041A685C08000412020003000FC021082808080001A0C0C0B2000" + "2903800321020240024020010D00419F81C08000411320021082808080001A200221040C01" + "0B419F81C080004113427F1082808080001A420021040B200020043703B80241B281C08000" + "410B200041B8026A410841011083808080001A20010D06200242C0843D520D07200041F802" + "6A410B108780808000024020002802F80222014103470D0041B584C08000412020003000FC" + "021082808080001A0C0C0B200041C0026A41186A200041F8026A41186A2903003703002000" + "41C0026A41206A200041F8026A41206A280200360200200041C0026A412C6A200041F8026A" + "412C6A290200370200200041C0026A41346A200041F8026A41346A28020036020020002000" + "290388033703D0022000200029029C033702E402200020002903800322023703C802200020" + "002802FC023602C402200020013602C00220014101470D0841BD81C080004113200041C002" + "6A41086A2201410841011083808080001A41D081C08000411E200141081084808080001A41" + "EE81C08000410B200041D0026A411441011083808080001A41F981C08000410D200041E402" + "6A411441011083808080001A200042003703F8020240024020014108418682C08000410820" + "0041F8026A4108410010858080800022014108470D00418E82C080004124200041F8026A41" + "0841011083808080001A418E82C080004124200041F8026A41081084808080001A0C010B41" + "B282C08000412D2001AC1082808080001A0B200020023703F80241E782C08000410B200041" + "F8026A410841011083808080001A200041F8026A41004130109B808080001A024002400240" + "410C4109200041F8026A410C108080808000220141014E0D00427F21020C010B2000200041" + "F8026A20011088808080002000280204410B4B0D01427C21020B41F183C08000411C200210" + "82808080001A0C0C0B200041B4036A20002802002201290000220242388620024280FE0383" + "422886842002428080FC0783421886200242808080F80F8342088684842002420888428080" + "80F80F832002421888428080FC07838420024228884280FE03832002423888848484220220" + "01280008220141187420014180FE03714108747220014108764180FE037120014118767272" + "220310928080800041F282C08000410D200041B4036A410C41011083808080001A200041FF" + "FFFF8F7F3600800320004280888CA2D6F82E3700F8020240200041B4036A200041F8026A10" + "8F808080000D0041FF82C08000411020021082808080001A418F83C0800041102003AC1082" + "808080001A200042003703C0034100210120032002200041C0036A41084100108680808000" + "1A200020002903C0033703C803419F83C080004116200041C8036A41081084808080001A41" + "B583C080004117200041C0036A410841011083808080001A0C0D0B41CC83C0800041254100" + "410041001083808080001A0C0B0B41D688C0800041254100410041001083808080001A0C0B" + "0B419588C0800041254100410041001083808080001A0C090B41D287C08000412641004100" + "41001083808080001A0C080B41CC86C0800041264100410041001083808080001A0C070B41" + "8986C0800041264100410041001083808080001A0C060B41C685C080004126410041004100" + "1083808080001A0C050B41FE84C0800041284100410041001083808080001A0C040B41D584" + "C0800041294100410041001083808080001A0C030B41BD81C08000411341DF82C080004108" + "41011083808080001A41E782C08000410B41DF82C08000410841011083808080001A418D84" + "C0800041284100410041001083808080001A0C020B41F286C08000411D2001AD4238864238" + "871082808080001A0C010B41FB88C08000411C20021082808080001A0B417F21010B200041" + "D0036A24808080800020010BF00101027F23808080800041206B2203248080808000200341" + "086A41086A22044100360200200342003703082003200142388620014280FE038342288684" + "2001428080FC0783421886200142808080F80F834208868484200142088842808080F80F83" + "2001421888428080FC07838420014228884280FE0383200142388884848437031820034108" + "6A4108200341186A41081096808080002003200241187420024180FE037141087472200241" + "08764180FE03712002411876727236021820044104200341186A4104109680808000200041" + "086A200428020036000020002003290308370000200341206A2480808080000BDB1C05027F" + "017E017F017E017F23808080800041D0036B2200248080808000200041F8026A4100413010" + "9B808080001A02400240024041014110200041F8026A4101108180808000220141004A0D00" + "41FC89C08000411B427F1082808080001A0C010B200041C8006A200041F8026A2001108880" + "808000418080C08000410C20002802482D00002201AD1082808080001A200020013A00F802" + "418C80C08000410A200041F8026A410141011083808080001A02400240200141FF01470D00" + "200041F8026A41004130109B808080001A02400240024041024101200041F8026A41021081" + "80808000220141014E0D00427F21020C010B200041C0006A200041F8026A20011088808080" + "00200028024441024F0D01427C21020B41BC89C08000411C20021082808080001A0C030B41" + "9680C08000410D20002802402F00002201AD1082808080001A200020013B015241A380C080" + "00410B200041D2006A410241011083808080001A200141FFFF03460D01419789C080004125" + "4100410041001083808080001A0C020B41D889C0800041244100410041001083808080001A" + "0C010B200041F8026A41004130109B808080001A0240024041034102200041F8026A410410" + "8180808000220141014E0D00427F21020C010B200041386A200041F8026A20011088808080" + "000240200028023C41034B0D00427C21020C010B20002000280238280000220336025441AE" + "80C08000410B200041D4006A410441011083808080001A417F210102400240024002400240" + "024002400240024002402003417F470D00200041F8026A41004130109B808080001A024002" + "40024041044103200041F8026A4108108180808000220341014E0D00427F21020C010B2000" + "41306A200041F8026A2003108880808000200028023441074B0D01427C21020B41BA88C080" + "00411C20021082808080001A0C0D0B41B980C08000410D2000280230290000220210828080" + "80001A2000200237035841C680C08000410B200041D8006A410841011083808080001A427F" + "21022000290358427F520D01200041F8026A41004130109B808080001A0240024041054104" + "200041F8026A411010818080800022014101480D00200041286A200041F8026A2001108880" + "808000427C2102200028022C410F4B0D010B41F887C08000411D20021082808080001A0C0C" + "0B200028022822012900002102200020012900083703682000200237036041D180C0800041" + "0C200041E0006A411041011083808080001A200041E0006A1090808080000D02200041F802" + "6A41004130109B808080001A02400240024041064111200041F8026A411410818080800022" + "0141014E0D00427F21020C010B200041206A200041F8026A20011088808080002000280224" + "41134B0D01427C21020B41B587C08000411D20021082808080001A0C0C0B20002802202201" + "41116A2F00002103200141096A29000021022001290001210420012D00002105200041D202" + "6A200141136A2D00003A0000200041C0026A41086A2002370300200041C0026A41106A2003" + "3B0100200041F4006A41096A2002370000200041F4006A41106A200041C0026A410F6A2800" + "00360000200020053A00742000200437007541ED80C08000410C200041F4006A4114410110" + "83808080001A20004188016A410F6A410036000020004188016A41086A4200370300200042" + "0037038801200041013A009B0102400240200041F4006A20004188016A108D808080000D00" + "200041F8026A41004130109B808080001A024041074115200041F8026A4118108180808000" + "220141004A0D0041FF0121010C0C0B200041186A200041F8026A2001108880808000024020" + "0028021C220141134B0D0041FC0121010C0C0B200141174B0D0141182001108B8080800000" + "0B418F87C0800041264100410041001083808080001A0C0C0B200028021822012D00002103" + "200041C0026A200141016A4117109C808080001A200041A9016A200041C0026A41086A2900" + "00370000200041B0016A200041C0026A410F6A290000370000200020033A00A00120002000" + "2900C0023700A10141F980C08000410C200041A0016A411841011083808080001A200041B8" + "016A410F6A4200370000200041B8016A41086A4200370300200042003703B801200041013A" + "00CF01200041A0016A200041B8016A108E808080000D03200041F8026A41004130109B8080" + "80001A02400240024041084105200041F8026A4120108180808000220141014E0D00427F21" + "020C010B200041106A200041F8026A20011088808080002000280214411F4B0D01427C2102" + "0B41AF86C08000411D20021082808080001A0C0C0B200028021022012D00002103200041C0" + "026A200141016A411F109C808080001A200041D9016A200041C0026A41086A290000370000" + "200041E1016A200041D0026A290000370000200041E8016A200041D7026A29000037000020" + "0020033A00D001200020002900C0023700D101418581C08000410C200041D0016A41204101" + "1083808080001A200042EFB4A8A69492A496253700880220004292BECDED89EBFFCF103700" + "8002200042DAA2C7AABABAF2CF483700F801200042D9ABE996FCBCDD8C703700F001200041" + "D0016A200041F0016A108C808080000D04200041F8026A41004130109B808080001A024002" + "400240410A4108200041F8026A4114108180808000220141014E0D00427F21020C010B2000" + "41086A200041F8026A2001108880808000200028020C41134B0D01427C21020B41EC85C080" + "00411D20021082808080001A0C0C0B2000280208220141116A2F00002103200141096A2900" + "0021022001290001210420012D00002105200041D2026A200141136A2D00003A0000200041" + "C8026A2002370300200041C0026A41106A20033B010020004190026A41096A200237000020" + "004190026A41106A200041CF026A280000360000200020053A009002200020043700910241" + "9181C08000410E20004190026A411441011083808080001A200041CF86AEE9033600B40220" + "0042958EC5B8A3EDFE87897F3700AC02200042AEA5E8A9E8EAFCE7917F3700A40220004190" + "026A200041A4026A108D808080000D05200041F8026A410B108A80808000024020002802F8" + "0222014103470D0041A685C08000412020003000FC021082808080001A0C0C0B2000290380" + "0321020240024020010D00419F81C08000411320021082808080001A200221040C010B419F" + "81C080004113427F1082808080001A420021040B200020043703B80241B281C08000410B20" + "0041B8026A410841011083808080001A20010D06200242C0843D520D07200041F8026A410C" + "108A80808000024020002802F80222014103470D0041B584C08000412020003000FC021082" + "808080001A0C0C0B200041C0026A41186A200041F8026A41186A290300370300200041C002" + "6A41206A200041F8026A41206A280200360200200041C0026A412C6A200041F8026A412C6A" + "290200370200200041C0026A41346A200041F8026A41346A28020036020020002000290388" + "033703D0022000200029029C033702E402200020002903800322023703C802200020002802" + "FC023602C402200020013602C00220014101470D0841BD81C080004113200041C0026A4108" + "6A2201410841011083808080001A41D081C08000411E200141081084808080001A41EE81C0" + "8000410B200041D0026A411441011083808080001A41F981C08000410D200041E4026A4114" + "41011083808080001A200042003703F8020240024020014108418682C080004108200041F8" + "026A4108410010858080800022014108470D00418E82C080004124200041F8026A41084101" + "1083808080001A418E82C080004124200041F8026A41081084808080001A0C010B41B282C0" + "8000412D2001AC1082808080001A0B200020023703F80241E782C08000410B200041F8026A" + "410841011083808080001A200041F8026A41004130109B808080001A024002400240410D41" + "09200041F8026A410C108180808000220141014E0D00427F21020C010B2000200041F8026A" + "20011088808080002000280204410B4B0D01427C21020B41F183C08000411C200210828080" + "80001A0C0C0B200041B4036A20002802002201290000220242388620024280FE0383422886" + "842002428080FC0783421886200242808080F80F834208868484200242088842808080F80F" + "832002421888428080FC07838420024228884280FE03832002423888848484220220012800" + "08220141187420014180FE03714108747220014108764180FE037120014118767272220310" + "928080800041F282C08000410D200041B4036A410C41011083808080001A200041FFFFFF8F" + "7F3600800320004280888CA2D6F82E3700F8020240200041B4036A200041F8026A108F8080" + "80000D0041FF82C08000411020021082808080001A418F83C0800041102003AC1082808080" + "001A200042003703C0034100210120032002200041C0036A410841001086808080001A2000" + "20002903C0033703C803419F83C080004116200041C8036A41081084808080001A41B583C0" + "80004117200041C0036A410841011083808080001A0C0D0B41CC83C0800041254100410041" + "001083808080001A0C0B0B41D688C0800041254100410041001083808080001A0C0B0B4195" + "88C0800041254100410041001083808080001A0C090B41D287C08000412641004100410010" + "83808080001A0C080B41CC86C0800041264100410041001083808080001A0C070B418986C0" + "800041264100410041001083808080001A0C060B41C685C080004126410041004100108380" + "8080001A0C050B41FE84C0800041284100410041001083808080001A0C040B41D584C08000" + "41294100410041001083808080001A0C030B41BD81C08000411341DF82C080004108410110" + "83808080001A41E782C08000410B41DF82C08000410841011083808080001A418D84C08000" + "41284100410041001083808080001A0C020B41F286C08000411D2001AD4238864238871082" + "808080001A0C010B41FB88C08000411C20021082808080001A0B417F21010B200041D0036A" + "24808080800020010B0300000B0900109480808000000B2400024020012003460D00200120" + "03109580808000000B200020022001109A808080001A0B4A01037F4100210302402002450D" + "000240034020002D0000220420012D00002205470D01200041016A2100200141016A210120" + "02417F6A2202450D020C000B0B200420056B21030B20030BB907010C7F2380808080004110" + "6B210302400240200241104F0D00200021040C010B024020002000410020006B4103712205" + "6A22064F0D002005417F6A2107200021042001210802402005450D00200521092000210420" + "0121080340200420082D00003A0000200841016A2108200441016A21042009417F6A22090D" + "000B0B20074107490D000340200420082D00003A0000200441016A200841016A2D00003A00" + "00200441026A200841026A2D00003A0000200441036A200841036A2D00003A000020044104" + "6A200841046A2D00003A0000200441056A200841056A2D00003A0000200441066A20084106" + "6A2D00003A0000200441076A200841076A2D00003A0000200841086A2108200441086A2204" + "2006470D000B0B2006200220056B2209417C7122076A210402400240200120056A22084103" + "7122010D00200620044F0D0120082101034020062001280200360200200141046A21012006" + "41046A22062004490D000C020B0B410021022003410036020C2003410C6A20017221050240" + "410420016B220A410171450D00200520082D00003A0000410121020B0240200A410271450D" + "00200520026A200820026A2F01003B01000B200820016B21022001410374210B200328020C" + "210502400240200641046A2004490D002006210C0C010B4100200B6B411871210D03402006" + "2005200B76200241046A22022802002205200D7472360200200641086A210A200641046A22" + "0C2106200A2004490D000B0B41002106200341003A0008200341003A000602400240200141" + "01470D00200341086A210D410021014100210A4100210E0C010B200241056A2D0000210A20" + "03200241046A2D000022013A0008200A410874210A4102210E200341066A210D0B02402008" + "410171450D00200D200241046A200E6A2D00003A000020032D0006411074210620032D0008" + "21010B200C200A200672200141FF0171724100200B6B411871742005200B76723602000B20" + "094103712102200820076A21010B02402004200420026A22064F0D002002417F6A21090240" + "20024107712208450D000340200420012D00003A0000200141016A2101200441016A210420" + "08417F6A22080D000B0B20094107490D000340200420012D00003A0000200441016A200141" + "016A2D00003A0000200441026A200141026A2D00003A0000200441036A200141036A2D0000" + "3A0000200441046A200141046A2D00003A0000200441056A200141056A2D00003A00002004" + "41066A200141066A2D00003A0000200441076A200141076A2D00003A0000200141086A2101" + "200441086A22042006470D000B0B20000B9C0E010E7F23808080800041206B210302400240" + "0240200020016B20024F0D00200120026A2104200020026A21050240200241104F0D002005" + "21060C020B4100200541037122076B210802402005417C71220920054F0D002007417F6A21" + "0A200521062004210B02402007450D002007210C200521062004210B03402006417F6A2206" + "200B417F6A220B2D00003A0000200C417F6A220C0D000B0B200A4103490D00200B417C6A21" + "0B03402006417F6A200B41036A2D00003A00002006417E6A200B41026A2D00003A00002006" + "417D6A200B41016A2D00003A00002006417C6A2206200B2D00003A0000200B417C6A210B20" + "092006490D000B0B2009200220076B220B417C71220C6B21064100200C6B210C0240024020" + "0420086A220441037122070D00200620094F0D01200B20016A417C6A210103402009417C6A" + "220920012802003602002001417C6A210120062009490D000C020B0B4100210A200341003A" + "0018200341003A0016200420076B210D02400240024020074101470D00200341186A210E41" + "00210F0C010B2003200D2D0000220A3A0018200D2D0001210F410021102004410171450D01" + "200341166A210E4102210A0B200E200D200A6A2D00003A000020032D001641107421102003" + "2D0018210A0B2007410374210E200F41FF0171410874201072200A41FF017172210A024020" + "0641046A221020094F0D002001200220076B6A21014100200E6B4118712102034020052008" + "6A417C6A200A2002742001417C6A220120086A220D280200220A200E767236020020102005" + "417C6A220520086A2209490D000B0B410021012003410036021C200D20076A417C6A210220" + "03411C6A20077221050240410420076B2207410171450D00200520022D00003A0000410121" + "010B02402007410271450D00200520016A200220016A2F01003B01000B2009417C6A200328" + "021C200E76200A4100200E6B41187174723602000B200B41037121022004200C6A21040C01" + "0B02400240200241104F0D00200021060C010B024020002000410020006B410371220C6A22" + "0B4F0D00200C417F6A210520002106200121090240200C450D00200C210420002106200121" + "090340200620092D00003A0000200941016A2109200641016A21062004417F6A22040D000B" + "0B20054107490D000340200620092D00003A0000200641016A200941016A2D00003A000020" + "0641026A200941026A2D00003A0000200641036A200941036A2D00003A0000200641046A20" + "0941046A2D00003A0000200641056A200941056A2D00003A0000200641066A200941066A2D" + "00003A0000200641076A200941076A2D00003A0000200941086A2109200641086A2206200B" + "470D000B0B200B2002200C6B2204417C7122056A2106024002402001200C6A220941037122" + "010D00200B20064F0D01200921010340200B2001280200360200200141046A2101200B4104" + "6A220B2006490D000C020B0B4100210220034100360210200341106A200172210C02404104" + "20016B2207410171450D00200C20092D00003A0000410121020B02402007410271450D0020" + "0C20026A200920026A2F01003B01000B200920016B2102200141037421082003280210210C" + "02400240200B41046A2006490D00200B210A0C010B410020086B411871210D0340200B200C" + "200876200241046A2202280200220C200D7472360200200B41086A2107200B41046A220A21" + "0B20072006490D000B0B4100210B200341003A000C200341003A000A024002402001410147" + "0D002003410C6A210D41002101410021074100210E0C010B200241056A2D00002107200320" + "0241046A2D000022013A000C200741087421074102210E2003410A6A210D0B024020094101" + "71450D00200D200241046A200E6A2D00003A000020032D000A411074210B20032D000C2101" + "0B200A2007200B72200141FF017172410020086B41187174200C200876723602000B200441" + "03712102200920056A21010B2006200620026A220B4F0D012002417F6A2104024020024107" + "712209450D000340200620012D00003A0000200141016A2101200641016A21062009417F6A" + "22090D000B0B20044107490D010340200620012D00003A0000200641016A200141016A2D00" + "003A0000200641026A200141026A2D00003A0000200641036A200141036A2D00003A000020" + "0641046A200141046A2D00003A0000200641056A200141056A2D00003A0000200641066A20" + "0141066A2D00003A0000200641076A200141076A2D00003A0000200141086A210120064108" + "6A2206200B470D000C020B0B200620026B220920064F0D002002417F6A210B024020024103" + "712201450D0003402006417F6A22062004417F6A22042D00003A00002001417F6A22010D00" + "0B0B200B4103490D002004417C6A210103402006417F6A200141036A2D00003A0000200641" + "7E6A200141026A2D00003A00002006417D6A200141016A2D00003A00002006417C6A220620" + "012D00003A00002001417C6A210120092006490D000B0B20000B0E00200020012002109880" + "8080000BAA0301057F02400240200241104F0D00200021030C010B02402000200041002000" + "6B41037122046A22054F0D002004417F6A21062000210302402004450D0020042107200021" + "030340200320013A0000200341016A21032007417F6A22070D000B0B20064107490D000340" + "200320013A0000200341076A20013A0000200341066A20013A0000200341056A20013A0000" + "200341046A20013A0000200341036A20013A0000200341026A20013A0000200341016A2001" + "3A0000200341086A22032005470D000B0B024020052005200220046B2202417C716A22034F" + "0D00200141FF017141818284086C2107034020052007360200200541046A22052003490D00" + "0B0B200241037121020B02402003200320026A22074F0D002002417F6A2104024020024107" + "712205450D000340200320013A0000200341016A21032005417F6A22050D000B0B20044107" + "490D000340200320013A0000200341076A20013A0000200341066A20013A0000200341056A" + "20013A0000200341046A20013A0000200341036A20013A0000200341026A20013A00002003" + "41016A20013A0000200341086A22032007470D000B0B20000B0E0020002001200210998080" + "80000B0BA10A0100418080C0000B970A55494E54382056616C75653A55494E543820486578" + "3A55494E5431362056616C75653A55494E543136204865783A55494E543332204865783A55" + "494E5436342056616C75653A55494E543634204865783A55494E54313238204865783A0000" + "000000000000000000000000000155494E54313630204865783A55494E5431393220486578" + "3A55494E54323536204865783A4143434F554E542056616C75653A414D4F554E542056616C" + "75652028585250293A414D4F554E54204865783A414D4F554E542056616C75652028494F55" + "293A414D4F554E542056616C75652028494F5529202D204F726967696E616C3A494F552049" + "73737565723A494F552043757272656E63793AD4838D7EA4C68000414D4F554E542056616C" + "75652028494F5529202D20416674657220616464696E6720313A4572726F7220616464696E" + "6720464C4F41545F4F4E4520746F20494F5520616D6F756E742C20726573756C743A000000" + "0000000000494F5520416D6F756E743A4E554D4245522056616C75653A4E554D424552204D" + "616E74697373613A4E554D424552204578706F6E656E743A4E554D424552206173204F7061" + "717565466C6F61743A4E554D424552204F7061717565466C6F6174204865783A4E554D4245" + "5220506172616D65746572204572726F723A20496E76616C69642056616C75654E554D4245" + "5220506172616D65746572204572726F7220436F64653A414D4F554E542E494F5520506172" + "616D65746572204572726F723A20496E76616C69642054797065414D4F554E5420494F5520" + "506172616D65746572204572726F7220436F64653A414D4F554E542E58525020506172616D" + "65746572204572726F723A20496E76616C69642056616C7565414D4F554E542E5852502050" + "6172616D65746572204572726F723A20496E76616C69642054797065414D4F554E54205852" + "5020506172616D65746572204572726F7220436F64653A4143434F554E5420506172616D65" + "746572204572726F723A20496E76616C69642056616C75654143434F554E5420506172616D" + "65746572204572726F7220436F64653A55494E5432353620506172616D6574657220457272" + "6F723A20496E76616C69642056616C756555494E5432353620506172616D65746572204572" + "726F7220436F64653A55494E5431393220506172616D65746572204572726F723A20496E76" + "616C69642056616C756555494E5431393220506172616D65746572204572726F7220436F64" + "653A55494E5431363020506172616D65746572204572726F723A20496E76616C6964205661" + "6C756555494E5431363020506172616D65746572204572726F7220436F64653A55494E5431" + "323820506172616D65746572204572726F723A20496E76616C69642056616C756555494E54" + "31323820506172616D65746572204572726F7220436F64653A55494E54363420506172616D" + "65746572204572726F723A20496E76616C69642056616C756555494E54363420506172616D" + "65746572204572726F7220436F64653A55494E54333220506172616D65746572204572726F" + "723A20496E76616C69642056616C756555494E54333220506172616D65746572204572726F" + "7220436F64653A55494E54313620506172616D65746572204572726F723A20496E76616C69" + "642056616C756555494E54313620506172616D65746572204572726F7220436F64653A5549" + "4E543820506172616D65746572204572726F723A20496E76616C69642056616C756555494E" + "543820506172616D65746572204572726F7220436F64653A"; + +extern std::string const contractDataWasmHex = + "0061736D0100000001430760057F7F7F7F7F017F60067F7F7F7F7F7F017F60037F7F7E017F" + "60087F7F7F7F7F7F7F7F017F60077F7F7F7F7F7F7F017F60097F7F7F7F7F7F7F7F7F017F60" + "00017F02E7020A08686F73745F6C6962057472616365000008686F73745F6C696215736574" + "5F646174615F6F626A6563745F6669656C64000108686F73745F6C6962156765745F646174" + "615F6F626A6563745F6669656C64000108686F73745F6C69620974726163655F6E756D0002" + "08686F73745F6C69621C7365745F646174615F6E65737465645F6F626A6563745F6669656C" + "64000308686F73745F6C69621C6765745F646174615F6E65737465645F6F626A6563745F66" + "69656C64000308686F73745F6C69621C7365745F646174615F61727261795F656C656D656E" + "745F6669656C64000408686F73745F6C69621C6765745F646174615F61727261795F656C65" + "6D656E745F6669656C64000408686F73745F6C6962237365745F646174615F6E6573746564" + "5F61727261795F656C656D656E745F6669656C64000508686F73745F6C6962236765745F64" + "6174615F6E65737465645F61727261795F656C656D656E745F6669656C6400050309080606" + "06060606060605030100110619037F01418080C0000B7F0041C499C0000B7F0041D099C000" + "0B07FF010B066D656D6F72790200146F626A6563745F73696D706C655F637265617465000A" + "146F626A6563745F73696D706C655F757064617465000B146F626A6563745F6E6573746564" + "5F637265617465000C146F626A6563745F6E65737465645F757064617465000D196F626A65" + "63745F776974685F6172726179735F637265617465000E196F626A6563745F776974685F61" + "72726179735F757064617465000F206F626A6563745F776974685F6E65737465645F617272" + "6179735F6372656174650010206F626A6563745F776974685F6E65737465645F6172726179" + "735F75706461746500110A5F5F646174615F656E6403010B5F5F686561705F626173650302" + "0AA83408C80902027F017E23808080800041306B2200248080808000418080C08000412441" + "00410041001080808080001A200041106A41002800B480C08000360200200041086A410029" + "00AC80C08000370300200041002900A480C0800037030041B880C08000410D410041004100" + "1080808080001A20004190D4003B00182000411441C580C080004108200041186A41021081" + "808080001A200041003A00180240024002400240024002402000411441C580C08000410820" + "0041186A41011082808080004101470D0041CD80C08000410D20003100181083808080001A" + "41DA80C08000410E4100410041001080808080001A200041D2013A001A20004181083B0018" + "2000411441E880C080004109200041186A41031081808080001A200041003B011820004114" + "41E880C080004109200041186A41021082808080004102470D0141F180C08000410E20002F" + "01182201410874200141087672AD42FFFF03831083808080001A41FF80C08000410E410041" + "0041001080808080001A200041033A001C2000410236001820004114418D81C08000410520" + "0041186A41051081808080001A2000410C3A001C2000410236001820004114419281C08000" + "4105200041186A41051081808080001A2000410036021820004114418D81C0800041052000" + "41186A41041082808080004104470D02419781C08000411020002802182201411874200141" + "80FE03714108747220014108764180FE037120014118767272AD1083808080001A41A781C0" + "8000410E4100410041001080808080001A20004280808090C089AC8B6A370019200041033A" + "00182000411441B581C080004109200041186A41091081808080001A200042003703182000" + "411441B581C080004109200041186A41081082808080004108470D0341BE81C08000410E20" + "00290318220242388620024280FE0383422886842002428080FC0783421886200242808080" + "F80F834208868484200242088842808080F80F832002421888428080FC0783842002422888" + "4280FE038320024238888484841083808080001A41CC81C080004114410041004100108080" + "8080001A200041226A41002900E881C080003700002000412A6A41002800F081C080003600" + "0020004188283B0018200041002900E081C0800037001A2000411441F481C08000410B2000" + "41186A41161081808080001A200041256A4200370000200041206A42003703002000420037" + "03182000411441F481C08000410B200041186A41151082808080004115470D0441FF81C080" + "0041204100410041001080808080001A419F82C08000411B4100410041001080808080001A" + "2000410036021802402000411441BA82C08000410B200041186A4104108280808000410447" + "0D0041C582C08000412C4100410041001080808080001A417F21010C060B4100210141F182" + "C08000412B4100410041001080808080001A419C83C0800041224100410041001080808080" + "001A0C050B41A284C0800041164100410041001080808080001A417F21010C040B418B84C0" + "800041174100410041001080808080001A417F21010C030B41F283C0800041194100410041" + "001080808080001A417F21010C020B41DB83C0800041174100410041001080808080001A41" + "7F21010C010B41BE83C08000411D4100410041001080808080001A417F21010B200041306A" + "24808080800020010BFA0401037F23808080800041206B220024808080800041B884C08000" + "41244100410041001080808080001A200041106A41002800B480C08000360200200041086A" + "41002900AC80C08000370300200041002900A480C0800037030041DC84C080004114410041" + "0041001080808080001A20004190C6013B00182000411441C580C080004108200041186A41" + "021081808080001A200041003A001802400240024002402000411441C580C0800041082000" + "41186A41011082808080004101470D0041F084C08000411520003100181083808080001A41" + "8585C0800041164100410041001080808080001A200041043A001C20004102360018200041" + "14418D81C080004105200041186A41051081808080001A2000410036021820004114418D81" + "C080004105200041186A41041082808080004104470D01419B85C080004118200028021822" + "0141187420014180FE03714108747220014108764180FE037120014118767272AD10838080" + "80001A4100210141B385C08000411C4100410041001080808080001A200041E4003A001C20" + "0041023600182000411441CF85C080004106200041186A41051081808080001A2000410036" + "021802402000411441CF85C080004106200041186A41041082808080004104470D0041D585" + "C0800041152000280218220241187420024180FE03714108747220024108764180FE037120" + "024118767272AD1083808080001A41EA85C0800041224100410041001080808080001A0C04" + "0B418C86C08000411A4100410041001080808080001A0C020B41A284C08000411641004100" + "41001080808080001A0C010B41F283C0800041194100410041001080808080001A0B417F21" + "010B200041206A24808080800020010BF80702027F017E23808080800041306B2200248080" + "80800041A686C0800041244100410041001080808080001A200041186A41002800B480C080" + "00360200200041106A41002900AC80C08000370300200041002900A480C0800037030841CA" + "86C0800041144100410041001080808080001A20004190183B0020200041086A411441DE86" + "C08000410341E186C080004106200041206A41021084808080001A200041003A0020024002" + "40024002400240200041086A411441DE86C08000410341E186C080004106200041206A4101" + "1085808080004101470D0041E786C08000411720003100201083808080001A41FE86C08000" + "41154100410041001080808080001A2000410F3A0024200041828080B80236002020004108" + "6A4114419387C080004105419887C080004105200041206A41051084808080001A20004100" + "360220200041086A4114419387C080004105419887C080004105200041206A410410858080" + "80004104470D01419D87C0800041152000280220220141187420014180FE03714108747220" + "014108764180FE037120014118767272AD1083808080001A41B287C08000412A4100410041" + "001080808080001A200041053A002420004102360020200041086A4114419387C080004105" + "41DC87C080004105200041206A41051084808080001A200041E8013A002420004182808018" + "360020200041086A4114419387C08000410541E187C080004105200041206A410510848080" + "80001A20004100360220200041086A4114419387C08000410541DC87C08000410520004120" + "6A41041085808080004104470D0241E687C0800041162000280220220141187420014180FE" + "03714108747220014108764180FE037120014118767272AD1083808080001A41FC87C08000" + "41154100410041001080808080001A2000428080808090C9A58152370021200041033A0020" + "200041086A4114419188C080004104419588C080004109200041206A41091084808080001A" + "200042003703200240200041086A4114419188C080004104419588C080004109200041206A" + "41081085808080004108470D00419E88C0800041152000290320220242388620024280FE03" + "83422886842002428080FC0783421886200242808080F80F83420886848420024208884280" + "8080F80F832002421888428080FC07838420024228884280FE038320024238888484841083" + "808080001A4100210141B388C0800041224100410041001080808080001A0C050B41D588C0" + "8000411E4100410041001080808080001A0C030B41B089C080004120410041004100108080" + "8080001A0C020B419289C08000411E4100410041001080808080001A0C010B41F388C08000" + "411F4100410041001080808080001A0B417F21010B200041306A24808080800020010BD405" + "01037F23808080800041206B220024808080800041D089C080004124410041004100108080" + "8080001A200041106A41002800B480C08000360200200041086A41002900AC80C080003703" + "00200041002900A480C0800037030041F489C0800041214100410041001080808080001A20" + "0041393A001C200041828080800336001820004114419387C080004105419887C080004105" + "200041186A41051084808080001A20004100360218024002400240024020004114419387C0" + "80004105419887C080004105200041186A41041085808080004104470D0041958AC0800041" + "1F2000280218220141187420014180FE03714108747220014108764180FE03712001411876" + "7272AD1083808080001A41B48AC08000411E4100410041001080808080001A2000410A3A00" + "1C2000410236001820004114419387C08000410541DC87C080004105200041186A41051084" + "808080001A2000410036021820004114419387C08000410541DC87C080004105200041186A" + "41041085808080004104470D0141D28AC0800041182000280218220141187420014180FE03" + "714108747220014108764180FE037120014118767272AD1083808080001A4100210141EA8A" + "C08000411A4100410041001080808080001A2000411E3A001C200041023600182000411441" + "848BC080004106418A8BC080004107200041186A41051084808080001A2000410036021802" + "402000411441848BC080004106418A8BC080004107200041186A4104108580808000410447" + "0D0041918BC08000411D2000280218220241187420024180FE037141087472200241087641" + "80FE037120024118767272AD1083808080001A41AE8BC08000412241004100410010808080" + "80001A0C040B41D08BC0800041224100410041001080808080001A0C020B418B8CC0800041" + "204100410041001080808080001A0C010B41F28BC080004119410041004100108080808000" + "1A0B417F21010B200041206A24808080800020010BBE0301027F23808080800041206B2200" + "24808080800041AB8CC0800041294100410041001080808080001A200041186A41002800B4" + "80C08000360200200041086A41086A41002900AC80C08000370300200041002900A480C080" + "0037030841D48CC0800041134100410041001080808080001A20004190143B001E20004108" + "6A411441E78CC08000410841002000411E6A41021086808080001A20004190283B001E2000" + "41086A411441E78CC08000410841012000411E6A41021086808080001A200041903C3B001E" + "200041086A411441E78CC08000410841022000411E6A41021086808080001A200041003A00" + "1E0240024002400240200041086A411441E78CC08000410841002000411E6A410110878080" + "80004101460D0041D28DC0800021010C010B41EF8CC080004111200031001E108380808000" + "1A200041003A001E200041086A411441E78CC08000410841012000411E6A41011087808080" + "004101460D0141B88DC0800021010B2001411A4100410041001080808080001A417F21010C" + "010B41808DC080004111200031001E1083808080001A4100210141918DC080004127410041" + "0041001080808080001A0B200041206A24808080800020010BFA0401027F23808080800041" + "206B220024808080800041EC8DC0800041294100410041001080808080001A200041106A41" + "002800B480C08000360200200041086A41002900AC80C08000370300200041002900A480C0" + "800037030041958EC0800041204100410041001080808080001A200041E1003A001C200041" + "828080F0013600182000411441B58EC0800041094100200041186A41051086808080001A20" + "00410036021802400240024002402000411441B58EC0800041094100200041186A41041087" + "808080004104470D0041BE8EC08000411F2000280218220141187420014180FE0371410874" + "7220014108764180FE037120014118767272AD1083808080001A41DD8EC080004128410041" + "0041001080808080001A2000412C3A001A20004181023B00182000411441858FC080004109" + "4102200041186A41031086808080001A200041003B01182000411441858FC0800041094102" + "200041186A41021087808080004102470D01418E8FC08000411B20002F0118220141087420" + "0141087672AD42FFFF03831083808080001A4100210141A98FC08000412C41004100410010" + "80808080001A20004190E4003B00182000411441E78CC0800041084105200041186A410210" + "86808080001A200041003A001802402000411441E78CC0800041084105200041186A410110" + "87808080004101470D0041D58FC08000411620003100181083808080001A41EB8FC0800041" + "274100410041001080808080001A0C040B419290C08000411F410041004100108080808000" + "1A0C020B41D190C0800041204100410041001080808080001A0C010B41B190C08000412041" + "00410041001080808080001A0B417F21010B200041206A24808080800020010B9F0902027F" + "017E23808080800041306B220024808080800041F190C08000413041004100410010808080" + "80001A200041186A41002800B480C08000360200200041106A41002900AC80C08000370300" + "200041002900A480C0800037030841A191C08000411A4100410041001080808080001A2000" + "4190EE003B0020200041086A411441BB91C08000410C410041C791C080004106200041206A" + "41021088808080001A2000419084013B0020200041086A411441BB91C08000410C410041CD" + "91C080004106200041206A41021088808080001A200041909A013B0020200041086A411441" + "BB91C08000410C410141C791C080004106200041206A41021088808080001A200041003A00" + "20024002400240024002400240200041086A411441BB91C08000410C410041C791C0800041" + "06200041206A41011089808080004101470D0041D391C08000411C20003100201083808080" + "001A200041003A0020200041086A411441BB91C08000410C410041CD91C080004106200041" + "206A41011089808080004101470D0141EF91C08000411C20003100201083808080001A2000" + "41003A0020200041086A411441BB91C08000410C410141C791C080004106200041206A4101" + "1089808080004101470D02418B92C08000411C20003100201083808080001A41A792C08000" + "411B4100410041001080808080001A200041B3013A0024200041828080A801360020200041" + "086A411441C292C080004110410041D292C080004105200041206A41051088808080001A20" + "00410A3A0024200041828080D001360020200041086A411441C292C080004110410141D292" + "C080004105200041206A41051088808080001A20004100360220200041086A411441C292C0" + "80004110410041D292C080004105200041206A41041089808080004104470D0341D792C080" + "00411F2000280220220141187420014180FE03714108747220014108764180FE0371200141" + "18767272AD1083808080001A4100210141F692C08000411B4100410041001080808080001A" + "2000428080808080A080C39F7F370021200041033A0020200041086A4114419193C0800041" + "054100419693C080004102200041206A41091088808080001A2000428080808080A080F1C0" + "00370021200041033A0020200041086A4114419193C0800041054100419893C08000410520" + "0041206A41091088808080001A200042003703200240200041086A4114419193C080004105" + "4100419693C080004102200041206A41081089808080004108470D00419D93C08000411120" + "00290320220242388620024280FE0383422886842002428080FC0783421886200242808080" + "F80F834208868484200242088842808080F80F832002421888428080FC0783842002422888" + "4280FE038320024238888484841083808080001A41AE93C08000412E410041004100108080" + "8080001A0C060B41DC93C08000411A4100410041001080808080001A0C040B41E894C08000" + "41254100410041001080808080001A0C030B41C394C0800041254100410041001080808080" + "001A0C020B419E94C0800041254100410041001080808080001A0C010B41F693C080004128" + "4100410041001080808080001A0B417F21010B200041306A24808080800020010BB2060102" + "7F23808080800041206B2200248080808000418D95C0800041304100410041001080808080" + "001A200041106A41002800B480C08000360200200041086A41002900AC80C0800037030020" + "0041002900A480C0800037030041BD95C0800041284100410041001080808080001A200041" + "90B0013B00182000411441BB91C08000410C410041C791C080004106200041186A41021088" + "808080001A200041003A0018024002400240024002402000411441BB91C08000410C410041" + "C791C080004106200041186A41011089808080004101470D0041E595C08000412920003100" + "181083808080001A418E96C0800041234100410041001080808080001A20004190DE013B00" + "182000411441BB91C08000410C410041B196C080004106200041186A41021088808080001A" + "200041003A00182000411441BB91C08000410C410041B196C080004106200041186A410110" + "89808080004101470D0141B796C08000412120003100181083808080001A41D896C0800041" + "194100410041001080808080001A20004190C6013B00182000411441BB91C08000410C4102" + "41C791C080004106200041186A41021088808080001A200041003A00182000411441BB91C0" + "8000410C410241C791C080004106200041186A41011089808080004101470D0241F196C080" + "00412120003100181083808080001A419297C08000412D4100410041001080808080001A20" + "0041B8013A001C20004182808090023600182000411441C292C080004110410141D292C080" + "004105200041186A41051088808080001A2000410036021802402000411441C292C0800041" + "10410141D292C080004105200041186A41041089808080004104470D0041BF97C08000412C" + "2000280218220141187420014180FE03714108747220014108764180FE0371200141187672" + "72AD1083808080001A4100210141EB97C08000412E4100410041001080808080001A0C050B" + "419998C08000412D4100410041001080808080001A0C030B419A99C08000412A4100410041" + "001080808080001A0C020B41F098C08000412A4100410041001080808080001A0C010B41C6" + "98C08000412A4100410041001080808080001A0B417F21010B200041206A24808080800020" + "010B0BCE190100418080C0000BC4193D3D3D205445535420313A2053696D706C65204F626A" + "65637420437265617465203D3D3DAE123A8556F3CF91154711376AFB0F894F832B3D546573" + "74696E672075382E2E2E76616C75655F753852656164206261636B2075383A54657374696E" + "67207531362E2E2E76616C75655F75313652656164206261636B207531363A54657374696E" + "67207533322E2E2E636F756E74746F74616C52656164206261636B20636F756E743A546573" + "74696E67207536342E2E2E76616C75655F75363452656164206261636B207536343A546573" + "74696E67204163636F756E7449442E2E2E0596915CFDEEE3A695B3EFD6BDA9AC788A368B7B" + "64657374696E6174696F6E52656164206261636B204163636F756E74494420737563636573" + "7366756C6C7954657374696E67206E6F6E2D6578697374656E74206B65792E2E2E6E6F6E65" + "78697374656E744552524F523A2053686F756C64206E6F74206861766520666F756E64206E" + "6F6E6578697374656E74206B6579436F72726563746C792072657475726E6564204E6F6E65" + "20666F72206E6F6E6578697374656E74206B657953696D706C65206F626A65637420637265" + "61746520746573747320706173736564214661696C656420746F2072656164206261636B20" + "4163636F756E7449444661696C656420746F2072656164206261636B207536344661696C65" + "6420746F2072656164206261636B20636F756E744661696C656420746F2072656164206261" + "636B207531364661696C656420746F2072656164206261636B2075383D3D3D205445535420" + "313A2053696D706C65204F626A65637420557064617465203D3D3D5570646174696E672075" + "3820746F2039392E2E2E52656164206261636B20757064617465642075383A557064617469" + "6E6720636F756E7420746F20342E2E2E52656164206261636B207570646174656420636F75" + "6E743A416464696E67206E6577206669656C642027737461747573272E2E2E737461747573" + "52656164206261636B206E6577207374617475733A53696D706C65206F626A656374207570" + "6461746520746573747320706173736564214661696C656420746F2072656164206261636B" + "207374617475733D3D3D205445535420323A204E6573746564204F626A6563742043726561" + "7465203D3D3D54657374696E67206E65737465642075382E2E2E6B65797375626B65795265" + "6164206261636B206E65737465642076616C75653A54657374696E67206E65737465642075" + "33322E2E2E737461747373636F726552656164206261636B206E6573746564207533323A41" + "6464696E67206D756C7469706C65206669656C647320746F206E6573746564206F626A6563" + "742E2E2E6C6576656C636F696E7352656164206261636B2073746174732E6C6576656C3A54" + "657374696E67206E6573746564207536342E2E2E6461746174696D657374616D7052656164" + "206261636B206E6573746564207536343A4E6573746564206F626A65637420637265617465" + "20746573747320706173736564214661696C656420746F2072656164206261636B206E6573" + "746564207536344661696C656420746F2072656164206261636B2073746174732E6C657665" + "6C4661696C656420746F2072656164206261636B206E6573746564207533324661696C6564" + "20746F2072656164206261636B206E65737465642076616C75653D3D3D205445535420323A" + "204E6573746564204F626A65637420557064617465203D3D3D5570646174696E67206E6573" + "7465642073636F726520746F2031323334352E2E2E52656164206261636B20757064617465" + "64206E65737465642073636F72653A5570646174696E67206E6573746564206C6576656C20" + "746F2031302E2E2E52656164206261636B2075706461746564206C6576656C3A416464696E" + "67206E6577206E6573746564206669656C642E2E2E636F6E66696774696D656F7574526561" + "64206261636B206E657720636F6E6669672E74696D656F75743A4E6573746564206F626A65" + "63742075706461746520746573747320706173736564214661696C656420746F2072656164" + "206261636B20636F6E6669672E74696D656F75744661696C656420746F2072656164206261" + "636B206C6576656C4661696C656420746F2072656164206261636B206E6573746564207363" + "6F72653D3D3D205445535420333A204F626A65637420776974682041727261797320437265" + "617465203D3D3D54657374696E672075382061727261792E2E2E61727261795F7538526561" + "642061727261795F75385B305D3A526561642061727261795F75385B315D3A4F626A656374" + "2077697468206172726179732063726561746520746573747320706173736564214661696C" + "656420746F20726561642061727261795F75385B315D4661696C656420746F207265616420" + "61727261795F75385B305D3D3D3D205445535420333A204F626A6563742077697468204172" + "7261797320557064617465203D3D3D5570646174696E672061727261795F7533325B305D20" + "746F20373737372E2E2E61727261795F75333252656164206261636B207570646174656420" + "61727261795F7533325B305D3A416464696E67206E657720617272617920656C656D656E74" + "2061727261795F7531365B325D2E2E2E61727261795F75313652656164206261636B206E65" + "772061727261795F7531365B325D3A416464696E672061727261795F75385B355D2028736B" + "697070696E6720696E646963657320332D34292E2E2E52656164206261636B206172726179" + "5F75385B355D3A4F626A656374207769746820617272617973207570646174652074657374" + "7320706173736564214661696C656420746F2072656164206261636B2061727261795F7538" + "5B355D4661696C656420746F2072656164206261636B2061727261795F7531365B325D4661" + "696C656420746F2072656164206261636B2061727261795F7533325B305D3D3D3D20544553" + "5420343A204F626A6563742077697468204E65737465642041727261797320437265617465" + "203D3D3D54657374696E67206E65737465642075382061727261792E2E2E6E65737465645F" + "61727261796669656C64316669656C643252656164206E65737465645F61727261795B305D" + "2E6669656C64313A52656164206E65737465645F61727261795B305D2E6669656C64323A52" + "656164206E65737465645F61727261795B315D2E6669656C64313A54657374696E67206E65" + "73746564207533322061727261792E2E2E6E65737465645F61727261795F75333276616C75" + "6552656164206E65737465645F61727261795F7533325B305D2E76616C75653A5465737469" + "6E67206E6573746564207536342061727261792E2E2E6974656D7369647072696365526561" + "64206974656D735B305D2E69643A4F626A6563742077697468206E65737465642061727261" + "79732063726561746520746573747320706173736564214661696C656420746F2072656164" + "206974656D735B305D2E69644661696C656420746F2072656164206E65737465645F617272" + "61795F7533325B305D2E76616C75654661696C656420746F2072656164206E65737465645F" + "61727261795B315D2E6669656C64314661696C656420746F2072656164206E65737465645F" + "61727261795B305D2E6669656C64324661696C656420746F2072656164206E65737465645F" + "61727261795B305D2E6669656C64313D3D3D205445535420343A204F626A65637420776974" + "68204E65737465642041727261797320557064617465203D3D3D5570646174696E67206E65" + "737465645F61727261795B305D2E6669656C643120746F2038382E2E2E5265616420626163" + "6B2075706461746564206E65737465645F61727261795B305D2E6669656C64313A41646469" + "6E67206669656C643320746F206E65737465645F61727261795B305D2E2E2E6669656C6433" + "52656164206261636B206E65737465645F61727261795B305D2E6669656C64333A41646469" + "6E67206E65737465645F61727261795B325D2E2E2E52656164206261636B206E6573746564" + "5F61727261795B325D2E6669656C64313A5570646174696E67206E65737465645F61727261" + "795F7533325B315D2E76616C756520746F20383838382E2E2E52656164206261636B207570" + "6461746564206E65737465645F61727261795F7533325B315D2E76616C75653A4F626A6563" + "742077697468206E6573746564206172726179732075706461746520746573747320706173" + "736564214661696C656420746F2072656164206261636B206E65737465645F61727261795F" + "7533325B315D2E76616C75654661696C656420746F2072656164206261636B206E65737465" + "645F61727261795B325D2E6669656C64314661696C656420746F2072656164206261636B20" + "6E65737465645F61727261795B305D2E6669656C64334661696C656420746F207265616420" + "6261636B206E65737465645F61727261795B305D2E6669656C6431"; + +extern std::string const eventsWasmHex = + "0061736D0100000001140460047F7F7F7F017F6000017F60017F00600000021701" + "08686F73745F6C69620A656D69745F6576656E7400000304030102030503010011" + "0619037F01418080C0000B7F00419181C0000B7F0041A081C0000B072E04066D65" + "6D6F72790200066576656E747300010A5F5F646174615F656E6403010B5F5F6865" + "61705F6261736503020AB12603A02601077F2380808080004190086B2200248080" + "8080002000410136028C08200041800C3B01042000420237028408410021014102" + "210202400240024002400240024002400240024002400240024002400240024002" + "400240024002400240024002400240024002400240024002400240024002400240" + "024002400240024002400240024002400240024002400240024002400240024002" + "400240024002400240024002400240024002400240024002400240024002400240" + "0240024002400240024002400340024020014106470D002000200241066A220136" + "02840820014180084F0D03200041046A20016A41093A0000200020002802840841" + "016A22013602840820014180084F0D04200041046A20016A41063A000020002000" + "2802840841016A22023602840841002101034020014108460D03200220016A2202" + "4180084F0D06200041046A20026A2001418680C080006A2D00003A000020014101" + "6A210120002802840821020C000B0B200220016A22024180084F0D05200041046A" + "20026A2001418080C080006A2D00003A0000200141016A21012000280284082102" + "0C000B0B2000200241086A220136028408417F2103200241A6786A41FF77490D45" + "20014180084F0D04200041046A20016A41083A0000200020002802840841016A22" + "02360284084100210102400340024020014108470D002000200241086A22013602" + "840820014180084F0D08200041046A20016A41153A000020002000280284084101" + "6A22013602840820014180084F0D09200041046A20016A411A3A00002000200028" + "02840841016A22023602840841002101034020014114460D03200220016A220241" + "80084F0D0B200041046A20026A2001419680C080006A2D00003A0000200141016A" + "210120002802840821020C000B0B200220016A22024180084F0D0A200041046A20" + "026A2001418E80C080006A2D00003A0000200141016A210120002802840821020C" + "000B0B2000200241146A220136028408200241B6786A41FF77490D452001418008" + "4F0D09200041046A20016A410B3A0000200020002802840841016A220236028408" + "410021010240034002402001410B470D0020002002410B6A220136028408200141" + "80084F0D0D200041046A20016A41163A0000200020002802840841016A22013602" + "840820014180084F0D0E200041046A20016A41083A000020002000280284084101" + "6A22013602840820014180084F0D0F200041046A20016A41143A00002000200028" + "02840841016A22023602840841002101034020014114460D03200220016A220241" + "80084F0D11200041046A20026A200141AA80C080006A2D00003A0000200141016A" + "210120002802840821020C000B0B200220016A22024180084F0D10200041046A20" + "026A200141BE80C080006A2D00003A0000200141016A210120002802840821020C" + "000B0B2000200241146A220136028408200241AD786A41FF77490D452001418008" + "4F0D0F200041046A20016A41073A0000200020002802840841016A220236028408" + "4100210102400340024020014107470D002000200241076A220136028408200141" + "80084F0D13200041046A20016A41113A0000200020002802840841016A22013602" + "840820014180084F0D14200041046A20016A41043A000020002000280284084101" + "6A22023602840841002101034020014110460D03200220016A22024180084F0D16" + "200041046A20026A41003A0000200141016A210120002802840821020C000B0B20" + "0220016A22024180084F0D15200041046A20026A200141C980C080006A2D00003A" + "0000200141016A210120002802840821020C000B0B2000200241106A2201360284" + "082002419A786A41FF77490D4520014180084F0D14200041046A20016A41063A00" + "00200020002802840841016A220236028408410021010240034002402001410647" + "0D002000200241066A22013602840820014180084F0D18200041046A20016A4103" + "3A0000200020002802840841016A22013602840820014180084F0D19200041046A" + "20016A41013A0000200020002802840841016A22013602840820014180084F0D1A" + "200041046A20016A41003A000020002802840841016A2201418008490D02200110" + "8280808000000B200220016A22024180084F0D1A200041046A20026A200141D080" + "C080006A2D00003A0000200141016A210120002802840821020C000B0B20004104" + "6A20016A41103A00002000200028028408220141026A2202360284082001419F78" + "6A41FF77490D4520024180084F0D19200041046A20026A41073A00002000200028" + "02840841016A2202360284084100210102400340024020014107470D0020002002" + "41076A22013602840820014180084F0D1D200041046A20016A41153A0000200020" + "002802840841016A22013602840820014180084F0D1E200041046A20016A41113A" + "0000200020002802840841016A22023602840841002101034020014114460D0320" + "0220016A22024180084F0D20200041046A20026A41003A0000200141016A210120" + "002802840821020C000B0B200220016A22024180084F0D1F200041046A20026A20" + "0141D680C080006A2D00003A0000200141016A210120002802840821020C000B0B" + "2000200241146A220136028408200241B5786A41FF77490D4520014180084F0D1E" + "200041046A20016A41073A0000200020002802840841016A220236028408410021" + "0102400340024020014107470D002000200241076A22013602840820014180084F" + "0D22200041046A20016A41193A0000200020002802840841016A22013602840820" + "014180084F0D23200041046A20016A41153A0000200020002802840841016A2202" + "3602840841002101034020014118460D03200220016A22024180084F0D25200041" + "046A20026A41003A0000200141016A210120002802840821020C000B0B20022001" + "6A22024180084F0D24200041046A20026A200141DD80C080006A2D00003A000020" + "0141016A210120002802840821020C000B0B2000200241186A2201360284082002" + "41C1786A41FF77490D4520014180084F0D23200041046A20016A41073A00002000" + "20002802840841016A2202360284084100210102400340024020014107470D0020" + "00200241076A22013602840820014180084F0D27200041046A20016A41213A0000" + "200020002802840841016A22013602840820014180084F0D28200041046A20016A" + "41053A0000200020002802840841016A2202360284084100210103402001412046" + "0D03200220016A22024180084F0D2A200041046A20026A41003A0000200141016A" + "210120002802840821020C000B0B200220016A22024180084F0D29200041046A20" + "026A200141E480C080006A2D00003A0000200141016A210120002802840821020C" + "000B0B2000200241206A220136028408200241AC786A41FF77490D452001418008" + "4F0D28200041046A20016A41063A0000200020002802840841016A220236028408" + "4100210102400340024020014106470D002000200241066A220136028408200141" + "80084F0D2C200041046A20016A41053A0000200020002802840841016A22013602" + "840820014180084F0D2D200041046A20016A41023A000020002000280284084101" + "6A22013602840820014180084F0D2E200041046A20016A41003A00002000280284" + "0841016A22014180084F0D2F200041046A20016A41003A00002000280284084102" + "6A22014180084F0D30200041046A20016A41003A000020002802840841036A2201" + "418008490D022001108280808000000B200220016A22024180084F0D3020004104" + "6A20026A200141EB80C080006A2D00003A0000200141016A210120002802840821" + "020C000B0B200041046A20016A41203A00002000200028028408220141046A2202" + "3602840820014194786A41FF77490D4520024180084F0D2F200041046A20026A41" + "063A0000200020002802840841016A220236028408410021010240034002402001" + "4106470D002000200241066A22013602840820014180084F0D33200041046A2001" + "6A41093A0000200020002802840841016A22013602840820014180084F0D342000" + "41046A20016A41033A0000200020002802840841016A2201360284082001418008" + "4F0D35200041046A20016A41003A000020002802840841016A22014180084F0D36" + "200041046A20016A41003A000020002802840841026A22014180084F0D37200041" + "046A20016A41003A000020002802840841036A22014180084F0D38200041046A20" + "016A41003A000020002802840841046A22014180084F0D39200041046A20016A41" + "003A000020002802840841056A22014180084F0D3A200041046A20016A41003A00" + "0020002802840841066A22014180084F0D3B200041046A20016A41003A00002000" + "2802840841076A2201418008490D022001108280808000000B200220016A220241" + "80084F0D3B200041046A20026A200141F180C080006A2D00003A0000200141016A" + "210120002802840821020C000B0B200041046A20016A41C0003A00002000200028" + "028408220141086A22023602840820014190786A41FF77490D4520024180084F0D" + "3A200041046A20026A41053A0000200020002802840841016A2202360284084100" + "210102400340024020014105470D002000200241056A2201360284082001418008" + "4F0D3E200041046A20016A41023A0000200020002802840841016A220136028408" + "20014180084F0D3F200041046A20016A41103A0000200020002802840841016A22" + "01360284082001418008490D022001108280808000000B200220016A2202418008" + "4F0D3F200041046A20026A200141F780C080006A2D00003A0000200141016A2101" + "20002802840821020C000B0B200041046A20016A41083A00002000200028028408" + "220141016A22023602840820014193786A41FF77490D4520024180084F0D3E2000" + "41046A20026A41023A0000200020002802840841016A2202360284084100210102" + "400340024020014102470D002000200241026A22013602840820014180084F0D42" + "200041046A20016A410F3A0000200020002802840841016A220136028408200141" + "80084F0D43200041046A20016A41073A0000200020002802840841016A22013602" + "8408200041046A20016A410D3A0000200020002802840841016A22023602840841" + "00210103402001410D460D03200220016A22024180084F0D45200041046A20026A" + "200141FE80C080006A2D00003A0000200141016A210120002802840821020C000B" + "0B200220016A22024180084F0D44200041046A20026A200141FC80C080006A2D00" + "003A0000200141016A210120002802840821020C000B0B20002002410D6A220436" + "0284084101410241032004200028028C0822036B220141C1E100491B200141C101" + "491B22052003460D442002410C6A2101200041046A200520036B22046A21060340" + "0240200141016A220220034B0D00200020002802840820046A2204360284082000" + "200536028C08200420056B21010C460B20024180084B0D440240200420016A2202" + "4180084F0D00200620016A200041046A20016A2D00003A00002001417F6A210120" + "0028028C0821030C010B0B2002108280808000000B2001108280808000000B2001" + "108280808000000B2002108280808000000B2002108280808000000B2001108280" + "808000000B2001108280808000000B2001108280808000000B2002108280808000" + "000B2002108280808000000B2001108280808000000B2001108280808000000B20" + "01108280808000000B2001108280808000000B2002108280808000000B20021082" + "80808000000B2001108280808000000B2001108280808000000B20011082808080" + "00000B2002108280808000000B2002108280808000000B2001108280808000000B" + "2001108280808000000B2001108280808000000B2001108280808000000B200210" + "8280808000000B2002108280808000000B2001108280808000000B200110828080" + "8000000B2002108280808000000B2002108280808000000B200110828080800000" + "0B2001108280808000000B2001108280808000000B2002108280808000000B2002" + "108280808000000B2001108280808000000B2001108280808000000B2001108280" + "808000000B2002108280808000000B2002108280808000000B2001108280808000" + "000B2001108280808000000B2001108280808000000B2001108280808000000B20" + "01108280808000000B2001108280808000000B2002108280808000000B20021082" + "80808000000B2001108280808000000B2001108280808000000B20011082808080" + "00000B2001108280808000000B2001108280808000000B2001108280808000000B" + "2001108280808000000B2001108280808000000B2001108280808000000B200210" + "8280808000000B2002108280808000000B2001108280808000000B200110828080" + "8000000B2002108280808000000B2002108280808000000B200110828080800000" + "0B2001108280808000000B2002108280808000000B2002108280808000000B2001" + "108280808000000B024002400240200141C101490D00200141C1E100490D012001" + "41D989384F0D022000200141BF9E7F6A22013A0006200020014108763A00052000" + "200141107641716A3A00040C020B200020013A00040C010B2000200141BF7E6A22" + "013A00052000200141087641416A3A00040B418B81C080004106200041046A2004" + "1080808080001A410021030B20004190086A24808080800020030B090010838080" + "8000000B0300000B0B9B010100418080C0000B9101616D6F756E74400000000000" + "00C063757272656E63790000000000000000000000005553440000000000596915" + "CFDEEE3A695B3EFD6BDA9AC788A368B70B64657374696E6174696F6E75696E7431" + "323875696E74313675696E7431363075696E7431393275696E7432353675696E74" + "333275696E74363475696E7438766C48656C6C6F2C20576F726C64216576656E74" + "31"; + +extern std::string const emitTxWasmHex = + "0061736D0100000001230660037F7F7F017F60017F017F60047F7F7F7F017F60037F7F7E01" + "7F6000017F60000002760508686F73745F6C69620C6765745F74785F6669656C6400000868" + "6F73745F6C6962096275696C645F74786E000108686F73745F6C69620D6164645F74786E5F" + "6669656C64000208686F73745F6C69620E656D69745F6275696C745F74786E000108686F73" + "745F6C69620974726163655F6E756D00030305040405000004050170010101050301001106" + "19037F01418080C0000B7F0041F280C0000B7F00418081C0000B072C04066D656D6F727902" + "0004656D697400050A5F5F646174615F656E6403010B5F5F686561705F6261736503020AEE" + "0C049D0502037F017E23808080800041B0086B2200248080808000200041C0006A41003602" + "00200041386A420037030020004200370330024041818020200041306A4114108080808000" + "22014114470D002000411E6A20002D00323A00002000200029003737032020002000413C6A" + "290000370025200020002F01303B011C200020002903203703082000200029002537000D20" + "002800332102416E2101024041001081808080004100480D00410041818018418B80C08000" + "41081082808080004100480D002000413D6A200029000D370000200041336A2000411E6A2D" + "00003A0000200041143A0030200020002F011C3B0031200020023600342000200029030837" + "0038410041838020200041306A41151082808080004100480D00200041366A410028009680" + "C08000360000200041306A41146A41002800A280C08000360000200041D2006A41002F00AE" + "80C0800022023B0000200041073A0032200041EAF9013B0030200041FD183B003A200041FE" + "143B0048200042E1D5F3A3E0ED9BBAE500370054200041FD3E3B005C2000410028009380C0" + "80003600332000410029009A80C0800037003C200041002900A680C08000220337004A2000" + "41F5006A41002900C780C08000370000200041EE006A41002900C080C08000370000200041" + "E6006A41002900B880C0800037000020004187016A20023B0000200041FE143B007D200041" + "E1D5F79B0236008901200041002900B080C0800037005E2000200337007F2000418D016A41" + "CF80C0800041231088808080001A200041E1E3033B00B00141004189803C200041306A4182" + "011082808080004100480D0041001083808080002201411F7520017121010B200041B0086A" + "24808080800020010F0B418080C08000410B2001417F2001417F481BAC1084808080001A10" + "8680808000000B0300000BB907010C7F23808080800041106B210302400240200241104F0D" + "00200021040C010B024020002000410020006B41037122056A22064F0D002005417F6A2107" + "200021042001210802402005450D002005210920002104200121080340200420082D00003A" + "0000200841016A2108200441016A21042009417F6A22090D000B0B20074107490D00034020" + "0420082D00003A0000200441016A200841016A2D00003A0000200441026A200841026A2D00" + "003A0000200441036A200841036A2D00003A0000200441046A200841046A2D00003A000020" + "0441056A200841056A2D00003A0000200441066A200841066A2D00003A0000200441076A20" + "0841076A2D00003A0000200841086A2108200441086A22042006470D000B0B200620022005" + "6B2209417C7122076A210402400240200120056A220841037122010D00200620044F0D0120" + "082101034020062001280200360200200141046A2101200641046A22062004490D000C020B" + "0B410021022003410036020C2003410C6A20017221050240410420016B220A410171450D00" + "200520082D00003A0000410121020B0240200A410271450D00200520026A200820026A2F01" + "003B01000B200820016B21022001410374210B200328020C210502400240200641046A2004" + "490D002006210C0C010B4100200B6B411871210D034020062005200B76200241046A220228" + "02002205200D7472360200200641086A210A200641046A220C2106200A2004490D000B0B41" + "002106200341003A0008200341003A00060240024020014101470D00200341086A210D4100" + "21014100210A4100210E0C010B200241056A2D0000210A2003200241046A2D000022013A00" + "08200A410874210A4102210E200341066A210D0B02402008410171450D00200D200241046A" + "200E6A2D00003A000020032D0006411074210620032D000821010B200C200A200672200141" + "FF0171724100200B6B411871742005200B76723602000B20094103712102200820076A2101" + "0B02402004200420026A22064F0D002002417F6A2109024020024107712208450D00034020" + "0420012D00003A0000200141016A2101200441016A21042008417F6A22080D000B0B200941" + "07490D000340200420012D00003A0000200441016A200141016A2D00003A0000200441026A" + "200141026A2D00003A0000200441036A200141036A2D00003A0000200441046A200141046A" + "2D00003A0000200441056A200141056A2D00003A0000200441066A200141066A2D00003A00" + "00200441076A200141076A2D00003A0000200141086A2101200441086A22042006470D000B" + "0B20000B0E002000200120021087808080000B0B7B0100418080C0000B726572726F725F63" + "6F64653D40000000000000C0696E766F696365494E562D323032342D303031746578742F70" + "6C61696E5061796D656E7420666F7220636F6E73756C74696E672073657276696365734164" + "646974696F6E616C207265666572656E63653A2050726F6A65637420416C70686100D20304" + "6E616D65000E0D656D69745F74786E2E7761736D019A030900395F5A4E31337872706C5F77" + "61736D5F73746434686F737431326765745F74785F6669656C643137686437313131323436" + "30313130383830374501355F5A4E31337872706C5F7761736D5F73746434686F7374396275" + "696C645F74786E3137686538306366626434343831333731306145023A5F5A4E3133787270" + "6C5F7761736D5F73746434686F737431336164645F74786E5F6669656C6431376862363630" + "66383563363663633231383945033B5F5A4E31337872706C5F7761736D5F73746434686F73" + "743134656D69745F6275696C745F74786E3137683533323664646138626634656664663345" + "04355F5A4E31337872706C5F7761736D5F73746434686F73743974726163655F6E756D3137" + "6830353133656331363265366461633238450504656D697406305F5A4E34636F7265397061" + "6E69636B696E673970616E69635F666D743137683466316135343338326265366435306645" + "07355F5A4E3137636F6D70696C65725F6275696C74696E73336D656D366D656D6370793137" + "68353161336632613835643832393762374508066D656D637079071201000F5F5F73746163" + "6B5F706F696E746572090A0100072E726F64617461004D0970726F64756365727302086C61" + "6E6775616765010452757374000C70726F6365737365642D6279010572757374631D312E38" + "392E30202832393438333838336520323032352D30382D30342900220F7461726765745F66" + "65617475726573012B0F6D757461626C652D676C6F62616C73"; +extern std::string const updateDataWasmHex = + "0061736d01000000010e0360027f7f017f6000006000017f02130103656e760b7570646174" + "655f64617461000003030201020503010002063f0a7f01419088040b7f004180080b7f0041" + "85080b7f004190080b7f00419088040b7f004180080b7f00419088040b7f00418080080b7f" + "0041000b7f0041010b07aa010c066d656d6f72790200115f5f7761736d5f63616c6c5f6374" + "6f727300010666696e69736800020c5f5f64736f5f68616e646c6503010a5f5f646174615f" + "656e6403020b5f5f737461636b5f6c6f7703030c5f5f737461636b5f6869676803040d5f5f" + "676c6f62616c5f6261736503050b5f5f686561705f6261736503060a5f5f686561705f656e" + "6403070d5f5f6d656d6f72795f6261736503080c5f5f7461626c655f6261736503090a3f02" + "02000b3a01017f230041106b220024002000410c6a4184082d00003a000020004180082800" + "00360208200041086a410410001a200041106a240041807e0b0b0b01004180080b04446174" + "61007f0970726f647563657273010c70726f6365737365642d62790105636c616e675f3139" + "2e312e352d776173692d73646b202868747470733a2f2f6769746875622e636f6d2f6c6c76" + "6d2f6c6c766d2d70726f6a6563742061623462356132646235383239353861663165653330" + "3861373930636664623432626432343732302900490f7461726765745f6665617475726573" + "042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f7265666572656e" + "63652d74797065732b0a6d756c746976616c7565"; diff --git a/src/test/app/wasm_fixtures/fixtures.h b/src/test/app/wasm_fixtures/fixtures.h new file mode 100644 index 0000000000..ac17ed1b9c --- /dev/null +++ b/src/test/app/wasm_fixtures/fixtures.h @@ -0,0 +1,39 @@ +#pragma once + +// TODO: consider moving these to separate files (and figure out the build) + +#include + +extern std::string const ledgerSqnWasmHex; + +extern std::string const allHostFunctionsWasmHex; + +extern std::string const deepRecursionHex; + +extern std::string const fibWasmHex; + +extern std::string const b58WasmHex; + +extern std::string const sha512PureWasmHex; + +extern std::string const hfPerfTest; + +extern std::string const allKeyletsWasmHex; + +extern std::string const codecovTestsWasmHex; + +extern std::string const floatTestsWasmHex; + +extern std::string const float0Hex; + +extern std::string const disabledFloatHex; + +extern std::string const parametersWasmHex; + +extern std::string const contractDataWasmHex; + +extern std::string const eventsWasmHex; + +extern std::string const emitTxWasmHex; + +extern std::string const updateDataWasmHex; diff --git a/src/test/app/wasm_fixtures/float_tests/Cargo.lock b/src/test/app/wasm_fixtures/float_tests/Cargo.lock new file mode 100644 index 0000000000..175adf0254 --- /dev/null +++ b/src/test/app/wasm_fixtures/float_tests/Cargo.lock @@ -0,0 +1,171 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "float_tests" +version = "0.0.1" +dependencies = [ + "xrpl-wasm-stdlib", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "xrpl-address-macro" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e" +dependencies = [ + "bs58", + "quote", + "sha2", + "syn", +] + +[[package]] +name = "xrpl-wasm-stdlib" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#d27d3e0b4abf3c0215aade729d89053805efe48e" +dependencies = [ + "xrpl-address-macro", +] diff --git a/src/test/app/wasm_fixtures/float_tests/Cargo.toml b/src/test/app/wasm_fixtures/float_tests/Cargo.toml new file mode 100644 index 0000000000..b369cac501 --- /dev/null +++ b/src/test/app/wasm_fixtures/float_tests/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "float_tests" +version = "0.0.1" +edition = "2024" + +# This empty workspace definition keeps this project independent of the parent workspace +[workspace] + +[lib] +crate-type = ["cdylib"] + +[profile.release] +lto = true +opt-level = 's' +panic = "abort" + +[dependencies] +xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-wasm-stdlib" } + +[profile.dev] +panic = "abort" diff --git a/src/test/app/wasm_fixtures/float_tests/src/lib.rs b/src/test/app/wasm_fixtures/float_tests/src/lib.rs new file mode 100644 index 0000000000..fc8a1ff5cb --- /dev/null +++ b/src/test/app/wasm_fixtures/float_tests/src/lib.rs @@ -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 +} diff --git a/src/test/app/wasm_fixtures/ledgerSqn.c b/src/test/app/wasm_fixtures/ledgerSqn.c new file mode 100644 index 0000000000..3458b02872 --- /dev/null +++ b/src/test/app/wasm_fixtures/ledgerSqn.c @@ -0,0 +1,27 @@ +#include + +int32_t +get_ledger_sqn(); +// int32_t trace(uint8_t const*, int32_t, uint8_t const*, int32_t, int32_t); +// int32_t trace_num(uint8_t const*, int32_t, int64_t); + +// uint8_t buf[1024]; + +// char const test_res[] = "sqn: "; +// char const test_name[] = "TEST get_ledger_sqn"; + +int +finish() +{ + // trace((uint8_t const *)test_name, sizeof(test_name) - 1, 0, 0, 0); + + // memset(buf, 0, sizeof(buf)); + // for(int i = 0; i < sizeof(buf); ++i) buf[i] = 0; + + int x = get_ledger_sqn(); + // if (x >= 0) + // x = *((int32_t*)buf); + // trace_num((uint8_t const *)test`_res, sizeof(test_res) - 1, x); + + return x < 0 ? x : (x >= 5 ? x : 0); +} diff --git a/src/test/app/wasm_fixtures/parameters/Cargo.lock b/src/test/app/wasm_fixtures/parameters/Cargo.lock new file mode 100644 index 0000000000..c42d205b52 --- /dev/null +++ b/src/test/app/wasm_fixtures/parameters/Cargo.lock @@ -0,0 +1,15 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "parameters" +version = "0.0.1" +dependencies = [ + "xrpl-wasm-std", +] + +[[package]] +name = "xrpl-wasm-std" +version = "0.5.1-devnet5" +source = "git+https://github.com/Transia-RnD/craft.git?branch=dangell%2Fsmart-contracts#3c8191ae9832ea25f7d8f3e5eeb33b65181d31b5" diff --git a/src/test/app/wasm_fixtures/parameters/Cargo.toml b/src/test/app/wasm_fixtures/parameters/Cargo.toml new file mode 100644 index 0000000000..ab2406a30f --- /dev/null +++ b/src/test/app/wasm_fixtures/parameters/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "parameters" +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-wasm-std = { git = "https://github.com/Transia-RnD/craft.git", branch = "dangell/smart-contracts", package = "xrpl-wasm-std" } + +[profile.dev] +panic = "abort" diff --git a/src/test/app/wasm_fixtures/parameters/src/lib.rs b/src/test/app/wasm_fixtures/parameters/src/lib.rs new file mode 100644 index 0000000000..d53c1f1717 --- /dev/null +++ b/src/test/app/wasm_fixtures/parameters/src/lib.rs @@ -0,0 +1,717 @@ +#![allow(unused_imports)] +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +use xrpl_wasm_std::host::trace::{DataRepr, trace, trace_data, trace_num, trace_float}; +use xrpl_wasm_std::host::{instance_param, function_param}; +use xrpl_wasm_std::core::params::function::{get_function_param}; +use xrpl_wasm_std::core::params::instance::{get_instance_param}; +use xrpl_wasm_std::core::type_codes::{ + STI_UINT8, STI_UINT16, STI_UINT32, STI_UINT64, STI_UINT128, + STI_UINT160, STI_UINT192, STI_UINT256, STI_AMOUNT, STI_VL, STI_ACCOUNT, + STI_OBJECT, STI_ARRAY, STI_CURRENCY, STI_NUMBER +}; +use xrpl_wasm_std::core::types::amount::opaque_float::OpaqueFloat; +use xrpl_wasm_std::core::types::number::Number; +use xrpl_wasm_std::core::types::account_id::AccountID; +use xrpl_wasm_std::core::types::amount::token_amount::TokenAmount; +use xrpl_wasm_std::host::{FLOAT_ROUNDING_MODES_TO_NEAREST, float_add, float_set}; +use xrpl_wasm_std::core::types::amount::opaque_float::{FLOAT_NEGATIVE_ONE, FLOAT_ONE}; +use xrpl_wasm_std::core::types::uint_160::UInt160; +use xrpl_wasm_std::core::types::uint_192::UInt192; +use xrpl_wasm_std::core::types::hash_256::Hash256; + + +#[unsafe(no_mangle)] +pub extern "C" fn function_params() -> i32 { + // UINT8 + let value = match get_function_param::(0) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT8 Parameter Error Code:", err as i64); + return -1; + } + }; + let _ = trace_num("UINT8 Value:", value as i64); + // as hex + let _ = trace_data("UINT8 Hex:", &[value], DataRepr::AsHex); + + // TODO: replace with require + if value != 255 { + let _ = trace("UINT8 Parameter Error: Invalid Value"); + return -1; + } + + // UINT16 + let value = match get_function_param::(1) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT16 Parameter Error Code:", err as i64); + return -1; + } + }; + let _ = trace_num("UINT16 Value:", value as i64); + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT16 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if value != 65535 { + let _ = trace("UINT16 Parameter Error: Invalid Value"); + return -1; + } + + // UINT32 + let value = match get_function_param::(2) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT32 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT32 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if value != 4294967295 { + let _ = trace("UINT32 Parameter Error: Invalid Value"); + return -1; + } + + // UINT64 + let value = match get_function_param::(3) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT64 Parameter Error Code:", err as i64); + return -1; + } + }; + let _ = trace_num("UINT64 Value:", value as i64); + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT64 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if buf != [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] { + let _ = trace("UINT64 Parameter Error: Invalid Value"); + return -1; + } + + // UINT128 + let value = match get_function_param::(4) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT128 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT128 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if buf != [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01] { + let _ = trace("UINT128 Parameter Error: Invalid Value"); + return -1; + } + + // UINT160 + let value = match get_function_param::(5) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT160 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.as_bytes(); + let _ = trace_data("UINT160 Hex:", buf, DataRepr::AsHex); + + // TODO: replace with require + let expected190: [u8; 20] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + ]; + if *buf != expected190 { + let _ = trace("UINT160 Parameter Error: Invalid Value"); + return -1; + } + + // UINT192 + let value = match get_function_param::(6) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT192 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.as_bytes(); + let _ = trace_data("UINT192 Hex:", buf, DataRepr::AsHex); + + // TODO: replace with require + let expected192: [u8; 24] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + ]; + if *buf != expected192 { + let _ = trace("UINT192 Parameter Error: Invalid Value"); + return -1; + } + + // UINT256 + let value = match get_function_param::(7) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT256 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.as_bytes(); + let _ = trace_data("UINT256 Hex:", buf, DataRepr::AsHex); + + // TODO: replace with require + let expected256: [u8; 32] = [ + 0xD9, 0x55, 0xDA, 0xC2, 0xE7, 0x75, 0x19, 0xF0, + 0x5A, 0xD1, 0x51, 0xA5, 0xD3, 0xC9, 0x9F, 0xC8, + 0x12, 0x5F, 0xB3, 0x9D, 0x58, 0xFF, 0x9F, 0x10, + 0x6F, 0x1A, 0xCA, 0x44, 0x91, 0x90, 0x2C, 0x25 + ]; + if *buf != expected256 { + let _ = trace("UINT256 Parameter Error: Invalid Value"); + return -1; + } + + // // VL + // let mut buf = [0x00; 4]; + // let output_len = unsafe { function_param(8, STI_VL.into(), buf.as_mut_ptr(), buf.len()) }; + // let _ = trace_num("VL Value Len:", output_len as i64); + // // as hex + // let _ = trace_data("VL Hex:", &buf[0..4], DataRepr::AsHex); + + // ACCOUNT + let account_id = match get_function_param::(9) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("ACCOUNT Parameter Error Code:", err as i64); + return -1; + } + }; + // trace the value + let _ = trace_data("ACCOUNT Value:", &account_id.0, DataRepr::AsHex); + + // TODO: replace with require + let expectedAccount: [u8; 20] = [ + 0xAE, 0x12, 0x3A, 0x85, 0x56, 0xF3, 0xCF, 0x91, + 0x15, 0x47, 0x11, 0x37, 0x6A, 0xFB, 0x0F, 0x89, + 0x4F, 0x83, 0x2B, 0x3D + ]; + if account_id.0 != expectedAccount { + let _ = trace("ACCOUNT Parameter Error: Invalid Value"); + return -1; + } + + // AMOUNT XRP + let xrp_token = match get_function_param::(10) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("AMOUNT XRP Parameter Error Code:", err as i64); + return -1; + } + }; + match xrp_token { + TokenAmount::XRP { num_drops } => { + let _ = trace_num("AMOUNT Value (XRP):", num_drops); + } + _ => { + let _ = trace_num("AMOUNT Value (XRP):", -1); + } + } + let buf = match xrp_token { + TokenAmount::XRP { num_drops } => num_drops.to_le_bytes(), + _ => [0u8; 8], + }; + let _ = trace_data("AMOUNT Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if let TokenAmount::XRP { num_drops } = xrp_token { + if num_drops != 1000000 { + let _ = trace("AMOUNT.XRP Parameter Error: Invalid Value"); + return -1; + } + } else { + let _ = trace("AMOUNT.XRP Parameter Error: Invalid Type"); + return -1; + } + + // AMOUNT IOU + let iou_token = match get_function_param::(11) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("AMOUNT IOU Parameter Error Code:", err as i64); + return -1; + } + }; + let (iou_amount, iou_issuer, iou_currency) = match &iou_token { + TokenAmount::IOU { amount, issuer, currency_code } => { + // trace amount hex + let _ = trace_data("AMOUNT Value (IOU):", &amount.0, DataRepr::AsHex); + let _ = trace_float("AMOUNT Value (IOU) - Original:", &amount.0); + let _ = trace_data("IOU Issuer:", &issuer.0, DataRepr::AsHex); + let _ = trace_data("IOU Currency:", ¤cy_code.0, DataRepr::AsHex); + + // Add FLOAT_ONE to the IOU amount + let mut new_amount: [u8; 8] = [0u8; 8]; + let result = unsafe { + float_add( + amount.0.as_ptr(), + 8, + FLOAT_ONE.as_ptr(), + 8, + new_amount.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + + if result == 8 { + // trace hex of the new amount + let _ = trace_data("AMOUNT Value (IOU) - After adding 1:", &new_amount, DataRepr::AsHex); + let _ = trace_float("AMOUNT Value (IOU) - After adding 1:", &new_amount); + + // Create a new TokenAmount with the updated amount + let updated_token = TokenAmount::IOU { + amount: new_amount.into(), + issuer: *issuer, + currency_code: *currency_code, + }; + + // You now have the updated token amount in `updated_token` + // and the raw float bytes in `new_amount` + + } else { + let _ = trace_num("Error adding FLOAT_ONE to IOU amount, result:", result as i64); + } + + (Some(*amount), Some(*issuer), Some(*currency_code)) + } + _ => { + let _ = trace_data("AMOUNT Value (IOU):", &[0u8; 8], DataRepr::AsHex); + (None, None, None) + } + }; + // trace new iou_amount as hex + if let Some(amount) = iou_amount { + let _ = trace_data("IOU Amount:", &amount.0, DataRepr::AsHex); + } else { + let _ = trace_data("IOU Amount:", &[0u8; 8], DataRepr::AsHex); + } + + // TODO: replace with require + if iou_amount.is_none() { + let _ = trace("AMOUNT.IOU Parameter Error: Invalid Type"); + return -1; + } + + // let mut buf = [0x00; 12]; + // let output_len = unsafe { function_param(12, STI_NUMBER.into(), buf.as_mut_ptr(), buf.len()) }; + // let _ = trace_num("NUMBER Value Len:", output_len as i64); + + // NUMBER + let number = match get_function_param::(12) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("NUMBER Parameter Error Code:", err as i64); + return -1; + } + }; + // trace the value + let buf = number.as_bytes(); + let _ = trace_data("NUMBER Value:", &buf, DataRepr::AsHex); + + // TODO: replace with require + let expectedNumber: [u8; 12] = [ + 0x00, 0x04, 0x43, 0x64, 0xC5, 0xBB, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xF1 + ]; + if buf != expectedNumber { + let _ = trace("NUMBER Parameter Error: Invalid Value"); + return -1; + } + + // // Parse Number to get mantissa and exponent + // let stnumber = Number::from(&buf).unwrap(); + let _ = trace_num("NUMBER Mantissa:", number.mantissa); + let _ = trace_num("NUMBER Exponent:", number.exponent as i64); + + let mut opaque_float_buf = [0x00; 8]; + let result = unsafe { + float_set( + number.exponent, + number.mantissa, + opaque_float_buf.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST + ) + }; + + let opaque = OpaqueFloat::from(opaque_float_buf); + let _ = trace_float("NUMBER as OpaqueFloat:", &opaque.0); + let _ = trace_data("NUMBER OpaqueFloat Hex:", &opaque_float_buf, DataRepr::AsHex); + + // AMOUNT (MPT) + // ISSUE (XRP) + // ISSUE (IOU) + // ISSUE (MPT) + // CURRENCY + + return 0; // Return success code +} + +#[unsafe(no_mangle)] +pub extern "C" fn instance_params() -> i32 { + // UINT8 + let value = match get_instance_param::(1) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT8 Parameter Error Code:", err as i64); + return -1; + } + }; + let _ = trace_num("UINT8 Value:", value as i64); + // as hex + let _ = trace_data("UINT8 Hex:", &[value], DataRepr::AsHex); + + // TODO: replace with require + if value != 255 { + let _ = trace("UINT8 Parameter Error: Invalid Value"); + return -1; + } + + // UINT16 + let value = match get_instance_param::(2) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT16 Parameter Error Code:", err as i64); + return -1; + } + }; + let _ = trace_num("UINT16 Value:", value as i64); + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT16 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if value != 65535 { + let _ = trace("UINT16 Parameter Error: Invalid Value"); + return -1; + } + + // UINT32 + let value = match get_instance_param::(3) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT32 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT32 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if value != 4294967295 { + let _ = trace("UINT32 Parameter Error: Invalid Value"); + return -1; + } + + // UINT64 + let value = match get_instance_param::(4) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT64 Parameter Error Code:", err as i64); + return -1; + } + }; + let _ = trace_num("UINT64 Value:", value as i64); + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT64 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if buf != [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] { + let _ = trace("UINT64 Parameter Error: Invalid Value"); + return -1; + } + + // UINT128 + let value = match get_instance_param::(5) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT128 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT128 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if buf != [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01] { + let _ = trace("UINT128 Parameter Error: Invalid Value"); + return -1; + } + + // UINT160 + let value = match get_instance_param::(6) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT160 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.as_bytes(); + let _ = trace_data("UINT160 Hex:", buf, DataRepr::AsHex); + + // TODO: replace with require + let expected190: [u8; 20] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + ]; + if *buf != expected190 { + let _ = trace("UINT160 Parameter Error: Invalid Value"); + return -1; + } + + // UINT192 + let value = match get_instance_param::(7) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT192 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.as_bytes(); + let _ = trace_data("UINT192 Hex:", buf, DataRepr::AsHex); + + // TODO: replace with require + let expected192: [u8; 24] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + ]; + if *buf != expected192 { + let _ = trace("UINT192 Parameter Error: Invalid Value"); + return -1; + } + + // UINT256 + let value = match get_instance_param::(8) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT256 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.as_bytes(); + let _ = trace_data("UINT256 Hex:", buf, DataRepr::AsHex); + + // TODO: replace with require + let expected256: [u8; 32] = [ + 0xD9, 0x55, 0xDA, 0xC2, 0xE7, 0x75, 0x19, 0xF0, + 0x5A, 0xD1, 0x51, 0xA5, 0xD3, 0xC9, 0x9F, 0xC8, + 0x12, 0x5F, 0xB3, 0x9D, 0x58, 0xFF, 0x9F, 0x10, + 0x6F, 0x1A, 0xCA, 0x44, 0x91, 0x90, 0x2C, 0x25 + ]; + if *buf != expected256 { + let _ = trace("UINT256 Parameter Error: Invalid Value"); + return -1; + } + + // // VL + // let mut buf = [0x00; 4]; + // let output_len = unsafe { instance_param(8, STI_VL.into(), buf.as_mut_ptr(), buf.len()) }; + // let _ = trace_num("VL Value Len:", output_len as i64); + // // as hex + // let _ = trace_data("VL Hex:", &buf[0..4], DataRepr::AsHex); + + // ACCOUNT + let account_id = match get_instance_param::(10) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("ACCOUNT Parameter Error Code:", err as i64); + return -1; + } + }; + // trace the value + let _ = trace_data("ACCOUNT Value:", &account_id.0, DataRepr::AsHex); + + // TODO: replace with require + let expectedAccount: [u8; 20] = [ + 0xAE, 0x12, 0x3A, 0x85, 0x56, 0xF3, 0xCF, 0x91, + 0x15, 0x47, 0x11, 0x37, 0x6A, 0xFB, 0x0F, 0x89, + 0x4F, 0x83, 0x2B, 0x3D + ]; + if account_id.0 != expectedAccount { + let _ = trace("ACCOUNT Parameter Error: Invalid Value"); + return -1; + } + + // AMOUNT XRP + let xrp_token = match get_instance_param::(11) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("AMOUNT XRP Parameter Error Code:", err as i64); + return -1; + } + }; + match xrp_token { + TokenAmount::XRP { num_drops } => { + let _ = trace_num("AMOUNT Value (XRP):", num_drops); + } + _ => { + let _ = trace_num("AMOUNT Value (XRP):", -1); + } + } + let buf = match xrp_token { + TokenAmount::XRP { num_drops } => num_drops.to_le_bytes(), + _ => [0u8; 8], + }; + let _ = trace_data("AMOUNT Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if let TokenAmount::XRP { num_drops } = xrp_token { + if num_drops != 1000000 { + let _ = trace("AMOUNT.XRP Parameter Error: Invalid Value"); + return -1; + } + } else { + let _ = trace("AMOUNT.XRP Parameter Error: Invalid Type"); + return -1; + } + + // AMOUNT IOU + let iou_token = match get_instance_param::(12) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("AMOUNT IOU Parameter Error Code:", err as i64); + return -1; + } + }; + let (iou_amount, iou_issuer, iou_currency) = match &iou_token { + TokenAmount::IOU { amount, issuer, currency_code } => { + // trace amount hex + let _ = trace_data("AMOUNT Value (IOU):", &amount.0, DataRepr::AsHex); + let _ = trace_float("AMOUNT Value (IOU) - Original:", &amount.0); + let _ = trace_data("IOU Issuer:", &issuer.0, DataRepr::AsHex); + let _ = trace_data("IOU Currency:", ¤cy_code.0, DataRepr::AsHex); + + // Add FLOAT_ONE to the IOU amount + let mut new_amount: [u8; 8] = [0u8; 8]; + let result = unsafe { + float_add( + amount.0.as_ptr(), + 8, + FLOAT_ONE.as_ptr(), + 8, + new_amount.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + + if result == 8 { + // trace hex of the new amount + let _ = trace_data("AMOUNT Value (IOU) - After adding 1:", &new_amount, DataRepr::AsHex); + let _ = trace_float("AMOUNT Value (IOU) - After adding 1:", &new_amount); + + // Create a new TokenAmount with the updated amount + let updated_token = TokenAmount::IOU { + amount: new_amount.into(), + issuer: *issuer, + currency_code: *currency_code, + }; + + // You now have the updated token amount in `updated_token` + // and the raw float bytes in `new_amount` + + } else { + let _ = trace_num("Error adding FLOAT_ONE to IOU amount, result:", result as i64); + } + + (Some(*amount), Some(*issuer), Some(*currency_code)) + } + _ => { + let _ = trace_data("AMOUNT Value (IOU):", &[0u8; 8], DataRepr::AsHex); + (None, None, None) + } + }; + // trace new iou_amount as hex + if let Some(amount) = iou_amount { + let _ = trace_data("IOU Amount:", &amount.0, DataRepr::AsHex); + } else { + let _ = trace_data("IOU Amount:", &[0u8; 8], DataRepr::AsHex); + } + + // TODO: replace with require + if iou_amount.is_none() { + let _ = trace("AMOUNT.IOU Parameter Error: Invalid Type"); + return -1; + } + + // let mut buf = [0x00; 12]; + // let output_len = unsafe { instance_param(12, STI_NUMBER.into(), buf.as_mut_ptr(), buf.len()) }; + // let _ = trace_num("NUMBER Value Len:", output_len as i64); + + // NUMBER + let number = match get_instance_param::(13) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("NUMBER Parameter Error Code:", err as i64); + return -1; + } + }; + // trace the value + let buf = number.as_bytes(); + let _ = trace_data("NUMBER Value:", &buf, DataRepr::AsHex); + + // TODO: replace with require + let expectedNumber: [u8; 12] = [ + 0x00, 0x04, 0x43, 0x64, 0xC5, 0xBB, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xF1 + ]; + if buf != expectedNumber { + let _ = trace("NUMBER Parameter Error: Invalid Value"); + return -1; + } + + // // Parse Number to get mantissa and exponent + // let stnumber = Number::from(&buf).unwrap(); + let _ = trace_num("NUMBER Mantissa:", number.mantissa); + let _ = trace_num("NUMBER Exponent:", number.exponent as i64); + + let mut opaque_float_buf = [0x00; 8]; + let result = unsafe { + float_set( + number.exponent, + number.mantissa, + opaque_float_buf.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST + ) + }; + + let opaque = OpaqueFloat::from(opaque_float_buf); + let _ = trace_float("NUMBER as OpaqueFloat:", &opaque.0); + let _ = trace_data("NUMBER OpaqueFloat Hex:", &opaque_float_buf, DataRepr::AsHex); + + // AMOUNT (MPT) + // ISSUE (XRP) + // ISSUE (IOU) + // ISSUE (MPT) + // CURRENCY + + return 0; // Return success code +} diff --git a/src/test/app/wasm_fixtures/sha512Pure.c b/src/test/app/wasm_fixtures/sha512Pure.c new file mode 100644 index 0000000000..fe82aa2a65 --- /dev/null +++ b/src/test/app/wasm_fixtures/sha512Pure.c @@ -0,0 +1,145 @@ +#include +#include + +static uint64_t const K512[] = { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, + 0xe9b5dba58189dbbc, 0x3956c25bf348b538, 0x59f111f1b605d019, + 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, + 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, + 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, + 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, 0x2de92c6f592b0275, + 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, + 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, + 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, + 0x92722c851482353b, 0xa2bfe8a14cf10364, 0xa81a664bbc423001, + 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, + 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, + 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, + 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, + 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, + 0xc67178f2e372532b, 0xca273eceea26619c, 0xd186b8c721c0c207, + 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, + 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, + 0x5fcb6fab3ad6faec, 0x6c44198c4a475817}; + +#define ROTATE(x, y) (((x) >> (y)) | ((x) << (64 - (y)))) +#define Sigma0(x) (ROTATE((x), 28) ^ ROTATE((x), 34) ^ ROTATE((x), 39)) +#define Sigma1(x) (ROTATE((x), 14) ^ ROTATE((x), 18) ^ ROTATE((x), 41)) +#define sigma0(x) (ROTATE((x), 1) ^ ROTATE((x), 8) ^ ((x) >> 7)) +#define sigma1(x) (ROTATE((x), 19) ^ ROTATE((x), 61) ^ ((x) >> 6)) + +#define Ch(x, y, z) (((x) & (y)) ^ ((~(x)) & (z))) +#define Maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) + +static inline uint64_t +B2U64(uint8_t val, uint8_t sh) +{ + return ((uint64_t)val) << sh; +} + +void* +allocate(int sz) +{ + return malloc(sz); +} +void +deallocate(void* p) +{ + free(p); +} + +uint8_t e_data[32 * 1024]; + +uint8_t* +sha512_process(uint8_t const* data, int32_t length) +{ + static uint64_t state[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + + uint64_t a, b, c, d, e, f, g, h, s0, s1, T1, T2; + uint64_t X[16]; + + uint64_t blocks = length / 128; + while (blocks--) + { + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + f = state[5]; + g = state[6]; + h = state[7]; + + unsigned i; + for (i = 0; i < 16; i++) + { + X[i] = B2U64(data[0], 56) | B2U64(data[1], 48) | + B2U64(data[2], 40) | B2U64(data[3], 32) | B2U64(data[4], 24) | + B2U64(data[5], 16) | B2U64(data[6], 8) | B2U64(data[7], 0); + data += 8; + + T1 = h; + T1 += Sigma1(e); + T1 += Ch(e, f, g); + T1 += K512[i]; + T1 += X[i]; + + T2 = Sigma0(a); + T2 += Maj(a, b, c); + + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + for (i = 16; i < 80; i++) + { + s0 = X[(i + 1) & 0x0f]; + s0 = sigma0(s0); + s1 = X[(i + 14) & 0x0f]; + s1 = sigma1(s1); + + T1 = X[i & 0xf] += s0 + s1 + X[(i + 9) & 0xf]; + T1 += h + Sigma1(e) + Ch(e, f, g) + K512[i]; + T2 = Sigma0(a) + Maj(a, b, c); + + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + state[5] += f; + state[6] += g; + state[7] += h; + } + + return (uint8_t*)(state); +} + +// int main () +//{ +// return 0; +// } diff --git a/src/test/app/wasm_fixtures/updateData.c b/src/test/app/wasm_fixtures/updateData.c new file mode 100644 index 0000000000..8436f1c390 --- /dev/null +++ b/src/test/app/wasm_fixtures/updateData.c @@ -0,0 +1,13 @@ +#include + +int32_t +update_data(uint8_t const*, int32_t); + +int +finish() +{ + uint8_t buf[] = "Data"; + update_data(buf, sizeof(buf) - 1); + + return -256; +} diff --git a/src/test/app/wasm_fixtures/wat/deep_recursion.wat b/src/test/app/wasm_fixtures/wat/deep_recursion.wat new file mode 100644 index 0000000000..8e1f02c001 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/deep_recursion.wat @@ -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)) +) diff --git a/src/test/jtx.h b/src/test/jtx.h index 1d7f38ff54..fe3134893a 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/jtx/contract.h b/src/test/jtx/contract.h new file mode 100644 index 0000000000..27c2cd15d8 --- /dev/null +++ b/src/test/jtx/contract.h @@ -0,0 +1,198 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TEST_JTX_CONTRACT_H_INCLUDED +#define RIPPLE_TEST_JTX_CONTRACT_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include + +#include "test/jtx/SignerUtils.h" + +#include +#include +#include + +namespace xrpl { +namespace test { +namespace jtx { + +/** Contract operations */ +namespace contract { + +Json::Value +create(jtx::Account const& account, std::string const& contractCode); + +Json::Value +create(jtx::Account const& account, uint256 const& contractHash); + +Json::Value +modify( + jtx::Account const& account, + jtx::Account const& contractAccount, + std::string const& contractCode); + +Json::Value +modify( + jtx::Account const& account, + jtx::Account const& contractAccount, + uint256 const& contractHash); + +Json::Value +modify( + jtx::Account const& account, + jtx::Account const& contractAccount, + jtx::Account const& owner); + +Json::Value +del(jtx::Account const& account, jtx::Account const& contractAccount); + +Json::Value +call( + jtx::Account const& account, + jtx::Account const& contractAccount, + std::string const& functionName); + +Json::Value +userDelete(jtx::Account const& account, jtx::Account const& contractAccount); + +/** Add Function on a JTx. */ +class add_function +{ +private: + std::string const name_; + std::vector> + call_params_; + +public: + explicit add_function( + std::string const& name, + std::vector> + call_params) + : name_{name}, call_params_{std::move(call_params)} + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + +/** Add Instance Parameter on a JTx. */ +template +class add_instance_param +{ +private: + std::uint32_t flags_; + std::string name_; + std::string type_; + T value_; + +public: + explicit add_instance_param( + std::uint32_t flags, + std::string const& name, + std::string const& type, + T value) + : flags_{flags}, name_{name}, type_{type}, value_{value} + { + } + + void + operator()(Env&, JTx& jtx) const + { + if (jtx.jv.isMember(sfContractCode.fieldName)) + { + // Add instance Parameters + if (!jtx.jv.isMember(sfInstanceParameters.fieldName)) + { + jtx.jv[sfInstanceParameters.fieldName] = + Json::Value(Json::arrayValue); + } + Json::Value param = Json::Value(Json::objectValue); + param[sfInstanceParameter.fieldName][sfParameterFlag.fieldName] = + flags_; + param[sfInstanceParameter.fieldName][sfParameterType.fieldName] + [jss::type] = type_; + jtx.jv[sfInstanceParameters.fieldName].append(param); + } + + // Add instance Parameter Values + if (!jtx.jv.isMember(sfInstanceParameterValues.fieldName)) + { + jtx.jv[sfInstanceParameterValues.fieldName] = + Json::Value(Json::arrayValue); + } + Json::Value param = Json::Value(Json::objectValue); + param[sfInstanceParameterValue.fieldName][sfParameterFlag.fieldName] = + flags_; + param[sfInstanceParameterValue.fieldName][sfParameterValue.fieldName] + [jss::type] = type_; + param[sfInstanceParameterValue.fieldName][sfParameterValue.fieldName] + [jss::value] = value_; + jtx.jv[sfInstanceParameterValues.fieldName].append(param); + } +}; + +/** Add Parameter Value on a JTx. */ +template +class add_param +{ +private: + std::uint32_t flags_; + std::string name_; + std::string type_; + T value_; + +public: + explicit add_param( + std::uint32_t flags, + std::string const& name, + std::string const& type, + T value) + : flags_(flags), name_(name), type_(type), value_(value) + { + } + + void + operator()(Env&, JTx& jtx) const + { + Json::Value param = Json::Value(Json::objectValue); + param[sfParameter] = Json::Value(Json::objectValue); + param[sfParameter][sfParameterFlag] = flags_; + param[sfParameter][sfParameterValue] = Json::Value(Json::objectValue); + param[sfParameter][sfParameterValue][jss::type] = type_; + param[sfParameter][sfParameterValue][jss::value] = value_; + jtx.jv[sfParameters].append(param); + } +}; + +} // namespace contract + +} // namespace jtx + +} // namespace test +} // namespace xrpl + +#endif diff --git a/src/test/jtx/escrow.h b/src/test/jtx/escrow.h index b8490d9534..5235bbdff2 100644 --- a/src/test/jtx/escrow.h +++ b/src/test/jtx/escrow.h @@ -85,6 +85,76 @@ auto const condition = JTxFieldWrapper(sfCondition); auto const fulfillment = JTxFieldWrapper(sfFulfillment); +struct finish_function +{ +private: + std::string value_; + +public: + explicit finish_function(std::string func) : value_(func) + { + } + + explicit finish_function(Slice const& func) : value_(strHex(func)) + { + } + + template + explicit finish_function(std::array const& f) + : finish_function(makeSlice(f)) + { + } + + void + operator()(Env&, JTx& jt) const + { + jt.jv[sfFinishFunction.jsonName] = value_; + } +}; + +struct data +{ +private: + std::string value_; + +public: + explicit data(std::string func) : value_(func) + { + } + + explicit data(Slice const& func) : value_(strHex(func)) + { + } + + template + explicit data(std::array const& f) : data(makeSlice(f)) + { + } + + void + operator()(Env&, JTx& jt) const + { + jt.jv[sfData.jsonName] = value_; + } +}; + +struct comp_allowance +{ +private: + std::uint32_t value_; + +public: + explicit comp_allowance(std::uint32_t const& value) : value_(value) + { + } + + void + operator()(Env&, JTx& jt) const + { + jt.jv[sfComputationAllowance.jsonName] = value_; + } +}; + } // namespace escrow } // namespace jtx diff --git a/src/test/jtx/impl/contract.cpp b/src/test/jtx/impl/contract.cpp new file mode 100644 index 0000000000..c5ac267f1a --- /dev/null +++ b/src/test/jtx/impl/contract.cpp @@ -0,0 +1,160 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +#include +#include + +namespace xrpl { +namespace test { +namespace jtx { + +namespace contract { + +Json::Value +create(jtx::Account const& account, std::string const& contractCode) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = account.human(); + jv[sfContractCode] = contractCode; + return jv; +} + +Json::Value +create(jtx::Account const& account, uint256 const& contractHash) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = account.human(); + jv[sfContractHash] = to_string(contractHash); + return jv; +} + +Json::Value +modify( + jtx::Account const& account, + jtx::Account const& contractAccount, + std::string const& contractCode) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractModify; + jv[jss::Account] = account.human(); + jv[sfContractAccount] = contractAccount.human(); + jv[sfContractCode] = contractCode; + return jv; +} + +Json::Value +modify( + jtx::Account const& account, + jtx::Account const& contractAccount, + uint256 const& contractHash) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractModify; + jv[jss::Account] = account.human(); + jv[sfContractAccount] = contractAccount.human(); + jv[sfContractHash] = to_string(contractHash); + return jv; +} + +Json::Value +modify( + jtx::Account const& account, + jtx::Account const& contractAccount, + jtx::Account const& owner) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractModify; + jv[jss::Account] = account.human(); + jv[sfContractAccount] = contractAccount.human(); + jv[sfOwner] = owner.human(); + return jv; +} + +Json::Value +del(jtx::Account const& account, jtx::Account const& contractAccount) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractDelete; + jv[jss::Account] = account.human(); + jv[sfContractAccount] = contractAccount.human(); + return jv; +} + +Json::Value +call( + jtx::Account const& account, + jtx::Account const& contractAccount, + std::string const& functionName) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCall; + jv[jss::Account] = account.human(); + jv[sfContractAccount] = contractAccount.human(); + jv[sfFunctionName] = strHex(functionName); + jv[sfParameters] = Json::Value(Json::arrayValue); + return jv; +} + +Json::Value +userDelete(jtx::Account const& account, jtx::Account const& contractAccount) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractUserDelete; + jv[jss::Account] = account.human(); + jv[sfContractAccount] = contractAccount.human(); + return jv; +} + +Json::Value +addCallParam( + std::uint32_t const& flags, + std::string const& name, + std::string const& typeName) +{ + Json::Value param = Json::Value(Json::objectValue); + param[sfParameter][sfParameterFlag] = flags; + param[sfParameter][sfParameterType][jss::type] = typeName; + return param; +}; + +void +add_function::operator()(Env&, JTx& jt) const +{ + auto const index = jt.jv[sfFunctions].size(); + Json::Value& function = jt.jv[sfFunctions][index]; + + function = Json::Value{}; + function[sfFunction][sfFunctionName] = strHex(name_); + for (auto const& [p_flags, p_name, p_type] : call_params_) + { + function[sfFunction][sfParameters].append( + addCallParam(p_flags, p_name, p_type)); + } +} + +} // namespace contract + +} // namespace jtx +} // namespace test +} // namespace xrpl diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index 67bbe3b457..292dd41d5f 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -14,9 +14,14 @@ setupConfigForUnitTests(Config& cfg) using namespace jtx; // Default fees to old values, so tests don't have to worry about changes in // Config.h + // NOTE: For new `FEES` fields, you need to wait for the first flag ledger + // to close for the values to be activated. cfg.FEES.reference_fee = UNIT_TEST_REFERENCE_FEE; cfg.FEES.account_reserve = XRP(200).value().xrp().drops(); cfg.FEES.owner_reserve = XRP(50).value().xrp().drops(); + cfg.FEES.extension_compute_limit = 1'000'000; + cfg.FEES.extension_size_limit = 100'000; + cfg.FEES.gas_price = 1'000'000; // 1 drop = 1,000,000 micro-drops // The Beta API (currently v2) is always available to tests cfg.BETA_RPC_API = true; diff --git a/src/test/protocol/Hooks_test.cpp b/src/test/protocol/Hooks_test.cpp deleted file mode 100644 index 27f455f202..0000000000 --- a/src/test/protocol/Hooks_test.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include - -#include - -#include -#include - -namespace xrpl { - -class Hooks_test : public beast::unit_test::suite -{ - /** - * This unit test was requested here: - * https://github.com/ripple/rippled/pull/4089#issuecomment-1050274539 - * These are tests that exercise facilities that are reserved for when Hooks - * is merged in the future. - **/ - - void - testHookFields() - { - testcase("Test Hooks fields"); - - using namespace test::jtx; - - std::vector> fields_to_test = { - sfHookResult, - sfHookStateChangeCount, - sfHookEmitCount, - sfHookExecutionIndex, - sfHookApiVersion, - sfHookStateCount, - sfEmitGeneration, - sfHookOn, - sfHookInstructionCount, - sfEmitBurden, - sfHookReturnCode, - sfReferenceCount, - sfEmitParentTxnID, - sfEmitNonce, - sfEmitHookHash, - sfHookStateKey, - sfHookHash, - sfHookNamespace, - sfHookSetTxnID, - sfHookStateData, - sfHookReturnString, - sfHookParameterName, - sfHookParameterValue, - sfEmitCallback, - sfHookAccount, - sfEmittedTxn, - sfHook, - sfHookDefinition, - sfHookParameter, - sfHookGrant, - sfEmitDetails, - sfHookExecutions, - sfHookExecution, - sfHookParameters, - sfHooks, - sfHookGrants}; - - for (auto const& rf : fields_to_test) - { - SField const& f = rf.get(); - - STObject dummy{sfGeneric}; - - BEAST_EXPECT(!dummy.isFieldPresent(f)); - - switch (f.fieldType) - { - case STI_UINT8: { - dummy.setFieldU8(f, 0); - BEAST_EXPECT(dummy.getFieldU8(f) == 0); - - dummy.setFieldU8(f, 255); - BEAST_EXPECT(dummy.getFieldU8(f) == 255); - - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_UINT16: { - dummy.setFieldU16(f, 0); - BEAST_EXPECT(dummy.getFieldU16(f) == 0); - - dummy.setFieldU16(f, 0xFFFFU); - BEAST_EXPECT(dummy.getFieldU16(f) == 0xFFFFU); - - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_UINT32: { - dummy.setFieldU32(f, 0); - BEAST_EXPECT(dummy.getFieldU32(f) == 0); - - dummy.setFieldU32(f, 0xFFFFFFFFU); - BEAST_EXPECT(dummy.getFieldU32(f) == 0xFFFFFFFFU); - - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_UINT64: { - dummy.setFieldU64(f, 0); - BEAST_EXPECT(dummy.getFieldU64(f) == 0); - - dummy.setFieldU64(f, 0xFFFFFFFFFFFFFFFFU); - BEAST_EXPECT(dummy.getFieldU64(f) == 0xFFFFFFFFFFFFFFFFU); - - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_UINT256: { - uint256 u = uint256::fromVoid( - "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBE" - "EFDEADBEEF"); - dummy.setFieldH256(f, u); - BEAST_EXPECT(dummy.getFieldH256(f) == u); - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_VL: { - std::vector v{1, 2, 3}; - dummy.setFieldVL(f, v); - BEAST_EXPECT(dummy.getFieldVL(f) == v); - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_ACCOUNT: { - AccountID id = *parseBase58( - "rwfSjJNK2YQuN64bSWn7T2eY9FJAyAPYJT"); - dummy.setAccountID(f, id); - BEAST_EXPECT(dummy.getAccountID(f) == id); - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_OBJECT: { - dummy.emplace_back(STObject{f}); - BEAST_EXPECT(dummy.getField(f).getFName() == f); - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_ARRAY: { - STArray dummy2{f, 2}; - dummy2.push_back(STObject{sfGeneric}); - dummy2.push_back(STObject{sfGeneric}); - dummy.setFieldArray(f, dummy2); - BEAST_EXPECT(dummy.getFieldArray(f) == dummy2); - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - default: - BEAST_EXPECT(false); - } - } - } - -public: - void - run() override - { - using namespace test::jtx; - testHookFields(); - } -}; - -BEAST_DEFINE_TESTSUITE(Hooks, protocol, xrpl); - -} // namespace xrpl diff --git a/src/test/protocol/STDataType_test.cpp b/src/test/protocol/STDataType_test.cpp new file mode 100644 index 0000000000..18e89fb3ed --- /dev/null +++ b/src/test/protocol/STDataType_test.cpp @@ -0,0 +1,664 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +namespace xrpl { +struct STDataType_test : public beast::unit_test::suite +{ + void + testConstructors() + { + testcase("constructors"); + + auto const& sf = sfParameterType; + + // Test default constructor + { + STDataType dt1(sf); + BEAST_EXPECT(dt1.getInnerSType() == STI_NOTPRESENT); + BEAST_EXPECT(dt1.getSType() == STI_DATATYPE); + BEAST_EXPECT(dt1.getFName() == sf); + } + + // Test constructor with SerializedTypeID + { + STDataType dt2(sf, STI_UINT32); + BEAST_EXPECT(dt2.getInnerSType() == STI_UINT32); + BEAST_EXPECT(!dt2.isDefault()); + } + + // Test deserialization constructor + { + Serializer s; + s.add16(STI_UINT64); + SerialIter sit(s.slice()); + STDataType dt3(sit, sf); + BEAST_EXPECT(dt3.getInnerSType() == STI_UINT64); + } + } + + void + testCopyMove() + { + testcase("copy and move"); + + auto const& sf = sfParameterType; + + // Test copy + { + STDataType original(sf, STI_UINT32); + + // Use aligned storage for placement new + alignas(STDataType) char buffer[sizeof(STDataType)]; + STBase* copied = original.copy(sizeof(buffer), buffer); + + BEAST_EXPECT(copied != nullptr); + auto* dt_copy = dynamic_cast(copied); + BEAST_EXPECT(dt_copy != nullptr); + BEAST_EXPECT(dt_copy->getInnerSType() == STI_UINT32); + BEAST_EXPECT(dt_copy->getFName() == sf); + + // Clean up + dt_copy->~STDataType(); + } + + // Test move + { + STDataType original(sf, STI_UINT64); + + alignas(STDataType) char buffer[sizeof(STDataType)]; + STBase* moved = original.move(sizeof(buffer), buffer); + + BEAST_EXPECT(moved != nullptr); + auto* dt_moved = dynamic_cast(moved); + BEAST_EXPECT(dt_moved != nullptr); + BEAST_EXPECT(dt_moved->getInnerSType() == STI_UINT64); + BEAST_EXPECT(dt_moved->getFName() == sf); + + // Clean up + dt_moved->~STDataType(); + } + } + + void + testSerialization() + { + testcase("serialization"); + + auto const& sf = sfParameterType; + + // Test all type serializations + struct TypeTest + { + SerializedTypeID type; + std::string expectedHex; + }; + + TypeTest tests[] = { + {STI_UINT16, "0001"}, + {STI_UINT32, "0002"}, + {STI_UINT64, "0003"}, + {STI_UINT128, "0004"}, + {STI_UINT256, "0005"}, + {STI_AMOUNT, "0006"}, + {STI_VL, "0007"}, + {STI_ACCOUNT, "0008"}, + {STI_UINT8, "0010"}, + {STI_UINT160, "0011"}, + {STI_PATHSET, "0012"}, + {STI_VECTOR256, "0013"}, + {STI_OBJECT, "000E"}, + {STI_ARRAY, "000F"}, + {STI_ISSUE, "0018"}, + {STI_XCHAIN_BRIDGE, "0019"}, + {STI_CURRENCY, "001A"}, + {STI_UINT192, "0015"}, + {STI_NUMBER, "0009"}}; + + for (auto const& test : tests) + { + Serializer s; + STDataType dt(sf); + dt.setInnerSType(test.type); + BEAST_EXPECT(dt.getInnerSType() == test.type); + dt.add(s); + BEAST_EXPECT(strHex(s) == test.expectedHex); + } + } + + void + testEquivalence() + { + testcase("equivalence"); + + auto const& sf1 = sfParameterType; + + // Test equivalent objects + { + STDataType dt1(sf1, STI_UINT32); + STDataType dt2(sf1, STI_UINT32); + BEAST_EXPECT(dt1.isEquivalent(dt2)); + } + + // Test non-equivalent objects (different inner types) + { + STDataType dt1(sf1, STI_UINT32); + STDataType dt2(sf1, STI_UINT64); + BEAST_EXPECT(!dt1.isEquivalent(dt2)); + } + + // Test non-equivalent objects (different default states) + { + STDataType dt1(sf1); + STDataType dt2(sf1, STI_NOTPRESENT); + // dt1 has default_ = true (implicit from first constructor) + // dt2 has default_ = false (set in second constructor) + BEAST_EXPECT(!dt1.isEquivalent(dt2)); + } + + // Test with non-STDataType object + { + STDataType dt1(sf1, STI_UINT32); + // Create a dummy STBase-derived object for comparison + // Since we can't easily create other STBase types here, + // we'll test that isEquivalent returns false for nullptr cast + struct DummySTBase : public STBase + { + DummySTBase() : STBase(sfInvalid) + { + } + SerializedTypeID + getSType() const override + { + return STI_NOTPRESENT; + } + void + add(Serializer&) const override + { + } + bool + isEquivalent(STBase const&) const override + { + return false; + } + bool + isDefault() const override + { + return true; + } + STBase* + copy(std::size_t, void*) const override + { + return nullptr; + } + STBase* + move(std::size_t, void*) override + { + return nullptr; + } + }; + DummySTBase dummy; + BEAST_EXPECT(!dt1.isEquivalent(dummy)); + } + } + + void + testDefault() + { + testcase("isDefault"); + + auto const& sf = sfParameterType; + + // Test default state + { + STDataType dt1(sf); + // First constructor doesn't set default_ explicitly, + // so it should be true (member initialization) + BEAST_EXPECT(dt1.isDefault()); + } + + { + STDataType dt2(sf, STI_UINT32); + BEAST_EXPECT(!dt2.isDefault()); + } + } + + void + testGetText() + { + testcase("getText"); + + auto const& sf = sfParameterType; + + // Test known types + { + STDataType dt(sf, STI_UINT8); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT8}"); + } + + { + STDataType dt(sf, STI_UINT16); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT16}"); + } + + { + STDataType dt(sf, STI_UINT32); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT32}"); + } + + { + STDataType dt(sf, STI_UINT64); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT64}"); + } + + { + STDataType dt(sf, STI_UINT128); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT128}"); + } + + { + STDataType dt(sf, STI_UINT160); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT160}"); + } + + { + STDataType dt(sf, STI_UINT192); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT192}"); + } + + { + STDataType dt(sf, STI_UINT256); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT256}"); + } + + { + STDataType dt(sf, STI_VL); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: VL}"); + } + + { + STDataType dt(sf, STI_ACCOUNT); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: ACCOUNT}"); + } + + { + STDataType dt(sf, STI_AMOUNT); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: AMOUNT}"); + } + + { + STDataType dt(sf, STI_ISSUE); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: ISSUE}"); + } + + { + STDataType dt(sf, STI_CURRENCY); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: CURRENCY}"); + } + + { + STDataType dt(sf, STI_NUMBER); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: NUMBER}"); + } + + // Test unknown type (should return numeric string) + { + STDataType dt(sf, static_cast(999)); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: 999}"); + } + } + + void + testGetJson() + { + testcase("getJson"); + + auto const& sf = sfParameterType; + + // Test JSON output for various types + { + STDataType dt(sf, STI_UINT32); + Json::Value json = dt.getJson(JsonOptions::none); + BEAST_EXPECT(json.isObject()); + BEAST_EXPECT(json[jss::type].asString() == "UINT32"); + } + + { + STDataType dt(sf, STI_AMOUNT); + Json::Value json = dt.getJson(JsonOptions::none); + BEAST_EXPECT(json.isObject()); + BEAST_EXPECT(json[jss::type].asString() == "AMOUNT"); + } + + { + STDataType dt(sf, STI_ACCOUNT); + Json::Value json = dt.getJson(JsonOptions::none); + BEAST_EXPECT(json.isObject()); + BEAST_EXPECT(json[jss::type].asString() == "ACCOUNT"); + } + + // Test unknown type + { + STDataType dt(sf, static_cast(999)); + Json::Value json = dt.getJson(JsonOptions::none); + BEAST_EXPECT(json.isObject()); + BEAST_EXPECT(json[jss::type].asString() == "999"); + } + } + + void + testDataTypeFromJson() + { + testcase("dataTypeFromJson"); + + auto const& sf = sfParameterType; + + // Test all valid type strings + { + Json::Value v; + v[jss::type] = "UINT8"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT8); + } + + { + Json::Value v; + v[jss::type] = "UINT16"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT16); + } + + { + Json::Value v; + v[jss::type] = "UINT32"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT32); + } + + { + Json::Value v; + v[jss::type] = "UINT64"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT64); + } + + { + Json::Value v; + v[jss::type] = "UINT128"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT128); + } + + { + Json::Value v; + v[jss::type] = "UINT160"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT160); + } + + { + Json::Value v; + v[jss::type] = "UINT192"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT192); + } + + { + Json::Value v; + v[jss::type] = "UINT256"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT256); + } + + { + Json::Value v; + v[jss::type] = "VL"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_VL); + } + + { + Json::Value v; + v[jss::type] = "ACCOUNT"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_ACCOUNT); + } + + { + Json::Value v; + v[jss::type] = "AMOUNT"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_AMOUNT); + } + + { + Json::Value v; + v[jss::type] = "ISSUE"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_ISSUE); + } + + { + Json::Value v; + v[jss::type] = "CURRENCY"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_CURRENCY); + } + + { + Json::Value v; + v[jss::type] = "NUMBER"; + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_NUMBER); + } + + // Test error cases + + // Non-object JSON should throw + { + Json::Value v = "not an object"; + try + { + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(false); // Should not reach here + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT( + std::string(e.what()) == "STData: expected object"); + } + } + + // Unknown type string should throw + { + Json::Value v; + v[jss::type] = "UNKNOWN_TYPE"; + try + { + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(false); // Should not reach here + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT( + std::string(e.what()) == + "STData: unsupported type string: UNKNOWN_TYPE"); + } + } + + // Empty type string should throw + { + Json::Value v; + v[jss::type] = ""; + try + { + STDataType dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(false); // Should not reach here + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT( + std::string(e.what()) == + "STData: unsupported type string: "); + } + } + } + + void + testRoundTrip() + { + testcase("round trip serialization"); + + auto const& sf = sfParameterType; + + // Test serialization and deserialization round trip + for (auto typeId : + {STI_UINT8, + STI_UINT16, + STI_UINT32, + STI_UINT64, + STI_UINT128, + STI_UINT160, + STI_UINT192, + STI_UINT256, + STI_VL, + STI_ACCOUNT, + STI_AMOUNT, + STI_ISSUE, + STI_CURRENCY, + STI_NUMBER}) + { + // Create original + STDataType original(sf, typeId); + + // Serialize + Serializer s; + original.add(s); + + // Deserialize + SerialIter sit(s.slice()); + STDataType deserialized(sit, sf); + + // Compare + BEAST_EXPECT(deserialized.getInnerSType() == typeId); + BEAST_EXPECT(original.isEquivalent(deserialized)); + } + } + + void + testJsonRoundTrip() + { + testcase("JSON round trip"); + + auto const& sf = sfParameterType; + + std::vector typeStrings = { + "UINT8", + "UINT16", + "UINT32", + "UINT64", + "UINT128", + "UINT160", + "UINT192", + "UINT256", + "VL", + "ACCOUNT", + "AMOUNT", + "ISSUE", + "CURRENCY", + "NUMBER"}; + + for (auto const& typeStr : typeStrings) + { + // Create from JSON + Json::Value input; + input[jss::type] = typeStr; + STDataType dt = dataTypeFromJson(sf, input); + + // Convert back to JSON + Json::Value output = dt.getJson(JsonOptions::none); + + // Verify + BEAST_EXPECT(output[jss::type].asString() == typeStr); + } + } + + void + testGetInnerTypeString() + { + testcase("getInnerTypeString"); + + auto const& sf = sfParameterType; + + struct TypeStringTest + { + SerializedTypeID type; + std::string expected; + }; + + TypeStringTest tests[] = { + {STI_UINT8, "UINT8"}, + {STI_UINT16, "UINT16"}, + {STI_UINT32, "UINT32"}, + {STI_UINT64, "UINT64"}, + {STI_UINT128, "UINT128"}, + {STI_UINT160, "UINT160"}, + {STI_UINT192, "UINT192"}, + {STI_UINT256, "UINT256"}, + {STI_VL, "VL"}, + {STI_ACCOUNT, "ACCOUNT"}, + {STI_AMOUNT, "AMOUNT"}, + {STI_ISSUE, "ISSUE"}, + {STI_CURRENCY, "CURRENCY"}, + {STI_NUMBER, "NUMBER"}, + {static_cast(999), "999"} // Unknown type + }; + + for (auto const& test : tests) + { + STDataType dt(sf, test.type); + BEAST_EXPECT(dt.getInnerTypeString() == test.expected); + } + } + + void + run() override + { + testConstructors(); + testCopyMove(); + testSerialization(); + testEquivalence(); + testDefault(); + testGetText(); + testGetJson(); + testDataTypeFromJson(); + testRoundTrip(); + testJsonRoundTrip(); + testGetInnerTypeString(); + } +}; + +BEAST_DEFINE_TESTSUITE(STDataType, protocol, xrpl); + +} // namespace xrpl diff --git a/src/test/protocol/STData_test.cpp b/src/test/protocol/STData_test.cpp new file mode 100644 index 0000000000..43ee6bbfe6 --- /dev/null +++ b/src/test/protocol/STData_test.cpp @@ -0,0 +1,1324 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +namespace xrpl { + +struct STData_test : public beast::unit_test::suite +{ + void + testConstructors() + { + testcase("Constructors"); + + auto const& sf = sfParameterValue; + + // Default constructor + { + STData data(sf); + BEAST_EXPECT(data.getSType() == STI_DATA); + BEAST_EXPECT(data.isDefault()); + } + + // Type-specific constructors + { + // UINT8 + STData data_u8(sf, static_cast(8)); + BEAST_EXPECT(data_u8.getFieldU8() == 8); + BEAST_EXPECT(data_u8.getInnerTypeString() == "UINT8"); + BEAST_EXPECT(data_u8.isDefault()); + + // UINT16 + STData data_u16(sf, static_cast(16)); + BEAST_EXPECT(data_u16.getFieldU16() == 16); + BEAST_EXPECT(data_u16.getInnerTypeString() == "UINT16"); + + // UINT32 + STData data_u32(sf, static_cast(32)); + BEAST_EXPECT(data_u32.getFieldU32() == 32); + BEAST_EXPECT(data_u32.getInnerTypeString() == "UINT32"); + + // UINT64 + STData data_u64(sf, static_cast(64)); + BEAST_EXPECT(data_u64.getFieldU64() == 64); + BEAST_EXPECT(data_u64.getInnerTypeString() == "UINT64"); + + // UINT128 + uint128 val128 = uint128(1); + STData data_u128(sf, val128); + BEAST_EXPECT(data_u128.getFieldH128() == val128); + BEAST_EXPECT(data_u128.getInnerTypeString() == "UINT128"); + + // UINT160 + uint160 val160 = uint160(1); + STData data_u160(sf, val160); + BEAST_EXPECT(data_u160.getFieldH160() == val160); + BEAST_EXPECT(data_u160.getInnerTypeString() == "UINT160"); + + // UINT192 + uint192 val192 = uint192(1); + STData data_u192(sf, val192); + BEAST_EXPECT(data_u192.getFieldH192() == val192); + BEAST_EXPECT(data_u192.getInnerTypeString() == "UINT192"); + + // UINT256 + uint256 val256 = uint256(1); + STData data_u256(sf, val256); + BEAST_EXPECT(data_u256.getFieldH256() == val256); + BEAST_EXPECT(data_u256.getInnerTypeString() == "UINT256"); + + // Blob + Blob blob = strUnHex("DEADBEEFCAFEBABE").value(); + STData data_blob(sf, blob); + BEAST_EXPECT(data_blob.getFieldVL() == blob); + BEAST_EXPECT(data_blob.getInnerTypeString() == "VL"); + + // Slice + std::string test_str = "Hello World"; + Slice slice(test_str.data(), test_str.size()); + STData data_slice(sf, slice); + Blob expected_blob(test_str.begin(), test_str.end()); + BEAST_EXPECT(data_slice.getFieldVL() == expected_blob); + + // AccountID + AccountID account(0x123456789ABCDEF0); + STData data_account(sf, account); + BEAST_EXPECT(data_account.getAccountID() == account); + BEAST_EXPECT(data_account.getInnerTypeString() == "ACCOUNT"); + + // STAmount (Native) + STAmount amount_native(1000); + STData data_amount_native(sf, amount_native); + BEAST_EXPECT(data_amount_native.getFieldAmount() == amount_native); + BEAST_EXPECT(data_amount_native.getInnerTypeString() == "AMOUNT"); + + // STAmount (IOU) + IOUAmount iou_amount(5000); + Issue const usd( + Currency(0x5553440000000000), + parseBase58("rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn") + .value()); + STAmount amount_iou(iou_amount, usd); + STData data_amount_iou(sf, amount_iou); + BEAST_EXPECT(data_amount_iou.getFieldAmount() == amount_iou); + } + } + + void + testSerializationDeserialization() + { + testcase("Serialization/Deserialization"); + + auto const& sf = sfParameterValue; + + // Test each type's serialization and deserialization round-trip + { + // UINT8 + std::uint8_t original_u8 = 8; + STData data_u8(sf, original_u8); + + Serializer s; + data_u8.add(s); + + SerialIter sit(s.slice()); + STData deserialized_u8(sit, sf); + + BEAST_EXPECT(deserialized_u8.getFieldU8() == original_u8); + BEAST_EXPECT(deserialized_u8.getInnerTypeString() == "UINT8"); + } + + { + // UINT16 + std::uint16_t original_u16 = 16; + STData data_u16(sf, original_u16); + + Serializer s; + data_u16.add(s); + + SerialIter sit(s.slice()); + STData deserialized_u16(sit, sf); + + BEAST_EXPECT(deserialized_u16.getFieldU16() == original_u16); + BEAST_EXPECT(deserialized_u16.getInnerTypeString() == "UINT16"); + } + + { + // UINT32 + std::uint32_t original_u32 = 32; + STData data_u32(sf, original_u32); + + Serializer s; + data_u32.add(s); + + SerialIter sit(s.slice()); + STData deserialized_u32(sit, sf); + + BEAST_EXPECT(deserialized_u32.getFieldU32() == original_u32); + BEAST_EXPECT(deserialized_u32.getInnerTypeString() == "UINT32"); + } + + { + // UINT64 + std::uint64_t original_u64 = 64; + STData data_u64(sf, original_u64); + + Serializer s; + data_u64.add(s); + + SerialIter sit(s.slice()); + STData deserialized_u64(sit, sf); + + BEAST_EXPECT(deserialized_u64.getFieldU64() == original_u64); + BEAST_EXPECT(deserialized_u64.getInnerTypeString() == "UINT64"); + } + + { + // UINT128 + uint128 original_u128 = uint128(1); + STData data_u128(sf, original_u128); + + Serializer s; + data_u128.add(s); + + SerialIter sit(s.slice()); + STData deserialized_u128(sit, sf); + + BEAST_EXPECT(deserialized_u128.getFieldH128() == original_u128); + BEAST_EXPECT(deserialized_u128.getInnerTypeString() == "UINT128"); + } + + { + // UINT160 + uint160 original_u160 = uint160(1); + STData data_u160(sf, original_u160); + + Serializer s; + data_u160.add(s); + + SerialIter sit(s.slice()); + STData deserialized_u160(sit, sf); + + BEAST_EXPECT(deserialized_u160.getFieldH160() == original_u160); + BEAST_EXPECT(deserialized_u160.getInnerTypeString() == "UINT160"); + } + + { + // UINT192 + uint192 original_u192 = uint192(1); + STData data_u192(sf, original_u192); + + Serializer s; + data_u192.add(s); + + SerialIter sit(s.slice()); + STData deserialized_u192(sit, sf); + + BEAST_EXPECT(deserialized_u192.getFieldH192() == original_u192); + BEAST_EXPECT(deserialized_u192.getInnerTypeString() == "UINT192"); + } + + { + // UINT256 + uint256 original_u256 = uint256(1); + STData data_u256(sf, original_u256); + + Serializer s; + data_u256.add(s); + + SerialIter sit(s.slice()); + STData deserialized_u256(sit, sf); + + BEAST_EXPECT(deserialized_u256.getFieldH256() == original_u256); + BEAST_EXPECT(deserialized_u256.getInnerTypeString() == "UINT256"); + } + + { + // VL (Variable Length) + Blob original_blob = + strUnHex("DEADBEEFCAFEBABE1234567890ABCDEF").value(); + STData data_vl(sf, original_blob); + + Serializer s; + data_vl.add(s); + + SerialIter sit(s.slice()); + STData deserialized_vl(sit, sf); + + BEAST_EXPECT(deserialized_vl.getFieldVL() == original_blob); + BEAST_EXPECT(deserialized_vl.getInnerTypeString() == "VL"); + } + + { + // ACCOUNT + AccountID original_account(0xFEDCBA9876543210); + STData data_account(sf, original_account); + + Serializer s; + data_account.add(s); + + SerialIter sit(s.slice()); + STData deserialized_account(sit, sf); + + BEAST_EXPECT( + deserialized_account.getAccountID() == original_account); + BEAST_EXPECT( + deserialized_account.getInnerTypeString() == "ACCOUNT"); + } + + { + // AMOUNT (Native) + STAmount original_amount(99999); + STData data_amount(sf, original_amount); + + Serializer s; + data_amount.add(s); + + SerialIter sit(s.slice()); + STData deserialized_amount(sit, sf); + + BEAST_EXPECT( + deserialized_amount.getFieldAmount() == original_amount); + BEAST_EXPECT(deserialized_amount.getInnerTypeString() == "AMOUNT"); + } + + { + // CURRENCY + } + + { + // ISSUE + } + + { + // NUMBER + } + } + + void + testSettersAndGetters() + { + testcase("Setters and Getters"); + + auto const& sf = sfParameterValue; + STData data(sf); + + // Test all setter/getter combinations + { + // UINT8 + unsigned char val_u8 = 8; + data.setFieldU8(val_u8); + BEAST_EXPECT(data.getFieldU8() == val_u8); + BEAST_EXPECT(data.getInnerTypeString() == "UINT8"); + } + + { + // UINT16 + std::uint16_t val_u16 = 16; + data.setFieldU16(val_u16); + BEAST_EXPECT(data.getFieldU16() == val_u16); + BEAST_EXPECT(data.getInnerTypeString() == "UINT16"); + } + + { + // UINT32 + std::uint32_t val_u32 = 32; + data.setFieldU32(val_u32); + BEAST_EXPECT(data.getFieldU32() == val_u32); + BEAST_EXPECT(data.getInnerTypeString() == "UINT32"); + } + + { + // UINT64 + std::uint64_t val_u64 = 64; + data.setFieldU64(val_u64); + BEAST_EXPECT(data.getFieldU64() == val_u64); + BEAST_EXPECT(data.getInnerTypeString() == "UINT64"); + } + + { + // UINT128 + uint128 val_u128 = uint128(1); + data.setFieldH128(val_u128); + BEAST_EXPECT(data.getFieldH128() == val_u128); + BEAST_EXPECT(data.getInnerTypeString() == "UINT128"); + } + + { + // UINT160 + uint160 val_u160 = uint160(1); + data.setFieldH160(val_u160); + BEAST_EXPECT(data.getFieldH160() == val_u160); + BEAST_EXPECT(data.getInnerTypeString() == "UINT160"); + } + + { + // UINT192 + uint192 val_u192 = uint192(1); + data.setFieldH192(val_u192); + BEAST_EXPECT(data.getFieldH192() == val_u192); + BEAST_EXPECT(data.getInnerTypeString() == "UINT192"); + } + + { + // UINT256 + uint256 val_u256 = uint256(1); + data.setFieldH256(val_u256); + BEAST_EXPECT(data.getFieldH256() == val_u256); + BEAST_EXPECT(data.getInnerTypeString() == "UINT256"); + } + + { + // VL (Variable Length) - Blob + Blob val_blob = + strUnHex("0102030405060708090A0B0C0D0E0F10").value(); + data.setFieldVL(val_blob); + BEAST_EXPECT(data.getFieldVL() == val_blob); + BEAST_EXPECT(data.getInnerTypeString() == "VL"); + } + + { + // VL (Variable Length) - Slice + std::string test_str = "Test String for Slice"; + Slice val_slice(test_str.data(), test_str.size()); + data.setFieldVL(val_slice); + Blob expected_blob(test_str.begin(), test_str.end()); + BEAST_EXPECT(data.getFieldVL() == expected_blob); + BEAST_EXPECT(data.getInnerTypeString() == "VL"); + } + + { + // ACCOUNT + AccountID val_account(0x123456789ABCDEF0); + data.setAccountID(val_account); + BEAST_EXPECT(data.getAccountID() == val_account); + BEAST_EXPECT(data.getInnerTypeString() == "ACCOUNT"); + } + + { + // AMOUNT + STAmount val_amount(777777); + data.setFieldAmount(val_amount); + BEAST_EXPECT(data.getFieldAmount() == val_amount); + BEAST_EXPECT(data.getInnerTypeString() == "AMOUNT"); + } + + { + // CURRENCY + } + + { + // ISSUE + } + + { + // NUMBER + } + } + + void + testJsonConversion() + { + testcase("JSON Conversion"); + + auto const& sf = sfParameterValue; + + // Test JSON serialization for each type + { + // UINT8 + STData data_u8(sf, static_cast(8)); + Json::Value json_u8 = data_u8.getJson(JsonOptions::none); + BEAST_EXPECT(json_u8[jss::type].asString() == "UINT8"); + BEAST_EXPECT(json_u8[jss::value].asUInt() == 8); + } + + { + // UINT16 + STData data_u16(sf, static_cast(16)); + Json::Value json_u16 = data_u16.getJson(JsonOptions::none); + BEAST_EXPECT(json_u16[jss::type].asString() == "UINT16"); + BEAST_EXPECT(json_u16[jss::value].asUInt() == 16); + } + + { + // UINT32 + STData data_u32(sf, static_cast(32)); + Json::Value json_u32 = data_u32.getJson(JsonOptions::none); + BEAST_EXPECT(json_u32[jss::type].asString() == "UINT32"); + BEAST_EXPECT(json_u32[jss::value].asUInt() == 32); + } + + { + // UINT64 + STData data_u64(sf, static_cast(64)); + Json::Value json_u64 = data_u64.getJson(JsonOptions::none); + BEAST_EXPECT(json_u64[jss::type].asString() == "UINT64"); + BEAST_EXPECT(json_u64[jss::value].asString() == "40"); + } + + { + // UINT128 + uint128 val_u128 = uint128(1); + STData data_u128(sf, val_u128); + Json::Value json_u128 = data_u128.getJson(JsonOptions::none); + BEAST_EXPECT(json_u128[jss::type].asString() == "UINT128"); + BEAST_EXPECT( + json_u128[jss::value].asString() == + "00000000000000000000000000000001"); + } + + { + // UINT160 + uint160 val_u160 = uint160(1); + STData data_u160(sf, val_u160); + Json::Value json_u160 = data_u160.getJson(JsonOptions::none); + BEAST_EXPECT(json_u160[jss::type].asString() == "UINT160"); + BEAST_EXPECT( + json_u160[jss::value].asString() == + "0000000000000000000000000000000000000001"); + } + + { + // UINT192 + uint192 val_u192 = uint192(1); + STData data_u192(sf, val_u192); + Json::Value json_u192 = data_u192.getJson(JsonOptions::none); + BEAST_EXPECT(json_u192[jss::type].asString() == "UINT192"); + BEAST_EXPECT( + json_u192[jss::value].asString() == + "000000000000000000000000000000000000000000000001"); + } + + { + // UINT256 + uint256 val_u256 = uint256(1); + STData data_u256(sf, val_u256); + Json::Value json_u256 = data_u256.getJson(JsonOptions::none); + BEAST_EXPECT(json_u256[jss::type].asString() == "UINT256"); + BEAST_EXPECT( + json_u256[jss::value].asString() == + "00000000000000000000000000000000000000000000000000000000000000" + "01"); + } + + { + // VL + Blob blob = strUnHex("DEADBEEF").value(); + STData data_vl(sf, blob); + Json::Value json_vl = data_vl.getJson(JsonOptions::none); + BEAST_EXPECT(json_vl[jss::type].asString() == "VL"); + BEAST_EXPECT(json_vl[jss::value].asString() == "DEADBEEF"); + } + + { + // ACCOUNT + AccountID account(0x123456789ABCDEF0); + STData data_account(sf, account); + Json::Value json_account = data_account.getJson(JsonOptions::none); + BEAST_EXPECT(json_account[jss::type].asString() == "ACCOUNT"); + BEAST_EXPECT( + json_account[jss::value].asString() == + "rrrrrrrrrrrrrLveWzSkxhcH3hGw6"); + } + + { + // AMOUNT + STAmount amount(1000); + STData data_amount(sf, amount); + Json::Value json_amount = data_amount.getJson(JsonOptions::none); + BEAST_EXPECT(json_amount[jss::type].asString() == "AMOUNT"); + BEAST_EXPECT(json_amount[jss::value].asString() == "1000"); + } + + { + // CURRENCY + } + + { + // ISSUE + } + + { + // NUMBER + } + } + + void + testDataFromJson() + { + testcase("Data From JSON"); + + auto const& sf = sfParameterValue; + + // Test JSON deserialization for each type + { + // UINT8 + Json::Value json_u8(Json::objectValue); + json_u8[jss::type] = "UINT8"; + json_u8[jss::value] = 8; + + STData data_u8 = dataFromJson(sf, json_u8); + BEAST_EXPECT(data_u8.getFieldU8() == 8); + BEAST_EXPECT(data_u8.getInnerTypeString() == "UINT8"); + } + + { + // UINT16 + Json::Value json_u16(Json::objectValue); + json_u16[jss::type] = "UINT16"; + json_u16[jss::value] = 16; + + STData data_u16 = dataFromJson(sf, json_u16); + BEAST_EXPECT(data_u16.getFieldU16() == 16); + BEAST_EXPECT(data_u16.getInnerTypeString() == "UINT16"); + } + + { + // UINT32 + Json::Value json_u32(Json::objectValue); + json_u32[jss::type] = "UINT32"; + json_u32[jss::value] = 32; + + STData data_u32 = dataFromJson(sf, json_u32); + BEAST_EXPECT(data_u32.getFieldU32() == 32); + BEAST_EXPECT(data_u32.getInnerTypeString() == "UINT32"); + } + + { + // UINT64 + Json::Value json_u64(Json::objectValue); + json_u64[jss::type] = "UINT64"; + json_u64[jss::value] = 64; + STData data_u64 = dataFromJson(sf, json_u64); + BEAST_EXPECT(data_u64.getFieldU64() == 64); + BEAST_EXPECT(data_u64.getInnerTypeString() == "UINT64"); + } + + { + // UINT128 + Json::Value json_u128(Json::objectValue); + json_u128[jss::type] = "UINT128"; + json_u128[jss::value] = "00000000000000000000000000000001"; + STData data_u128 = dataFromJson(sf, json_u128); + uint128 expected; + bool ok = expected.parseHex("00000000000000000000000000000001"); + BEAST_EXPECT(ok); + BEAST_EXPECT(data_u128.getFieldH128() == expected); + BEAST_EXPECT(data_u128.getInnerTypeString() == "UINT128"); + } + + { + // UINT160 + Json::Value json_u160(Json::objectValue); + json_u160[jss::type] = "UINT160"; + json_u160[jss::value] = "0000000000000000000000000000000000000001"; + STData data_u160 = dataFromJson(sf, json_u160); + uint160 expected; + bool ok = + expected.parseHex("0000000000000000000000000000000000000001"); + BEAST_EXPECT(ok); + BEAST_EXPECT(data_u160.getFieldH160() == expected); + BEAST_EXPECT(data_u160.getInnerTypeString() == "UINT160"); + } + + { + // UINT192 + Json::Value json_u192(Json::objectValue); + json_u192[jss::type] = "UINT192"; + json_u192[jss::value] = + "000000000000000000000000000000000000000000000001"; + STData data_u192 = dataFromJson(sf, json_u192); + uint192 expected; + bool ok = expected.parseHex( + "000000000000000000000000000000000000000000000001"); + BEAST_EXPECT(ok); + BEAST_EXPECT(data_u192.getFieldH192() == expected); + BEAST_EXPECT(data_u192.getInnerTypeString() == "UINT192"); + } + + { + // UINT256 + Json::Value json_u256(Json::objectValue); + json_u256[jss::type] = "UINT256"; + json_u256[jss::value] = + "00000000000000000000000000000000000000000000000000000000000000" + "01"; + STData data_u256 = dataFromJson(sf, json_u256); + uint256 expected; + bool ok = expected.parseHex( + "00000000000000000000000000000000000000000000000000000000000000" + "01"); + BEAST_EXPECT(ok); + BEAST_EXPECT(data_u256.getFieldH256() == expected); + BEAST_EXPECT(data_u256.getInnerTypeString() == "UINT256"); + } + + { + // VL + Json::Value json_vl(Json::objectValue); + json_vl[jss::type] = "VL"; + json_vl[jss::value] = "DEADBEEFCAFEBABE"; + + STData data_vl = dataFromJson(sf, json_vl); + Blob expected_blob = strUnHex("DEADBEEFCAFEBABE").value(); + BEAST_EXPECT(data_vl.getFieldVL() == expected_blob); + BEAST_EXPECT(data_vl.getInnerTypeString() == "VL"); + } + + { + // ACCOUNT + Json::Value json_account(Json::objectValue); + json_account[jss::type] = "ACCOUNT"; + json_account[jss::value] = "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn"; + + STData data_account = dataFromJson(sf, json_account); + AccountID expected_account = + parseBase58("rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn") + .value(); + BEAST_EXPECT(data_account.getAccountID() == expected_account); + BEAST_EXPECT(data_account.getInnerTypeString() == "ACCOUNT"); + } + + { + // AMOUNT + Json::Value json_amount(Json::objectValue); + json_amount[jss::type] = "AMOUNT"; + json_amount[jss::value] = "1000"; + + STData data_amount = dataFromJson(sf, json_amount); + STAmount expected_amount(1000); + BEAST_EXPECT(data_amount.getFieldAmount() == expected_amount); + BEAST_EXPECT(data_amount.getInnerTypeString() == "AMOUNT"); + } + + { + // CURRENCY + } + + { + // ISSUE + } + + { + // NUMBER + } + } + + // void + // testErrorCases() + // { + // testcase("Error Cases"); + + // auto const& sf = sfParameterValue; + + // // Test JSON parsing errors + // { + // // Missing type + // Json::Value json_no_type(Json::objectValue); + // json_no_type[jss::value] = 123; + + // try { + // STData data = dataFromJson(sf, json_no_type); + // fail("Expected exception for missing type"); + // } catch (std::runtime_error const& e) { + // pass(); + // } + // } + + // { + // // Missing value + // Json::Value json_no_value(Json::objectValue); + // json_no_value[jss::type] = "UINT8"; + + // try { + // STData data = dataFromJson(sf, json_no_value); + // fail("Expected exception for missing value"); + // } catch (std::runtime_error const& e) { + // pass(); + // } + // } + + // { + // // Invalid type string + // Json::Value json_invalid_type(Json::objectValue); + // json_invalid_type[jss::type] = "INVALID_TYPE"; + // json_invalid_type[jss::value] = 123; + + // try { + // STData data = dataFromJson(sf, json_invalid_type); + // fail("Expected exception for invalid type"); + // } catch (std::runtime_error const& e) { + // pass(); + // } + // } + + // { + // // Invalid UINT256 hex + // Json::Value json_invalid_hex(Json::objectValue); + // json_invalid_hex[jss::type] = "UINT256"; + // json_invalid_hex[jss::value] = "INVALID_HEX_STRING"; + + // try { + // STData data = dataFromJson(sf, json_invalid_hex); + // fail("Expected exception for invalid hex"); + // } catch (std::runtime_error const& e) { + // pass(); + // } + // } + + // { + // // Invalid VL hex + // Json::Value json_invalid_vl(Json::objectValue); + // json_invalid_vl[jss::type] = "VL"; + // json_invalid_vl[jss::value] = "INVALID_HEX"; + + // try { + // STData data = dataFromJson(sf, json_invalid_vl); + // fail("Expected exception for invalid VL data"); + // } catch (std::invalid_argument const& e) { + // pass(); + // } + // } + + // { + // // Invalid account + // Json::Value json_invalid_account(Json::objectValue); + // json_invalid_account[jss::type] = "ACCOUNT"; + // json_invalid_account[jss::value] = "INVALID_ACCOUNT_STRING"; + + // try { + // STData data = dataFromJson(sf, json_invalid_account); + // fail("Expected exception for invalid account"); + // } catch (std::runtime_error const& e) { + // pass(); + // } + // } + + // { + // // Non-object JSON + // Json::Value json_not_object = "not an object"; + + // try { + // STData data = dataFromJson(sf, json_not_object); + // fail("Expected exception for non-object JSON"); + // } catch (std::runtime_error const& e) { + // pass(); + // } + // } + // } + + // void + // testEquivalence() + // { + // testcase("Equivalence"); + + // auto const& sf = sfParameterValue; + + // // Test equivalence for same types and values + // { + // STData data1(sf, static_cast(42)); + // STData data2(sf, static_cast(42)); + // BEAST_EXPECT(data1.isEquivalent(data2)); + // } + + // // Test non-equivalence for different values + // { + // STData data1(sf, static_cast(42)); + // STData data2(sf, static_cast(43)); + // BEAST_EXPECT(!data1.isEquivalent(data2)); + // } + + // // Test non-equivalence for different types + // { + // STData data1(sf, static_cast(42)); + // STData data2(sf, static_cast(42)); + // BEAST_EXPECT(!data1.isEquivalent(data2)); + // } + + // // Test equivalence with complex types + // { + // uint256 val; + // val.parseHex("123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0"); + // STData data1(sf, val); + // STData data2(sf, val); + // BEAST_EXPECT(data1.isEquivalent(data2)); + // } + // } + + // void + // testSize() + // { + // testcase("Size Calculation"); + + // auto const& sf = sfParameterValue; + + // // Test size calculation for each type + // { + // STData data_u8(sf, static_cast(42)); + // BEAST_EXPECT(data_u8.size() == sizeof(uint8_t)); + // } + + // { + // STData data_u16(sf, static_cast(1234)); + // BEAST_EXPECT(data_u16.size() == sizeof(uint16_t)); + // } + + // { + // STData data_u32(sf, static_cast(123456)); + // BEAST_EXPECT(data_u32.size() == sizeof(uint32_t)); + // } + + // { + // STData data_u64(sf, static_cast(123456789)); + // BEAST_EXPECT(data_u64.size() == sizeof(uint64_t)); + // } + + // { + // uint128 val_u128(0x12345678, 0x9ABCDEF0); + // STData data_u128(sf, val_u128); + // BEAST_EXPECT(data_u128.size() == uint128::size()); + // } + + // { + // uint256 val_u256; + // val_u256.parseHex("123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0"); + // STData data_u256(sf, val_u256); + // BEAST_EXPECT(data_u256.size() == uint256::size()); + // } + + // { + // Blob blob = strUnHex("DEADBEEFCAFEBABE").value(); + // STData data_vl(sf, blob); + // BEAST_EXPECT(data_vl.size() == blob.size()); + // } + + // { + // AccountID account(0x123456789ABCDEF0); + // STData data_account(sf, account); + // BEAST_EXPECT(data_account.size() == uint160::size()); + // } + + // { + // // Native amount + // STAmount amount_native(1000); + // STData data_amount_native(sf, amount_native); + // BEAST_EXPECT(data_amount_native.size() == 8); // Native amounts + // are 8 bytes + // } + + // { + // // IOU amount + // IOUAmount iou_amount(5000); + // Issue const usd( + // Currency(0x5553440000000000), + // parseBase58("rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn").value()); + // STAmount amount_iou(iou_amount, usd); + // STData data_amount_iou(sf, amount_iou); + // BEAST_EXPECT(data_amount_iou.size() == 48); // IOU amounts are 48 + // bytes + // } + // } + + // void + // testTextRepresentation() + // { + // testcase("Text Representation"); + + // auto const& sf = sfParameterValue; + + // // Test getText() for various types + // { + // STData data_u8(sf, static_cast(42)); + // std::string text = data_u8.getText(); + // BEAST_EXPECT(text.find("STData") != std::string::npos); + // BEAST_EXPECT(text.find("UINT8") != std::string::npos); + // } + + // { + // uint256 val; + // val.parseHex("123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0"); + // STData data_u256(sf, val); + // std::string text = data_u256.getText(); + // BEAST_EXPECT(text.find("STData") != std::string::npos); + // BEAST_EXPECT(text.find("UINT256") != std::string::npos); + // } + + // { + // Blob blob = strUnHex("DEADBEEF").value(); + // STData data_vl(sf, blob); + // std::string text = data_vl.getText(); + // BEAST_EXPECT(text.find("STData") != std::string::npos); + // BEAST_EXPECT(text.find("VL") != std::string::npos); + // } + // } + + // void + // testCopyAndMove() + // { + // testcase("Copy and Move Operations"); + + // auto const& sf = sfParameterValue; + + // // Test copy functionality + // { + // uint256 val; + // val.parseHex("123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0"); + // STData original(sf, val); + + // // Test copy + // char buffer[1024]; + // STBase* copied = original.copy(sizeof(buffer), buffer); + // BEAST_EXPECT(copied != nullptr); + + // STData* copied_data = dynamic_cast(copied); + // BEAST_EXPECT(copied_data != nullptr); + // BEAST_EXPECT(copied_data->getFieldH256() == val); + // BEAST_EXPECT(copied_data->getInnerTypeString() == "UINT256"); + // } + + // // Test move functionality + // { + // Blob blob = strUnHex("DEADBEEFCAFEBABE").value(); + // STData original(sf, blob); + + // char buffer[1024]; + // STBase* moved = original.move(sizeof(buffer), buffer); + // BEAST_EXPECT(moved != nullptr); + + // STData* moved_data = dynamic_cast(moved); + // BEAST_EXPECT(moved_data != nullptr); + // BEAST_EXPECT(moved_data->getFieldVL() == blob); + // BEAST_EXPECT(moved_data->getInnerTypeString() == "VL"); + // } + // } + + // void + // testBoundaryValues() + // { + // testcase("Boundary Values"); + + // auto const& sf = sfParameterValue; + + // // Test minimum and maximum values for each numeric type + // { + // // UINT8 boundaries + // STData data_u8_min(sf, static_cast(0)); + // BEAST_EXPECT(data_u8_min.getFieldU8() == 0); + + // STData data_u8_max(sf, static_cast(255)); + // BEAST_EXPECT(data_u8_max.getFieldU8() == 255); + // } + + // { + // // UINT16 boundaries + // STData data_u16_min(sf, static_cast(0)); + // BEAST_EXPECT(data_u16_min.getFieldU16() == 0); + + // STData data_u16_max(sf, static_cast(65535)); + // BEAST_EXPECT(data_u16_max.getFieldU16() == 65535); + // } + + // { + // // UINT32 boundaries + // STData data_u32_min(sf, static_cast(0)); + // BEAST_EXPECT(data_u32_min.getFieldU32() == 0); + + // STData data_u32_max(sf, static_cast(0xFFFFFFFF)); + // BEAST_EXPECT(data_u32_max.getFieldU32() == 0xFFFFFFFF); + // } + + // { + // // UINT64 boundaries + // STData data_u64_min(sf, static_cast(0)); + // BEAST_EXPECT(data_u64_min.getFieldU64() == 0); + + // STData data_u64_max(sf, + // static_cast(0xFFFFFFFFFFFFFFFF)); + // BEAST_EXPECT(data_u64_max.getFieldU64() == 0xFFFFFFFFFFFFFFFF); + // } + + // { + // // Empty blob + // Blob empty_blob; + // STData data_empty_vl(sf, empty_blob); + // BEAST_EXPECT(data_empty_vl.getFieldVL() == empty_blob); + // BEAST_EXPECT(data_empty_vl.size() == 0); + // } + + // { + // // Large blob (test with reasonably sized data) + // Blob large_blob(1000, 0xAB); // 1000 bytes of 0xAB + // STData data_large_vl(sf, large_blob); + // BEAST_EXPECT(data_large_vl.getFieldVL() == large_blob); + // BEAST_EXPECT(data_large_vl.size() == 1000); + // } + // } + + // void + // testSpecialSerializationCases() + // { + // testcase("Special Serialization Cases"); + + // auto const& sf = sfParameterValue; + + // // Test serialization format compliance + // { + // // Verify type prefix is included in serialization + // STData data_u8(sf, static_cast(0x42)); + // Serializer s; + // data_u8.add(s); + + // // Should start with type identifier (STI_UINT8 = 0x0010) + // auto slice = s.slice(); + // BEAST_EXPECT(slice.size() >= 2); + // std::uint16_t type_id = (static_cast(slice[0]) << + // 8) | slice[1]; BEAST_EXPECT(type_id == STI_UINT8); + // } + + // { + // // Test VL serialization includes length + // Blob test_blob = strUnHex("DEADBEEF").value(); + // STData data_vl(sf, test_blob); + // Serializer s; + // data_vl.add(s); + + // auto slice = s.slice(); + // BEAST_EXPECT(slice.size() >= 2); + // std::uint16_t type_id = (static_cast(slice[0]) << + // 8) | slice[1]; BEAST_EXPECT(type_id == STI_VL); + // } + // } + + // void + // testTypeConsistency() + // { + // testcase("Type Consistency"); + + // auto const& sf = sfParameterValue; + + // // Verify that changing types properly updates internal state + // { + // STData data(sf); + + // // Start with UINT8 + // data.setFieldU8(42); + // BEAST_EXPECT(data.getInnerTypeString() == "UINT8"); + // BEAST_EXPECT(data.getFieldU8() == 42); + + // // Change to UINT16 + // data.setFieldU16(1234); + // BEAST_EXPECT(data.getInnerTypeString() == "UINT16"); + // BEAST_EXPECT(data.getFieldU16() == 1234); + + // // Change to VL + // Blob blob = strUnHex("DEADBEEF").value(); + // data.setFieldVL(blob); + // BEAST_EXPECT(data.getInnerTypeString() == "VL"); + // BEAST_EXPECT(data.getFieldVL() == blob); + + // // Change to UINT256 + // uint256 val; + // val.parseHex("123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0"); + // data.setFieldH256(val); + // BEAST_EXPECT(data.getInnerTypeString() == "UINT256"); + // BEAST_EXPECT(data.getFieldH256() == val); + // } + // } + + // void + // testComplexAmountTypes() + // { + // testcase("Complex Amount Types"); + + // auto const& sf = sfParameterValue; + + // // Test various STAmount configurations + // { + // // Zero native amount + // STAmount zero_native(0); + // STData data_zero(sf, zero_native); + // BEAST_EXPECT(data_zero.getFieldAmount() == zero_native); + // BEAST_EXPECT(data_zero.size() == 8); // Native amounts are 8 + // bytes + // } + + // { + // // Maximum native amount + // STAmount max_native(100000000000000000ULL); // Max XRP in drops + // STData data_max(sf, max_native); + // BEAST_EXPECT(data_max.getFieldAmount() == max_native); + // } + + // { + // // IOU with zero value + // IOUAmount zero_iou(0); + // Issue const eur( + // Currency(0x4555520000000000), + // parseBase58("rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn").value()); + // STAmount zero_iou_amount(zero_iou, eur); + // STData data_zero_iou(sf, zero_iou_amount); + // BEAST_EXPECT(data_zero_iou.getFieldAmount() == zero_iou_amount); + // BEAST_EXPECT(data_zero_iou.size() == 48); // IOU amounts are 48 + // bytes + // } + // } + + // void + // testJsonRoundTrip() + // { + // testcase("JSON Round Trip"); + + // auto const& sf = sfParameterValue; + + // // Test complete round trip: STData -> JSON -> STData + // { + // // UINT8 + // STData original_u8(sf, static_cast(123)); + // Json::Value json_u8 = original_u8.getJson(JsonOptions::none); + // STData restored_u8 = dataFromJson(sf, json_u8); + // BEAST_EXPECT(original_u8.isEquivalent(restored_u8)); + // } + + // { + // // UINT256 + // uint256 val; + // val.parseHex("FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210"); + // STData original_u256(sf, val); + // Json::Value json_u256 = original_u256.getJson(JsonOptions::none); + // STData restored_u256 = dataFromJson(sf, json_u256); + // BEAST_EXPECT(original_u256.isEquivalent(restored_u256)); + // } + + // { + // // VL + // Blob blob = strUnHex("0123456789ABCDEF").value(); + // STData original_vl(sf, blob); + // Json::Value json_vl = original_vl.getJson(JsonOptions::none); + // STData restored_vl = dataFromJson(sf, json_vl); + // BEAST_EXPECT(original_vl.isEquivalent(restored_vl)); + // } + + // { + // // ACCOUNT + // AccountID account_id = + // parseBase58("rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn").value(); + // STData original_account(sf, account_id); + // Json::Value json_account = + // original_account.getJson(JsonOptions::none); STData + // restored_account = dataFromJson(sf, json_account); + // BEAST_EXPECT(original_account.isEquivalent(restored_account)); + // } + // } + + // void + // testSerializationRoundTrip() + // { + // testcase("Serialization Round Trip"); + + // auto const& sf = sfParameterValue; + + // // Test complete serialization round trip for all types + // std::vector test_data; + + // // Populate test data with various types + // test_data.emplace_back(sf, static_cast(0xFF)); + // test_data.emplace_back(sf, static_cast(0xFFFF)); + // test_data.emplace_back(sf, static_cast(0xFFFFFFFF)); + // test_data.emplace_back(sf, + // static_cast(0xFFFFFFFFFFFFFFFF)); + + // uint128 val128(0xFFFFFFFF, 0xFFFFFFFF); + // test_data.emplace_back(sf, val128); + + // uint256 val256; + // val256.parseHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + // test_data.emplace_back(sf, val256); + + // Blob blob = strUnHex("DEADBEEFCAFEBABE1234567890ABCDEF").value(); + // test_data.emplace_back(sf, blob); + + // AccountID account(0xFFFFFFFFFFFFFFFF); + // test_data.emplace_back(sf, account); + + // STAmount amount(999999); + // test_data.emplace_back(sf, amount); + + // // Test round trip for each + // for (auto const& original : test_data) + // { + // Serializer s; + // original.add(s); + + // SerialIter sit(s.slice()); + // STData deserialized(sit, sf); + + // BEAST_EXPECT(original.isEquivalent(deserialized)); + // BEAST_EXPECT(original.getInnerTypeString() == + // deserialized.getInnerTypeString()); + // } + // } + + // void + // testMakeFieldPresent() + // { + // testcase("Make Field Present"); + + // auto const& sf = sfParameterValue; + + // // Test makeFieldPresent functionality + // { + // STData data(sf); + // STBase* field = data.makeFieldPresent(); + // BEAST_EXPECT(field != nullptr); + + // // Field should now be present (not STI_NOTPRESENT) + // BEAST_EXPECT(field->getSType() != STI_NOTPRESENT); + // } + // } + + void + run() override + { + testConstructors(); + testSerializationDeserialization(); + testSettersAndGetters(); + testJsonConversion(); + testDataFromJson(); + // testErrorCases(); + // testEquivalence(); + // testSize(); + // testTextRepresentation(); + // testCopyAndMove(); + // testBoundaryValues(); + // testSpecialSerializationCases(); + // testTypeConsistency(); + // testComplexAmountTypes(); + // testJsonRoundTrip(); + // testSerializationRoundTrip(); + // testMakeFieldPresent(); + } +}; + +BEAST_DEFINE_TESTSUITE(STData, protocol, xrpl); + +} // namespace xrpl diff --git a/src/test/protocol/STJson_test.cpp b/src/test/protocol/STJson_test.cpp new file mode 100644 index 0000000000..b0cfbb8807 --- /dev/null +++ b/src/test/protocol/STJson_test.cpp @@ -0,0 +1,887 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +struct STJson_test : public beast::unit_test::suite +{ + void + testDefaultConstructor() + { + testcase("Default constructor"); + STJson json; + BEAST_EXPECT(json.isObject()); + BEAST_EXPECT(!json.isArray()); + BEAST_EXPECT(json.getMap().empty()); + } + + void + testSetAndGet() + { + testcase("setObjectField() and getObjectField()"); + STJson json; + auto value = std::make_shared(sfLedgerIndex, 12345); + json.setObjectField("foo", value); + + auto retrieved = json.getObjectField("foo"); + BEAST_EXPECT(retrieved.has_value()); + BEAST_EXPECT((*retrieved)->getSType() == STI_UINT32); + BEAST_EXPECT( + std::dynamic_pointer_cast(*retrieved)->value() == 12345); + + // Test non-existent key + auto missing = json.getObjectField("bar"); + BEAST_EXPECT(!missing.has_value()); + } + + void + testMoveConstructor() + { + testcase("Move constructor (Object)"); + STJson::Map map; + map["bar"] = std::make_shared(sfTransactionType, 42); + STJson json(std::move(map)); + BEAST_EXPECT(json.isObject()); + BEAST_EXPECT(json.getMap().size() == 1); + BEAST_EXPECT( + std::dynamic_pointer_cast(json.getMap().at("bar")) + ->value() == 42); + } + + void + testArrayConstruction() + { + testcase("Array constructor"); + STJson::Array arr; + arr.push_back(std::make_shared(sfNetworkID, 100)); + arr.push_back(std::make_shared(sfNetworkID, 200)); + + STJson json(std::move(arr)); + BEAST_EXPECT(json.isArray()); + BEAST_EXPECT(!json.isObject()); + BEAST_EXPECT(json.arraySize() == 2); + + auto elem0 = json.getArrayElement(0); + BEAST_EXPECT(elem0.has_value()); + BEAST_EXPECT( + std::dynamic_pointer_cast(*elem0)->value() == 100); + } + + void + testTypeChecking() + { + testcase("Type checking methods"); + STJson objJson; + BEAST_EXPECT(objJson.isObject()); + BEAST_EXPECT(!objJson.isArray()); + BEAST_EXPECT(objJson.getType() == STJson::JsonType::Object); + + STJson arrJson(STJson::Array{}); + BEAST_EXPECT(arrJson.isArray()); + BEAST_EXPECT(!arrJson.isObject()); + BEAST_EXPECT(arrJson.getType() == STJson::JsonType::Array); + } + + void + testArrayOperations() + { + testcase("Array operations"); + STJson json(STJson::Array{}); + + // Test push + json.pushArrayElement(std::make_shared(sfCloseResolution, 10)); + json.pushArrayElement(std::make_shared(sfCloseResolution, 20)); + json.pushArrayElement(std::make_shared(sfCloseResolution, 30)); + + BEAST_EXPECT(json.arraySize() == 3); + + // Test get + auto elem1 = json.getArrayElement(1); + BEAST_EXPECT(elem1.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*elem1)->value() == 20); + + // Test set (replace) + json.setArrayElement( + 1, std::make_shared(sfCloseResolution, 25)); + auto elem1Updated = json.getArrayElement(1); + BEAST_EXPECT( + std::dynamic_pointer_cast(*elem1Updated)->value() == 25); + + // Test out of bounds + auto missing = json.getArrayElement(10); + BEAST_EXPECT(!missing.has_value()); + } + + void + testArrayAutoResize() + { + testcase("Array auto-resize"); + STJson json(STJson::Array{}); + + // Set element at index 5 (should auto-resize with nulls) + json.setArrayElement(5, std::make_shared(sfNetworkID, 999)); + + BEAST_EXPECT(json.arraySize() == 6); + + // Check nulls were added + for (size_t i = 0; i < 5; ++i) + { + auto elem = json.getArrayElement(i); + BEAST_EXPECT(elem.has_value()); + BEAST_EXPECT(*elem == nullptr); + } + + // Check value at index 5 + auto elem5 = json.getArrayElement(5); + BEAST_EXPECT(elem5.has_value()); + BEAST_EXPECT( + std::dynamic_pointer_cast(*elem5)->value() == 999); + } + + void + testArrayElementFields() + { + testcase("Array element field operations"); + STJson json(STJson::Array{}); + + // Set field in array element (auto-creates object) + json.setArrayElementField( + 0, "name", std::make_shared(sfNetworkID, 42)); + json.setArrayElementField( + 0, "value", std::make_shared(sfNetworkID, 100)); + + // Get fields + auto name = json.getArrayElementField(0, "name"); + BEAST_EXPECT(name.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*name)->value() == 42); + + auto value = json.getArrayElementField(0, "value"); + BEAST_EXPECT(value.has_value()); + BEAST_EXPECT( + std::dynamic_pointer_cast(*value)->value() == 100); + + // Set field at higher index (auto-resize) + json.setArrayElementField( + 3, "test", std::make_shared(sfCloseResolution, 99)); + BEAST_EXPECT(json.arraySize() == 4); + + auto test = json.getArrayElementField(3, "test"); + BEAST_EXPECT(test.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*test)->value() == 99); + } + + void + testNestedObjectField() + { + testcase("Nested object field operations"); + STJson json; + + json.setNestedObjectField( + "user", "id", std::make_shared(sfNetworkID, 123)); + json.setNestedObjectField( + "user", "name", std::make_shared(sfNetworkID, 456)); + + auto id = json.getNestedObjectField("user", "id"); + BEAST_EXPECT(id.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*id)->value() == 123); + + auto name = json.getNestedObjectField("user", "name"); + BEAST_EXPECT(name.has_value()); + BEAST_EXPECT( + std::dynamic_pointer_cast(*name)->value() == 456); + + // Test non-existent nested key + auto missing = json.getNestedObjectField("user", "age"); + BEAST_EXPECT(!missing.has_value()); + } + + void + testNestedArrayOperations() + { + testcase("Nested array operations"); + STJson json; + + // Set entire elements in nested array + json.setNestedArrayElement( + "items", 0, std::make_shared(sfNetworkID, 10)); + json.setNestedArrayElement( + "items", 1, std::make_shared(sfNetworkID, 20)); + json.setNestedArrayElement( + "items", 2, std::make_shared(sfNetworkID, 30)); + + // Get elements + auto item0 = json.getNestedArrayElement("items", 0); + BEAST_EXPECT(item0.has_value()); + BEAST_EXPECT( + std::dynamic_pointer_cast(*item0)->value() == 10); + + auto item2 = json.getNestedArrayElement("items", 2); + BEAST_EXPECT(item2.has_value()); + BEAST_EXPECT( + std::dynamic_pointer_cast(*item2)->value() == 30); + + // Auto-resize test + json.setNestedArrayElement( + "items", 5, std::make_shared(sfNetworkID, 60)); + auto item5 = json.getNestedArrayElement("items", 5); + BEAST_EXPECT(item5.has_value()); + BEAST_EXPECT( + std::dynamic_pointer_cast(*item5)->value() == 60); + } + + void + testNestedArrayElementFields() + { + testcase("Nested array element field operations"); + STJson json; + + // Set fields in nested array elements + json.setNestedArrayElementField( + "users", 0, "id", std::make_shared(sfNetworkID, 100)); + json.setNestedArrayElementField( + "users", 0, "name", std::make_shared(sfNetworkID, 200)); + json.setNestedArrayElementField( + "users", 1, "id", std::make_shared(sfNetworkID, 101)); + json.setNestedArrayElementField( + "users", 1, "name", std::make_shared(sfNetworkID, 201)); + + // Get fields + auto user0id = json.getNestedArrayElementField("users", 0, "id"); + BEAST_EXPECT(user0id.has_value()); + BEAST_EXPECT( + std::dynamic_pointer_cast(*user0id)->value() == 100); + + auto user1name = json.getNestedArrayElementField("users", 1, "name"); + BEAST_EXPECT(user1name.has_value()); + BEAST_EXPECT( + std::dynamic_pointer_cast(*user1name)->value() == 201); + + // Test missing field + auto missing = json.getNestedArrayElementField("users", 0, "age"); + BEAST_EXPECT(!missing.has_value()); + } + + void + testDepthValidation() + { + testcase("Depth validation (max 1 level)"); + + // Valid: Object with nested object (depth 1) + { + STJson json; + auto nested = std::make_shared(); + nested->setObjectField( + "x", std::make_shared(sfNetworkID, 42)); + + try + { + json.setObjectField("nested", nested); + pass(); + } + catch (...) + { + fail("Should allow depth 1 nesting"); + } + } + + // Invalid: Object with nested object containing nested object (depth 2) + { + STJson json; + auto nested1 = std::make_shared(); + auto nested2 = std::make_shared(); + nested2->setObjectField( + "x", std::make_shared(sfNetworkID, 42)); + nested1->setObjectField("nested", nested2); + + try + { + json.setObjectField("nested", nested1); + fail("Should reject depth 2 nesting"); + } + catch (std::runtime_error const& e) + { + pass(); + } + } + + // Valid: Array with object elements (depth 1) + { + STJson json(STJson::Array{}); + auto elem = std::make_shared(); + elem->setObjectField( + "x", std::make_shared(sfNetworkID, 42)); + + try + { + json.pushArrayElement(elem); + pass(); + } + catch (...) + { + fail("Should allow depth 1 in array"); + } + } + + // Invalid: Array with nested arrays (depth 2) + { + STJson json(STJson::Array{}); + auto innerArray = std::make_shared(STJson::Array{}); + innerArray->pushArrayElement( + std::make_shared(sfNetworkID, 42)); + + try + { + json.pushArrayElement(innerArray); + fail("Should reject array of arrays"); + } + catch (std::runtime_error const& e) + { + pass(); + } + } + + // Valid: Object with nested array (depth 1) + { + STJson json; + auto arr = std::make_shared(STJson::Array{}); + arr->pushArrayElement(std::make_shared(sfNetworkID, 42)); + + try + { + json.setObjectField("arr", arr); + pass(); + } + catch (...) + { + fail("Should allow object with array"); + } + } + + // Test depth validation in setNestedObjectField + { + STJson json; + auto nested = std::make_shared(); + nested->setObjectField( + "x", std::make_shared(sfNetworkID, 42)); + + try + { + json.setNestedObjectField("outer", "inner", nested); + fail("Should reject depth 2 via setNestedObjectField"); + } + catch (std::runtime_error const& e) + { + pass(); + } + } + } + + void + testAddAndFromBlob() + { + testcase("add() and fromBlob() for objects"); + STJson json; + json.setObjectField( + "a", std::make_shared(sfCloseResolution, 7)); + json.setObjectField( + "b", std::make_shared(sfNetworkID, 123456)); + + Serializer s; + json.add(s); + + auto blob = s.peekData(); + auto parsed = STJson::fromBlob(blob.data(), blob.size()); + BEAST_EXPECT(parsed->isObject()); + BEAST_EXPECT(parsed->getMap().size() == 2); + + auto a = parsed->getObjectField("a"); + BEAST_EXPECT(a.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*a)->value() == 7); + + auto b = parsed->getObjectField("b"); + BEAST_EXPECT(b.has_value()); + BEAST_EXPECT( + std::dynamic_pointer_cast(*b)->value() == 123456); + } + + void + testArraySerialization() + { + testcase("Array serialization and deserialization"); + STJson json(STJson::Array{}); + json.pushArrayElement(std::make_shared(sfCloseResolution, 10)); + json.pushArrayElement(std::make_shared(sfNetworkID, 20)); + json.pushArrayElement(std::make_shared(sfIndexNext, 30)); + + Serializer s; + json.add(s); + + auto blob = s.peekData(); + auto parsed = STJson::fromBlob(blob.data(), blob.size()); + + BEAST_EXPECT(parsed->isArray()); + BEAST_EXPECT(parsed->arraySize() == 3); + + auto elem0 = parsed->getArrayElement(0); + BEAST_EXPECT(std::dynamic_pointer_cast(*elem0)->value() == 10); + + auto elem1 = parsed->getArrayElement(1); + BEAST_EXPECT( + std::dynamic_pointer_cast(*elem1)->value() == 20); + + auto elem2 = parsed->getArrayElement(2); + BEAST_EXPECT( + std::dynamic_pointer_cast(*elem2)->value() == 30); + } + + void + testFromSerialIter() + { + testcase("fromSerialIter()"); + STJson json; + json.setObjectField( + "x", std::make_shared(sfCloseResolution, 99)); + Serializer s; + json.add(s); + + SerialIter sit(s.peekData().data(), s.peekData().size()); + auto parsed = STJson::fromSerialIter(sit); + BEAST_EXPECT(parsed->isObject()); + BEAST_EXPECT(parsed->getMap().size() == 1); + + auto x = parsed->getObjectField("x"); + BEAST_EXPECT(x.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*x)->value() == 99); + } + + void + testFromSField() + { + testcase("Constructor from SField"); + STJson json; + json.setObjectField( + "x", std::make_shared(sfCloseResolution, 99)); + Serializer s; + json.add(s); + + SerialIter sit(s.peekData().data(), s.peekData().size()); + auto parsed = STJson{sit, sfContractCode}; + BEAST_EXPECT(parsed.isObject()); + BEAST_EXPECT(parsed.getMap().size() == 1); + + auto x = parsed.getObjectField("x"); + BEAST_EXPECT(x.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*x)->value() == 99); + } + + void + testGetJson() + { + testcase("getJson() for objects"); + STJson json; + json.setObjectField( + "foo", std::make_shared(sfTransactionType, 65535)); + json.setObjectField("bar", nullptr); // test null value + + Json::Value jv = json.getJson(JsonOptions::none); + BEAST_EXPECT(jv.isObject()); + BEAST_EXPECT(jv["foo"].asUInt() == 65535); + BEAST_EXPECT(jv["bar"].isNull()); + } + + void + testGetJsonArray() + { + testcase("getJson() for arrays"); + STJson json(STJson::Array{}); + json.pushArrayElement(std::make_shared(sfNetworkID, 100)); + json.pushArrayElement(std::make_shared(sfNetworkID, 200)); + json.pushArrayElement(nullptr); // null element + + Json::Value jv = json.getJson(JsonOptions::none); + BEAST_EXPECT(jv.isArray()); + BEAST_EXPECT(jv.size() == 3); + BEAST_EXPECT(jv[Json::UInt(0)].asUInt() == 100); + BEAST_EXPECT(jv[Json::UInt(1)].asUInt() == 200); + BEAST_EXPECT(jv[Json::UInt(2)].isNull()); + } + + void + testMakeValueFromVLWithType() + { + testcase("makeValueFromVLWithType()"); + Serializer s; + s.add8(STI_UINT32); + s.add32(0xDEADBEEF); + SerialIter sit(s.peekData().data(), s.peekData().size()); + auto value = STJson::makeValueFromVLWithType(sit); + BEAST_EXPECT(value->getSType() == STI_UINT32); + BEAST_EXPECT( + std::dynamic_pointer_cast(value)->value() == 0xDEADBEEF); + } + + void + testMixedStructures() + { + testcase("Mixed structures (objects with arrays)"); + STJson json; + + // Add simple fields + json.setObjectField("id", std::make_shared(sfNetworkID, 1)); + + // Add nested object + json.setNestedObjectField( + "metadata", "version", std::make_shared(sfNetworkID, 2)); + + // Add nested array with objects + json.setNestedArrayElementField( + "users", 0, "name", std::make_shared(sfNetworkID, 100)); + json.setNestedArrayElementField( + "users", 0, "age", std::make_shared(sfNetworkID, 25)); + json.setNestedArrayElementField( + "users", 1, "name", std::make_shared(sfNetworkID, 101)); + + // Serialize and deserialize + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + + // Verify structure + BEAST_EXPECT(parsed->isObject()); + + auto id = parsed->getObjectField("id"); + BEAST_EXPECT(std::dynamic_pointer_cast(*id)->value() == 1); + + auto version = parsed->getNestedObjectField("metadata", "version"); + BEAST_EXPECT( + std::dynamic_pointer_cast(*version)->value() == 2); + + auto user0name = parsed->getNestedArrayElementField("users", 0, "name"); + BEAST_EXPECT( + std::dynamic_pointer_cast(*user0name)->value() == 100); + + auto user1name = parsed->getNestedArrayElementField("users", 1, "name"); + BEAST_EXPECT( + std::dynamic_pointer_cast(*user1name)->value() == 101); + } + + void + testSTTypes() + { + testcase("All STypes roundtrip"); + + // STI_UINT8 + { + STJson json; + json.setObjectField( + "u8", std::make_shared(sfCloseResolution, 200)); + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto u8 = parsed->getObjectField("u8"); + BEAST_EXPECT( + std::dynamic_pointer_cast(*u8)->value() == 200); + } + + // STI_UINT16 + { + STJson json; + json.setObjectField( + "u16", std::make_shared(sfSignerWeight, 4242)); + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto u16 = parsed->getObjectField("u16"); + BEAST_EXPECT( + std::dynamic_pointer_cast(*u16)->value() == 4242); + } + + // STI_UINT32 + { + STJson json; + json.setObjectField( + "u32", std::make_shared(sfNetworkID, 0xABCDEF01)); + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto u32 = parsed->getObjectField("u32"); + BEAST_EXPECT( + std::dynamic_pointer_cast(*u32)->value() == + 0xABCDEF01); + } + + // STI_UINT64 + { + STJson json; + json.setObjectField( + "u64", + std::make_shared(sfIndexNext, 0x123456789ABCDEF0ull)); + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto u64 = parsed->getObjectField("u64"); + BEAST_EXPECT( + std::dynamic_pointer_cast(*u64)->value() == + 0x123456789ABCDEF0ull); + } + + // STI_UINT160 + { + STJson json; + uint160 val; + val.data()[0] = 0x01; + val.data()[19] = 0xFF; + json.setObjectField( + "u160", std::make_shared(sfTakerPaysCurrency, val)); + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto u160 = parsed->getObjectField("u160"); + BEAST_EXPECT( + std::dynamic_pointer_cast(*u160)->value() == val); + } + + // STI_UINT256 + { + STJson json; + uint256 val; + val.data()[0] = 0xAA; + val.data()[31] = 0xBB; + json.setObjectField( + "u256", std::make_shared(sfLedgerHash, val)); + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto u256 = parsed->getObjectField("u256"); + BEAST_EXPECT( + std::dynamic_pointer_cast(*u256)->value() == val); + } + + // STI_AMOUNT + { + STJson json; + // XRP amount + STAmount xrp(sfAmount, static_cast(123456789u)); + json.setObjectField("amount", std::make_shared(xrp)); + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto amount = parsed->getObjectField("amount"); + auto parsedAmt = std::dynamic_pointer_cast(*amount); + BEAST_EXPECT(parsedAmt->mantissa() == 123456789u); + BEAST_EXPECT(parsedAmt->issue() == xrp.issue()); + } + + // STI_VL (STBlob) + { + STJson json; + std::vector blobData = {0xDE, 0xAD, 0xBE, 0xEF}; + json.setObjectField( + "blob", + std::make_shared( + sfPublicKey, blobData.data(), blobData.size())); + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto blob = parsed->getObjectField("blob"); + auto parsedBlob = std::dynamic_pointer_cast(*blob); + BEAST_EXPECT(parsedBlob->size() == blobData.size()); + BEAST_EXPECT( + std::memcmp( + parsedBlob->data(), blobData.data(), blobData.size()) == 0); + } + + // STI_ACCOUNT + { + STJson json; + // Use a known AccountID (20 bytes) + AccountID acct = AccountID{}; + json.setObjectField( + "acct", std::make_shared(sfAccount, acct)); + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto account = parsed->getObjectField("acct"); + auto parsedAcct = std::dynamic_pointer_cast(*account); + BEAST_EXPECT(parsedAcct->value() == acct); + } + + // STI_CURRENCY (STCurrency) + { + STJson json; + Currency cur; + cur.data()[0] = 0xAA; + cur.data()[19] = 0xBB; + json.setObjectField( + "currency", std::make_shared(sfGeneric, cur)); + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto currency = parsed->getObjectField("currency"); + auto parsedCur = std::dynamic_pointer_cast(*currency); + BEAST_EXPECT(parsedCur->value() == cur); + } + + // STI_JSON (STJson) Nested JSON + { + STJson innerJson; + // XRP amount + STAmount xrp(sfAmount, static_cast(123456789u)); + innerJson.setObjectField("amount", std::make_shared(xrp)); + + STJson json; + json.setObjectField("nested", std::make_shared(innerJson)); + Serializer s; + json.add(s); + + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto nested = parsed->getObjectField("nested"); + auto parsedNested = std::dynamic_pointer_cast(*nested); + auto amount = parsedNested->getObjectField("amount"); + BEAST_EXPECT( + std::dynamic_pointer_cast(*amount)->mantissa() == + 123456789u); + } + } + + void + testEdgeCases() + { + testcase("Edge cases"); + + // Empty object + { + STJson json; + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + BEAST_EXPECT(parsed->isObject()); + BEAST_EXPECT(parsed->getMap().empty()); + } + + // Empty array + { + STJson json(STJson::Array{}); + Serializer s; + json.add(s); + auto parsed = + STJson::fromBlob(s.peekData().data(), s.peekData().size()); + BEAST_EXPECT(parsed->isArray()); + BEAST_EXPECT(parsed->arraySize() == 0); + } + + // Array with null elements + { + STJson json(STJson::Array{}); + json.pushArrayElement(nullptr); + json.pushArrayElement(std::make_shared(sfNetworkID, 42)); + json.pushArrayElement(nullptr); + + BEAST_EXPECT(json.arraySize() == 3); + + auto elem0 = json.getArrayElement(0); + BEAST_EXPECT(elem0.has_value()); + BEAST_EXPECT(*elem0 == nullptr); + + auto elem1 = json.getArrayElement(1); + BEAST_EXPECT(elem1.has_value()); + BEAST_EXPECT(*elem1 != nullptr); + } + + // Object with null value + { + STJson json; + json.setObjectField("null_field", nullptr); + + auto val = json.getObjectField("null_field"); + BEAST_EXPECT(val.has_value()); + BEAST_EXPECT(*val == nullptr); + } + } + + void + run() override + { + testDefaultConstructor(); + testSetAndGet(); + testMoveConstructor(); + testArrayConstruction(); + testTypeChecking(); + testArrayOperations(); + testArrayAutoResize(); + testArrayElementFields(); + testNestedObjectField(); + testNestedArrayOperations(); + testNestedArrayElementFields(); + testDepthValidation(); + testAddAndFromBlob(); + testArraySerialization(); + testFromSerialIter(); + testFromSField(); + testGetJson(); + testGetJsonArray(); + testMakeValueFromVLWithType(); + testMixedStructures(); + testSTTypes(); + testEdgeCases(); + } +}; + +BEAST_DEFINE_TESTSUITE(STJson, protocol, xrpl); + +} // namespace xrpl diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index 1637554f5c..54b970b0ae 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -515,6 +515,22 @@ public: if (jv.isMember(jss::reserve_inc) != isFlagLedger) return false; + if (env.closed()->rules().enabled(featureSmartEscrow)) + { + if (jv.isMember(jss::extension_compute) != isFlagLedger) + return false; + + if (jv.isMember(jss::extension_size) != isFlagLedger) + return false; + } + else + { + if (jv.isMember(jss::extension_compute)) + return false; + + if (jv.isMember(jss::extension_size)) + return false; + } return true; }; @@ -1579,7 +1595,8 @@ public: testTransactions_APIv1(); testTransactions_APIv2(); testManifests(); - testValidations(all - xrpFees); + testValidations(all - featureXRPFees - featureSmartEscrow); + testValidations(all - featureSmartEscrow); testValidations(all); testSubErrors(true); testSubErrors(false); diff --git a/src/xrpld/app/ledger/Ledger.cpp b/src/xrpld/app/ledger/Ledger.cpp index e21cbb6d54..96b99404cc 100644 --- a/src/xrpld/app/ledger/Ledger.cpp +++ b/src/xrpld/app/ledger/Ledger.cpp @@ -202,6 +202,15 @@ Ledger::Ledger( sle->at(sfReserveIncrement) = *f; sle->at(sfReferenceFeeUnits) = Config::FEE_UNITS_DEPRECATED; } + if (std::find( + amendments.begin(), amendments.end(), featureSmartEscrow) != + amendments.end()) + { + sle->at(sfExtensionComputeLimit) = + config.FEES.extension_compute_limit; + sle->at(sfExtensionSizeLimit) = config.FEES.extension_size_limit; + sle->at(sfGasPrice) = config.FEES.gas_price; + } rawInsert(sle); } @@ -598,6 +607,7 @@ Ledger::setup() { bool oldFees = false; bool newFees = false; + bool extensionFees = false; { auto const baseFee = sle->at(~sfBaseFee); auto const reserveBase = sle->at(~sfReserveBase); @@ -615,6 +625,7 @@ Ledger::setup() auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops); auto const reserveIncrementXRP = sle->at(~sfReserveIncrementDrops); + auto assign = [&ret]( XRPAmount& dest, std::optional const& src) { @@ -631,12 +642,35 @@ Ledger::setup() assign(fees_.increment, reserveIncrementXRP); newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP; } + { + auto const extensionComputeLimit = + sle->at(~sfExtensionComputeLimit); + auto const extensionSizeLimit = sle->at(~sfExtensionSizeLimit); + auto const gasPrice = sle->at(~sfGasPrice); + + auto assign = [](std::uint32_t& dest, + std::optional const& src) { + if (src) + { + dest = src.value(); + } + }; + assign(fees_.extensionComputeLimit, extensionComputeLimit); + assign(fees_.extensionSizeLimit, extensionSizeLimit); + assign(fees_.gasPrice, gasPrice); + extensionFees = + extensionComputeLimit || extensionSizeLimit || gasPrice; + } if (oldFees && newFees) // Should be all of one or the other, but not both ret = false; if (!rules_.enabled(featureXRPFees) && newFees) // Can't populate the new fees before the amendment is enabled ret = false; + if (!rules_.enabled(featureSmartEscrow) && extensionFees) + // Can't populate the extension fees before the amendment is + // enabled + ret = false; } } catch (SHAMapMissingNode const&) @@ -664,6 +698,13 @@ Ledger::defaultFees(Config const& config) fees_.reserve = config.FEES.account_reserve; if (fees_.increment == 0) fees_.increment = config.FEES.owner_reserve; + + if (fees_.extensionComputeLimit == 0) + fees_.extensionComputeLimit = config.FEES.extension_compute_limit; + if (fees_.extensionSizeLimit == 0) + fees_.extensionSizeLimit = config.FEES.extension_size_limit; + if (fees_.gasPrice == 0) + fees_.gasPrice = config.FEES.gas_price; } std::shared_ptr diff --git a/src/xrpld/app/misc/ContractUtils.cpp b/src/xrpld/app/misc/ContractUtils.cpp new file mode 100644 index 0000000000..ae87b42720 --- /dev/null +++ b/src/xrpld/app/misc/ContractUtils.cpp @@ -0,0 +1,678 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include + +#include + +namespace xrpl { +namespace contract { + +struct BlobHash +{ + std::size_t + operator()(Blob const& b) const noexcept + { + if (b.empty()) + return 0; + return std::hash{}(std::string_view( + reinterpret_cast(b.data()), b.size())); + } +}; + +int64_t +contractCreateFee(uint64_t byteCount) +{ + constexpr uint64_t mul = static_cast(createByteMultiplier); + if (byteCount > std::numeric_limits::max() / mul) + return feeCalculationFailed; // overflow + uint64_t uf = byteCount * mul; + if (uf > static_cast(std::numeric_limits::max())) + return feeCalculationFailed; + return static_cast(uf); +} + +NotTEC +preflightFunctions(STTx const& tx, beast::Journal j) +{ + // Functions must be present if ContractCode is present. + if (!tx.isFieldPresent(sfContractCode)) + return tesSUCCESS; + + if (!tx.isFieldPresent(sfFunctions)) + { + JLOG(j.trace()) << "ContractCreate/Modify: ContractCode present but " + "Functions missing."; + return temARRAY_EMPTY; + } + + auto const& functions = tx.getFieldArray(sfFunctions); + + if (functions.empty()) + { + JLOG(j.trace()) << "ContractCreate/Modify: Functions array empty."; + return temARRAY_EMPTY; + } + + // Functions must not exceed n entries. + if (functions.size() > contract::maxContractFunctions) + { + JLOG(j.trace()) << "ContractCreate/Modify: Functions array too large."; + return temARRAY_TOO_LARGE; + } + + std::unordered_set uniqueFunctions; + uniqueFunctions.reserve(functions.size()); + for (auto const& function : functions) + { + // Functions must be unique by name. + auto const& functionName = function.getFieldVL(sfFunctionName); + if (!uniqueFunctions.insert(functionName).second) + { + JLOG(j.trace()) + << "Duplicate function name: " << strHex(functionName); + return temREDUNDANT; + } + + auto const& parameters = function.getFieldArray(sfParameters); + + // Function Parameters must not exceed n entries each. + if (parameters.size() > contract::maxContractParams) + { + JLOG(j.trace()) << "ContractCreate/Modify: Function Parameters " + "array is too large."; + return temARRAY_TOO_LARGE; + } + + std::unordered_set uniqueParameters; + uniqueParameters.reserve(parameters.size()); + + for (auto const& param : parameters) + { + // Function Parameter must have a flag. + if (!param.isFieldPresent(sfParameterFlag)) + { + JLOG(j.trace()) << "ContractCreate/Modify: Function Parameter " + "is missing flag."; + return temMALFORMED; + } + + // Function Parameter must have a type. + if (!param.isFieldPresent(sfParameterType)) + { + JLOG(j.trace()) << "ContractCreate/Modify: Function Parameter " + "is missing type."; + return temMALFORMED; + } + + // Function Parameter flags must be valid. + auto const flags = param.getFieldU32(sfParameterFlag); + if (flags & tfContractParameterMask) + { + JLOG(j.trace()) << "ContractCreate/Modify: Invalid parameter " + "flag in Function."; + return temINVALID_FLAG; + } + } + } + return tesSUCCESS; +} + +NotTEC +preflightInstanceParameters(STTx const& tx, beast::Journal j) +{ + if (!tx.isFieldPresent(sfInstanceParameters)) + return tesSUCCESS; + + auto const& instanceParameters = tx.getFieldArray(sfInstanceParameters); + + // InstanceParameters must not be empty. + if (instanceParameters.empty()) + { + JLOG(j.trace()) + << "ContractCreate/Modify: InstanceParameters empty array."; + return temARRAY_EMPTY; + } + + // InstanceParameters must not exceed n entries. + if (instanceParameters.size() > contract::maxContractParams) + { + JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameters array " + "is too large."; + return temARRAY_TOO_LARGE; + } + + std::unordered_set uniqueParameters; + uniqueParameters.reserve(instanceParameters.size()); + for (auto const& param : instanceParameters) + { + // Instance Parameter must have a flag. + if (!param.isFieldPresent(sfParameterFlag)) + { + JLOG(j.trace()) + << "ContractCreate/Modify: Instance Parameter is missing flag."; + return temMALFORMED; + } + + // Instance Parameter must have a type. + if (!param.isFieldPresent(sfParameterType)) + { + JLOG(j.trace()) + << "ContractCreate/Modify: Instance Parameter is missing type."; + return temMALFORMED; + } + + // Instance Parameter flags must be valid. + auto const flags = param.getFieldU32(sfParameterFlag); + if (flags & tfContractParameterMask) + { + JLOG(j.trace()) << "ContractCreate/Modify: Invalid parameter " + "flag in Instance Parameter."; + return temINVALID_FLAG; + } + } + return tesSUCCESS; +} + +bool +validateParameterMapping( + STArray const& params, + STArray const& values, + beast::Journal j) +{ + if (params.size() != values.size()) + { + JLOG(j.trace()) + << "ContractCreate/Modify: InstanceParameterValues size " + "does not match InstanceParameters size."; + return false; + } + return true; +} + +NotTEC +preflightInstanceParameterValues(STTx const& tx, beast::Journal j) +{ + if (!tx.isFieldPresent(sfInstanceParameterValues)) + return tesSUCCESS; + + auto const& instanceParameterValues = + tx.getFieldArray(sfInstanceParameterValues); + + // InstanceParameters must not be empty. + if (instanceParameterValues.empty()) + { + JLOG(j.trace()) + << "ContractCreate/Modify: InstanceParameterValues is missing."; + return temARRAY_EMPTY; + } + + // InstanceParameterValues must not exceed n entries. + if (instanceParameterValues.size() > contract::maxContractParams) + { + JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameterValues " + "array is too large."; + return temARRAY_TOO_LARGE; + } + + std::unordered_set uniqueParameters; + uniqueParameters.reserve(instanceParameterValues.size()); + for (auto const& param : instanceParameterValues) + { + // Instance Parameter must have a flag. + if (!param.isFieldPresent(sfParameterFlag)) + { + JLOG(j.trace()) + << "ContractCreate/Modify: Instance Parameter is missing flag."; + return temMALFORMED; + } + + // Instance Parameter must have a value. + if (!param.isFieldPresent(sfParameterValue)) + { + JLOG(j.trace()) << "ContractCreate/Modify: Instance Parameter is " + "missing value."; + return temMALFORMED; + } + + // Instance Parameter flags must be valid. + auto const flags = param.getFieldU32(sfParameterFlag); + if (flags & tfContractParameterMask) + { + JLOG(j.trace()) << "ContractCreate/Modify: Invalid parameter " + "flag in Instance Parameter."; + return temINVALID_FLAG; + } + } + + // Only validate the mapping if InstanceParameters are present + bool valid = true; + if (tx.isFieldPresent(sfInstanceParameters)) + valid = validateParameterMapping( + tx.getFieldArray(sfInstanceParameters), + tx.getFieldArray(sfInstanceParameterValues), + j); + if (!valid) + { + JLOG(j.trace()) + << "ContractCreate/Modify: InstanceParameterValues do not match " + "InstanceParameters."; + return temMALFORMED; + } + + // Validate flags in InstanceParameterValues + if (auto const res = preflightFlagParameters(instanceParameterValues, j); + !isTesSuccess(res)) + { + JLOG(j.trace()) + << "ContractCreate/Modify: InstanceParameterValues flag " + "validation failed: " + << transToken(res); + return res; + } + + return tesSUCCESS; +} + +bool +isValidParameterFlag(std::uint32_t flags) +{ + return (flags & tfContractParameterMask) == 0; +} + +NotTEC +preflightFlagParameters(STArray const& parameters, beast::Journal j) +{ + for (auto const& param : parameters) + { + if (!param.isFieldPresent(sfParameterFlag) || + !isValidParameterFlag(param.getFieldU32(sfParameterFlag))) + continue; // Skip invalid flags + + switch (param.getFieldU32(sfParameterFlag)) + { + case tfSendAmount: { + if (!param.isFieldPresent(sfParameterValue)) + return temMALFORMED; + auto const& value = param.getFieldData(sfParameterValue); + STAmount amount = value.getFieldAmount(); + // Preflight Transfer Amount + if (isXRP(amount)) + { + if (amount <= beast::zero) + return temBAD_AMOUNT; + } + else if (amount.holds()) + { + if (amount.native() || amount <= beast::zero) + return temBAD_AMOUNT; + + if (badCurrency() == amount.getCurrency()) + return temBAD_CURRENCY; + } + else if (amount.holds()) + { + if (amount.native() || + amount.mpt() > MPTAmount{maxMPTokenAmount} || + amount <= beast::zero) + return temBAD_AMOUNT; + } + break; + } + case tfSendNFToken: { + break; + } + case tfAuthorizeToken: { + return temDISABLED; + break; + } + } + } + return tesSUCCESS; +} + +TER +preclaimFlagParameters( + ReadView const& view, + AccountID const& sourceAccount, + AccountID const& contractAccount, + STArray const& parameters, + beast::Journal j) +{ + for (auto const& param : parameters) + { + if (!param.isFieldPresent(sfParameterFlag) || + !isValidParameterFlag(param.getFieldU32(sfParameterFlag))) + continue; // Skip invalid flags + + switch (param.getFieldU32(sfParameterFlag)) + { + case tfSendAmount: { + if (!param.isFieldPresent(sfParameterValue)) + return tecINTERNAL; + + auto const& value = param.getFieldData(sfParameterValue); + STAmount amount = value.getFieldAmount(); + // Preclaim Transfer Amount + if (isXRP(amount)) + { + auto const accountSle = + view.read(keylet::account(sourceAccount)); + if (!accountSle) + return tecINTERNAL; + + auto const& mSourceBalance = + accountSle->getFieldAmount(sfBalance); + if (mSourceBalance < amount.xrp()) + return tecUNFUNDED; + } + else + { + if (auto ter = canTransferFT( + view, + sourceAccount, + contractAccount, + amount, + j, + SendIssuerHandling::ihIGNORE, + SendEscrowHandling::ehIGNORE, + SendAuthHandling::ahBOTH, + SendFreezeHandling::fhBOTH, + SendTransferHandling::thIGNORE, + SendBalanceHandling::bhCHECK)) + { + JLOG(j.trace()) << "preclaimFlagParameters: Cannot " + "transfer amount: " + << amount; + return ter; + } + } + break; + } + case tfSendNFToken: { + if (!param.isFieldPresent(sfParameterValue)) + return tecINTERNAL; + auto const& value = param.getFieldData(sfParameterValue); + auto const& nftokenID = value.getFieldH256(); + // Preclaim Transfer NFT Token + if (!nft::findToken(view, sourceAccount, nftokenID)) + { + JLOG(j.trace()) + << "preclaimFlagParameters: Cannot transfer NFT token: " + << nftokenID; + return tecNO_ENTRY; + } + break; + } + case tfAuthorizeToken: { + break; + } + } + } + return tesSUCCESS; +} + +TER +doApplyFlagParameters( + ApplyView& view, + STTx const& tx, + AccountID const& sourceAccount, + AccountID const& contractAccount, + STArray const& parameters, + XRPAmount const& priorBalance, + beast::Journal j) +{ + for (auto const& param : parameters) + { + if (!param.isFieldPresent(sfParameterFlag) || + !isValidParameterFlag(param.getFieldU32(sfParameterFlag))) + continue; // Skip invalid flags + + switch (param.getFieldU32(sfParameterFlag)) + { + case tfSendAmount: { + if (!param.isFieldPresent(sfParameterValue)) + return tecINTERNAL; + + auto const& value = param.getFieldData(sfParameterValue); + STAmount amount = value.getFieldAmount(); + if (auto ter = accountSend( + view, + sourceAccount, + contractAccount, + amount, + j, + WaiveTransferFee::No); + !isTesSuccess(ter)) + { + JLOG(j.trace()) + << "doApplyFlagParameters: Failed to send amount: " + << amount; + return ter; + } + break; + } + case tfSendNFToken: { + if (!param.isFieldPresent(sfParameterValue)) + return tecINTERNAL; + auto const& value = param.getFieldData(sfParameterValue); + auto const& nftokenID = value.getFieldH256(); + if (auto ter = nft::transferNFToken( + view, sourceAccount, contractAccount, nftokenID); + !isTesSuccess(ter)) + { + JLOG(j.trace()) + << "doApplyFlagParameters: Failed to send NFT token: " + << nftokenID; + return ter; + } + break; + } + case tfAuthorizeToken: { + return tecINTERNAL; + // if (!param.isFieldPresent(sfParameterValue)) + // return tecINTERNAL; + // // Handle tfAuthorizeToken if needed + // auto const& value = param.getFieldData(sfParameterValue); + // STAmount limit = value.getFieldAmount(); + // Asset asset = Asset{limit.issue()}; + // if (auto ter = canAddHolding(view, asset); + // !isTesSuccess(ter)) + // { + // JLOG(j.trace()) << "doApplyFlagParameters: Cannot add " + // "holding for asset: " + // << to_string(asset); + // return ter; + // } + // // Set the issuer to the contract account for the holding + // limit.setIssuer(contractAccount); + // if (auto ter = addEmptyHolding( + // view, contractAccount, priorBalance, asset, limit, + // j); + // !isTesSuccess(ter)) + // { + // JLOG(j.trace()) << "doApplyFlagParameters: Failed to add + // " + // "holding for asset: " + // << to_string(asset); + // return ter; + // } + // break; + } + } + } + return tesSUCCESS; +} + +uint32_t +contractDataReserve(uint32_t size) +{ + // Divide by dataByteMultiplier and round up to the nearest whole number + return (size + dataByteMultiplier - 1U) / dataByteMultiplier; +} + +TER +setContractData( + ApplyContext& applyCtx, + AccountID const& account, + AccountID const& contractAccount, + STJson const& data) +{ + auto& view = applyCtx.view(); + auto j = applyCtx.app.journal("View"); + auto const sleAccount = view.peek(keylet::account(account)); + if (!sleAccount) + return tefINTERNAL; + + // if the blob is too large don't set it + if (data.size() > maxContractDataSize) + return temARRAY_TOO_LARGE; + + auto dataKeylet = keylet::contractData(account, contractAccount); + auto dataSle = view.peek(dataKeylet); + + // DELETE + if (data.size() == 0) + { + if (!dataSle) + return tesSUCCESS; + + uint32_t oldDataReserve = + contractDataReserve(dataSle->getFieldJson(sfContractJson).size()); + + std::uint64_t const page = (*dataSle)[sfOwnerNode]; + // Remove the page from the account directory + if (!view.dirRemove( + keylet::ownerDir(account), page, dataKeylet.key, false)) + return tefBAD_LEDGER; + + // remove the actual contract data sle + view.erase(dataSle); + + // reduce the owner count + adjustOwnerCount(view, sleAccount, -oldDataReserve, j); + return tesSUCCESS; + } + + std::uint32_t ownerCount{(*sleAccount)[sfOwnerCount]}; + bool createNew = !dataSle; + if (createNew) + { + // CREATE + uint32_t dataReserve = contractDataReserve(data.size()); + uint32_t newReserve = ownerCount + dataReserve; + XRPAmount const newReserveAmount{ + view.fees().accountReserve(newReserve)}; + if (STAmount((*sleAccount)[sfBalance]).xrp() < newReserveAmount) + return tecINSUFFICIENT_RESERVE; + + adjustOwnerCount(view, sleAccount, dataReserve, j); + // create an entry + dataSle = std::make_shared(dataKeylet); + dataSle->setFieldJson(sfContractJson, data); + dataSle->setAccountID(sfOwner, account); + dataSle->setAccountID(sfContractAccount, contractAccount); + + auto const page = view.dirInsert( + keylet::ownerDir(account), + dataKeylet.key, + describeOwnerDir(account)); + if (!page) + return tecDIR_FULL; + + dataSle->setFieldU64(sfOwnerNode, *page); + + // add new data to ledger + view.insert(dataSle); + } + else + { + // UPDATE + uint32_t oldDataReserve = + contractDataReserve(dataSle->getFieldJson(sfContractJson).size()); + uint32_t newDataReserve = contractDataReserve(data.size()); + if (newDataReserve != oldDataReserve) + { + // if the reserve changes, we need to adjust the owner count + uint32_t newReserve = ownerCount - oldDataReserve + newDataReserve; + XRPAmount const newReserveAmount{ + view.fees().accountReserve(newReserve)}; + if (STAmount((*sleAccount)[sfBalance]).xrp() < newReserveAmount) + return tecINSUFFICIENT_RESERVE; + + adjustOwnerCount(view, sleAccount, newReserve, j); + } + + // update the data + dataSle->setFieldJson(sfContractJson, data); + view.update(dataSle); + } + return tesSUCCESS; +} + +TER +finalizeContractData( + ApplyContext& applyCtx, + AccountID const& contractAccount, + ContractDataMap const& dataMap, + ContractEventMap const& eventMap, + uint256 const& txnID) +{ + auto const& j = applyCtx.app.journal("View"); + uint16_t changeCount = 0; + + for (auto const& [name, data] : eventMap) + applyCtx.app.getOPs().pubContractEvent(name, data); + + for (auto const& accEntry : dataMap) + { + auto const& acc = accEntry.first; + auto const& cacheEntry = accEntry.second; + bool is_modified = cacheEntry.first; + auto const& jsonData = cacheEntry.second; + if (is_modified) + { + changeCount++; + if (changeCount > maxDataModifications) + { + // overflow + JLOG(j.trace()) + << "ContractError[TX:" << txnID + << "]: SetContractData failed: Too many data changes"; + return tecWASM_REJECTED; + } + + TER result = + setContractData(applyCtx, acc, contractAccount, jsonData); + if (!isTesSuccess(result)) + { + JLOG(j.warn()) << "ContractError[TX:" << txnID + << "]: SetContractData failed: " << result + << " Account: " << acc; + return result; + } + } + } + return tesSUCCESS; +} + +} // namespace contract +} // namespace xrpl diff --git a/src/xrpld/app/misc/ContractUtils.h b/src/xrpld/app/misc/ContractUtils.h new file mode 100644 index 0000000000..2c4c0f666c --- /dev/null +++ b/src/xrpld/app/misc/ContractUtils.h @@ -0,0 +1,113 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_APP_WASM_CONTRACTUTILS_H_INCLUDED +#define RIPPLE_APP_WASM_CONTRACTUTILS_H_INCLUDED + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { +namespace contract { + +/** The maximum number of data modifications in a single function. */ +int64_t constexpr maxDataModifications = 1000; + +/** The maximum number of bytes the data can occupy. */ +int64_t constexpr maxContractDataSize = 1024; + +/** The multiplier for contract data size calculations. */ +int64_t constexpr dataByteMultiplier = 512; + +/** The cost multiplier of creating a contract in bytes. */ +int64_t constexpr createByteMultiplier = 500ULL; + +/** The value to return when the fee calculation failed. */ +int64_t constexpr feeCalculationFailed = 0x7FFFFFFFFFFFFFFFLL; + +/** The maximum number of contract parameters that can be in a transaction. */ +std::size_t constexpr maxContractParams = 8; + +/** The maximum number of contract functions that can be in a transaction. */ +std::size_t constexpr maxContractFunctions = 8; + +int64_t +contractCreateFee(uint64_t byteCount); + +NotTEC +preflightFunctions(STTx const& tx, beast::Journal j); + +NotTEC +preflightInstanceParameters(STTx const& tx, beast::Journal j); + +bool +validateParameterMapping( + STArray const& params, + STArray const& values, + beast::Journal j); + +NotTEC +preflightInstanceParameterValues(STTx const& tx, beast::Journal j); + +NotTEC +preflightFlagParameters(STArray const& parameters, beast::Journal j); + +bool +isValidParameterFlag(std::uint32_t flags); + +TER +preclaimFlagParameters( + ReadView const& view, + AccountID const& sourceAccount, + AccountID const& contractAccount, + STArray const& parameters, + beast::Journal j); + +TER +doApplyFlagParameters( + ApplyView& view, + STTx const& tx, + AccountID const& sourceAccount, + AccountID const& contractAccount, + STArray const& parameters, + XRPAmount const& priorBalance, + beast::Journal j); + +TER +finalizeContractData( + ApplyContext& applyCtx, + AccountID const& contractAccount, + ContractDataMap const& dataMap, + ContractEventMap const& eventMap, + uint256 const& txnID); + +} // namespace contract +} // namespace xrpl + +#endif diff --git a/src/xrpld/app/misc/DeleteUtils.cpp b/src/xrpld/app/misc/DeleteUtils.cpp new file mode 100644 index 0000000000..50ba91236b --- /dev/null +++ b/src/xrpld/app/misc/DeleteUtils.cpp @@ -0,0 +1,415 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace xrpl { + +// Local function definitions that provides signature compatibility. +TER +offerDelete( + Application& app, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return offerDelete(view, sleDel, j); +} + +TER +removeSignersFromLedger( + Application& app, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return SetSignerList::removeFromLedger(app, view, account, j); +} + +TER +removeTicketFromLedger( + Application&, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const&, + beast::Journal j) +{ + return Transactor::ticketDelete(view, account, delIndex, j); +} + +TER +removeDepositPreauthFromLedger( + Application&, + ApplyView& view, + AccountID const&, + uint256 const& delIndex, + std::shared_ptr const&, + beast::Journal j) +{ + return DepositPreauth::removeFromLedger(view, delIndex, j); +} + +TER +removeNFTokenOfferFromLedger( + Application& app, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal) +{ + if (!nft::deleteTokenOffer(view, sleDel)) + return tefBAD_LEDGER; + + return tesSUCCESS; +} + +TER +removeDIDFromLedger( + Application& app, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return DIDDelete::deleteSLE(view, sleDel, account, j); +} + +TER +removeOracleFromLedger( + Application&, + ApplyView& view, + AccountID const& account, + uint256 const&, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return DeleteOracle::deleteOracle(view, sleDel, account, j); +} + +TER +removeCredentialFromLedger( + Application&, + ApplyView& view, + AccountID const&, + uint256 const&, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return credentials::deleteSLE(view, sleDel, j); +} + +TER +removeDelegateFromLedger( + Application& app, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return DelegateSet::deleteDelegate(view, sleDel, account, j); +} + +TER +removeContractFromLedger( + Application& app, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return ContractDelete::deleteContract(view, sleDel, account, j); +} + +// Return nullptr if the LedgerEntryType represents an obligation that can't +// be deleted. Otherwise return the pointer to the function that can delete +// the non-obligation +DeleterFuncPtr +nonObligationDeleter(LedgerEntryType t) +{ + switch (t) + { + case ltOFFER: + return offerDelete; + case ltSIGNER_LIST: + return removeSignersFromLedger; + case ltTICKET: + return removeTicketFromLedger; + case ltDEPOSIT_PREAUTH: + return removeDepositPreauthFromLedger; + case ltNFTOKEN_OFFER: + return removeNFTokenOfferFromLedger; + case ltDID: + return removeDIDFromLedger; + case ltORACLE: + return removeOracleFromLedger; + case ltCREDENTIAL: + return removeCredentialFromLedger; + case ltDELEGATE: + return removeDelegateFromLedger; + case ltCONTRACT: + return removeContractFromLedger; + default: + return nullptr; + } +} + +TER +deletePreclaim( + PreclaimContext const& ctx, + std::uint32_t seqDelta, + AccountID const account, + AccountID const dest, + bool isPseudoAccount) +{ + auto destSle = ctx.view.read(keylet::account(dest)); + + if (!destSle) + return tecNO_DST; + + if ((*destSle)[sfFlags] & lsfRequireDestTag && !ctx.tx[~sfDestinationTag]) + return tecDST_TAG_NEEDED; + + // If credentials are provided - check them anyway + if (auto const err = credentials::valid(ctx.tx, ctx.view, account, ctx.j); + !isTesSuccess(err)) + return err; + + // if credentials then postpone auth check to doApply, to check for expired + // credentials + if (!ctx.tx.isFieldPresent(sfCredentialIDs)) + { + // Check whether the destination account requires deposit authorization. + if (destSle->getFlags() & lsfDepositAuth) + { + if (!ctx.view.exists(keylet::depositPreauth(dest, account)) && + !isPseudoAccount) + return tecNO_PERMISSION; + } + } + + auto srcSle = ctx.view.read(keylet::account(account)); + XRPL_ASSERT(srcSle, "xrpl::DeleteAccount::preclaim : non-null account"); + if (!srcSle) + return terNO_ACCOUNT; + + { + // If an issuer has any issued NFTs resident in the ledger then it + // cannot be deleted. + if ((*srcSle)[~sfMintedNFTokens] != (*srcSle)[~sfBurnedNFTokens]) + return tecHAS_OBLIGATIONS; + + // If the account owns any NFTs it cannot be deleted. + Keylet const first = keylet::nftpage_min(account); + Keylet const last = keylet::nftpage_max(account); + + auto const cp = ctx.view.read(Keylet( + ltNFTOKEN_PAGE, + ctx.view.succ(first.key, last.key.next()).value_or(last.key))); + if (cp) + return tecHAS_OBLIGATIONS; + } + + // We don't allow an account to be deleted if its sequence number + // is within 256 of the current ledger. This prevents replay of old + // transactions if this account is resurrected after it is deleted. + // + // We look at the account's Sequence rather than the transaction's + // Sequence in preparation for Tickets. + if ((*srcSle)[sfSequence] + seqDelta > ctx.view.seq()) + return tecTOO_SOON; + + // When fixNFTokenRemint is enabled, we don't allow an account to be + // deleted if is within 256 of the + // current ledger. This is to prevent having duplicate NFTokenIDs after + // account re-creation. + // + // Without this restriction, duplicate NFTokenIDs can be reproduced when + // authorized minting is involved. Because when the minter mints a NFToken, + // the issuer's sequence does not change. So when the issuer re-creates + // their account and mints a NFToken, it is possible that the + // NFTokenSequence of this NFToken is the same as the one that the + // authorized minter minted in a previous ledger. + if ((*srcSle)[~sfFirstNFTokenSequence].value_or(0) + + (*srcSle)[~sfMintedNFTokens].value_or(0) + seqDelta > + ctx.view.seq()) + return tecTOO_SOON; + + // Verify that the account does not own any objects that would prevent + // the account from being deleted. + Keylet const ownerDirKeylet{keylet::ownerDir(account)}; + if (dirIsEmpty(ctx.view, ownerDirKeylet)) + return tesSUCCESS; + + std::shared_ptr sleDirNode{}; + unsigned int uDirEntry{0}; + uint256 dirEntry{beast::zero}; + + // Account has no directory at all. This _should_ have been caught + // by the dirIsEmpty() check earlier, but it's okay to catch it here. + if (!cdirFirst( + ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry)) + return tesSUCCESS; + + std::int32_t deletableDirEntryCount{0}; + do + { + // Make sure any directory node types that we find are the kind + // we can delete. + auto sleItem = ctx.view.read(keylet::child(dirEntry)); + if (!sleItem) + { + // Directory node has an invalid index. Bail out. + JLOG(ctx.j.fatal()) + << "DeleteAccount: directory node in ledger " << ctx.view.seq() + << " has index to object that is missing: " + << to_string(dirEntry); + return tefBAD_LEDGER; + } + + LedgerEntryType const nodeType{ + safe_cast((*sleItem)[sfLedgerEntryType])}; + + if (!nonObligationDeleter(nodeType)) + return tecHAS_OBLIGATIONS; + + // We found a deletable directory entry. Count it. If we find too + // many deletable directory entries then bail out. + if (++deletableDirEntryCount > maxDeletableDirEntries) + return tefTOO_BIG; + + } while (cdirNext( + ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry)); + + return tesSUCCESS; +} + +TER +deleteDoApply( + ApplyContext& applyCtx, + STAmount const& accountBalance, + AccountID const& account, + AccountID const& dest) +{ + auto& view = applyCtx.view(); + STTx const tx = applyCtx.tx; + beast::Journal j = applyCtx.journal; + + auto srcSle = view.peek(keylet::account(account)); + XRPL_ASSERT( + srcSle, "xrpl::DeleteAccount::doApply : non-null source account"); + + if (!srcSle) + return tefBAD_LEDGER; + + auto destSle = view.peek(keylet::account(dest)); + XRPL_ASSERT( + destSle, "xrpl::DeleteAccount::doApply : non-null destination account"); + + if (!destSle) + return tefBAD_LEDGER; + + if (tx.isFieldPresent(sfCredentialIDs)) + { + if (auto err = + verifyDepositPreauth(tx, view, account, dest, destSle, j); + !isTesSuccess(err)) + return err; + } + + Keylet const ownerDirKeylet{keylet::ownerDir(account)}; + auto const ter = cleanupOnAccountDelete( + view, + ownerDirKeylet, + [&](LedgerEntryType nodeType, + uint256 const& dirEntry, + std::shared_ptr& sleItem) -> std::pair { + if (auto deleter = nonObligationDeleter(nodeType)) + { + TER const result{ + deleter(applyCtx.app, view, account, dirEntry, sleItem, j)}; + + return {result, SkipEntry::No}; + } + + UNREACHABLE( + "xrpl::DeleteAccount::doApply : undeletable item not found " + "in preclaim"); + JLOG(j.error()) << "DeleteAccount undeletable item not " + "found in preclaim."; + return {tecHAS_OBLIGATIONS, SkipEntry::No}; + }, + j); + if (ter != tesSUCCESS) + return ter; + + // Transfer any XRP remaining after the fee is paid to the destination: + (*destSle)[sfBalance] = (*destSle)[sfBalance] + accountBalance; + (*srcSle)[sfBalance] = (*srcSle)[sfBalance] - accountBalance; + applyCtx.deliver(accountBalance); + + // DA: Pseudo accounts can have 0 balance, so we skip this assert. + // FIX FIX FIX: DA FIX + // XRPL_ASSERT( + // (*srcSle)[sfBalance] == XRPAmount(0), + // "xrpl::DeleteAccount::doApply : source balance is zero"); + + // If there's still an owner directory associated with the source account + // delete it. + if (view.exists(ownerDirKeylet) && !view.emptyDirDelete(ownerDirKeylet)) + { + JLOG(j.error()) << "DeleteAccount cannot delete root dir node of " + << toBase58(account); + return tecHAS_OBLIGATIONS; + } + + // Re-arm the password change fee if we can and need to. + if (accountBalance > XRPAmount(0) && (*destSle).isFlag(lsfPasswordSpent)) + (*destSle).clearFlag(lsfPasswordSpent); + + view.update(destSle); + view.erase(srcSle); + + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/xrpld/app/misc/DeleteUtils.h b/src/xrpld/app/misc/DeleteUtils.h new file mode 100644 index 0000000000..14645c731d --- /dev/null +++ b/src/xrpld/app/misc/DeleteUtils.h @@ -0,0 +1,64 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_APP_MISC_DELETEUTILS_H_INCLUDED +#define RIPPLE_APP_MISC_DELETEUTILS_H_INCLUDED + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +// Define a function pointer type that can be used to delete ledger node types. +using DeleterFuncPtr = TER (*)( + Application& app, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j); + +DeleterFuncPtr +nonObligationDeleter(LedgerEntryType t); + +TER +deletePreclaim( + PreclaimContext const& ctx, + std::uint32_t seqDelta, + AccountID const account, + AccountID const dest, + bool isPseudoAccount = false); + +TER +deleteDoApply( + ApplyContext& applyCtx, + STAmount const& accountBalance, + AccountID const& account, + AccountID const& dest); + +} // namespace xrpl + +#endif diff --git a/src/xrpld/app/misc/FeeVoteImpl.cpp b/src/xrpld/app/misc/FeeVoteImpl.cpp index a6b140f691..aeba70bb2e 100644 --- a/src/xrpld/app/misc/FeeVoteImpl.cpp +++ b/src/xrpld/app/misc/FeeVoteImpl.cpp @@ -9,10 +9,10 @@ namespace xrpl { namespace detail { +template class VotableValue { private: - using value_type = XRPAmount; value_type const current_; // The current setting value_type const target_; // The setting we want std::map voteMap_; @@ -47,8 +47,9 @@ public: getVotes() const; }; -auto -VotableValue::getVotes() const -> std::pair +template +std::pair +VotableValue::getVotes() const { value_type ourVote = current_; int weight = 0; @@ -171,6 +172,33 @@ FeeVoteImpl::doValidation( "reserve increment", sfReserveIncrement); } + if (rules.enabled(featureSmartEscrow)) + { + auto vote = [&v, this]( + auto const current, + std::uint32_t target, + char const* name, + auto const& sfield) { + if (current != target) + { + JLOG(journal_.info()) + << "Voting for " << name << " of " << target; + + v[sfield] = target; + } + }; + vote( + lastFees.extensionComputeLimit, + target_.extension_compute_limit, + "extension compute limit", + sfExtensionComputeLimit); + vote( + lastFees.extensionSizeLimit, + target_.extension_size_limit, + "extension size limit", + sfExtensionSizeLimit); + vote(lastFees.gasPrice, target_.gas_price, "gas price", sfGasPrice); + } } void @@ -193,11 +221,22 @@ FeeVoteImpl::doVoting( detail::VotableValue incReserveVote( lastClosedLedger->fees().increment, target_.owner_reserve); + detail::VotableValue extensionComputeVote( + lastClosedLedger->fees().extensionComputeLimit, + target_.extension_compute_limit); + + detail::VotableValue extensionSizeVote( + lastClosedLedger->fees().extensionSizeLimit, + target_.extension_size_limit); + + detail::VotableValue gasPriceVote( + lastClosedLedger->fees().gasPrice, target_.gas_price); + auto const& rules = lastClosedLedger->rules(); if (rules.enabled(featureXRPFees)) { auto doVote = [](std::shared_ptr const& val, - detail::VotableValue& value, + detail::VotableValue& value, SF_AMOUNT const& xrpField) { if (auto const field = ~val->at(~xrpField); field && field->native()) @@ -226,7 +265,7 @@ FeeVoteImpl::doVoting( else { auto doVote = [](std::shared_ptr const& val, - detail::VotableValue& value, + detail::VotableValue& value, auto const& valueField) { if (auto const field = val->at(~valueField)) { @@ -257,6 +296,30 @@ FeeVoteImpl::doVoting( doVote(val, incReserveVote, sfReserveIncrement); } } + if (rules.enabled(featureSmartEscrow)) + { + auto doVote = [](std::shared_ptr const& val, + detail::VotableValue& value, + SF_UINT32 const& extensionField) { + if (auto const field = ~val->at(~extensionField); field) + { + value.addVote(field.value()); + } + else + { + value.noVote(); + } + }; + + for (auto const& val : set) + { + if (!val->isTrusted()) + continue; + doVote(val, extensionComputeVote, sfExtensionComputeLimit); + doVote(val, extensionSizeVote, sfExtensionSizeLimit); + doVote(val, gasPriceVote, sfGasPrice); + } + } // choose our positions // TODO: Use structured binding once LLVM 16 is the minimum supported @@ -265,11 +328,15 @@ FeeVoteImpl::doVoting( auto const baseFee = baseFeeVote.getVotes(); auto const baseReserve = baseReserveVote.getVotes(); auto const incReserve = incReserveVote.getVotes(); + auto const extensionCompute = extensionComputeVote.getVotes(); + auto const extensionSize = extensionSizeVote.getVotes(); + auto const gasPrice = gasPriceVote.getVotes(); auto const seq = lastClosedLedger->header().seq + 1; // add transactions to our position - if (baseFee.second || baseReserve.second || incReserve.second) + if (baseFee.second || baseReserve.second || incReserve.second || + extensionCompute.second || extensionSize.second || gasPrice.second) { JLOG(journal_.warn()) << "We are voting for a fee change: " << baseFee.first << "/" @@ -297,6 +364,12 @@ FeeVoteImpl::doVoting( incReserveVote.current()); obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED; } + if (rules.enabled(featureSmartEscrow)) + { + obj[sfExtensionComputeLimit] = extensionCompute.first; + obj[sfExtensionSizeLimit] = extensionSize.first; + obj[sfGasPrice] = gasPrice.first; + } }); uint256 txID = feeTx.getTransactionID(); diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 084a584377..b3849b72d1 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -557,6 +558,12 @@ public: subConsensus(InfoSub::ref ispListener) override; bool unsubConsensus(std::uint64_t uListener) override; + bool + subContractEvent(InfoSub::ref ispListener) override; + bool + unsubContractEvent(std::uint64_t uListener) override; + void + pubContractEvent(std::string const& name, STJson const& event) override; InfoSub::pointer findRpcSub(std::string const& strUrl) override; @@ -765,6 +772,7 @@ private: sPeerStatus, // Peer status changes. sConsensusPhase, // Consensus phase sBookChanges, // Per-ledger order book changes + sContractEvents, // Contract events sLastEntry // Any new entry must be ADDED ABOVE this one }; @@ -2390,6 +2398,34 @@ NetworkOPsImp::pubConsensus(ConsensusPhase phase) } } +void +NetworkOPsImp::pubContractEvent(std::string const& name, STJson const& event) +{ + std::lock_guard sl(mSubLock); + + auto& streamMap = mStreamMaps[sContractEvents]; + if (!streamMap.empty()) + { + Json::Value jvObj(Json::objectValue); + jvObj[jss::type] = "contractEvent"; + jvObj[jss::name] = name; + jvObj[jss::data] = event.getJson(JsonOptions::none); + + for (auto i = streamMap.begin(); i != streamMap.end();) + { + if (auto p = i->second.lock()) + { + p->send(jvObj, true); + ++i; + } + else + { + i = streamMap.erase(i); + } + } + } +} + void NetworkOPsImp::pubValidation(std::shared_ptr const& val) { @@ -2469,6 +2505,18 @@ NetworkOPsImp::pubValidation(std::shared_ptr const& val) reserveIncXRP && reserveIncXRP->native()) jvObj[jss::reserve_inc] = reserveIncXRP->xrp().jsonClipped(); + if (auto const extensionComputeLimit = + ~val->at(~sfExtensionComputeLimit); + extensionComputeLimit) + jvObj[jss::extension_compute] = *extensionComputeLimit; + + if (auto const extensionSizeLimit = ~val->at(~sfExtensionSizeLimit); + extensionSizeLimit) + jvObj[jss::extension_size] = *extensionSizeLimit; + + if (auto const gasPrice = ~val->at(~sfGasPrice); gasPrice) + jvObj[jss::gas_price] = *gasPrice; + // NOTE Use MultiApiJson to publish two slightly different JSON objects // for consumers supporting different API versions MultiApiJson multiObj{jvObj}; @@ -2918,11 +2966,20 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) l[jss::seq] = Json::UInt(lpClosed->header().seq); l[jss::hash] = to_string(lpClosed->header().hash); + bool const smartEscrowEnabled = + lpClosed->rules().enabled(featureSmartEscrow); if (!human) { l[jss::base_fee] = baseFee.jsonClipped(); l[jss::reserve_base] = lpClosed->fees().reserve.jsonClipped(); l[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped(); + if (smartEscrowEnabled) + { + l[jss::extension_compute] = + lpClosed->fees().extensionComputeLimit; + l[jss::extension_size] = lpClosed->fees().extensionSizeLimit; + l[jss::gas_price] = lpClosed->fees().gasPrice; + } l[jss::close_time] = Json::Value::UInt( lpClosed->header().closeTime.time_since_epoch().count()); } @@ -2931,6 +2988,13 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) l[jss::base_fee_xrp] = baseFee.decimalXRP(); l[jss::reserve_base_xrp] = lpClosed->fees().reserve.decimalXRP(); l[jss::reserve_inc_xrp] = lpClosed->fees().increment.decimalXRP(); + if (smartEscrowEnabled) + { + l[jss::extension_compute] = + lpClosed->fees().extensionComputeLimit; + l[jss::extension_size] = lpClosed->fees().extensionSizeLimit; + l[jss::gas_price] = lpClosed->fees().gasPrice; + } if (auto const closeOffset = app_.timeKeeper().closeOffset(); std::abs(closeOffset.count()) >= 60) @@ -3124,6 +3188,14 @@ NetworkOPsImp::pubLedger(std::shared_ptr const& lpAccepted) jvObj[jss::reserve_base] = lpAccepted->fees().reserve.jsonClipped(); jvObj[jss::reserve_inc] = lpAccepted->fees().increment.jsonClipped(); + if (lpAccepted->rules().enabled(featureSmartEscrow)) + { + jvObj[jss::extension_compute] = + lpAccepted->fees().extensionComputeLimit; + jvObj[jss::extension_size] = + lpAccepted->fees().extensionSizeLimit; + jvObj[jss::gas_price] = lpAccepted->fees().gasPrice; + } jvObj[jss::txn_count] = Json::UInt(alpAccepted->size()); @@ -3494,8 +3566,8 @@ NetworkOPsImp::pubAccountTransaction( } JLOG(m_journal.trace()) - << "pubAccountTransaction: " - << "proposed=" << iProposed << ", accepted=" << iAccepted; + << "pubAccountTransaction: " << "proposed=" << iProposed + << ", accepted=" << iAccepted; if (!notify.empty() || !accountHistoryNotify.empty()) { @@ -4201,6 +4273,13 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult) jvResult[jss::reserve_base] = lpClosed->fees().reserve.jsonClipped(); jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped(); jvResult[jss::network_id] = app_.config().NETWORK_ID; + if (lpClosed->rules().enabled(featureSmartEscrow)) + { + jvResult[jss::extension_compute] = + lpClosed->fees().extensionComputeLimit; + jvResult[jss::extension_size] = lpClosed->fees().extensionSizeLimit; + jvResult[jss::gas_price] = lpClosed->fees().gasPrice; + } } if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger()) @@ -4393,6 +4472,24 @@ NetworkOPsImp::unsubConsensus(std::uint64_t uSeq) return mStreamMaps[sConsensusPhase].erase(uSeq); } +// <-- bool: true=added, false=already there +bool +NetworkOPsImp::subContractEvent(InfoSub::ref isrListener) +{ + std::lock_guard sl(mSubLock); + return mStreamMaps[sContractEvents] + .emplace(isrListener->getSeq(), isrListener) + .second; +} + +// <-- bool: true=erased, false=was not there +bool +NetworkOPsImp::unsubContractEvent(std::uint64_t uSeq) +{ + std::lock_guard sl(mSubLock); + return mStreamMaps[sContractEvents].erase(uSeq); +} + InfoSub::pointer NetworkOPsImp::findRpcSub(std::string const& strUrl) { diff --git a/src/xrpld/app/misc/NetworkOPs.h b/src/xrpld/app/misc/NetworkOPs.h index 800f473959..8392fa715d 100644 --- a/src/xrpld/app/misc/NetworkOPs.h +++ b/src/xrpld/app/misc/NetworkOPs.h @@ -255,6 +255,9 @@ public: virtual void pubValidation(std::shared_ptr const& val) = 0; + virtual void + pubContractEvent(std::string const& name, STJson const& event) = 0; + virtual void stateAccounting(Json::Value& obj) = 0; }; diff --git a/src/xrpld/app/tx/apply.h b/src/xrpld/app/tx/apply.h index d578334c31..5a0e32f1f4 100644 --- a/src/xrpld/app/tx/apply.h +++ b/src/xrpld/app/tx/apply.h @@ -109,6 +109,15 @@ apply( ApplyFlags flags, beast::Journal journal); +ApplyResult +apply( + Application& app, + OpenView& view, + uint256 const& parentBatchId, + STTx const& tx, + ApplyFlags flags, + beast::Journal j); + /** Enum class for return value from `applyTransaction` @see applyTransaction diff --git a/src/xrpld/app/tx/detail/ApplyContext.cpp b/src/xrpld/app/tx/detail/ApplyContext.cpp index d364950b44..0e3a9e5d3e 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.cpp +++ b/src/xrpld/app/tx/detail/ApplyContext.cpp @@ -28,20 +28,33 @@ ApplyContext::ApplyContext( XRPL_ASSERT( parentBatchId.has_value() == ((flags_ & tapBATCH) == tapBATCH), "Parent Batch ID should be set if batch apply flag is set"); - view_.emplace(&base_, flags_); + view_.emplace(&base_.view(), flags_); } void ApplyContext::discard() { - view_.emplace(&base_, flags_); + base_.discard(); + view_.emplace(&base_.view(), flags_); +} + +void +ApplyContext::finalize() +{ + base_.commit(); + view_.emplace(&base_.view(), flags_); } std::optional ApplyContext::apply(TER ter) { + if (wasmReturnCode_.has_value()) + { + view_->setWasmReturnCode(*wasmReturnCode_); + } + view_->setGasUsed(gasUsed_); return view_->apply( - base_, tx, ter, parentBatchId_, flags_ & tapDRY_RUN, journal); + base_.view(), tx, ter, parentBatchId_, flags_ & tapDRY_RUN, journal); } std::size_t @@ -57,7 +70,7 @@ ApplyContext::visit(std::function const&, std::shared_ptr const&)> const& func) { - view_->visit(base_, func); + view_->visit(base_.view(), func); } TER diff --git a/src/xrpld/app/tx/detail/ApplyContext.h b/src/xrpld/app/tx/detail/ApplyContext.h index 4ae2f11a85..8bd78706dd 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.h +++ b/src/xrpld/app/tx/detail/ApplyContext.h @@ -2,14 +2,17 @@ #define XRPL_TX_APPLYCONTEXT_H_INCLUDED #include +#include #include #include #include +#include #include #include #include +#include namespace xrpl { @@ -55,6 +58,12 @@ public: XRPAmount const baseFee; beast::Journal const journal; + OpenView& + openView() + { + return base_.view(); + } + ApplyView& view() { @@ -87,10 +96,42 @@ public: view_->deliver(amount); } + /** Sets the gas used in the metadata */ + void + setGasUsed(std::uint32_t const gasUsed) + { + gasUsed_ = gasUsed; + } + + /** Sets the gas used in the metadata */ + void + setWasmReturnCode(std::int32_t const wasmReturnCode) + { + wasmReturnCode_ = wasmReturnCode; + } + + /** Sets the gas used in the metadata */ + void + setEmittedTxns( + std::queue> const emittedTxns) + { + emittedTxns_ = emittedTxns; + } + + std::queue> + getEmittedTxns() + { + return emittedTxns_; + } + /** Discard changes and start fresh. */ void discard(); + /** Finalize changes. */ + void + finalize(); + /** Apply the transaction result to the base. */ std::optional apply(TER); @@ -132,11 +173,13 @@ private: XRPAmount const fee, std::index_sequence); - OpenView& base_; + OpenViewSandbox base_; ApplyFlags flags_; std::optional view_; - // The ID of the batch transaction we are executing under, if seated. + std::optional gasUsed_; + std::optional wasmReturnCode_; + std::queue> emittedTxns_; std::optional parentBatchId_; }; diff --git a/src/xrpld/app/tx/detail/Change.cpp b/src/xrpld/app/tx/detail/Change.cpp index 191d91863d..cc499ee2ac 100644 --- a/src/xrpld/app/tx/detail/Change.cpp +++ b/src/xrpld/app/tx/detail/Change.cpp @@ -109,6 +109,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: @@ -273,6 +287,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); diff --git a/src/xrpld/app/tx/detail/ContractCall.cpp b/src/xrpld/app/tx/detail/ContractCall.cpp new file mode 100644 index 0000000000..7f01ca1207 --- /dev/null +++ b/src/xrpld/app/tx/detail/ContractCall.cpp @@ -0,0 +1,360 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +XRPAmount +ContractCall::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + XRPAmount extraFee{0}; + if (auto const allowance = tx[~sfComputationAllowance]; allowance) + { + extraFee += (*allowance) * view.fees().gasPrice / MICRO_DROPS_PER_DROP; + } + return Transactor::calculateBaseFee(view, tx) + extraFee; +} + +NotTEC +ContractCall::preflight(PreflightContext const& ctx) +{ + auto const flags = ctx.tx.getFlags(); + if (flags & tfUniversalMask) + { + JLOG(ctx.j.trace()) + << "ContractCreate: tfUniversalMask is not allowed."; + return temINVALID_FLAG; + } + + return tesSUCCESS; +} + +TER +ContractCall::preclaim(PreclaimContext const& ctx) +{ + AccountID const account = ctx.tx[sfAccount]; + auto const accountSle = ctx.view.read(keylet::account(account)); + if (!accountSle) + { + JLOG(ctx.j.trace()) << "ContractCall: Account does not exist."; + return tecNO_TARGET; + } + + // The ContractAccount doesn't exist or isn't a smart contract + // pseudo-account. + AccountID const contractAccount = ctx.tx[sfContractAccount]; + auto const caSle = ctx.view.read(keylet::account(contractAccount)); + if (!caSle) + { + JLOG(ctx.j.trace()) << "ContractCall: Contract Account does not exist."; + return tecNO_TARGET; + } + + // The function doesn't exist on the provided contract. + uint256 const contractID = caSle->getFieldH256(sfContractID); + auto const contractSle = ctx.view.read(keylet::contract(contractID)); + if (!contractSle) + { + JLOG(ctx.j.trace()) << "ContractCall: Contract does not exist."; + return tecNO_TARGET; + } + + if (!contractSle->at(sfContractHash)) + { + JLOG(ctx.j.trace()) << "ContractCall: Contract does not have a hash."; + return tecNO_TARGET; + } + + auto const contractSourceSle = + ctx.view.read(keylet::contractSource(contractSle->at(sfContractHash))); + if (!contractSourceSle) + { + JLOG(ctx.j.trace()) << "ContractCall: ContractSource does not exist."; + return tecNO_TARGET; + } + + if (!contractSourceSle->isFieldPresent(sfFunctions)) + { + JLOG(ctx.j.trace()) + << "ContractCall: Contract does not have any functions defined."; + return temMALFORMED; + } + + auto const& functions = contractSourceSle->getFieldArray(sfFunctions); + auto const functionName = ctx.tx.getFieldVL(sfFunctionName); + std::string functionNameHexStr(functionName.begin(), functionName.end()); + auto it = std::find_if( + functions.begin(), + functions.end(), + [&functionNameHexStr](STObject const& func) { + auto const funcName = func.getFieldVL(sfFunctionName); + std::string functionNameDefHexStr(funcName.begin(), funcName.end()); + return functionNameDefHexStr == functionNameHexStr; + }); + + if (it == functions.end()) + { + JLOG(ctx.j.trace()) + << "ContractCall: FunctionName: " << functionNameHexStr + << " does not exist in contract abi."; + return temMALFORMED; + } + + if (ctx.tx.isFieldPresent(sfParameters)) + { + STArray const& params = ctx.tx.getFieldArray(sfParameters); + if (auto ter = contract::preclaimFlagParameters( + ctx.view, account, contractAccount, params, ctx.j); + !isTesSuccess(ter)) + { + JLOG(ctx.j.trace()) + << "ContractCreate: Failed to preclaim flag parameters."; + return ter; + } + } + + // The parameters don't match the function's ABI. + return tesSUCCESS; +} + +TER +ContractCall::doApply() +{ + AccountID const contractAccount = ctx_.tx[sfContractAccount]; + + auto const caSle = ctx_.view().read(keylet::account(contractAccount)); + if (!caSle) + { + JLOG(j_.trace()) << "ContractCall: ContractAccount does not exist."; + return tefINTERNAL; + } + + auto const accountSle = ctx_.view().read(keylet::account(account_)); + if (!accountSle) + { + JLOG(j_.trace()) << "ContractCall: Account does not exist."; + return tefINTERNAL; + } + + uint256 const contractID = caSle->getFieldH256(sfContractID); + Keylet const k = keylet::contract(contractID); + auto const contractSle = ctx_.view().read(k); + if (!contractSle) + { + JLOG(j_.trace()) << "ContractCall: Contract does not exist."; + return tefINTERNAL; + } + + uint256 const contractHash = contractSle->at(sfContractHash); + auto const contractSourceSle = ctx_.view().read( + keylet::contractSource(contractSle->at(sfContractHash))); + if (!contractSourceSle) + { + JLOG(j_.trace()) << "ContractCall: ContractSource does not exist."; + return tefINTERNAL; + } + + // // Handle the flags for the contract call. + if (ctx_.tx.isFieldPresent(sfParameters)) + { + STArray const& params = ctx_.tx.getFieldArray(sfParameters); + if (auto ter = contract::doApplyFlagParameters( + ctx_.view(), + ctx_.tx, + account_, + contractAccount, + params, + mPriorBalance, + ctx_.journal); + !isTesSuccess(ter)) + { + JLOG(ctx_.journal.trace()) + << "ContractCall: Failed to handle flag parameters."; + return ter; + } + } + + // WASM execution + auto const wasmStr = contractSourceSle->getFieldVL(sfContractCode); + std::vector wasm(wasmStr.begin(), wasmStr.end()); + auto const functionName = ctx_.tx.getFieldVL(sfFunctionName); + std::string funcName(functionName.begin(), functionName.end()); + + auto const contractFunctions = contractSle->isFieldPresent(sfFunctions) + ? contractSle->getFieldArray(sfFunctions) + : contractSourceSle->getFieldArray(sfFunctions); + std::optional function; + for (auto const& contractFunction : contractFunctions) + { + if (contractFunction.getFieldVL(sfFunctionName) == functionName) + function = contractFunction; + } + if (!function) + { + JLOG(j_.trace()) + << "ContractCall: FunctionName does not exist in contract."; + return tefINTERNAL; + } + + // ContractCall Parameters + std::vector functionParameters; + if (ctx_.tx.isFieldPresent(sfParameters)) + { + STArray const& funcParams = ctx_.tx.getFieldArray(sfParameters); + functionParameters = getParameterValueVec(funcParams); + } + + // ContractSource/Contract Default Parameters + std::vector instanceParameters; + if (contractSle->isFieldPresent(sfInstanceParameterValues)) + { + STArray const& instParams = + contractSle->getFieldArray(sfInstanceParameterValues); + instanceParameters = getParameterValueVec(instParams); + } + + // The parameters don't match the function's ABI. + std::vector typeVec; + if (function->isFieldPresent(sfParameters)) + { + STArray const& funcParamsDef = function->getFieldArray(sfParameters); + typeVec = xrpl::getParameterTypeVec(funcParamsDef); + if (functionParameters.size() != typeVec.size()) + return tecINVALID_PARAMETERS; + } + + for (std::size_t i = 0; i < functionParameters.size(); i++) + { + if (functionParameters[i].value.getInnerSType() != + typeVec[i].type.getInnerSType()) + return tecINVALID_PARAMETERS; + } + + xrpl::ContractDataMap dataMap; + xrpl::ContractEventMap eventMap; + ContractContext contractCtx = { + .applyCtx = ctx_, + .instanceParameters = instanceParameters, + .functionParameters = functionParameters, + .built_txns = {}, + .expected_etxn_count = 1, + .generation = 0, + .burden = 0, + .result = + { + .contractHash = contractHash, + .contractKeylet = k, + .contractSourceKeylet = k, + .contractAccountKeylet = k, + .contractAccount = contractAccount, + .nextSequence = caSle->getFieldU32(sfSequence), + .otxnAccount = account_, + .otxnId = ctx_.tx.getTransactionID(), + .exitReason = "", + .exitCode = -1, + .dataMap = dataMap, + .eventMap = eventMap, + .changedDataCount = 0, + }, + }; + + ContractHostFunctionsImpl ledgerDataProvider(contractCtx); + + if (!ctx_.tx.isFieldPresent(sfComputationAllowance)) + { + JLOG(j_.trace()) << "ContractCall: Computation allowance is not set."; + return tefINTERNAL; + } + + std::uint32_t allowance = ctx_.tx[sfComputationAllowance]; + auto re = runEscrowWasm(wasm, ledgerDataProvider, funcName, {}, allowance); + + // Wasm Result + if (re.has_value()) + { + // TODO: better error handling for this conversion + // if (allowance > re.value().cost) + // { + // allowance -= static_cast(re.value().cost); + // // auto const returnAllowance = [&]() { + // // ctx_.view().update( + // // keylet::account(contractAccount), + // // [allowance](SLE& sle) { + // // sle.setFieldU32( + // // sfBalance, + // // sle.getFieldU32(sfBalance) + allowance); + // // }); + // // }; + // // returnAllowance(); + // } + + ctx_.setGasUsed(static_cast(re.value().cost)); + auto ret = re.value().result; + if (ret < 0) + { + JLOG(j_.trace()) + << "WASM Execution Failed: " << contractCtx.result.exitReason; + ctx_.setWasmReturnCode(ret); + // ctx_.setWasmReturnStr(contractCtx.result.exitReason); + return tecWASM_REJECTED; + } + + if (auto res = contract::finalizeContractData( + ctx_, + contractAccount, + contractCtx.result.dataMap, + contractCtx.result.eventMap, + ctx_.tx.getTransactionID()); + !isTesSuccess(res)) + { + JLOG(j_.trace()) + << "Contract data finalization failed: " << transHuman(res); + return res; + } + + ctx_.setWasmReturnCode(ret); + // ctx_.setWasmReturnStr(contractCtx.result.exitReason); + ctx_.setEmittedTxns(contractCtx.result.emittedTxns); + return tesSUCCESS; + } + else + { + JLOG(j_.trace()) << "WASM Failure: " + transHuman(re.error()); + auto const errorCode = TERtoInt(re.error()); + ctx_.setWasmReturnCode(errorCode); + // ctx_.setWasmReturnStr(contractCtx.result.exitReason); + return re.error(); + } + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/xrpld/app/tx/detail/ContractCall.h b/src/xrpld/app/tx/detail/ContractCall.h new file mode 100644 index 0000000000..aa0a39ac1d --- /dev/null +++ b/src/xrpld/app/tx/detail/ContractCall.h @@ -0,0 +1,51 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_CONTRACTCALL_H_INCLUDED +#define RIPPLE_TX_CONTRACTCALL_H_INCLUDED + +#include + +namespace xrpl { + +class ContractCall : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit ContractCall(ApplyContext& ctx) : Transactor(ctx) + { + } + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace xrpl + +#endif diff --git a/src/xrpld/app/tx/detail/ContractClawback.cpp b/src/xrpld/app/tx/detail/ContractClawback.cpp new file mode 100644 index 0000000000..5fdef0497f --- /dev/null +++ b/src/xrpld/app/tx/detail/ContractClawback.cpp @@ -0,0 +1,53 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include +#include + +namespace xrpl { + +NotTEC +ContractClawback::preflight(PreflightContext const& ctx) +{ + auto const flags = ctx.tx.getFlags(); + if (flags & tfUniversalMask) + { + JLOG(ctx.j.trace()) + << "ContractClawback: tfUniversalMask is not allowed."; + return temINVALID_FLAG; + } + + return tesSUCCESS; +} + +TER +ContractClawback::preclaim(PreclaimContext const& ctx) +{ + return tesSUCCESS; +} + +TER +ContractClawback::doApply() +{ + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/xrpld/app/tx/detail/ContractClawback.h b/src/xrpld/app/tx/detail/ContractClawback.h new file mode 100644 index 0000000000..0a6436534b --- /dev/null +++ b/src/xrpld/app/tx/detail/ContractClawback.h @@ -0,0 +1,48 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_CONTRACTCLAWBACK_H_INCLUDED +#define RIPPLE_TX_CONTRACTCLAWBACK_H_INCLUDED + +#include + +namespace xrpl { + +class ContractClawback : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit ContractClawback(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace xrpl + +#endif diff --git a/src/xrpld/app/tx/detail/ContractCreate.cpp b/src/xrpld/app/tx/detail/ContractCreate.cpp new file mode 100644 index 0000000000..17a1849ae6 --- /dev/null +++ b/src/xrpld/app/tx/detail/ContractCreate.cpp @@ -0,0 +1,326 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +XRPAmount +ContractCreate::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + XRPAmount const maxAmount{ + std::numeric_limits::max()}; + XRPAmount createFee{0}; + + if (tx.isFieldPresent(sfCreateCode)) + createFee = XRPAmount{ + contract::contractCreateFee(tx.getFieldVL(sfCreateCode).size())}; + + if (createFee > maxAmount - view.fees().increment) + { + JLOG(debugLog().trace()) + << "ContractCreate: Create fee overflow detected."; + return XRPAmount{INITIAL_XRP}; + } + + createFee += view.fees().increment; + + auto baseFee = Transactor::calculateBaseFee(view, tx); + if (baseFee > maxAmount - createFee) + { + JLOG(debugLog().trace()) + << "ContractCreate: Total fee overflow detected."; + return XRPAmount{INITIAL_XRP}; + } + + return createFee + baseFee; +} + +NotTEC +ContractCreate::preflight(PreflightContext const& ctx) +{ + auto const flags = ctx.tx.getFlags(); + if (flags & tfContractMask) + { + JLOG(ctx.j.trace()) << "ContractCreate: tfContractMask is not allowed."; + return temINVALID_FLAG; + } + + if ((flags & (tfCodeImmutable | tfABIImmutable | tfImmutable)) > + tfImmutable) + { + JLOG(ctx.j.trace()) + << "ContractCreate: Cannot set more than one immutability flag."; + return temINVALID_FLAG; + } + + if (!ctx.tx.isFieldPresent(sfContractCode) && + !ctx.tx.isFieldPresent(sfContractHash)) + { + JLOG(ctx.j.trace()) + << "ContractCreate: Neither ContractCode nor ContractHash present"; + return temMALFORMED; + } + + if (ctx.tx.isFieldPresent(sfContractCode) && + ctx.tx.isFieldPresent(sfContractHash)) + { + JLOG(ctx.j.trace()) + << "ContractCreate: Both ContractCode and ContractHash present"; + return temMALFORMED; + } + + if (auto const res = contract::preflightFunctions(ctx.tx, ctx.j); + !isTesSuccess(res)) + { + JLOG(ctx.j.trace()) << "ContractCreate: Functions validation failed: " + << transToken(res); + return res; + } + + if (auto const res = contract::preflightInstanceParameters(ctx.tx, ctx.j); + !isTesSuccess(res)) + { + JLOG(ctx.j.trace()) + << "ContractCreate: InstanceParameters validation failed: " + << transToken(res); + return res; + } + + if (auto const res = + contract::preflightInstanceParameterValues(ctx.tx, ctx.j); + !isTesSuccess(res)) + { + JLOG(ctx.j.trace()) + << "ContractCreate: InstanceParameterValues validation failed: " + << transToken(res); + return res; + } + + return tesSUCCESS; +} + +TER +ContractCreate::preclaim(PreclaimContext const& ctx) +{ + // ContractHash is provided but there is no existing corresponding + // ContractSource ledger object + bool isInstall = ctx.tx.isFieldPresent(sfContractHash); + auto contractHash = ctx.tx.at(~sfContractHash); + if (isInstall && !ctx.view.exists(keylet::contractSource(*contractHash))) + { + JLOG(ctx.j.trace()) << "ContractCreate: ContractHash provided but no " + "corresponding ContractSource exists"; + return temMALFORMED; + } + + // The ContractCode provided is invalid. + if (ctx.tx.isFieldPresent(sfContractCode)) + { + xrpl::Blob wasmBytes = ctx.tx.getFieldVL(sfContractCode); + if (wasmBytes.empty()) + { + JLOG(ctx.j.trace()) + << "ContractCreate: ContractCode provided is empty."; + return temMALFORMED; + } + + contractHash = + xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size())); + if (ctx.view.exists(keylet::contractSource(*contractHash))) + isInstall = true; + + // Iterate through the functions and validate them? + // HostFunctions mock; + // auto const re = preflightEscrowWasm(wasmBytes, "finish", {}, &mock, + // ctx.j); if (!isTesSuccess(re)) + // { + // JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM"; + // return re; + // } + } + + // The ABI provided in Functions doesn't match the code. + + // InstanceParameters don't match what's in the existing ContractSource + // ledger object. + if (isInstall && ctx.tx.isFieldPresent(sfInstanceParameterValues)) + { + auto const sle = ctx.view.read(keylet::contractSource(*contractHash)); + if (!sle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // Already validated in preflight, but we can check here too. + auto const& instanceParams = sle->getFieldArray(sfInstanceParameters); + auto const& instanceParamValues = + ctx.tx.getFieldArray(sfInstanceParameterValues); + if (auto const isValid = contract::validateParameterMapping( + instanceParams, instanceParamValues, ctx.j); + !isValid) + { + JLOG(ctx.j.trace()) + << "ContractCreate: InstanceParameters do not match what's in " + "the existing ContractSource ledger object."; + return temMALFORMED; + } + } + + return tesSUCCESS; +} + +TER +ContractCreate::doApply() +{ + auto const accountSle = ctx_.view().peek(keylet::account(account_)); + if (!accountSle) + { + JLOG(j_.trace()) << "ContractCreate: Account not found."; + return tefINTERNAL; // LCOV_EXCL_LINE + } + + std::shared_ptr sourceSle; + bool isInstall = ctx_.tx.isFieldPresent(sfContractHash); + auto contractHash = ctx_.tx[~sfContractHash]; + xrpl::Blob wasmBytes; + if (ctx_.tx.isFieldPresent(sfContractCode)) + { + wasmBytes = ctx_.tx.getFieldVL(sfContractCode); + contractHash = + xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size())); + if (ctx_.view().exists(keylet::contractSource(*contractHash))) + isInstall = true; + } + + if (isInstall) + { + sourceSle = ctx_.view().peek(keylet::contractSource(*contractHash)); + if (!sourceSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + sourceSle->at(sfReferenceCount) = + sourceSle->getFieldU64(sfReferenceCount) + 1; + ctx_.view().update(sourceSle); + } + else + { + sourceSle = + std::make_shared(keylet::contractSource(*contractHash)); + sourceSle->at(sfContractHash) = *contractHash; + sourceSle->at(sfContractCode) = makeSlice(wasmBytes); + sourceSle->setFieldArray( + sfFunctions, ctx_.tx.getFieldArray(sfFunctions)); + if (ctx_.tx.isFieldPresent(sfInstanceParameters)) + sourceSle->setFieldArray( + sfInstanceParameters, + ctx_.tx.getFieldArray(sfInstanceParameters)); + sourceSle->at(sfReferenceCount) = 1; + + ctx_.view().insert(sourceSle); + } + + std::uint32_t const seq = ctx_.tx.getSeqValue(); + auto const contractKeylet = keylet::contract(*contractHash, account_, seq); + auto contractSle = std::make_shared(contractKeylet); + + auto maybePseudo = + createPseudoAccount(view(), contractSle->key(), sfContractID); + if (!maybePseudo) + return maybePseudo.error(); // LCOV_EXCL_LINE + + auto& pseudoSle = *maybePseudo; + auto pseudoAccount = pseudoSle->at(sfAccount); + + contractSle->at(sfContractAccount) = pseudoAccount; + contractSle->at(sfOwner) = account_; + contractSle->at(sfFlags) = ctx_.tx.getFlags(); + contractSle->at(sfSequence) = seq; + contractSle->at(sfContractHash) = *contractHash; + if (ctx_.tx.isFieldPresent(sfInstanceParameterValues)) + contractSle->setFieldArray( + sfInstanceParameterValues, + ctx_.tx.getFieldArray(sfInstanceParameterValues)); + + if (ctx_.tx.isFieldPresent(sfURI)) + contractSle->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI)); + + ctx_.view().insert(contractSle); + + // Handle the instance parameters for the contract creation. + if (ctx_.tx.isFieldPresent(sfInstanceParameterValues)) + { + STArray const& params = + ctx_.tx.getFieldArray(sfInstanceParameterValues); + + // Note: We have to do preclaim and apply here because we will only have + // the pseudo account after the contract is created. + if (auto ter = contract::preclaimFlagParameters( + ctx_.view(), account_, pseudoAccount, params, j_); + !isTesSuccess(ter)) + { + JLOG(j_.trace()) + << "ContractCreate: Failed to preclaim flag parameters."; + return ter; + } + + if (auto ter = contract::doApplyFlagParameters( + ctx_.view(), + ctx_.tx, + account_, + pseudoAccount, + params, + mPriorBalance, + j_); + !isTesSuccess(ter)) + { + JLOG(j_.trace()) + << "ContractCreate: Failed to apply flag parameters."; + return ter; + } + } + + // Add Contract to ContractAccount Dir + // TODO: use dirLink + { + auto const page = view().dirInsert( + keylet::ownerDir(pseudoAccount), + contractKeylet, + describeOwnerDir(pseudoAccount)); + + if (!page) + return tecDIR_FULL; + + contractSle->setFieldU64(sfOwnerNode, *page); + adjustOwnerCount(ctx_.view(), pseudoSle, 1, j_); + } + + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/xrpld/app/tx/detail/ContractCreate.h b/src/xrpld/app/tx/detail/ContractCreate.h new file mode 100644 index 0000000000..e1a4563483 --- /dev/null +++ b/src/xrpld/app/tx/detail/ContractCreate.h @@ -0,0 +1,51 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_CONTRACTCREATE_H_INCLUDED +#define RIPPLE_TX_CONTRACTCREATE_H_INCLUDED + +#include + +namespace xrpl { + +class ContractCreate : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit ContractCreate(ApplyContext& ctx) : Transactor(ctx) + { + } + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace xrpl + +#endif diff --git a/src/xrpld/app/tx/detail/ContractDelete.cpp b/src/xrpld/app/tx/detail/ContractDelete.cpp new file mode 100644 index 0000000000..57720265dd --- /dev/null +++ b/src/xrpld/app/tx/detail/ContractDelete.cpp @@ -0,0 +1,173 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +#include +#include + +namespace xrpl { + +NotTEC +ContractDelete::preflight(PreflightContext const& ctx) +{ + auto const flags = ctx.tx.getFlags(); + if (flags & tfUniversalMask) + { + JLOG(ctx.j.trace()) + << "ContractDelete: only tfUniversalMask is allowed."; + return temINVALID_FLAG; + } + + return tesSUCCESS; +} + +TER +ContractDelete::preclaim(PreclaimContext const& ctx) +{ + AccountID const account = ctx.tx.getAccountID(sfAccount); + AccountID const contractAccount = ctx.tx.isFieldPresent(sfContractAccount) + ? ctx.tx.getAccountID(sfContractAccount) + : account; + + auto const caSle = ctx.view.read(keylet::account(contractAccount)); + if (!caSle) + { + JLOG(ctx.j.trace()) << "ContractDelete: Account does not exist."; + return terNO_ACCOUNT; + } + + if (!caSle->isFieldPresent(sfContractID)) + { + JLOG(ctx.j.trace()) << "ContractDelete: Account is not a smart " + "contract pseudo-account."; + return tecNO_PERMISSION; + } + + uint256 const contractID = caSle->getFieldH256(sfContractID); + auto const contractSle = ctx.view.read(keylet::contract(contractID)); + if (!contractSle) + { + JLOG(ctx.j.trace()) << "ContractDelete: Contract does not exist."; + return tecNO_TARGET; + } + + if (contractSle->getAccountID(sfOwner) != account) + { + JLOG(ctx.j.trace()) << "ContractDelete: Cannot delete a contract that " + "does not belong to the account."; + return tecNO_PERMISSION; + } + + std::uint32_t flags = contractSle->getFlags(); + + // Check if the contract is undeletable. + if (flags & tfUndeletable) + { + JLOG(ctx.j.trace()) << "ContractDelete: Contract is undeletable."; + return tecNO_PERMISSION; + } + + AccountID const owner = contractSle->getAccountID(sfOwner); + if (auto const res = deletePreclaim(ctx, 0, account, owner, true); + !isTesSuccess(res)) + return res; + return tesSUCCESS; +} + +TER +ContractDelete::deleteContract( + ApplyView& view, + std::shared_ptr const& sle, + AccountID const& account, + beast::Journal j) +{ + if (!sle) + return tecINTERNAL; // LCOV_EXCL_LINE + + if (!view.dirRemove( + keylet::ownerDir(account), (*sle)[sfOwnerNode], sle->key(), false)) + { + // LCOV_EXCL_START + JLOG(j.trace()) << "Unable to delete Delegate from owner."; + return tefBAD_LEDGER; + // LCOV_EXCL_STOP + } + + auto const sleOwner = view.peek(keylet::account(account)); + if (!sleOwner) + return tecINTERNAL; // LCOV_EXCL_LINE + + adjustOwnerCount(view, sleOwner, -1, j); + view.erase(sle); + return tesSUCCESS; +} + +TER +ContractDelete::doApply() +{ + AccountID const account = ctx_.tx.getAccountID(sfAccount); + AccountID const contractAccount = ctx_.tx.isFieldPresent(sfContractAccount) + ? ctx_.tx.getAccountID(sfContractAccount) + : account; + + auto const caSle = ctx_.view().read(keylet::account(contractAccount)); + if (!caSle) + { + JLOG(j_.trace()) << "ContractModify: Account does not exist."; + return tefBAD_LEDGER; + } + + uint256 const contractID = caSle->getFieldH256(sfContractID); + auto const contractSle = ctx_.view().read(keylet::contract(contractID)); + if (!contractSle) + { + JLOG(j_.trace()) << "ContractDelete: Contract does not exist."; + return tecNO_TARGET; + } + + // Lower the reference count of the ContractSource or remove the source from + // the ledger. + uint256 const contractHash = contractSle->getFieldH256(sfContractHash); + auto oldSourceSle = ctx_.view().peek(keylet::contractSource(contractHash)); + if (oldSourceSle->getFieldU64(sfReferenceCount) == 1) + { + // NOTE: We dont adjust the owner count because ContractSource is an + // unowned object. + ctx_.view().erase(oldSourceSle); + } + else + { + oldSourceSle->setFieldU64( + sfReferenceCount, oldSourceSle->getFieldU64(sfReferenceCount) - 1); + ctx_.view().update(oldSourceSle); + } + + AccountID const owner = contractSle->getAccountID(sfOwner); + STAmount const contractBalance = (*caSle)[sfBalance]; + if (auto const res = + deleteDoApply(ctx_, contractBalance, contractAccount, owner); + !isTesSuccess(res)) + return res; + + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/xrpld/app/tx/detail/ContractDelete.h b/src/xrpld/app/tx/detail/ContractDelete.h new file mode 100644 index 0000000000..7e8780eb01 --- /dev/null +++ b/src/xrpld/app/tx/detail/ContractDelete.h @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_CONTRACTDELETE_H_INCLUDED +#define RIPPLE_TX_CONTRACTDELETE_H_INCLUDED + +#include + +namespace xrpl { + +class ContractDelete : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit ContractDelete(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + // Interface used by DeleteAccount + static TER + deleteContract( + ApplyView& view, + std::shared_ptr const& sle, + AccountID const& account, + beast::Journal j); + + TER + doApply() override; +}; + +} // namespace xrpl + +#endif diff --git a/src/xrpld/app/tx/detail/ContractModify.cpp b/src/xrpld/app/tx/detail/ContractModify.cpp new file mode 100644 index 0000000000..0eddafe6fe --- /dev/null +++ b/src/xrpld/app/tx/detail/ContractModify.cpp @@ -0,0 +1,411 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +#include +#include +#include + +namespace xrpl { + +XRPAmount +ContractModify::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + XRPAmount const maxAmount{ + std::numeric_limits::max()}; + XRPAmount createFee{0}; + + if (tx.isFieldPresent(sfCreateCode)) + createFee = XRPAmount{ + contract::contractCreateFee(tx.getFieldVL(sfCreateCode).size())}; + + if (createFee > maxAmount - view.fees().increment) + { + JLOG(debugLog().trace()) + << "ContractModify: Create fee overflow detected."; + return XRPAmount{INITIAL_XRP}; + } + + auto baseFee = Transactor::calculateBaseFee(view, tx); + if (baseFee > maxAmount - createFee) + { + JLOG(debugLog().trace()) + << "ContractModify: Total fee overflow detected."; + return XRPAmount{INITIAL_XRP}; + } + + return createFee + baseFee; +} + +NotTEC +ContractModify::preflight(PreflightContext const& ctx) +{ + auto const flags = ctx.tx.getFlags(); + if (flags & tfUniversalMask) + { + JLOG(ctx.j.trace()) + << "ContractModify: only tfUniversalMask is allowed."; + return temINVALID_FLAG; + } + + // Either ContractCode or ContractHash must be present. + if (ctx.tx.isFieldPresent(sfContractCode) && + ctx.tx.isFieldPresent(sfContractHash)) + { + JLOG(ctx.j.trace()) + << "ContractModify: Both ContractCode and ContractHash present"; + return temMALFORMED; + } + + // Validate Functions & Function Parameters. + if (auto const res = contract::preflightFunctions(ctx.tx, ctx.j); + !isTesSuccess(res)) + return res; + + // Validate Instance Parameters. + if (auto const res = contract::preflightInstanceParameters(ctx.tx, ctx.j); + !isTesSuccess(res)) + return res; + + // Validate Instance Parameter Values. + if (auto const res = + contract::preflightInstanceParameterValues(ctx.tx, ctx.j); + !isTesSuccess(res)) + return res; + + if (ctx.tx.isFieldPresent(sfOwner)) + { + if (ctx.tx.getAccountID(sfOwner) == ctx.tx.getAccountID(sfAccount)) + return temMALFORMED; + + if (ctx.tx.getAccountID(sfOwner) == + ctx.tx.getAccountID(sfContractAccount)) + return temMALFORMED; + } + + return tesSUCCESS; +} + +TER +ContractModify::preclaim(PreclaimContext const& ctx) +{ + AccountID const account = ctx.tx.getAccountID(sfAccount); + AccountID const contractAccount = ctx.tx.isFieldPresent(sfContractAccount) + ? ctx.tx.getAccountID(sfContractAccount) + : account; + + auto const contractAccountSle = + ctx.view.read(keylet::account(contractAccount)); + if (!contractAccountSle) + { + JLOG(ctx.j.trace()) + << "ContractModify: Contract Account does not exist."; + return tecNO_TARGET; + } + + uint256 const contractID = contractAccountSle->getFieldH256(sfContractID); + auto const contractSle = ctx.view.read(keylet::contract(contractID)); + if (!contractSle) + { + JLOG(ctx.j.trace()) << "ContractModify: Contract does not exist."; + return tecNO_TARGET; + } + + if (ctx.tx.isFieldPresent(sfContractAccount) && + contractSle->getAccountID(sfOwner) != account) + { + JLOG(ctx.j.trace()) << "ContractModify: Cannot modify a contract that " + "does not belong to the account."; + return tecNO_PERMISSION; + } + + std::uint32_t flags = contractSle->getFlags(); + + // Check if the contract is immutable. + if (flags & tfImmutable) + { + JLOG(ctx.j.trace()) << "ContractModify: Contract is immutable."; + return tecNO_PERMISSION; + } + + // Check if the contract code is immutable. + if (flags & tfCodeImmutable && ctx.tx.isFieldPresent(sfContractCode)) + { + JLOG(ctx.j.trace()) << "ContractModify: ContractCode is immutable."; + return tecNO_PERMISSION; + } + + // Check if the contract ABI is immutable. + if (flags & tfABIImmutable) + { + if (!ctx.tx.isFieldPresent(sfContractCode)) + { + JLOG(ctx.j.trace()) << "ContractModify: ContractCode must be " + "present when modifying ABI."; + return tecNO_PERMISSION; + } + + if (!ctx.tx.isFieldPresent(sfFunctions)) + { + JLOG(ctx.j.trace()) << "ContractModify: Functions must be present " + "when modifying ABI."; + return tecNO_PERMISSION; + } + + JLOG(ctx.j.trace()) << "ContractModify: ABI is immutable."; + return tecNO_PERMISSION; + } + + // Can only include 1 of the 3 flags: tfCodeImmutable, tfABIImmutable, + // tfImmutable. + if ((flags & (tfCodeImmutable | tfABIImmutable | tfImmutable)) > + tfImmutable) + { + JLOG(ctx.j.trace()) + << "ContractModify: Cannot set more than one immutability flag."; + return temINVALID_FLAG; + } + + bool isInstall = ctx.tx.isFieldPresent(sfContractHash); + auto contractHash = ctx.tx.at(~sfContractHash); + if (ctx.tx.isFieldPresent(sfContractCode)) + { + xrpl::Blob wasmBytes = ctx.tx.getFieldVL(sfContractCode); + if (wasmBytes.empty()) + { + JLOG(ctx.j.trace()) + << "ContractModify: ContractCode provided is empty."; + return temMALFORMED; + } + + contractHash = + xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size())); + if (ctx.view.exists(keylet::contractSource(*contractHash))) + isInstall = true; + + // Iterate through the functions and validate them? + // HostFunctions mock; + // auto const re = preflightEscrowWasm(wasmBytes, "finish", {}, &mock, + // ctx.j); if (!isTesSuccess(re)) + // { + // JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM"; + // return re; + // } + } + + // The ABI provided in Functions doesn't match the code. + + if (isInstall) + { + auto const sle = ctx.view.read(keylet::contractSource(*contractHash)); + if (!sle) + { + JLOG(ctx.j.trace()) + << "ContractModify: ContractSource ledger object not found for " + "the provided ContractHash."; + return tefINTERNAL; // LCOV_EXCL_LINE + } + + if (sle->isFieldPresent(sfInstanceParameters) && + !ctx.tx.isFieldPresent(sfInstanceParameterValues)) + { + JLOG(ctx.j.trace()) + << "ContractModify: ContractHash is present, but " + "InstanceParameterValues is missing."; + return temMALFORMED; + } + + auto const& instanceParams = sle->getFieldArray(sfInstanceParameters); + auto const& instanceParamValues = + ctx.tx.getFieldArray(sfInstanceParameterValues); + if (auto const isValit = contract::validateParameterMapping( + instanceParams, instanceParamValues, ctx.j); + !isValit) + { + JLOG(ctx.j.trace()) + << "ContractModify: InstanceParameters do not match what's in " + "the existing ContractSource ledger object."; + return temMALFORMED; + } + } + + if (ctx.tx.isFieldPresent(sfOwner)) + { + auto const ownerSle = + ctx.view.read(keylet::account(ctx.tx.getAccountID(sfOwner))); + if (!ownerSle) + { + JLOG(ctx.j.trace()) + << "ContractModify: New owner account does not exist."; + return tecNO_TARGET; + } + } + + return tesSUCCESS; +} + +TER +ContractModify::doApply() +{ + AccountID const account = ctx_.tx.getAccountID(sfAccount); + AccountID const contractAccount = ctx_.tx.isFieldPresent(sfContractAccount) + ? ctx_.tx.getAccountID(sfContractAccount) + : account; + + auto const contractAccountSle = + ctx_.view().read(keylet::account(contractAccount)); + if (!contractAccountSle) + { + JLOG(ctx_.journal.trace()) << "ContractModify: Account does not exist."; + return tefINTERNAL; + } + + uint256 const contractID = contractAccountSle->getFieldH256(sfContractID); + auto const contractSle = ctx_.view().peek(keylet::contract(contractID)); + if (!contractSle) + { + JLOG(ctx_.journal.trace()) + << "ContractModify: Contract does not exist."; + return tefINTERNAL; + } + + auto currentSourceSle = ctx_.view().peek( + keylet::contractSource(contractSle->getFieldH256(sfContractHash))); + if (!currentSourceSle) + { + JLOG(ctx_.journal.trace()) + << "ContractModify: ContractSource does not exist."; + return tefINTERNAL; + } + + if (ctx_.tx.isFieldPresent(sfContractCode)) + { + JLOG(ctx_.journal.trace()) + << "ContractModify: Modifying ContractCode/ContractHash."; + xrpl::Blob wasmBytes = ctx_.tx.getFieldVL(sfContractCode); + auto const contractHash = + xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size())); + auto const sourceKeylet = keylet::contractSource(contractHash); + auto sourceSle = ctx_.view().peek(sourceKeylet); + if (!sourceSle) + { + JLOG(ctx_.journal.trace()) + << "ContractModify: Creating new ContractSource."; + // create the new ContractSource + sourceSle = std::make_shared(sourceKeylet); + sourceSle->at(sfContractHash) = contractHash; + sourceSle->at(sfContractCode) = makeSlice(wasmBytes); + sourceSle->setFieldArray( + sfFunctions, ctx_.tx.getFieldArray(sfFunctions)); + if (ctx_.tx.isFieldPresent(sfInstanceParameters)) + sourceSle->setFieldArray( + sfInstanceParameters, + ctx_.tx.getFieldArray(sfInstanceParameters)); + sourceSle->at(sfReferenceCount) = 1; + ctx_.view().insert(sourceSle); + } + + // update the Contract + contractSle->setFieldH256(sfContractHash, contractHash); + if (ctx_.tx.isFieldPresent(sfInstanceParameterValues)) + contractSle->setFieldArray( + sfInstanceParameterValues, + ctx_.tx.getFieldArray(sfInstanceParameterValues)); + + ctx_.view().update(contractSle); + + // update the existing ContractSource + if (currentSourceSle->getFieldU64(sfReferenceCount) == 1) + { + // remove the old ContractSource if no more references + ctx_.view().erase(currentSourceSle); + } + else + { + // decrement the reference count + currentSourceSle->setFieldU64( + sfReferenceCount, + currentSourceSle->getFieldU64(sfReferenceCount) - 1); + ctx_.view().update(currentSourceSle); + } + } + else if (ctx_.tx.isFieldPresent(sfContractHash)) + { + auto sourceSle = ctx_.view().peek( + keylet::contractSource(ctx_.tx.getFieldH256(sfContractHash))); + if (!sourceSle) + { + JLOG(ctx_.journal.trace()) + << "ContractModify: ContractSource does not exist."; + return tefINTERNAL; + } + + // set new contract hash + contractSle->setFieldH256( + sfContractHash, ctx_.tx.getFieldH256(sfContractHash)); + + // set new instance parameter values if present + if (ctx_.tx.isFieldPresent(sfInstanceParameterValues)) + contractSle->setFieldArray( + sfInstanceParameterValues, + ctx_.tx.getFieldArray(sfInstanceParameterValues)); + + ctx_.view().update(contractSle); + + sourceSle->setFieldU64( + sfReferenceCount, sourceSle->getFieldU64(sfReferenceCount) + 1); + ctx_.view().update(sourceSle); + + // update the existing ContractSource + if (currentSourceSle->getFieldU64(sfReferenceCount) == 1) + { + // remove the old ContractSource if no more references + ctx_.view().erase(currentSourceSle); + } + else + { + // decrement the reference count + currentSourceSle->setFieldU64( + sfReferenceCount, + currentSourceSle->getFieldU64(sfReferenceCount) - 1); + ctx_.view().update(currentSourceSle); + } + } + else if (ctx_.tx.isFieldPresent(sfInstanceParameterValues)) + { + // only updating instance parameter values + contractSle->setFieldArray( + sfInstanceParameterValues, + ctx_.tx.getFieldArray(sfInstanceParameterValues)); + + ctx_.view().update(contractSle); + } + + if (ctx_.tx.isFieldPresent(sfOwner)) + { + contractSle->setAccountID(sfOwner, ctx_.tx.getAccountID(sfOwner)); + ctx_.view().update(contractSle); + } + + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/xrpld/app/tx/detail/ContractModify.h b/src/xrpld/app/tx/detail/ContractModify.h new file mode 100644 index 0000000000..3cc6f6352c --- /dev/null +++ b/src/xrpld/app/tx/detail/ContractModify.h @@ -0,0 +1,51 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_CONTRACTMODIFY_H_INCLUDED +#define RIPPLE_TX_CONTRACTMODIFY_H_INCLUDED + +#include + +namespace xrpl { + +class ContractModify : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit ContractModify(ApplyContext& ctx) : Transactor(ctx) + { + } + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace xrpl + +#endif diff --git a/src/xrpld/app/tx/detail/ContractUserDelete.cpp b/src/xrpld/app/tx/detail/ContractUserDelete.cpp new file mode 100644 index 0000000000..6efcf7a9ef --- /dev/null +++ b/src/xrpld/app/tx/detail/ContractUserDelete.cpp @@ -0,0 +1,53 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include +#include + +namespace xrpl { + +NotTEC +ContractUserDelete::preflight(PreflightContext const& ctx) +{ + auto const flags = ctx.tx.getFlags(); + if (flags & tfUniversalMask) + { + JLOG(ctx.j.trace()) + << "ContractUserDelete: tfUniversalMask is not allowed."; + return temINVALID_FLAG; + } + + return tesSUCCESS; +} + +TER +ContractUserDelete::preclaim(PreclaimContext const& ctx) +{ + return tesSUCCESS; +} + +TER +ContractUserDelete::doApply() +{ + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/xrpld/app/tx/detail/ContractUserDelete.h b/src/xrpld/app/tx/detail/ContractUserDelete.h new file mode 100644 index 0000000000..6edb1d669a --- /dev/null +++ b/src/xrpld/app/tx/detail/ContractUserDelete.h @@ -0,0 +1,48 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_CONTRACTUSERDELETE_H_INCLUDED +#define RIPPLE_TX_CONTRACTUSERDELETE_H_INCLUDED + +#include + +namespace xrpl { + +class ContractUserDelete : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; + + explicit ContractUserDelete(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace xrpl + +#endif diff --git a/src/xrpld/app/tx/detail/DeleteAccount.cpp b/src/xrpld/app/tx/detail/DeleteAccount.cpp index 422ed37e2f..692e6851f5 100644 --- a/src/xrpld/app/tx/detail/DeleteAccount.cpp +++ b/src/xrpld/app/tx/detail/DeleteAccount.cpp @@ -1,10 +1,7 @@ +#include #include #include #include -#include -#include -#include -#include #include #include @@ -50,161 +47,6 @@ DeleteAccount::calculateBaseFee(ReadView const& view, STTx const& tx) return calculateOwnerReserveFee(view, tx); } -namespace { -// Define a function pointer type that can be used to delete ledger node types. -using DeleterFuncPtr = TER (*)( - Application& app, - ApplyView& view, - AccountID const& account, - uint256 const& delIndex, - std::shared_ptr const& sleDel, - beast::Journal j); - -// Local function definitions that provides signature compatibility. -TER -offerDelete( - Application& app, - ApplyView& view, - AccountID const& account, - uint256 const& delIndex, - std::shared_ptr const& sleDel, - beast::Journal j) -{ - return offerDelete(view, sleDel, j); -} - -TER -removeSignersFromLedger( - Application& app, - ApplyView& view, - AccountID const& account, - uint256 const& delIndex, - std::shared_ptr const& sleDel, - beast::Journal j) -{ - return SetSignerList::removeFromLedger(app, view, account, j); -} - -TER -removeTicketFromLedger( - Application&, - ApplyView& view, - AccountID const& account, - uint256 const& delIndex, - std::shared_ptr const&, - beast::Journal j) -{ - return Transactor::ticketDelete(view, account, delIndex, j); -} - -TER -removeDepositPreauthFromLedger( - Application&, - ApplyView& view, - AccountID const&, - uint256 const& delIndex, - std::shared_ptr const&, - beast::Journal j) -{ - return DepositPreauth::removeFromLedger(view, delIndex, j); -} - -TER -removeNFTokenOfferFromLedger( - Application& app, - ApplyView& view, - AccountID const& account, - uint256 const& delIndex, - std::shared_ptr const& sleDel, - beast::Journal) -{ - if (!nft::deleteTokenOffer(view, sleDel)) - return tefBAD_LEDGER; // LCOV_EXCL_LINE - - return tesSUCCESS; -} - -TER -removeDIDFromLedger( - Application& app, - ApplyView& view, - AccountID const& account, - uint256 const& delIndex, - std::shared_ptr const& sleDel, - beast::Journal j) -{ - return DIDDelete::deleteSLE(view, sleDel, account, j); -} - -TER -removeOracleFromLedger( - Application&, - ApplyView& view, - AccountID const& account, - uint256 const&, - std::shared_ptr const& sleDel, - beast::Journal j) -{ - return DeleteOracle::deleteOracle(view, sleDel, account, j); -} - -TER -removeCredentialFromLedger( - Application&, - ApplyView& view, - AccountID const&, - uint256 const&, - std::shared_ptr const& sleDel, - beast::Journal j) -{ - return credentials::deleteSLE(view, sleDel, j); -} - -TER -removeDelegateFromLedger( - Application& app, - ApplyView& view, - AccountID const& account, - uint256 const& delIndex, - std::shared_ptr const& sleDel, - beast::Journal j) -{ - return DelegateSet::deleteDelegate(view, sleDel, account, j); -} - -// Return nullptr if the LedgerEntryType represents an obligation that can't -// be deleted. Otherwise return the pointer to the function that can delete -// the non-obligation -DeleterFuncPtr -nonObligationDeleter(LedgerEntryType t) -{ - switch (t) - { - case ltOFFER: - return offerDelete; - case ltSIGNER_LIST: - return removeSignersFromLedger; - case ltTICKET: - return removeTicketFromLedger; - case ltDEPOSIT_PREAUTH: - return removeDepositPreauthFromLedger; - case ltNFTOKEN_OFFER: - return removeNFTokenOfferFromLedger; - case ltDID: - return removeDIDFromLedger; - case ltORACLE: - return removeOracleFromLedger; - case ltCREDENTIAL: - return removeCredentialFromLedger; - case ltDELEGATE: - return removeDelegateFromLedger; - default: - return nullptr; - } -} - -} // namespace - TER DeleteAccount::preclaim(PreclaimContext const& ctx) { @@ -330,6 +172,9 @@ DeleteAccount::preclaim(PreclaimContext const& ctx) } while (cdirNext( ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry)); + if (auto const res = deletePreclaim(ctx, 255, account, dst); + !isTesSuccess(res)) + return res; return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index 4e5f41a427..595e573041 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include @@ -99,6 +101,30 @@ escrowCreatePreflightHelper(PreflightContext const& ctx) return tesSUCCESS; } +XRPAmount +EscrowCreate::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + XRPAmount txnFees{Transactor::calculateBaseFee(view, tx)}; + if (tx.isFieldPresent(sfFinishFunction)) + { + // 10 base fees for the transaction (1 is in + // `Transactor::calculateBaseFee`), plus 5 drops per byte + txnFees += 9 * view.fees().base + 5 * tx[sfFinishFunction].size(); + } + return txnFees; +} + +bool +EscrowCreate::checkExtraFeatures(PreflightContext const& ctx) +{ + if ((ctx.tx.isFieldPresent(sfFinishFunction) || + ctx.tx.isFieldPresent(sfData)) && + !ctx.rules.enabled(featureSmartEscrow)) + return false; + + return true; +} + NotTEC EscrowCreate::preflight(PreflightContext const& ctx) { @@ -132,12 +158,21 @@ EscrowCreate::preflight(PreflightContext const& ctx) ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter]) return temBAD_EXPIRATION; + if (ctx.tx.isFieldPresent(sfFinishFunction) && + !ctx.tx.isFieldPresent(sfCancelAfter)) + return temBAD_EXPIRATION; + // In the absence of a FinishAfter, the escrow can be finished // immediately, which can be confusing. When creating an escrow, // we want to ensure that either a FinishAfter time is explicitly // specified or a completion condition is attached. - if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition]) + if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition] && + !ctx.tx[~sfFinishFunction]) + { + JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, " + "Condition, or FinishFunction."; return temMALFORMED; + } if (auto const cb = ctx.tx[~sfCondition]) { @@ -155,6 +190,42 @@ EscrowCreate::preflight(PreflightContext const& ctx) } } + if (ctx.tx.isFieldPresent(sfData)) + { + if (!ctx.tx.isFieldPresent(sfFinishFunction)) + { + JLOG(ctx.j.debug()) + << "EscrowCreate with Data requires FinishFunction"; + return temMALFORMED; + } + auto const data = ctx.tx.getFieldVL(sfData); + if (data.size() > maxWasmDataLength) + { + JLOG(ctx.j.debug()) << "EscrowCreate.Data bad size " << data.size(); + return temMALFORMED; + } + } + + if (ctx.tx.isFieldPresent(sfFinishFunction)) + { + auto const code = ctx.tx.getFieldVL(sfFinishFunction); + if (code.size() == 0 || + code.size() > ctx.app.config().FEES.extension_size_limit) + { + JLOG(ctx.j.debug()) + << "EscrowCreate.FinishFunction bad size " << code.size(); + return temMALFORMED; + } + + HostFunctions mock(ctx.j); + auto const re = preflightEscrowWasm(code, mock, ESCROW_FUNCTION_NAME); + if (!isTesSuccess(re)) + { + JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM"; + return re; + } + } + return tesSUCCESS; } @@ -413,6 +484,17 @@ escrowLockApplyHelper( return tesSUCCESS; } +template +static uint32_t +calculateAdditionalReserve(T const& finishFunction) +{ + if (!finishFunction) + return 1; + // First 500 bytes included in the normal reserve + // Each additional 500 bytes requires an additional reserve + return 1 + (finishFunction->size() / 500); +} + TER EscrowCreate::doApply() { @@ -430,9 +512,11 @@ EscrowCreate::doApply() // Check reserve and funds availability STAmount const amount{ctx_.tx[sfAmount]}; + auto const reserveToAdd = + calculateAdditionalReserve(ctx_.tx[~sfFinishFunction]); auto const reserve = - ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1); + ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + reserveToAdd); if (mSourceBalance < reserve) return tecINSUFFICIENT_RESERVE; @@ -467,6 +551,8 @@ EscrowCreate::doApply() (*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter]; (*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter]; (*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag]; + (*slep)[~sfFinishFunction] = ctx_.tx[~sfFinishFunction]; + (*slep)[~sfData] = ctx_.tx[~sfData]; if (ctx_.view().rules().enabled(fixIncludeKeyletFields)) { @@ -536,7 +622,7 @@ EscrowCreate::doApply() } // increment owner count - adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); + adjustOwnerCount(ctx_.view(), sle, reserveToAdd, ctx_.journal); ctx_.view().update(sle); return tesSUCCESS; } @@ -564,8 +650,16 @@ checkCondition(Slice f, Slice c) bool EscrowFinish::checkExtraFeatures(PreflightContext const& ctx) { - return !ctx.tx.isFieldPresent(sfCredentialIDs) || - ctx.rules.enabled(featureCredentials); + if (ctx.tx.isFieldPresent(sfCredentialIDs) && + !ctx.rules.enabled(featureCredentials)) + return false; + + if (ctx.tx.isFieldPresent(sfComputationAllowance) && + !ctx.rules.enabled(featureSmartEscrow)) + { + return false; + } + return true; } NotTEC @@ -577,7 +671,10 @@ EscrowFinish::preflight(PreflightContext const& ctx) // If you specify a condition, then you must also specify // a fulfillment. if (static_cast(cb) != static_cast(fb)) + { + JLOG(ctx.j.debug()) << "Condition != Fulfillment"; return temMALFORMED; + } return tesSUCCESS; } @@ -607,6 +704,20 @@ EscrowFinish::preflightSigValidated(PreflightContext const& ctx) } } + if (auto const allowance = ctx.tx[~sfComputationAllowance]; allowance) + { + if (*allowance == 0) + { + return temBAD_LIMIT; + } + if (*allowance > ctx.app.config().FEES.extension_compute_limit) + { + JLOG(ctx.j.debug()) + << "ComputationAllowance too large: " << *allowance; + return temBAD_LIMIT; + } + } + if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err)) return err; @@ -623,7 +734,16 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx) { extraFee += view.fees().base * (32 + (fb->size() / 16)); } - + if (std::optional const allowance = tx[~sfComputationAllowance]; + allowance) + { + // The extra fee is the allowance in drops, rounded up to the nearest + // whole drop. + // Integer math rounds down by default, so we add 1 to round up. + uint64_t const allowanceFee = + ((*allowance) * view.fees().gasPrice) / MICRO_DROPS_PER_DROP + 1; + extraFee += allowanceFee; + } return Transactor::calculateBaseFee(view, tx) + extraFee; } @@ -703,25 +823,52 @@ EscrowFinish::preclaim(PreclaimContext const& ctx) return err; } - if (ctx.view.rules().enabled(featureTokenEscrow)) + if (ctx.view.rules().enabled(featureTokenEscrow) || + ctx.view.rules().enabled(featureSmartEscrow)) { + // this check is done in doApply before this amendment is enabled auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]); auto const slep = ctx.view.read(k); if (!slep) return tecNO_TARGET; - AccountID const dest = (*slep)[sfDestination]; - STAmount const amount = (*slep)[sfAmount]; - - if (!isXRP(amount)) + if (ctx.view.rules().enabled(featureSmartEscrow)) { - if (auto const ret = std::visit( - [&](T const&) { - return escrowFinishPreclaimHelper(ctx, dest, amount); - }, - amount.asset().value()); - !isTesSuccess(ret)) - return ret; + if (slep->isFieldPresent(sfFinishFunction)) + { + if (!ctx.tx.isFieldPresent(sfComputationAllowance)) + { + JLOG(ctx.j.debug()) + << "FinishFunction requires ComputationAllowance"; + return tefWASM_FIELD_NOT_INCLUDED; + } + } + else + { + if (ctx.tx.isFieldPresent(sfComputationAllowance)) + { + JLOG(ctx.j.debug()) << "FinishFunction not present, " + "ComputationAllowance present"; + return tefNO_WASM; + } + } + } + if (ctx.view.rules().enabled(featureTokenEscrow)) + { + AccountID const dest = (*slep)[sfDestination]; + STAmount const amount = (*slep)[sfAmount]; + + if (!isXRP(amount)) + { + if (auto const ret = std::visit( + [&](T const&) { + return escrowFinishPreclaimHelper( + ctx, dest, amount); + }, + amount.asset().value()); + !isTesSuccess(ret)) + return ret; + } } } return tesSUCCESS; @@ -956,7 +1103,8 @@ EscrowFinish::doApply() auto const slep = ctx_.view().peek(k); if (!slep) { - if (ctx_.view().rules().enabled(featureTokenEscrow)) + if (ctx_.view().rules().enabled(featureTokenEscrow) || + ctx_.view().rules().enabled(featureSmartEscrow)) return tecINTERNAL; // LCOV_EXCL_LINE return tecNO_TARGET; @@ -974,6 +1122,20 @@ EscrowFinish::doApply() if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter])) return tecNO_PERMISSION; + AccountID const destID = (*slep)[sfDestination]; + auto const sled = ctx_.view().peek(keylet::account(destID)); + if (ctx_.view().rules().enabled(featureSmartEscrow)) + { + // NOTE: Escrow payments cannot be used to fund accounts. + if (!sled) + return tecNO_DST; + + if (auto err = verifyDepositPreauth( + ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal); + !isTesSuccess(err)) + return err; + } + // Check cryptocondition fulfillment { auto const id = ctx_.tx.getTransactionID(); @@ -1023,16 +1185,69 @@ EscrowFinish::doApply() return tecCRYPTOCONDITION_ERROR; } - // NOTE: Escrow payments cannot be used to fund accounts. - AccountID const destID = (*slep)[sfDestination]; - auto const sled = ctx_.view().peek(keylet::account(destID)); - if (!sled) - return tecNO_DST; + if (!ctx_.view().rules().enabled(featureSmartEscrow)) + { + // NOTE: Escrow payments cannot be used to fund accounts. + if (!sled) + return tecNO_DST; - if (auto err = verifyDepositPreauth( - ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal); - !isTesSuccess(err)) - return err; + if (auto err = verifyDepositPreauth( + ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal); + !isTesSuccess(err)) + return err; + } + + // Execute custom release function + if ((*slep)[~sfFinishFunction]) + { + JLOG(j_.trace()) + << "The escrow has a finish function, running WASM code..."; + // WASM execution + auto const wasmStr = slep->getFieldVL(sfFinishFunction); + std::vector wasm(wasmStr.begin(), wasmStr.end()); + + WasmHostFunctionsImpl ledgerDataProvider(ctx_, k); + + if (!ctx_.tx.isFieldPresent(sfComputationAllowance)) + { + // already checked above, this check is just in case + return tecINTERNAL; + } + std::uint32_t allowance = ctx_.tx[sfComputationAllowance]; + auto re = runEscrowWasm( + wasm, ledgerDataProvider, ESCROW_FUNCTION_NAME, {}, allowance); + JLOG(j_.trace()) << "Escrow WASM ran"; + + if (auto const& data = ledgerDataProvider.getData(); data.has_value()) + { + slep->setFieldVL(sfData, makeSlice(*data)); + ctx_.view().update(slep); + } + + if (re.has_value()) + { + auto reValue = re.value().result; + auto reCost = re.value().cost; + JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue) + << ", cost: " << reCost; + + ctx_.setWasmReturnCode(reValue); + + if (reCost < 0 || reCost > std::numeric_limits::max()) + return tecINTERNAL; // LCOV_EXCL_LINE + ctx_.setGasUsed(static_cast(reCost)); + + if (reValue <= 0) + { + return tecWASM_REJECTED; + } + } + else + { + JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error()); + return re.error(); + } + } AccountID const account = (*slep)[sfAccount]; @@ -1110,9 +1325,12 @@ EscrowFinish::doApply() ctx_.view().update(sled); + auto const reserveToSubtract = + calculateAdditionalReserve((*slep)[~sfFinishFunction]); + // Adjust source owner count auto const sle = ctx_.view().peek(keylet::account(account)); - adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); + adjustOwnerCount(ctx_.view(), sle, -1 * reserveToSubtract, ctx_.journal); ctx_.view().update(sle); // Remove escrow from ledger @@ -1312,7 +1530,9 @@ EscrowCancel::doApply() } } - adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); + auto const reserveToSubtract = + calculateAdditionalReserve((*slep)[~sfFinishFunction]); + adjustOwnerCount(ctx_.view(), sle, -1 * reserveToSubtract, ctx_.journal); ctx_.view().update(sle); // Remove escrow from ledger diff --git a/src/xrpld/app/tx/detail/Escrow.h b/src/xrpld/app/tx/detail/Escrow.h index 935fb27cd0..5c4cec5e83 100644 --- a/src/xrpld/app/tx/detail/Escrow.h +++ b/src/xrpld/app/tx/detail/Escrow.h @@ -14,9 +14,15 @@ public: { } + static bool + checkExtraFeatures(PreflightContext const& ctx); + static TxConsequences makeTxConsequences(PreflightContext const& ctx); + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + static NotTEC preflight(PreflightContext const& ctx); diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index dadc5a7d74..59316a904d 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -84,9 +84,10 @@ operator|(Privilege lhs, Privilege rhs) #pragma push_macro("TRANSACTION") #undef TRANSACTION -#define TRANSACTION(tag, value, name, delegatable, amendment, privileges, ...) \ - case tag: { \ - return (privileges) & priv; \ +#define TRANSACTION( \ + tag, value, name, delegatable, amendment, privileges, emitable, ...) \ + case tag: { \ + return (privileges) & priv; \ } bool @@ -1782,7 +1783,8 @@ ValidPseudoAccounts::visitEntry( errors_.emplace_back(error.str()); } } - if (before && before->at(sfSequence) != after->at(sfSequence)) + if (before && before->at(sfSequence) != after->at(sfSequence) && + !after->isFieldPresent(sfContractID)) { errors_.emplace_back("pseudo-account sequence changed"); } diff --git a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp index f3205f6df3..af102847c0 100644 --- a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp @@ -360,60 +360,6 @@ NFTokenAcceptOffer::pay( return tesSUCCESS; } -TER -NFTokenAcceptOffer::transferNFToken( - AccountID const& buyer, - AccountID const& seller, - uint256 const& nftokenID) -{ - auto tokenAndPage = nft::findTokenAndPage(view(), seller, nftokenID); - - if (!tokenAndPage) - return tecINTERNAL; // LCOV_EXCL_LINE - - if (auto const ret = nft::removeToken( - view(), seller, nftokenID, std::move(tokenAndPage->page)); - !isTesSuccess(ret)) - return ret; - - auto const sleBuyer = view().read(keylet::account(buyer)); - if (!sleBuyer) - return tecINTERNAL; // LCOV_EXCL_LINE - - std::uint32_t const buyerOwnerCountBefore = - sleBuyer->getFieldU32(sfOwnerCount); - - auto const insertRet = - nft::insertToken(view(), buyer, std::move(tokenAndPage->token)); - - // if fixNFTokenReserve is enabled, check if the buyer has sufficient - // reserve to own a new object, if their OwnerCount changed. - // - // There was an issue where the buyer accepts a sell offer, the ledger - // didn't check if the buyer has enough reserve, meaning that buyer can get - // NFTs free of reserve. - if (view().rules().enabled(fixNFTokenReserve)) - { - // To check if there is sufficient reserve, we cannot use mPriorBalance - // because NFT is sold for a price. So we must use the balance after - // the deduction of the potential offer price. A small caveat here is - // that the balance has already deducted the transaction fee, meaning - // that the reserve requirement is a few drops higher. - auto const buyerBalance = sleBuyer->getFieldAmount(sfBalance); - - auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount); - if (buyerOwnerCountAfter > buyerOwnerCountBefore) - { - if (auto const reserve = - view().fees().accountReserve(buyerOwnerCountAfter); - buyerBalance < reserve) - return tecINSUFFICIENT_RESERVE; - } - } - - return insertRet; -} - TER NFTokenAcceptOffer::acceptOffer(std::shared_ptr const& offer) { @@ -446,7 +392,7 @@ NFTokenAcceptOffer::acceptOffer(std::shared_ptr const& offer) } // Now transfer the NFT: - return transferNFToken(buyer, seller, nftokenID); + return nft::transferNFToken(ctx_.view(), buyer, seller, nftokenID); } TER @@ -539,7 +485,7 @@ NFTokenAcceptOffer::doApply() } // Now transfer the NFT: - return transferNFToken(buyer, seller, nftokenID); + return nft::transferNFToken(ctx_.view(), buyer, seller, nftokenID); } if (bo) diff --git a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.h b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.h index 83cd71b734..03e7711c5f 100644 --- a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.h +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.h @@ -19,12 +19,6 @@ private: std::shared_ptr const& buy, std::shared_ptr const& sell); - TER - transferNFToken( - AccountID const& buyer, - AccountID const& seller, - uint256 const& nfTokenID); - public: static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; diff --git a/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp b/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp index 00e69ccc9f..78d13f8086 100644 --- a/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp @@ -39,6 +39,10 @@ NFTokenCreateOffer::preflight(PreflightContext const& ctx) TER NFTokenCreateOffer::preclaim(PreclaimContext const& ctx) { + auto const sle = ctx.view.read(keylet::account(ctx.tx[sfAccount])); + auto const balance = sle ? (*sle)[sfBalance] : XRPAmount{0}; + JLOG(ctx.j.error()) << "NFTokenCreateOffer::preclaim.Balance: " << balance; + if (hasExpired(ctx.view, ctx.tx[~sfExpiration])) return tecEXPIRED; diff --git a/src/xrpld/app/tx/detail/NFTokenUtils.cpp b/src/xrpld/app/tx/detail/NFTokenUtils.cpp index c737855840..cab2bf0df4 100644 --- a/src/xrpld/app/tx/detail/NFTokenUtils.cpp +++ b/src/xrpld/app/tx/detail/NFTokenUtils.cpp @@ -1135,5 +1135,60 @@ checkTrustlineDeepFrozen( return tesSUCCESS; } +TER +transferNFToken( + ApplyView& view, + AccountID const& buyer, + AccountID const& seller, + uint256 const& nftokenID) +{ + auto tokenAndPage = nft::findTokenAndPage(view, seller, nftokenID); + + if (!tokenAndPage) + return tecINTERNAL; // LCOV_EXCL_LINE + + if (auto const ret = nft::removeToken( + view, seller, nftokenID, std::move(tokenAndPage->page)); + !isTesSuccess(ret)) + return ret; + + auto const sleBuyer = view.read(keylet::account(buyer)); + if (!sleBuyer) + return tecINTERNAL; // LCOV_EXCL_LINE + + std::uint32_t const buyerOwnerCountBefore = + sleBuyer->getFieldU32(sfOwnerCount); + + auto const insertRet = + nft::insertToken(view, buyer, std::move(tokenAndPage->token)); + + // if fixNFTokenReserve is enabled, check if the buyer has sufficient + // reserve to own a new object, if their OwnerCount changed. + // + // There was an issue where the buyer accepts a sell offer, the ledger + // didn't check if the buyer has enough reserve, meaning that buyer can get + // NFTs free of reserve. + if (view.rules().enabled(fixNFTokenReserve)) + { + // To check if there is sufficient reserve, we cannot use mPriorBalance + // because NFT is sold for a price. So we must use the balance after + // the deduction of the potential offer price. A small caveat here is + // that the balance has already deducted the transaction fee, meaning + // that the reserve requirement is a few drops higher. + auto const buyerBalance = sleBuyer->getFieldAmount(sfBalance); + + auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount); + if (buyerOwnerCountAfter > buyerOwnerCountBefore) + { + if (auto const reserve = + view.fees().accountReserve(buyerOwnerCountAfter); + buyerBalance < reserve) + return tecINSUFFICIENT_RESERVE; + } + } + + return insertRet; +} + } // namespace nft } // namespace xrpl diff --git a/src/xrpld/app/tx/detail/NFTokenUtils.h b/src/xrpld/app/tx/detail/NFTokenUtils.h index 640f12f9d2..fcb91bf977 100644 --- a/src/xrpld/app/tx/detail/NFTokenUtils.h +++ b/src/xrpld/app/tx/detail/NFTokenUtils.h @@ -147,6 +147,13 @@ checkTrustlineDeepFrozen( beast::Journal const j, Issue const& issue); +TER +transferNFToken( + ApplyView& view, + AccountID const& buyer, + AccountID const& seller, + uint256 const& nftokenID); + } // namespace nft } // namespace xrpl diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 7b0cbbbdd4..6cd9587eca 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -603,6 +603,32 @@ Transactor::ticketDelete( return tesSUCCESS; } +std::pair +Transactor::checkInvariants(TER result, XRPAmount fee) +{ + // Check invariants: if `tecINVARIANT_FAILED` is not returned, we can + // proceed to apply the tx + result = ctx_.checkInvariants(result, fee); + + if (result == tecINVARIANT_FAILED) + { + // if invariants checking failed again, reset the context and + // attempt to only claim a fee. + auto const resetResult = reset(fee); + if (!isTesSuccess(resetResult.first)) + result = resetResult.first; + + fee = resetResult.second; + + // Check invariants again to ensure the fee claiming doesn't + // violate invariants. + if (isTesSuccess(result) || isTecClaim(result)) + result = ctx_.checkInvariants(result, fee); + } + + return {result, fee}; +} + // check stuff before you bother to lock the ledger void Transactor::preCompute() @@ -658,19 +684,9 @@ Transactor::checkSign( STObject const& sigObject, beast::Journal const j) { - { - auto const sle = view.read(keylet::account(idAccount)); - - if (view.rules().enabled(featureLendingProtocol) && - isPseudoAccount(sle)) - // Pseudo-accounts can't sign transactions. This check is gated on - // the Lending Protocol amendment because that's the project it was - // added under, and it doesn't justify another amendment - return tefBAD_AUTH; - } - auto const pkSigner = sigObject.getFieldVL(sfSigningPubKey); - // Ignore signature check on batch inner transactions + // Ignore signature check on batch inner transactions (e.g., emitted + // transactions from contracts) if (parentBatchId && view.rules().enabled(featureBatch)) { // Defensive Check: These values are also checked in Batch::preflight @@ -682,6 +698,17 @@ Transactor::checkSign( return tesSUCCESS; } + { + auto const sle = view.read(keylet::account(idAccount)); + + if (view.rules().enabled(featureLendingProtocol) && + isPseudoAccount(sle)) + // Pseudo-accounts can't sign transactions. This check is gated on + // the Lending Protocol amendment because that's the project it was + // added under, and it doesn't justify another amendment + return tefBAD_AUTH; + } + if ((flags & tapDRY_RUN) && pkSigner.empty() && !sigObject.isFieldPresent(sfSigners)) { @@ -1032,6 +1059,22 @@ removeExpiredCredentials( } } +static void +modifyWasmDataFields( + ApplyView& view, + std::vector> const& wasmObjects, + beast::Journal viewJ) +{ + for (auto const& [index, data] : wasmObjects) + { + if (auto const sle = view.peek(keylet::escrow(index))) + { + sle->setFieldVL(sfData, data); + view.update(sle); + } + } +} + static void removeDeletedTrustLines( ApplyView& view, @@ -1190,6 +1233,7 @@ Transactor::operator()() else if ( (result == tecOVERSIZE) || (result == tecKILLED) || (result == tecINCOMPLETE) || (result == tecEXPIRED) || + (result == tecWASM_REJECTED) || (isTecClaimHardFail(result, view().flags()))) { JLOG(j_.trace()) << "reapplying because of " << transToken(result); @@ -1202,13 +1246,16 @@ Transactor::operator()() std::vector removedTrustLines; std::vector expiredNFTokenOffers; std::vector expiredCredentials; + std::vector> modifiedWasmObjects; bool const doOffers = ((result == tecOVERSIZE) || (result == tecKILLED)); bool const doLines = (result == tecINCOMPLETE); bool const doNFTokenOffers = (result == tecEXPIRED); bool const doCredentials = (result == tecEXPIRED); - if (doOffers || doLines || doNFTokenOffers || doCredentials) + bool const doWasmData = (result == tecWASM_REJECTED); + if (doOffers || doLines || doNFTokenOffers || doCredentials || + doWasmData) { ctx_.visit([doOffers, &removedOffers, @@ -1217,7 +1264,9 @@ Transactor::operator()() doNFTokenOffers, &expiredNFTokenOffers, doCredentials, - &expiredCredentials]( + &expiredCredentials, + doWasmData, + &modifiedWasmObjects]( uint256 const& index, bool isDelete, std::shared_ptr const& before, @@ -1252,6 +1301,13 @@ Transactor::operator()() (before->getType() == ltCREDENTIAL)) expiredCredentials.push_back(index); } + + if (doWasmData && before && after && + (before->getType() == ltESCROW)) + { + modifiedWasmObjects.push_back( + std::make_pair(index, after->getFieldVL(sfData))); + } }); } @@ -1281,31 +1337,18 @@ Transactor::operator()() removeExpiredCredentials( view(), expiredCredentials, ctx_.app.journal("View")); + if (result == tecWASM_REJECTED) + modifyWasmDataFields( + view(), modifiedWasmObjects, ctx_.app.journal("View")); + applied = isTecClaim(result); } if (applied) { - // Check invariants: if `tecINVARIANT_FAILED` is not returned, we can - // proceed to apply the tx - result = ctx_.checkInvariants(result, fee); - - if (result == tecINVARIANT_FAILED) - { - // if invariants checking failed again, reset the context and - // attempt to only claim a fee. - auto const resetResult = reset(fee); - if (!isTesSuccess(resetResult.first)) - result = resetResult.first; - - fee = resetResult.second; - - // Check invariants again to ensure the fee claiming doesn't - // violate invariants. - if (isTesSuccess(result) || isTecClaim(result)) - result = ctx_.checkInvariants(result, fee); - } - + auto const invariantsResult = checkInvariants(result, fee); + result = invariantsResult.first; + fee = invariantsResult.second; // We ran through the invariant checker, which can, in some cases, // return a tef error code. Don't apply the transaction in that case. if (!isTecClaim(result) && !isTesSuccess(result)) @@ -1340,6 +1383,78 @@ Transactor::operator()() applied = false; } + if (metadata && ctx_.getEmittedTxns().size() > 0) + { + OpenView emittedTxnsView(batch_view, ctx_.openView()); + auto const parentBatchId = ctx_.tx.getTransactionID(); + + auto applyOneTransaction = [this, &parentBatchId, &emittedTxnsView]( + STTx const& tx) { + OpenView perTxBatchView(batch_view, emittedTxnsView); + + auto const ret = xrpl::apply( + ctx_.app, + perTxBatchView, + parentBatchId, + tx, + tapBATCH, + ctx_.journal); + XRPL_ASSERT( + ret.applied == (isTesSuccess(ret.ter) || isTecClaim(ret.ter)), + "Inner transaction should not be applied"); + + JLOG(ctx_.journal.debug()) << "BatchTrace[" << parentBatchId + << "]: " << tx.getTransactionID() << " " + << (ret.applied ? "applied" : "failure") + << ": " << transToken(ret.ter); + + // If the transaction should be applied push its changes to the + // whole-batch view. + if (ret.applied && (isTesSuccess(ret.ter) || isTecClaim(ret.ter))) + perTxBatchView.apply(emittedTxnsView); + + return ret; + }; + + bool emitResult = true; + auto emittedTxns = ctx_.getEmittedTxns(); + while (!emittedTxns.empty()) + { + auto txn = emittedTxns.front(); + emittedTxns.pop(); + auto const result = applyOneTransaction(*txn->getSTransaction()); + XRPL_ASSERT( + result.applied == + (isTesSuccess(result.ter) || isTecClaim(result.ter)), + "Outer Batch failure, inner transaction should not be applied"); + + if (!isTesSuccess(result.ter)) + emitResult = false; + } + + if (emitResult) + emittedTxnsView.apply(ctx_.openView()); + else + { + // reset context + result = tecWASM_REJECTED; + auto const resetResult = reset(fee); + if (!isTesSuccess(resetResult.first)) + result = resetResult.first; + fee = resetResult.second; + + // InvariantCheck + auto const invariantsResult = checkInvariants(result, fee); + result = invariantsResult.first; + fee = invariantsResult.second; + + // apply + metadata = ctx_.apply(result); + } + } + + ctx_.finalize(); + JLOG(j_.trace()) << (applied ? "applied " : "not applied ") << transToken(result); diff --git a/src/xrpld/app/tx/detail/Transactor.h b/src/xrpld/app/tx/detail/Transactor.h index 7d24df0a3b..2247af73f4 100644 --- a/src/xrpld/app/tx/detail/Transactor.h +++ b/src/xrpld/app/tx/detail/Transactor.h @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -313,6 +314,9 @@ private: std::pair reset(XRPAmount fee); + std::pair + checkInvariants(TER result, XRPAmount fee); + TER consumeSeqProxy(SLE::pointer const& sleAccount); TER diff --git a/src/xrpld/app/tx/detail/VaultCreate.cpp b/src/xrpld/app/tx/detail/VaultCreate.cpp index 893a1108fa..a22341adeb 100644 --- a/src/xrpld/app/tx/detail/VaultCreate.cpp +++ b/src/xrpld/app/tx/detail/VaultCreate.cpp @@ -151,6 +151,8 @@ VaultCreate::doApply() auto pseudoId = pseudo->at(sfAccount); auto asset = tx[sfAsset]; + Issue const issue = asset.get(); + STAmount limit{issue, 0}; if (auto ter = addEmptyHolding(view(), pseudoId, mPriorBalance, asset, j_); !isTesSuccess(ter)) return ter; diff --git a/src/xrpld/app/wasm/ContractContext.h b/src/xrpld/app/wasm/ContractContext.h new file mode 100644 index 0000000000..bb5165adeb --- /dev/null +++ b/src/xrpld/app/wasm/ContractContext.h @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#pragma once + +#include +#include + +#include +#include +#include + +#include + +namespace xrpl { + +class ContractDataMap + : public std::map> +{ +public: + uint32_t modifiedCount = 0; +}; + +class ContractEventMap : public std::map +{ +}; + +struct ParameterValueVec +{ + xrpl::STData const value; +}; + +struct FunctionParameterValueVecWithName +{ + xrpl::Blob const name; + xrpl::STData const value; +}; + +struct ParameterTypeVec +{ + xrpl::STDataType const type; +}; + +std::vector +getParameterValueVec(xrpl::STArray const& functionParameters); + +std::vector +getParameterTypeVec(xrpl::STArray const& functionParameters); + +enum ExitType : uint8_t { + UNSET = 0, + WASM_ERROR = 1, + ROLLBACK = 2, + ACCEPT = 3, +}; + +struct ContractResult +{ + xrpl::uint256 const contractHash; // Hash of the contract code + xrpl::Keylet const contractKeylet; // Keylet for the contract instance + xrpl::Keylet const contractSourceKeylet; // Keylet for the contract source + xrpl::Keylet const + contractAccountKeylet; // Keylet for the contract account + xrpl::AccountID const contractAccount; // AccountID of the contract account + std::uint32_t + nextSequence; // Next sequence number for the contract account + xrpl::AccountID const + otxnAccount; // AccountID for the originating transaction + xrpl::uint256 const otxnId; // ID for the originating transaction + std::string exitReason{""}; + int64_t exitCode{-1}; + ContractDataMap dataMap; + ContractEventMap eventMap; + std::queue> emittedTxns{}; + std::size_t changedDataCount{0}; +}; + +struct ContractContext +{ + xrpl::ApplyContext& applyCtx; + std::vector instanceParameters; + std::vector functionParameters; + std::vector built_txns; + int64_t expected_etxn_count{-1}; // expected emitted transaction count + std::map + nonce_used{}; // nonces used in this execution + uint32_t generation = 0; // generation of the contract being executed + uint64_t burden = 0; // computational burden used + ContractResult result; +}; + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/ContractHostFuncImpl.h b/src/xrpld/app/wasm/ContractHostFuncImpl.h new file mode 100644 index 0000000000..f7caee384e --- /dev/null +++ b/src/xrpld/app/wasm/ContractHostFuncImpl.h @@ -0,0 +1,123 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#pragma once + +#include +#include +#include + +namespace xrpl { +class ContractHostFunctionsImpl : public WasmHostFunctionsImpl +{ + ContractContext& contractCtx; + uint256 const contractId = contractCtx.result.contractKeylet.key; + +public: + // Constructor for contract-specific functionality + ContractHostFunctionsImpl(ContractContext& contractContext) + : WasmHostFunctionsImpl( + contractContext.applyCtx, + contractContext.result.contractKeylet) + , contractCtx(contractContext) + { + } + + // Expected + // getFieldBytesFromSTData(xrpl::STData const& funcParam, std::uint32_t + // stTypeId); + + Expected + instanceParam(std::uint32_t index, std::uint32_t stTypeId) override; + + Expected + functionParam(std::uint32_t index, std::uint32_t stTypeId) override; + + Expected + getDataObjectField(AccountID const& account, std::string_view const& key) + override; + + Expected + getDataNestedObjectField( + AccountID const& account, + std::string_view const& key, + std::string_view const& nestedKey) override; + + Expected + getDataArrayElementField( + AccountID const& account, + size_t index, + std::string_view const& key) override; + + Expected + getDataNestedArrayElementField( + AccountID const& account, + std::string_view const& key, + size_t index, + std::string_view const& nestedKey) override; + + Expected + setDataObjectField( + AccountID const& account, + std::string_view const& key, + STJson::Value const& value) override; + + Expected + setDataNestedObjectField( + AccountID const& account, + std::string_view const& nestedKey, + std::string_view const& key, + STJson::Value const& value) override; + + Expected + setDataArrayElementField( + AccountID const& account, + size_t index, + std::string_view const& key, + STJson::Value const& value) override; + + Expected + setDataNestedArrayElementField( + AccountID const& account, + std::string_view const& key, + size_t index, + std::string_view const& nestedKey, + STJson::Value const& value) override; + + Expected + buildTxn(std::uint16_t const& txType) override; + + Expected + addTxnField( + std::uint32_t const& index, + SField const& field, + Slice const& data) override; + + Expected + emitBuiltTxn(std::uint32_t const& index) override; + + Expected + emitTxn(std::shared_ptr const& stxPtr) override; + + Expected + emitEvent(std::string_view const& eventName, STJson const& eventData) + override; +}; + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/HostFunc.h b/src/xrpld/app/wasm/HostFunc.h new file mode 100644 index 0000000000..b93db8a04c --- /dev/null +++ b/src/xrpld/app/wasm/HostFunc.h @@ -0,0 +1,641 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +enum class HostFunctionError : int32_t { + SUCCESS = 0, + INTERNAL = -1, + FIELD_NOT_FOUND = -2, + BUFFER_TOO_SMALL = -3, + NO_ARRAY = -4, + NOT_LEAF_FIELD = -5, + LOCATOR_MALFORMED = -6, + SLOT_OUT_RANGE = -7, + SLOTS_FULL = -8, + EMPTY_SLOT = -9, + LEDGER_OBJ_NOT_FOUND = -10, + DECODING = -11, + DATA_FIELD_TOO_LARGE = -12, + POINTER_OUT_OF_BOUNDS = -13, + NO_MEM_EXPORTED = -14, + INVALID_PARAMS = -15, + INVALID_ACCOUNT = -16, + INVALID_FIELD = -17, + INDEX_OUT_OF_BOUNDS = -18, + FLOAT_INPUT_MALFORMED = -19, + FLOAT_COMPUTATION_ERROR = -20, + NO_RUNTIME = -21, + OUT_OF_GAS = -22, + SUBMIT_TXN_FAILURE = -23, + INVALID_STATE = -24, +}; + +inline int32_t +HfErrorToInt(HostFunctionError e) +{ + return static_cast(e); +} + +std::string +floatToString(Slice const& data); + +Expected +floatFromIntImpl(int64_t x, int32_t mode); + +Expected +floatFromUintImpl(uint64_t x, int32_t mode); + +Expected +floatSetImpl(int64_t mantissa, int32_t exponent, int32_t mode); + +Expected +floatCompareImpl(Slice const& x, Slice const& y); + +Expected +floatAddImpl(Slice const& x, Slice const& y, int32_t mode); + +Expected +floatSubtractImpl(Slice const& x, Slice const& y, int32_t mode); + +Expected +floatMultiplyImpl(Slice const& x, Slice const& y, int32_t mode); + +Expected +floatDivideImpl(Slice const& x, Slice const& y, int32_t mode); + +Expected +floatRootImpl(Slice const& x, int32_t n, int32_t mode); + +Expected +floatPowerImpl(Slice const& x, int32_t n, int32_t mode); + +Expected +floatLogImpl(Slice const& x, int32_t mode); + +struct HostFunctions +{ + beast::Journal j_; + + HostFunctions( + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : j_(j) + { + } + + // LCOV_EXCL_START + virtual void + setRT(void const*) + { + } + + virtual void const* + getRT() const + { + return nullptr; + } + + std::int64_t + getGas() + { + return -1; + } + + void + setGas(std::int64_t) + { + return; + } + + beast::Journal + getJournal() + { + return j_; + } + + virtual Expected + getLedgerSqn() + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getParentLedgerTime() + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getParentLedgerHash() + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getBaseFee() + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + isAmendmentEnabled(uint256 const& amendmentId) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + isAmendmentEnabled(std::string_view const& amendmentName) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getTxField(SField const& fname) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getCurrentLedgerObjField(SField const& fname) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getLedgerObjField(int32_t cacheIdx, SField const& fname) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getTxNestedField(Slice const& locator) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getCurrentLedgerObjNestedField(Slice const& locator) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getTxArrayLen(SField const& fname) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getCurrentLedgerObjArrayLen(SField const& fname) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getTxNestedArrayLen(Slice const& locator) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getCurrentLedgerObjNestedArrayLen(Slice const& locator) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + updateData(Slice const& data) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + checkSignature( + Slice const& message, + Slice const& signature, + Slice const& pubkey) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + computeSha512HalfHash(Slice const& data) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + accountKeylet(AccountID const& account) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + ammKeylet(Asset const& issue1, Asset const& issue2) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + checkKeylet(AccountID const& account, std::uint32_t seq) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + credentialKeylet( + AccountID const& subject, + AccountID const& issuer, + Slice const& credentialType) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + didKeylet(AccountID const& account) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + delegateKeylet(AccountID const& account, AccountID const& authorize) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + depositPreauthKeylet(AccountID const& account, AccountID const& authorize) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + escrowKeylet(AccountID const& account, std::uint32_t seq) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + lineKeylet( + AccountID const& account1, + AccountID const& account2, + Currency const& currency) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + mptokenKeylet(MPTID const& mptid, AccountID const& holder) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + nftOfferKeylet(AccountID const& account, std::uint32_t seq) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + offerKeylet(AccountID const& account, std::uint32_t seq) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + oracleKeylet(AccountID const& account, std::uint32_t docId) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + paychanKeylet( + AccountID const& account, + AccountID const& destination, + std::uint32_t seq) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + signersKeylet(AccountID const& account) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + ticketKeylet(AccountID const& account, std::uint32_t seq) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + vaultKeylet(AccountID const& account, std::uint32_t seq) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getNFT(AccountID const& account, uint256 const& nftId) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getNFTIssuer(uint256 const& nftId) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getNFTTaxon(uint256 const& nftId) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getNFTFlags(uint256 const& nftId) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getNFTTransferFee(uint256 const& nftId) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getNFTSerial(uint256 const& nftId) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + trace(std::string_view const& msg, Slice const& data, bool asHex) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + traceNum(std::string_view const& msg, int64_t data) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + traceAccount(std::string_view const& msg, AccountID const& account) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + traceFloat(std::string_view const& msg, Slice const& data) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + traceAmount(std::string_view const& msg, STAmount const& amount) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatFromInt(int64_t x, int32_t mode) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatFromUint(uint64_t x, int32_t mode) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatSet(int64_t mantissa, int32_t exponent, int32_t mode) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatCompare(Slice const& x, Slice const& y) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatAdd(Slice const& x, Slice const& y, int32_t mode) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatSubtract(Slice const& x, Slice const& y, int32_t mode) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatMultiply(Slice const& x, Slice const& y, int32_t mode) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatDivide(Slice const& x, Slice const& y, int32_t mode) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatRoot(Slice const& x, int32_t n, int32_t mode) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatPower(Slice const& x, int32_t n, int32_t mode) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatLog(Slice const& x, int32_t mode) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + instanceParam(std::uint32_t index, std::uint32_t stTypeId) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + functionParam(std::uint32_t index, std::uint32_t stTypeId) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getDataObjectField(AccountID const& account, std::string_view const& key) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getDataNestedObjectField( + AccountID const& account, + std::string_view const& key, + std::string_view const& nestedKey) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getDataArrayElementField( + AccountID const& account, + size_t index, + std::string_view const& key) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getDataNestedArrayElementField( + AccountID const& account, + std::string_view const& key, + size_t index, + std::string_view const& nestedKey) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + setDataObjectField( + AccountID const& account, + std::string_view const& keyName, + STJson::Value const& value) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + setDataNestedObjectField( + AccountID const& account, + std::string_view const& nestedKey, + std::string_view const& key, + STJson::Value const& value) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + setDataArrayElementField( + AccountID const& account, + size_t index, + std::string_view const& key, + STJson::Value const& value) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + setDataNestedArrayElementField( + AccountID const& account, + std::string_view const& key, + size_t index, + std::string_view const& nestedKey, + STJson::Value const& value) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + buildTxn(std::uint16_t const& txType) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + addTxnField( + std::uint32_t const& index, + SField const& field, + Slice const& data) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + emitBuiltTxn(std::uint32_t const& index) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + emitTxn(std::shared_ptr const& stxPtr) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + emitEvent(std::string_view const& eventName, STJson const& eventData) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual ~HostFunctions() = default; + // LCOV_EXCL_STOP +}; + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/HostFuncImpl.h b/src/xrpld/app/wasm/HostFuncImpl.h new file mode 100644 index 0000000000..fccea11252 --- /dev/null +++ b/src/xrpld/app/wasm/HostFuncImpl.h @@ -0,0 +1,274 @@ +#pragma once + +#include +#include + +namespace xrpl { +class WasmHostFunctionsImpl : public HostFunctions +{ + ApplyContext& ctx; + Keylet leKey; + std::shared_ptr currentLedgerObj = nullptr; + bool isLedgerObjCached = false; + + static int constexpr MAX_CACHE = 256; + std::array, MAX_CACHE> cache; + std::optional data_; + + void const* rt_ = nullptr; + + Expected, HostFunctionError> + getCurrentLedgerObj() + { + if (!isLedgerObjCached) + { + isLedgerObjCached = true; + currentLedgerObj = ctx.view().read(leKey); + } + if (currentLedgerObj) + return currentLedgerObj; + return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + } + + Expected + normalizeCacheIndex(int32_t cacheIdx) + { + --cacheIdx; + if (cacheIdx < 0 || cacheIdx >= MAX_CACHE) + return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + if (!cache[cacheIdx]) + return Unexpected(HostFunctionError::EMPTY_SLOT); + return cacheIdx; + } + +public: + WasmHostFunctionsImpl(ApplyContext& ct, Keylet const& leKey) + : HostFunctions(ct.journal), ctx(ct), leKey(leKey) + { + } + + virtual void + setRT(void const* rt) override + { + rt_ = rt; + } + + virtual void const* + getRT() const override + { + return rt_; + } + + std::optional const& + getData() const + { + return data_; + } + + Expected + getLedgerSqn() override; + + Expected + getParentLedgerTime() override; + + Expected + getParentLedgerHash() override; + + Expected + getBaseFee() override; + + Expected + isAmendmentEnabled(uint256 const& amendmentId) override; + + Expected + isAmendmentEnabled(std::string_view const& amendmentName) override; + + Expected + cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override; + + Expected + getTxField(SField const& fname) override; + + Expected + getCurrentLedgerObjField(SField const& fname) override; + + Expected + getLedgerObjField(int32_t cacheIdx, SField const& fname) override; + + Expected + getTxNestedField(Slice const& locator) override; + + Expected + getCurrentLedgerObjNestedField(Slice const& locator) override; + + Expected + getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) override; + + Expected + getTxArrayLen(SField const& fname) override; + + Expected + getCurrentLedgerObjArrayLen(SField const& fname) override; + + Expected + getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) override; + + Expected + getTxNestedArrayLen(Slice const& locator) override; + + Expected + getCurrentLedgerObjNestedArrayLen(Slice const& locator) override; + + Expected + getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) override; + + Expected + updateData(Slice const& data) override; + + Expected + checkSignature( + Slice const& message, + Slice const& signature, + Slice const& pubkey) override; + + Expected + computeSha512HalfHash(Slice const& data) override; + + Expected + accountKeylet(AccountID const& account) override; + + Expected + ammKeylet(Asset const& issue1, Asset const& issue2) override; + + Expected + checkKeylet(AccountID const& account, std::uint32_t seq) override; + + Expected + credentialKeylet( + AccountID const& subject, + AccountID const& issuer, + Slice const& credentialType) override; + + Expected + didKeylet(AccountID const& account) override; + + Expected + delegateKeylet(AccountID const& account, AccountID const& authorize) + override; + + Expected + depositPreauthKeylet(AccountID const& account, AccountID const& authorize) + override; + + Expected + escrowKeylet(AccountID const& account, std::uint32_t seq) override; + + Expected + lineKeylet( + AccountID const& account1, + AccountID const& account2, + Currency const& currency) override; + + Expected + mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) override; + + Expected + mptokenKeylet(MPTID const& mptid, AccountID const& holder) override; + + Expected + nftOfferKeylet(AccountID const& account, std::uint32_t seq) override; + + Expected + offerKeylet(AccountID const& account, std::uint32_t seq) override; + + Expected + oracleKeylet(AccountID const& account, std::uint32_t docId) override; + + Expected + paychanKeylet( + AccountID const& account, + AccountID const& destination, + std::uint32_t seq) override; + + Expected + permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) + override; + + Expected + signersKeylet(AccountID const& account) override; + + Expected + ticketKeylet(AccountID const& account, std::uint32_t seq) override; + + Expected + vaultKeylet(AccountID const& account, std::uint32_t seq) override; + + Expected + getNFT(AccountID const& account, uint256 const& nftId) override; + + Expected + getNFTIssuer(uint256 const& nftId) override; + + Expected + getNFTTaxon(uint256 const& nftId) override; + + Expected + getNFTFlags(uint256 const& nftId) override; + + Expected + getNFTTransferFee(uint256 const& nftId) override; + + Expected + getNFTSerial(uint256 const& nftId) override; + + Expected + trace(std::string_view const& msg, Slice const& data, bool asHex) override; + + Expected + traceNum(std::string_view const& msg, int64_t data) override; + + Expected + traceAccount(std::string_view const& msg, AccountID const& account) + override; + + Expected + traceFloat(std::string_view const& msg, Slice const& data) override; + + Expected + traceAmount(std::string_view const& msg, STAmount const& amount) override; + + Expected + floatFromInt(int64_t x, int32_t mode) override; + + Expected + floatFromUint(uint64_t x, int32_t mode) override; + + Expected + floatSet(int64_t mantissa, int32_t exponent, int32_t mode) override; + + Expected + floatCompare(Slice const& x, Slice const& y) override; + + Expected + floatAdd(Slice const& x, Slice const& y, int32_t mode) override; + + Expected + floatSubtract(Slice const& x, Slice const& y, int32_t mode) override; + + Expected + floatMultiply(Slice const& x, Slice const& y, int32_t mode) override; + + Expected + floatDivide(Slice const& x, Slice const& y, int32_t mode) override; + + Expected + floatRoot(Slice const& x, int32_t n, int32_t mode) override; + + Expected + floatPower(Slice const& x, int32_t n, int32_t mode) override; + + Expected + floatLog(Slice const& x, int32_t mode) override; +}; + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/HostFuncWrapper.h b/src/xrpld/app/wasm/HostFuncWrapper.h new file mode 100644 index 0000000000..cd43cfc30a --- /dev/null +++ b/src/xrpld/app/wasm/HostFuncWrapper.h @@ -0,0 +1,684 @@ +#pragma once + +#include + +namespace xrpl { + +using getLedgerSqn_proto = int32_t(); +wasm_trap_t* +getLedgerSqn_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getParentLedgerTime_proto = int32_t(); +wasm_trap_t* +getParentLedgerTime_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getParentLedgerHash_proto = int32_t(uint8_t*, int32_t); +wasm_trap_t* +getParentLedgerHash_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getBaseFee_proto = int32_t(); +wasm_trap_t* +getBaseFee_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using isAmendmentEnabled_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +isAmendmentEnabled_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using cacheLedgerObj_proto = int32_t(uint8_t const*, int32_t, int32_t); +wasm_trap_t* +cacheLedgerObj_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getTxField_proto = int32_t(int32_t, uint8_t*, int32_t); +wasm_trap_t* +getTxField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getCurrentLedgerObjField_proto = int32_t(int32_t, uint8_t*, int32_t); +wasm_trap_t* +getCurrentLedgerObjField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getLedgerObjField_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getLedgerObjField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getTxNestedField_proto = + int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getTxNestedField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getCurrentLedgerObjNestedField_proto = + int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getCurrentLedgerObjNestedField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getLedgerObjNestedField_proto = + int32_t(int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getLedgerObjNestedField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getTxArrayLen_proto = int32_t(int32_t); +wasm_trap_t* +getTxArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getCurrentLedgerObjArrayLen_proto = int32_t(int32_t); +wasm_trap_t* +getCurrentLedgerObjArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getLedgerObjArrayLen_proto = int32_t(int32_t, int32_t); +wasm_trap_t* +getLedgerObjArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getTxNestedArrayLen_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +getTxNestedArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getCurrentLedgerObjNestedArrayLen_proto = + int32_t(uint8_t const*, int32_t); +wasm_trap_t* +getCurrentLedgerObjNestedArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getLedgerObjNestedArrayLen_proto = + int32_t(int32_t, uint8_t const*, int32_t); +wasm_trap_t* +getLedgerObjNestedArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using updateData_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +updateData_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using checkSignature_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t const*, + int32_t); +wasm_trap_t* +checkSignature_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using computeSha512HalfHash_proto = + int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +computeSha512HalfHash_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using accountKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +accountKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using ammKeylet_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +ammKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using checkKeylet_proto = + int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +checkKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using credentialKeylet_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +credentialKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using delegateKeylet_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +delegateKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using depositPreauthKeylet_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +depositPreauthKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using didKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +didKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using escrowKeylet_proto = + int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +escrowKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using lineKeylet_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +lineKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using mptIssuanceKeylet_proto = + int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +mptIssuanceKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using mptokenKeylet_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +mptokenKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using nftOfferKeylet_proto = + int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +nftOfferKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using offerKeylet_proto = + int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +offerKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using oracleKeylet_proto = + int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +oracleKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using paychanKeylet_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +paychanKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using permissionedDomainKeylet_proto = + int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +permissionedDomainKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using signersKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +signersKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using ticketKeylet_proto = + int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +ticketKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using vaultKeylet_proto = + int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +vaultKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getNFT_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +getNFT_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getNFTIssuer_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getNFTIssuer_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getNFTTaxon_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getNFTTaxon_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getNFTFlags_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +getNFTFlags_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getNFTTransferFee_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +getNFTTransferFee_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getNFTSerial_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getNFTSerial_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using trace_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, int32_t); +wasm_trap_t* +trace_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using traceNum_proto = int32_t(uint8_t const*, int32_t, int64_t); +wasm_trap_t* +traceNum_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using traceAccount_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* +traceAccount_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using traceFloat_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* +traceFloat_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using traceAmount_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* +traceAmount_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using floatFromInt_proto = int32_t(int64_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* +floatFromInt_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using floatFromUint_proto = + int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* +floatFromUint_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using floatSet_proto = int32_t(int32_t, int64_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* +floatSet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using floatCompare_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* +floatCompare_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using floatAdd_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t, + int32_t); +wasm_trap_t* +floatAdd_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using floatSubtract_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t, + int32_t); +wasm_trap_t* +floatSubtract_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using floatMultiply_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t, + int32_t); +wasm_trap_t* +floatMultiply_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using floatDivide_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t, + int32_t); +wasm_trap_t* +floatDivide_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using floatRoot_proto = + int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* +floatRoot_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using floatPower_proto = + int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* +floatPower_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using floatLog_proto = + int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* +floatLog_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using instanceParam_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +instanceParam_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using functionParam_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +functionParam_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getDataObjectField_proto = + int32_t(uint8_t*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getDataObjectField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getDataNestedObjectField_proto = int32_t( + uint8_t*, + int32_t, + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +getDataNestedObjectField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using setDataObjectField_proto = + int32_t(uint8_t*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +setDataObjectField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using setDataNestedObjectField_proto = int32_t( + uint8_t*, + int32_t, + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +setDataNestedObjectField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getDataArrayElementField_proto = int32_t( + uint8_t*, + int32_t, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +getDataArrayElementField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getDataNestedArrayElementField_proto = int32_t( + uint8_t*, + int32_t, + uint8_t const*, + int32_t, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +getDataNestedArrayElementField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using setDataArrayElementField_proto = int32_t( + uint8_t*, + int32_t, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +setDataArrayElementField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using setDataNestedArrayElementField_proto = int32_t( + uint8_t*, + int32_t, + uint8_t const*, + int32_t, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +setDataNestedArrayElementField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using buildTxn_proto = int32_t(int32_t); +wasm_trap_t* +buildTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using addTxnField_proto = int32_t(int32_t, int32_t, uint8_t const*, int32_t); +wasm_trap_t* +addTxnField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using emitBuiltTxn_proto = int32_t(int32_t); +wasm_trap_t* +emitBuiltTxn_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using emitTxn_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +emitTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using emitEvent_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* +emitEvent_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/ParamsHelper.h b/src/xrpld/app/wasm/ParamsHelper.h new file mode 100644 index 0000000000..1b4fee4a0b --- /dev/null +++ b/src/xrpld/app/wasm/ParamsHelper.h @@ -0,0 +1,251 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace bft = boost::function_types; + +namespace xrpl { + +using Bytes = std::vector; +using Hash = xrpl::uint256; + +struct wmem +{ + std::uint8_t* p = nullptr; + std::size_t s = 0; +}; + +template +struct WasmResult +{ + T result; + int64_t cost; +}; +typedef WasmResult EscrowResult; +typedef WasmResult WasmRunResult; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +enum WasmTypes { WT_I32, WT_I64, WT_U8V }; + +struct WasmImportFunc +{ + std::string name; + std::optional result; + std::vector params; + // void* udata = nullptr; + // wasm_func_callback_with_env_t + void* wrap = nullptr; + uint32_t gas = 0; +}; + +typedef std::pair WasmUserData; +typedef std::vector ImportVec; + +#define WASM_IMPORT_FUNC(v, f, ...) \ + WasmImpFunc( \ + v, #f, reinterpret_cast(&f##_wrap), ##__VA_ARGS__) + +#define WASM_IMPORT_FUNC2(v, f, n, ...) \ + WasmImpFunc( \ + v, n, reinterpret_cast(&f##_wrap), ##__VA_ARGS__) + +template +void +WasmImpArgs(WasmImportFunc& e) +{ + if constexpr (N < C) + { + using at = typename boost::mpl::at_c::type; + if constexpr (std::is_pointer_v) + e.params.push_back(WT_I32); + else if constexpr (std::is_same_v) + e.params.push_back(WT_I32); + else if constexpr (std::is_same_v) + e.params.push_back(WT_I64); + else + static_assert(std::is_pointer_v, "Unsupported argument type"); + + return WasmImpArgs(e); + } + return; +} + +template +void +WasmImpRet(WasmImportFunc& e) +{ + if constexpr (std::is_pointer_v) + e.result = WT_I32; + else if constexpr (std::is_same_v) + e.result = WT_I32; + else if constexpr (std::is_same_v) + e.result = WT_I64; + else if constexpr (std::is_void_v) + e.result.reset(); +#if (defined(__GNUC__) && (__GNUC__ >= 14)) || \ + ((defined(__clang_major__)) && (__clang_major__ >= 18)) + else + static_assert(false, "Unsupported return type"); +#endif +} + +template +void +WasmImpFuncHelper(WasmImportFunc& e) +{ + using rt = typename bft::result_type::type; + using pt = typename bft::parameter_types::type; + // typename boost::mpl::at_c::type + + WasmImpRet(e); + WasmImpArgs<0, bft::function_arity::value, pt>(e); + // WasmImpWrap(e, std::forward(f)); +} + +template +void +WasmImpFunc( + ImportVec& v, + std::string_view imp_name, + void* f_wrap, + void* data = nullptr, + uint32_t gas = 0) +{ + WasmImportFunc e; + e.name = imp_name; + e.wrap = f_wrap; + e.gas = gas; + WasmImpFuncHelper(e); + v.push_back(std::make_pair(data, std::move(e))); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +struct WasmParamVec +{ + std::uint8_t const* d = nullptr; + std::int32_t sz = 0; +}; + +struct WasmParam +{ + WasmTypes type = WT_I32; + union + { + std::int32_t i32; + std::int64_t i64 = 0; + float f32; + double f64; + WasmParamVec u8v; + } of; +}; + +template +inline void +wasmParamsHlp(std::vector& v, std::int32_t p, Types&&... args) +{ + v.push_back({.type = WT_I32, .of = {.i32 = p}}); + wasmParamsHlp(v, std::forward(args)...); +} + +template +inline void +wasmParamsHlp(std::vector& v, std::int64_t p, Types&&... args) +{ + v.push_back({.type = WT_I64, .of = {.i64 = p}}); + wasmParamsHlp(v, std::forward(args)...); +} + +// We are not supporting float/double for now +// Leaving this code here so that it is easier to add later if needed +// template +// inline void +// wasmParamsHlp(std::vector& v, float p, Types&&... args) +// { +// v.push_back({.type = WT_F32, .of = {.f32 = p}}); +// wasmParamsHlp(v, std::forward(args)...); +// } + +// template +// inline void +// wasmParamsHlp(std::vector& v, double p, Types&&... args) +// { +// v.push_back({.type = WT_F64, .of = {.f64 = p}}); +// wasmParamsHlp(v, std::forward(args)...); +// } + +template +inline void +wasmParamsHlp( + std::vector& v, + std::uint8_t const* dt, + std::int32_t sz, + Types&&... args) +{ + v.push_back({.type = WT_U8V, .of = {.u8v = {.d = dt, .sz = sz}}}); + wasmParamsHlp(v, std::forward(args)...); +} + +template +inline void +wasmParamsHlp(std::vector& v, Bytes const& p, Types&&... args) +{ + wasmParamsHlp( + v, + p.data(), + static_cast(p.size()), + std::forward(args)...); +} + +template +inline void +wasmParamsHlp( + std::vector& v, + std::string_view const& p, + Types&&... args) +{ + wasmParamsHlp( + v, + reinterpret_cast(p.data()), + static_cast(p.size()), + std::forward(args)...); +} + +template +inline void +wasmParamsHlp(std::vector& v, std::string const& p, Types&&... args) +{ + wasmParamsHlp( + v, + reinterpret_cast(p.c_str()), + static_cast(p.size()), + std::forward(args)...); +} + +inline void +wasmParamsHlp(std::vector& v) +{ + return; +} + +template +inline std::vector +wasmParams(Types&&... args) +{ + std::vector v; + v.reserve(sizeof...(args)); + wasmParamsHlp(v, std::forward(args)...); + return v; +} + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/WasmVM.h b/src/xrpld/app/wasm/WasmVM.h new file mode 100644 index 0000000000..a94d30b734 --- /dev/null +++ b/src/xrpld/app/wasm/WasmVM.h @@ -0,0 +1,107 @@ +#pragma once + +#include + +#include + +namespace xrpl { + +static std::string_view const W_ENV = "env"; +static std::string_view const W_HOST_LIB = "host_lib"; +static std::string_view const W_MEM = "memory"; +static std::string_view const W_STORE = "store"; +static std::string_view const W_LOAD = "load"; +static std::string_view const W_SIZE = "size"; +static std::string_view const W_ALLOC = "allocate"; +static std::string_view const W_DEALLOC = "deallocate"; +static std::string_view const W_PROC_EXIT = "proc_exit"; + +static std::string_view const ESCROW_FUNCTION_NAME = "finish"; + +uint32_t const MAX_PAGES = 128; // 8MB = 64KB*128 + +class WasmiEngine; + +class WasmEngine +{ + std::unique_ptr const impl; + + WasmEngine(); + + WasmEngine(WasmEngine const&) = delete; + WasmEngine(WasmEngine&&) = delete; + WasmEngine& + operator=(WasmEngine const&) = delete; + WasmEngine& + operator=(WasmEngine&&) = delete; + +public: + ~WasmEngine() = default; + + static WasmEngine& + instance(); + + Expected, TER> + run(Bytes const& wasmCode, + std::string_view funcName = {}, + std::vector const& params = {}, + ImportVec const& imports = {}, + HostFunctions* hfs = nullptr, + int64_t gasLimit = -1, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}); + + NotTEC + check( + Bytes const& wasmCode, + std::string_view funcName, + std::vector const& params = {}, + ImportVec const& imports = {}, + HostFunctions* hfs = nullptr, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}); + + // Host functions helper functionality + void* + newTrap(std::string const& txt = std::string()); + + beast::Journal + getJournal() const; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +ImportVec +createWasmImport(HostFunctions& hfs); + +Expected +runEscrowWasm( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName = ESCROW_FUNCTION_NAME, + std::vector const& params = {}, + int64_t gasLimit = -1); + +NotTEC +preflightEscrowWasm( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName = ESCROW_FUNCTION_NAME, + std::vector const& params = {}); + +// Expected +// runContractWasm( +// Bytes const& wasmCode, +// std::string_view funcName, +// std::vector const& params = {}, +// HostFunctions* hfs = nullptr, +// int64_t gasLimit = -1, +// beast::Journal j = beast::Journal(beast::Journal::getNullSink())); + +// NotTEC +// preflightContractWasm( +// Bytes const& wasmCode, +// std::string_view funcName, +// std::vector const& params = {}, +// HostFunctions* hfs = nullptr, +// beast::Journal j = beast::Journal(beast::Journal::getNullSink())); + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/WasmiVM.h b/src/xrpld/app/wasm/WasmiVM.h new file mode 100644 index 0000000000..06e18ef29e --- /dev/null +++ b/src/xrpld/app/wasm/WasmiVM.h @@ -0,0 +1,348 @@ +#pragma once + +#include + +#include +#include + +namespace xrpl { + +template +struct WasmVec +{ + T vec_; + + WasmVec(size_t s = 0) : vec_ WASM_EMPTY_VEC + { + if (s > 0) + Create(&vec_, s); // zeroes memory + } + + ~WasmVec() + { + clear(); + } + + WasmVec(WasmVec const&) = delete; + WasmVec& + operator=(WasmVec const&) = delete; + + WasmVec(WasmVec&& other) noexcept : vec_ WASM_EMPTY_VEC + { + *this = std::move(other); + } + + WasmVec& + operator=(WasmVec&& other) noexcept + { + if (this != &other) + { + clear(); + vec_ = other.vec_; + other.vec_ = WASM_EMPTY_VEC; + } + return *this; + } + + void + clear() + { + Destroy(&vec_); // call destructor for every elements too + vec_ = WASM_EMPTY_VEC; + } + + T + release() + { + T result = vec_; + vec_ = WASM_EMPTY_VEC; + return result; + } +}; + +using WasmValtypeVec = WasmVec< + wasm_valtype_vec_t, + &wasm_valtype_vec_new_uninitialized, + &wasm_valtype_vec_delete>; +using WasmValVec = WasmVec< + wasm_val_vec_t, + &wasm_val_vec_new_uninitialized, + &wasm_val_vec_delete>; +using WasmExternVec = WasmVec< + wasm_extern_vec_t, + &wasm_extern_vec_new_uninitialized, + &wasm_extern_vec_delete>; +using WasmExporttypeVec = WasmVec< + wasm_exporttype_vec_t, + &wasm_exporttype_vec_new_uninitialized, + &wasm_exporttype_vec_delete>; +using WasmImporttypeVec = WasmVec< + wasm_importtype_vec_t, + &wasm_importtype_vec_new_uninitialized, + &wasm_importtype_vec_delete>; + +struct WasmiResult +{ + WasmValVec r; + bool f; // failure flag + + WasmiResult(unsigned N = 0) : r(N), f(false) + { + } + + ~WasmiResult() = default; + WasmiResult(WasmiResult&& o) = default; + WasmiResult& + operator=(WasmiResult&& o) = default; +}; + +using ModulePtr = std::unique_ptr; +using InstancePtr = + std::unique_ptr; +using EnginePtr = std::unique_ptr; +using StorePtr = std::unique_ptr; + +using FuncInfo = std::pair; + +struct InstanceWrapper +{ + wasm_store_t* store_ = nullptr; + WasmExternVec exports_; + InstancePtr instance_; + beast::Journal j_ = beast::Journal(beast::Journal::getNullSink()); + +private: + static InstancePtr + init( + StorePtr& s, + ModulePtr& m, + WasmExternVec& expt, + WasmExternVec const& imports, + beast::Journal j); + +public: + InstanceWrapper(); + + InstanceWrapper(InstanceWrapper&& o); + + InstanceWrapper& + operator=(InstanceWrapper&& o); + + InstanceWrapper( + StorePtr& s, + ModulePtr& m, + WasmExternVec const& imports, + beast::Journal j); + + ~InstanceWrapper() = default; + + operator bool() const; + + FuncInfo + getFunc(std::string_view funcName, WasmExporttypeVec const& exportTypes) + const; + + wmem + getMem() const; + + std::int64_t + getGas() const; + + std::int64_t setGas(std::int64_t) const; +}; + +struct ModuleWrapper +{ + ModulePtr module_; + InstanceWrapper instanceWrap_; + WasmExporttypeVec exportTypes_; + beast::Journal j_ = beast::Journal(beast::Journal::getNullSink()); + +private: + static ModulePtr + init(StorePtr& s, Bytes const& wasmBin, beast::Journal j); + +public: + ModuleWrapper(); + ModuleWrapper(ModuleWrapper&& o); + ModuleWrapper& + operator=(ModuleWrapper&& o); + ModuleWrapper( + StorePtr& s, + Bytes const& wasmBin, + bool instantiate, + ImportVec const& imports, + beast::Journal j); + ~ModuleWrapper() = default; + + operator bool() const; + + FuncInfo + getFunc(std::string_view funcName) const; + wmem + getMem() const; + + InstanceWrapper const& + getInstance(int i = 0) const; + + int + addInstance(StorePtr& s, WasmExternVec const& imports); + + std::int64_t + getGas(); + +private: + WasmExternVec + buildImports(StorePtr& s, ImportVec const& imports); +}; + +class WasmiEngine +{ + EnginePtr engine_; + StorePtr store_; + std::unique_ptr moduleWrap_; + beast::Journal j_ = beast::Journal(beast::Journal::getNullSink()); + + std::mutex m_; // 1 instance mutex + +public: + WasmiEngine(); + ~WasmiEngine() = default; + + static EnginePtr + init(); + + Expected, TER> + run(Bytes const& wasmCode, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + HostFunctions* hfs, + int64_t gas, + beast::Journal j); + + NotTEC + check( + Bytes const& wasmCode, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + HostFunctions* hfs, + beast::Journal j); + + std::int64_t + getGas(); + + // Host functions helper functionality + wasm_trap_t* + newTrap(std::string const& msg); + + beast::Journal + getJournal() const; + +private: + InstanceWrapper const& + getRT(int m = 0, int i = 0); + + wmem + getMem() const; + + int32_t + allocate(int32_t size); + + Expected, TER> + runHlp( + Bytes const& wasmCode, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + HostFunctions* hfs, + int64_t gas); + + NotTEC + checkHlp( + Bytes const& wasmCode, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports); + + int + addModule( + Bytes const& wasmCode, + bool instantiate, + int64_t gas, + ImportVec const& imports); + void + clearModules(); + + // int addInstance(); + + int32_t + runFunc(std::string_view const funcName, int32_t p); + + int32_t + makeModule(Bytes const& wasmCode, WasmExternVec const& imports = {}); + + FuncInfo + getFunc(std::string_view funcName); + + std::vector + convertParams(std::vector const& params); + + static int + compareParamTypes( + wasm_valtype_vec_t const* ftp, + std::vector const& p); + + static void + add_param(std::vector& in, int32_t p); + static void + add_param(std::vector& in, int64_t p); + + template + inline WasmiResult + call(std::string_view func, Types&&... args); + + template + inline WasmiResult + call(FuncInfo const& f, Types&&... args); + + template + inline WasmiResult + call(FuncInfo const& f, std::vector& in); + + template + inline WasmiResult + call( + FuncInfo const& f, + std::vector& in, + std::int32_t p, + Types&&... args); + + template + inline WasmiResult + call( + FuncInfo const& f, + std::vector& in, + std::int64_t p, + Types&&... args); + + template + inline WasmiResult + call( + FuncInfo const& f, + std::vector& in, + uint8_t const* d, + std::size_t sz, + Types&&... args); + + template + inline WasmiResult + call( + FuncInfo const& f, + std::vector& in, + Bytes const& p, + Types&&... args); +}; + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/detail/ContractContext.cpp b/src/xrpld/app/wasm/detail/ContractContext.cpp new file mode 100644 index 0000000000..393b5a1c4c --- /dev/null +++ b/src/xrpld/app/wasm/detail/ContractContext.cpp @@ -0,0 +1,53 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +#include +#include +#include + +namespace xrpl { + +std::vector +getParameterValueVec(STArray const& functionParameters) +{ + std::vector param_map; + for (auto const& param : functionParameters) + { + auto const& value = param.getFieldData(sfParameterValue); + param_map.emplace_back(value); + } + return param_map; +} + +std::vector +getParameterTypeVec(STArray const& functionParameters) +{ + std::vector param_map; + for (auto const& param : functionParameters) + { + auto const& type = param.getFieldDataType(sfParameterType); + param_map.emplace_back(type); + } + return param_map; +} + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/detail/ContractHostFuncImpl.cpp b/src/xrpld/app/wasm/detail/ContractHostFuncImpl.cpp new file mode 100644 index 0000000000..f51dab8679 --- /dev/null +++ b/src/xrpld/app/wasm/detail/ContractHostFuncImpl.cpp @@ -0,0 +1,1116 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +Expected +getFieldBytesFromSTData(xrpl::STData const& funcParam, std::uint32_t stTypeId) +{ + switch (stTypeId) + { + case STI_UINT8: { + if (funcParam.getInnerSType() != STI_UINT8) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint8_t data = funcParam.getFieldU8(); + return Bytes{data}; + } + case STI_UINT16: { + if (funcParam.getInnerSType() != STI_UINT16) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint16_t data = funcParam.getFieldU16(); + return Bytes{ + static_cast(data & 0xFF), + static_cast((data >> 8) & 0xFF)}; + } + case STI_UINT32: { + if (funcParam.getInnerSType() != STI_UINT32) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint32_t data = funcParam.getFieldU32(); + return Bytes{ + static_cast(data & 0xFF), + static_cast((data >> 8) & 0xFF), + static_cast((data >> 16) & 0xFF), + static_cast((data >> 24) & 0xFF)}; + } + case STI_UINT64: { + if (funcParam.getInnerSType() != STI_UINT64) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint64_t data = funcParam.getFieldU64(); + return Bytes{ + static_cast(data & 0xFF), + static_cast((data >> 8) & 0xFF), + static_cast((data >> 16) & 0xFF), + static_cast((data >> 24) & 0xFF), + static_cast((data >> 32) & 0xFF), + static_cast((data >> 40) & 0xFF), + static_cast((data >> 48) & 0xFF), + static_cast((data >> 56) & 0xFF)}; + } + case STI_UINT128: { + if (funcParam.getInnerSType() != STI_UINT128) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint128 data = funcParam.getFieldH128(); + return Bytes{ + reinterpret_cast(&data), + reinterpret_cast(&data) + sizeof(uint128)}; + } + case STI_UINT160: { + if (funcParam.getInnerSType() != STI_UINT160) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint160 data = funcParam.getFieldH160(); + return Bytes{data.begin(), data.end()}; + } + case STI_UINT192: { + if (funcParam.getInnerSType() != STI_UINT192) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint192 data = funcParam.getFieldH192(); + return Bytes{data.begin(), data.end()}; + } + case STI_UINT256: { + if (funcParam.getInnerSType() != STI_UINT256) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint256 data = funcParam.getFieldH256(); + return Bytes{data.begin(), data.end()}; + } + case STI_VL: { + if (funcParam.getInnerSType() != STI_VL) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto data = funcParam.getFieldVL(); + return Bytes{data.begin(), data.end()}; + } + case STI_ACCOUNT: { + if (funcParam.getInnerSType() != STI_ACCOUNT) + return Unexpected(HostFunctionError::INVALID_PARAMS); + AccountID data = funcParam.getAccountID(); + return Bytes{data.data(), data.data() + data.size()}; + } + case STI_AMOUNT: { + if (funcParam.getInnerSType() != STI_AMOUNT) + return Unexpected(HostFunctionError::INVALID_PARAMS); + STAmount data = funcParam.getFieldAmount(); + Serializer s; + data.add(s); + auto const& serialized = s.getData(); + return Bytes{serialized.begin(), serialized.end()}; + } + case STI_NUMBER: { + if (funcParam.getInnerSType() != STI_NUMBER) + return Unexpected(HostFunctionError::INVALID_PARAMS); + STNumber data = funcParam.getFieldNumber(); + Serializer s; + data.add(s); + auto const& serialized = s.getData(); + return Bytes{serialized.begin(), serialized.end()}; + } + case STI_ISSUE: { + if (funcParam.getInnerSType() != STI_ISSUE) + return Unexpected(HostFunctionError::INVALID_PARAMS); + STIssue data = funcParam.getFieldIssue(); + Serializer s; + data.add(s); + auto const& serialized = s.getData(); + return Bytes{serialized.begin(), serialized.end()}; + } + case STI_CURRENCY: { + if (funcParam.getInnerSType() != STI_CURRENCY) + return Unexpected(HostFunctionError::INVALID_PARAMS); + STCurrency data = funcParam.getFieldCurrency(); + Serializer s; + data.add(s); + auto const& serialized = s.getData(); + return Bytes{serialized.begin(), serialized.end()}; + } + case STI_PATHSET: + case STI_VECTOR256: + case STI_XCHAIN_BRIDGE: + case STI_DATA: + case STI_DATATYPE: + case STI_JSON: + default: + return Unexpected(HostFunctionError::INVALID_PARAMS); + } + return Unexpected(HostFunctionError::INVALID_PARAMS); +} + +Expected +ContractHostFunctionsImpl::instanceParam( + std::uint32_t index, + std::uint32_t stTypeId) +{ + auto j = getJournal(); + auto const& instanceParams = contractCtx.instanceParameters; + + if (instanceParams.size() <= index) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "instanceParam: Index out of bounds"; + return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS); + } + + xrpl::STData const& instParam = instanceParams[index].value; + return getFieldBytesFromSTData(instParam, stTypeId); +} + +Expected +ContractHostFunctionsImpl::functionParam( + std::uint32_t index, + std::uint32_t stTypeId) +{ + auto j = getJournal(); + auto const& funcParams = contractCtx.functionParameters; + + if (funcParams.size() <= index) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "functionParam: Index out of bounds"; + return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS); + } + + xrpl::STData const& funcParam = funcParams[index].value; + return getFieldBytesFromSTData(funcParam, stTypeId); +} + +inline std::optional const>> +getDataCache(ContractContext& contractCtx, xrpl::AccountID const& account) +{ + auto& dataMap = contractCtx.result.dataMap; + if (dataMap.find(account) == dataMap.end()) + return std::nullopt; + + auto const& ret = dataMap[account]; + return std::cref(ret); +} + +inline std::pair +getDataOrCache(ContractContext& contractCtx, AccountID const& account) +{ + auto cacheEntryLookup = getDataCache(contractCtx, account); + if (!cacheEntryLookup) + { + AccountID const& contractAccount = contractCtx.result.contractAccount; + auto const dataKeylet = keylet::contractData(account, contractAccount); + auto& view = contractCtx.applyCtx.view(); + auto const dataSle = view.read(dataKeylet); + if (dataSle) + { + // Return the STJson from the SLE + STJson data = dataSle->getFieldJson(sfContractJson); + return {data.isObject(), data}; + } + + // Return New STJson if not found + STJson data; + return {true, data}; + } + + // Return the cached STJson + auto const& cacheEntry = cacheEntryLookup->get(); + return {cacheEntry.second.isObject(), cacheEntry.second}; +} + +inline HostFunctionError +setDataCache( + ContractContext& contractCtx, + AccountID const& account, + STJson const& data, + beast::Journal const& j, + bool modified = true) +{ + auto& dataMap = contractCtx.result.dataMap; + auto& view = contractCtx.applyCtx.view(); + auto const contractId = contractCtx.result.contractKeylet.key; + + auto const sleAccount = view.read(keylet::account(account)); + if (!sleAccount) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "setDataCache: Account not found"; + return HostFunctionError::INVALID_ACCOUNT; + } + + uint32_t maxDataModifications = 1000u; + + if (modified && dataMap.modifiedCount >= maxDataModifications) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataCache: Exceeded max data modifications"; + return HostFunctionError::INTERNAL; + } + + if (dataMap.find(account) == dataMap.end()) + { + auto const& fees = contractCtx.applyCtx.view().fees(); + STAmount bal = sleAccount->getFieldAmount(sfBalance); + + int64_t availableForReserves = bal.xrp().drops() - + fees.accountReserve(sleAccount->getFieldU32(sfOwnerCount)).drops(); + int64_t increment = fees.increment.drops(); + if (increment <= 0) + increment = 1; + + availableForReserves /= increment; + + if (availableForReserves < 1 && modified) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "setDataCache: Insufficient reserve"; + return HostFunctionError::INTERNAL; + } + + dataMap.modifiedCount++; + dataMap[account] = {modified, data}; + + // for (auto const& [acct, entry] : dataMap) + // { + // JLOG(j.trace()) + // << "Account: " << to_string(acct) + // << ", Modified: " << entry.first << ", Data: " + // << entry.second.getJson(JsonOptions::none).toStyledString(); + // } + + return HostFunctionError::SUCCESS; + } + + // auto& availableForReserves = std::get<0>(dataMap[account]); + // bool const canReserveNew = availableForReserves > 0; + if (modified) + { + // if (!canReserveNew) + // return HostFunctionError::INSUFFICIENT_RESERVE; + + // availableForReserves--; + dataMap.modifiedCount++; + } + + dataMap[account] = {modified, data}; + // for (auto const& [acct, entry] : dataMap) + // { + // JLOG(j.trace()) + // << "Account: " << to_string(acct) << ", Modified: " << + // entry.first + // << ", Data: " + // << entry.second.getJson(JsonOptions::none).toStyledString(); + // } + return HostFunctionError::SUCCESS; +} + +Expected +ContractHostFunctionsImpl::getDataObjectField( + AccountID const& account, + std::string_view const& key) +{ + auto j = getJournal(); + auto& view = contractCtx.applyCtx.view(); + AccountID const& contractAccount = contractCtx.result.contractAccount; + try + { + auto const sleAccount = view.read(keylet::account(account)); + if (!sleAccount) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "getDataObjectField: Account not found"; + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + } + + // first check if the requested state was previously cached this session + auto cacheEntryLookup = getDataCache(contractCtx, account); + if (cacheEntryLookup) + { + auto const& cacheEntry = cacheEntryLookup->get(); + STJson const data = cacheEntry.second; + auto const keyValue = data.getObjectField(std::string(key)); + if (!keyValue) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "getDataObjectField: Invalid field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + keyValue.value()->add(s); + return Bytes{ + s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + + auto const dataKeylet = keylet::contractData(account, contractAccount); + auto const dataSle = view.read(dataKeylet); + if (!dataSle) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId + << "]: " << "getDataObjectField: Data SLE not found"; + return Unexpected(HostFunctionError::INTERNAL); + } + + STJson const data = dataSle->getFieldJson(sfContractJson); + // it exists add it to cache and return it + if (setDataCache(contractCtx, account, data, j, false) != + HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataObjectField: Failed to set data cache"; + return Unexpected(HostFunctionError::INTERNAL); + } + + auto const keyValue = data.getObjectField(std::string(key)); + if (!keyValue) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "getDataObjectField: Invalid field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + keyValue.value()->add(s); + return Bytes{ + s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataObjectField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::getDataNestedObjectField( + AccountID const& account, + std::string_view const& key, + std::string_view const& nestedKey) +{ + auto j = getJournal(); + auto& view = contractCtx.applyCtx.view(); + AccountID const& contractAccount = contractCtx.result.contractAccount; + try + { + auto const sleAccount = view.read(keylet::account(account)); + if (!sleAccount) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedObjectField: Account not found"; + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + } + + // first check if the requested state was previously cached this session + auto cacheEntryLookup = getDataCache(contractCtx, account); + if (cacheEntryLookup) + { + auto const& cacheEntry = cacheEntryLookup->get(); + STJson const data = cacheEntry.second; + auto const keyValue = data.getNestedObjectField( + std::string(key), std::string(nestedKey)); + if (!keyValue) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId + << "]: " << "getDataNestedObjectField: Invalid field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + keyValue.value()->add(s); + return Bytes{ + s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + + auto const dataKeylet = keylet::contractData(account, contractAccount); + auto const dataSle = view.read(dataKeylet); + if (!dataSle) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedObjectField: Data SLE not found"; + return Unexpected(HostFunctionError::INTERNAL); + } + + STJson const data = dataSle->getFieldJson(sfContractJson); + // it exists add it to cache and return it + if (setDataCache(contractCtx, account, data, j, false) != + HostFunctionError::SUCCESS) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "getDataNestedObjectField: Failed to set data cache"; + return Unexpected(HostFunctionError::INTERNAL); + } + + auto const keyValue = + data.getNestedObjectField(std::string(key), std::string(nestedKey)); + if (!keyValue) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId + << "]: " << "getDataNestedObjectField: Invalid field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + keyValue.value()->add(s); + return Bytes{ + s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedObjectField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::setDataObjectField( + AccountID const& account, + std::string_view const& key, + STJson::Value const& value) +{ + auto j = getJournal(); + try + { + auto [isObject, data] = getDataOrCache(contractCtx, account); + if (!isObject) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "setDataObjectField: Invalid state: not an object"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + data.setObjectField(std::string(key), value); + if (HostFunctionError ret = + setDataCache(contractCtx, account, data, j, true); + ret != HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataObjectField: Failed to set object field"; + return Unexpected(ret); + } + + return static_cast(HostFunctionError::SUCCESS); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataObjectField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::setDataNestedObjectField( + AccountID const& account, + std::string_view const& key, + std::string_view const& nestedKey, + STJson::Value const& value) +{ + auto j = getJournal(); + try + { + auto [isObject, data] = getDataOrCache(contractCtx, account); + if (!isObject) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "setDataNestedObjectField: Invalid state: not an object"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + data.setNestedObjectField( + std::string(key), std::string(nestedKey), value); + if (HostFunctionError ret = + setDataCache(contractCtx, account, data, j, true); + ret != HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedObjectField: Failed to set nested " + "object field"; + return Unexpected(ret); + } + + return static_cast(HostFunctionError::SUCCESS); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedObjectField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::getDataArrayElementField( + AccountID const& account, + size_t index, + std::string_view const& key) +{ + auto j = getJournal(); + auto& view = contractCtx.applyCtx.view(); + AccountID const& contractAccount = contractCtx.result.contractAccount; + try + { + auto const sleAccount = view.read(keylet::account(account)); + if (!sleAccount) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Account not found"; + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + } + + // first check if the requested state was previously cached this session + auto cacheEntryLookup = getDataCache(contractCtx, account); + if (cacheEntryLookup) + { + auto const& cacheEntry = cacheEntryLookup->get(); + STJson const data = cacheEntry.second; + + if (!data.isArray()) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Invalid state: not an array"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + auto const fieldValue = + data.getArrayElementField(index, std::string(key)); + if (!fieldValue) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Failed to get array " + "element field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + fieldValue.value()->add(s); + return Bytes{ + s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + + auto const dataKeylet = keylet::contractData(account, contractAccount); + auto const dataSle = view.read(dataKeylet); + if (!dataSle) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Failed to read contract data"; + return Unexpected(HostFunctionError::INTERNAL); + } + + STJson const data = dataSle->getFieldJson(sfContractJson); + + if (!data.isArray()) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Invalid state: not an array"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + // it exists add it to cache and return it + if (setDataCache(contractCtx, account, data, j, false) != + HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataArrayElementField: Failed to set array " + "element field"; + return Unexpected(HostFunctionError::INTERNAL); + } + + auto const fieldValue = + data.getArrayElementField(index, std::string(key)); + if (!fieldValue) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Failed to get array " + "element field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + fieldValue.value()->add(s); + return Bytes{ + s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::getDataNestedArrayElementField( + AccountID const& account, + std::string_view const& key, + size_t index, + std::string_view const& nestedKey) +{ + auto j = getJournal(); + auto& view = contractCtx.applyCtx.view(); + AccountID const& contractAccount = contractCtx.result.contractAccount; + try + { + auto const sleAccount = view.read(keylet::account(account)); + if (!sleAccount) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Account not found"; + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + } + + // if (account != contractCtx.result.otxnAccount) + // { + // JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + // << "getDataNestedArrayElementField: Unauthorized + // access to account data"; + // return Unexpected(HostFunctionError::INVALID_ACCOUNT); + // } + + // first check if the requested state was previously cached this session + auto cacheEntryLookup = getDataCache(contractCtx, account); + if (cacheEntryLookup) + { + auto const& cacheEntry = cacheEntryLookup->get(); + STJson const data = cacheEntry.second; + + if (!data.isObject()) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Invalid state: " + "not an object"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + auto const fieldValue = data.getNestedArrayElementField( + std::string(key), index, std::string(nestedKey)); + if (!fieldValue) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Failed to get " + "nested array element field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + fieldValue.value()->add(s); + return Bytes{ + s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + + auto const dataKeylet = keylet::contractData(account, contractAccount); + auto const dataSle = view.read(dataKeylet); + if (!dataSle) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Failed to read " + "contract data"; + return Unexpected(HostFunctionError::INTERNAL); + } + + STJson const data = dataSle->getFieldJson(sfContractJson); + + if (!data.isObject()) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Invalid state: " + "not an object"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + // it exists add it to cache and return it + if (setDataCache(contractCtx, account, data, j, false) != + HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedArrayElementField: Failed to set " + "nested array element field"; + return Unexpected(HostFunctionError::INTERNAL); + } + + auto const fieldValue = data.getNestedArrayElementField( + std::string(key), index, std::string(nestedKey)); + if (!fieldValue) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Failed to get " + "nested array element field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + fieldValue.value()->add(s); + return Bytes{ + s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Exception: " + << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::setDataArrayElementField( + AccountID const& account, + size_t index, + std::string_view const& key, + STJson::Value const& value) +{ + auto j = getJournal(); + auto [isObject, data] = getDataOrCache(contractCtx, account); + + try + { + // For array operations, we expect isObject to be false (indicating it's + // an array) But getDataOrCache returns isObject=true for new data, so + // we need to check the actual type + if (isObject && data.getMap().size() > 0) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "setDataArrayElementField: Invalid state: not an array"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + // If it's a new empty object, convert it to an array + if (isObject && data.getMap().empty()) + { + data = STJson(STJson::Array{}); + } + + if (!data.isArray()) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "setDataArrayElementField: Invalid state: not an array"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + data.setArrayElementField(index, std::string(key), value); + if (HostFunctionError ret = + setDataCache(contractCtx, account, data, j, true); + ret != HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataArrayElementField: Failed to set array " + "element field"; + return Unexpected(ret); + } + + return static_cast(HostFunctionError::SUCCESS); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataArrayElementField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::setDataNestedArrayElementField( + AccountID const& account, + std::string_view const& key, + size_t index, + std::string_view const& nestedKey, + STJson::Value const& value) +{ + auto j = getJournal(); + try + { + auto [isObject, data] = getDataOrCache(contractCtx, account); + if (!isObject) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedArrayElementField: Invalid state: " + "not an object"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + data.setNestedArrayElementField( + std::string(key), index, std::string(nestedKey), value); + if (HostFunctionError ret = + setDataCache(contractCtx, account, data, j, true); + ret != HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedArrayElementField: Failed to set " + "nested array element field"; + return Unexpected(ret); + } + + return static_cast(HostFunctionError::SUCCESS); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedArrayElementField: Exception: " + << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::buildTxn(std::uint16_t const& txType) +{ + auto j = getJournal(); + auto& app = contractCtx.applyCtx.app; + + if (!Emitable::getInstance().isEmitable(txType)) + { + JLOG(j.trace()) << "Transaction type: " << txType + << " is not emitable."; + return Unexpected(HostFunctionError::SUBMIT_TXN_FAILURE); + } + + try + { + auto jv = Json::Value(Json::objectValue); + auto item = + TxFormats::getInstance().findByType(safe_cast(txType)); + jv[sfTransactionType] = item->getName(); + jv[sfFee] = "0"; + jv[sfFlags] = 1073741824; + jv[sfSequence] = contractCtx.result.nextSequence; + jv[sfAccount] = to_string(contractCtx.result.contractAccount); + jv[sfSigningPubKey] = ""; + if (app.config().NETWORK_ID != 0) + jv[sfNetworkID] = app.config().NETWORK_ID; + + STParsedJSONObject parsed("txn", jv); + contractCtx.built_txns.push_back(*parsed.object); + contractCtx.result.nextSequence += 1; + return contractCtx.built_txns.size() - 1; + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: Exception in buildTxn: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::addTxnField( + std::uint32_t const& index, + SField const& field, + Slice const& data) +{ + auto j = getJournal(); + try + { + if (index >= contractCtx.built_txns.size()) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "addTxnField: index out of bounds: " << index; + return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS); + } + + // Get the transaction STObject + auto& obj = contractCtx.built_txns[index]; + + // Ensure the transaction has a TransactionType field + if (!obj.isFieldPresent(sfTransactionType)) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "addTxnField: TransactionType field not present " + "in transaction."; + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + + // Extract the numeric tx type from the STObject and convert to TxType + auto txTypeVal = obj.getFieldU16(sfTransactionType); + auto txFormat = + TxFormats::getInstance().findByType(safe_cast(txTypeVal)); + if (!txFormat) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " + << "addTxnField: Invalid TransactionType: " << txTypeVal; + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + + // Check if the provided field is allowed for this transaction type + bool found = false; + for (auto const& e : txFormat->getSOTemplate()) + { + if (e.sField().getName() == field.getName()) + { + found = true; + break; + } + } + if (!found) + { + JLOG(j.trace()) + << "WasmTrace[" << contractId << "]: " << "addTxnField: Field " + << field.getName() << " not allowed in transaction type " + << txFormat->getName(); + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + + obj.addFieldFromSlice(field, data); + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "addTxnField: TXN: " + << obj.getJson(JsonOptions::none).toStyledString(); + return static_cast(HostFunctionError::SUCCESS); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: Exception in addTxnField: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::emitBuiltTxn(std::uint32_t const& index) +{ + auto j = getJournal(); + auto& app = contractCtx.applyCtx.app; + auto& parentTx = contractCtx.applyCtx.tx; + auto const parentBatchId = parentTx.getTransactionID(); + try + { + if (index >= contractCtx.built_txns.size()) + { + JLOG(j.trace()) + << "WasmTrace[" << parentBatchId + << "]: " << "emitBuiltTxn: index out of bounds: " << index; + return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS); + } + + auto stxPtr = + std::make_shared(std::move(contractCtx.built_txns[index])); + + std::string reason; + auto tpTrans = std::make_shared(stxPtr, reason, app); + if (tpTrans->getStatus() != NEW) + { + JLOG(j.trace()) + << "WasmTrace[" << parentBatchId << "]: " + << "emitBuiltTxn: Failed to decode transaction: " << reason; + return Unexpected(HostFunctionError::SUBMIT_TXN_FAILURE); + } + + OpenView wholeBatchView(batch_view, contractCtx.applyCtx.openView()); + auto applyOneTransaction = [&app, &j, &parentBatchId, &wholeBatchView]( + std::shared_ptr const& tx) { + auto const pfresult = preflight( + app, wholeBatchView.rules(), parentBatchId, *tx, tapBATCH, j); + auto const ret = preclaim(pfresult, app, wholeBatchView); + JLOG(j.trace()) << "WasmTrace[" << parentBatchId + << "]: " << tx->getTransactionID() << " " + << transToken(ret.ter); + return ret; + }; + + auto const result = applyOneTransaction(tpTrans->getSTransaction()); + if (isTesSuccess(result.ter)) + contractCtx.result.emittedTxns.push(tpTrans); + return TERtoInt(result.ter); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << parentBatchId + << "]: Exception in emitBuiltTxn: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::emitTxn(std::shared_ptr const& stxPtr) +{ + auto& app = contractCtx.applyCtx.app; + auto& parentTx = contractCtx.applyCtx.tx; + auto j = getJournal(); + + try + { + std::string reason; + auto tpTrans = std::make_shared(stxPtr, reason, app); + if (tpTrans->getStatus() != NEW) + return Unexpected(HostFunctionError::SUBMIT_TXN_FAILURE); + + OpenView wholeBatchView(batch_view, contractCtx.applyCtx.openView()); + auto const parentBatchId = parentTx.getTransactionID(); + auto applyOneTransaction = [&app, &j, &parentBatchId, &wholeBatchView]( + std::shared_ptr const& tx) { + auto const pfresult = preflight( + app, wholeBatchView.rules(), parentBatchId, *tx, tapBATCH, j); + auto const ret = preclaim(pfresult, app, wholeBatchView); + JLOG(j.trace()) << "WasmTrace[" << parentBatchId + << "]: " << tx->getTransactionID() << " " + << transToken(ret.ter); + return ret; + }; + + auto const result = applyOneTransaction(tpTrans->getSTransaction()); + if (isTesSuccess(result.ter)) + contractCtx.result.emittedTxns.push(tpTrans); + return TERtoInt(result.ter); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << parentTx.getTransactionID() + << "]: Exception in emitTxn: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::emitEvent( + std::string_view const& eventName, + STJson const& eventData) +{ + auto j = getJournal(); + + try + { + // TODO: Validation + auto& eventMap = contractCtx.result.eventMap; + eventMap[std::string(eventName)] = eventData; + return static_cast(HostFunctionError::SUCCESS); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: Exception in emitEvent: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/detail/HostFuncImpl.cpp b/src/xrpld/app/wasm/detail/HostFuncImpl.cpp new file mode 100644 index 0000000000..219dcfe72a --- /dev/null +++ b/src/xrpld/app/wasm/detail/HostFuncImpl.cpp @@ -0,0 +1,1305 @@ +#include +#include +#include + +#include +#include + +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif + +namespace xrpl { + +Expected +WasmHostFunctionsImpl::getLedgerSqn() +{ + auto seq = ctx.view().seq(); + if (seq > std::numeric_limits::max()) + return Unexpected(HostFunctionError::INTERNAL); // LCOV_EXCL_LINE + return static_cast(seq); +} + +Expected +WasmHostFunctionsImpl::getParentLedgerTime() +{ + auto time = ctx.view().parentCloseTime().time_since_epoch().count(); + if (time > std::numeric_limits::max()) + return Unexpected(HostFunctionError::INTERNAL); + return static_cast(time); +} + +Expected +WasmHostFunctionsImpl::getParentLedgerHash() +{ + return ctx.view().header().parentHash; +} + +Expected +WasmHostFunctionsImpl::getBaseFee() +{ + auto fee = ctx.view().fees().base.drops(); + if (fee > std::numeric_limits::max()) + return Unexpected(HostFunctionError::INTERNAL); + return static_cast(fee); +} + +Expected +WasmHostFunctionsImpl::isAmendmentEnabled(uint256 const& amendmentId) +{ + return ctx.view().rules().enabled(amendmentId); +} + +Expected +WasmHostFunctionsImpl::isAmendmentEnabled(std::string_view const& amendmentName) +{ + auto const& table = ctx.app.getAmendmentTable(); + auto const amendment = table.find(std::string(amendmentName)); + return ctx.view().rules().enabled(amendment); +} + +Expected +WasmHostFunctionsImpl::cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) +{ + auto const& keylet = keylet::unchecked(objId); + if (cacheIdx < 0 || cacheIdx > MAX_CACHE) + return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + + if (cacheIdx == 0) + { + for (cacheIdx = 0; cacheIdx < MAX_CACHE; ++cacheIdx) + if (!cache[cacheIdx]) + break; + } + else + { + cacheIdx--; // convert to 0-based index + } + + if (cacheIdx >= MAX_CACHE) + return Unexpected(HostFunctionError::SLOTS_FULL); + + cache[cacheIdx] = ctx.view().read(keylet); + if (!cache[cacheIdx]) + return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + return cacheIdx + 1; // return 1-based index +} + +static Expected +getAnyFieldData(STBase const* obj) +{ + // auto const& fname = obj.getFName(); + if (!obj) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + auto const stype = obj->getSType(); + switch (stype) + { + // LCOV_EXCL_START + case STI_UNKNOWN: + case STI_NOTPRESENT: + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + break; + // LCOV_EXCL_STOP + case STI_OBJECT: + case STI_ARRAY: + return Unexpected(HostFunctionError::NOT_LEAF_FIELD); + break; + case STI_ACCOUNT: { + auto const* account(static_cast(obj)); + auto const& data = account->value(); + return Bytes{data.begin(), data.end()}; + } + break; + case STI_AMOUNT: + // will be processed by serializer + break; + case STI_ISSUE: { + auto const* issue(static_cast(obj)); + Asset const& asset(issue->value()); + // XRP and IOU will be processed by serializer + if (asset.holds()) + { + // MPT + auto const& mptIssue = asset.get(); + auto const& mptID = mptIssue.getMptID(); + return Bytes{mptID.cbegin(), mptID.cend()}; + } + } + break; + case STI_VL: { + auto const* vl(static_cast(obj)); + auto const& data = vl->value(); + return Bytes{data.begin(), data.end()}; + } + break; + case STI_UINT16: { + auto const& num(static_cast const*>(obj)); + std::uint16_t const data = num->value(); + auto const* b = reinterpret_cast(&data); + auto const* e = reinterpret_cast(&data + 1); + return Bytes{b, e}; + } + case STI_UINT32: { + auto const* num(static_cast const*>(obj)); + std::uint32_t const data = num->value(); + auto const* b = reinterpret_cast(&data); + auto const* e = reinterpret_cast(&data + 1); + return Bytes{b, e}; + } + break; + default: + break; // default to serializer + } + + Serializer msg; + obj->add(msg); + auto const data = msg.getData(); + + return data; +} + +Expected +WasmHostFunctionsImpl::getTxField(SField const& fname) +{ + return getAnyFieldData(ctx.tx.peekAtPField(fname)); +} + +Expected +WasmHostFunctionsImpl::getCurrentLedgerObjField(SField const& fname) +{ + auto const sle = getCurrentLedgerObj(); + if (!sle.has_value()) + return Unexpected(sle.error()); + return getAnyFieldData(sle.value()->peekAtPField(fname)); +} + +Expected +WasmHostFunctionsImpl::getLedgerObjField(int32_t cacheIdx, SField const& fname) +{ + auto const normalizedIdx = normalizeCacheIndex(cacheIdx); + if (!normalizedIdx.has_value()) + return Unexpected(normalizedIdx.error()); + return getAnyFieldData(cache[normalizedIdx.value()]->peekAtPField(fname)); +} + +static inline bool +noField(STBase const* field) +{ + return !field || (STI_NOTPRESENT == field->getSType()) || + (STI_UNKNOWN == field->getSType()); +} + +static Expected +locateField(STObject const& obj, Slice const& locator) +{ + if (locator.empty() || (locator.size() & 3)) // must be multiple of 4 + return Unexpected(HostFunctionError::LOCATOR_MALFORMED); + + int32_t const* locPtr = reinterpret_cast(locator.data()); + int32_t const locSize = locator.size() / 4; + STBase const* field = nullptr; + auto const& knownSFields = SField::getKnownCodeToField(); + + { + int32_t const sfieldCode = locPtr[0]; + auto const it = knownSFields.find(sfieldCode); + if (it == knownSFields.end()) + return Unexpected(HostFunctionError::INVALID_FIELD); + + auto const& fname(*it->second); + field = obj.peekAtPField(fname); + if (noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + + for (int i = 1; i < locSize; ++i) + { + int32_t const sfieldCode = locPtr[i]; + + if (STI_ARRAY == field->getSType()) + { + auto const* arr = static_cast(field); + if (sfieldCode >= arr->size()) + return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS); + field = &(arr->operator[](sfieldCode)); + } + else if (STI_OBJECT == field->getSType()) + { + auto const* o = static_cast(field); + + auto const it = knownSFields.find(sfieldCode); + if (it == knownSFields.end()) + return Unexpected(HostFunctionError::INVALID_FIELD); + + auto const& fname(*it->second); + field = o->peekAtPField(fname); + } + else // simple field must be the last one + { + return Unexpected(HostFunctionError::LOCATOR_MALFORMED); + } + + if (noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + + return field; +} + +Expected +WasmHostFunctionsImpl::getTxNestedField(Slice const& locator) +{ + auto const r = locateField(ctx.tx, locator); + if (!r) + return Unexpected(r.error()); + + return getAnyFieldData(r.value()); +} + +Expected +WasmHostFunctionsImpl::getCurrentLedgerObjNestedField(Slice const& locator) +{ + auto const sle = getCurrentLedgerObj(); + if (!sle.has_value()) + return Unexpected(sle.error()); + + auto const r = locateField(*sle.value(), locator); + if (!r) + return Unexpected(r.error()); + + return getAnyFieldData(r.value()); +} + +Expected +WasmHostFunctionsImpl::getLedgerObjNestedField( + int32_t cacheIdx, + Slice const& locator) +{ + auto const normalizedIdx = normalizeCacheIndex(cacheIdx); + if (!normalizedIdx.has_value()) + return Unexpected(normalizedIdx.error()); + + auto const r = locateField(*cache[normalizedIdx.value()], locator); + if (!r) + return Unexpected(r.error()); + + return getAnyFieldData(r.value()); +} + +Expected +WasmHostFunctionsImpl::getTxArrayLen(SField const& fname) +{ + if (fname.fieldType != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + + auto const* field = ctx.tx.peekAtPField(fname); + if (noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + if (field->getSType() != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); // LCOV_EXCL_LINE + int32_t const sz = static_cast(field)->size(); + + return sz; +} + +Expected +WasmHostFunctionsImpl::getCurrentLedgerObjArrayLen(SField const& fname) +{ + if (fname.fieldType != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + + auto const sle = getCurrentLedgerObj(); + if (!sle.has_value()) + return Unexpected(sle.error()); + + auto const* field = sle.value()->peekAtPField(fname); + if (noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + if (field->getSType() != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); // LCOV_EXCL_LINE + int32_t const sz = static_cast(field)->size(); + + return sz; +} + +Expected +WasmHostFunctionsImpl::getLedgerObjArrayLen( + int32_t cacheIdx, + SField const& fname) +{ + if (fname.fieldType != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + + auto const normalizedIdx = normalizeCacheIndex(cacheIdx); + if (!normalizedIdx.has_value()) + return Unexpected(normalizedIdx.error()); + + auto const* field = cache[normalizedIdx.value()]->peekAtPField(fname); + if (noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + if (field->getSType() != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); // LCOV_EXCL_LINE + + int32_t const sz = static_cast(field)->size(); + + return sz; +} + +Expected +WasmHostFunctionsImpl::getTxNestedArrayLen(Slice const& locator) +{ + auto const r = locateField(ctx.tx, locator); + if (!r) + return Unexpected(r.error()); + + auto const* field = r.value(); + if (field->getSType() != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + int32_t const sz = static_cast(field)->size(); + + return sz; +} + +Expected +WasmHostFunctionsImpl::getCurrentLedgerObjNestedArrayLen(Slice const& locator) +{ + auto const sle = getCurrentLedgerObj(); + if (!sle.has_value()) + return Unexpected(sle.error()); + auto const r = locateField(*sle.value(), locator); + if (!r) + return Unexpected(r.error()); + + auto const* field = r.value(); + if (field->getSType() != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + int32_t const sz = static_cast(field)->size(); + + return sz; +} + +Expected +WasmHostFunctionsImpl::getLedgerObjNestedArrayLen( + int32_t cacheIdx, + Slice const& locator) +{ + auto const normalizedIdx = normalizeCacheIndex(cacheIdx); + if (!normalizedIdx.has_value()) + return Unexpected(normalizedIdx.error()); + + auto const r = locateField(*cache[normalizedIdx.value()], locator); + if (!r) + return Unexpected(r.error()); + + auto const* field = r.value(); + if (field->getSType() != STI_ARRAY) + return Unexpected(HostFunctionError::NO_ARRAY); + int32_t const sz = static_cast(field)->size(); + + return sz; +} + +Expected +WasmHostFunctionsImpl::updateData(Slice const& data) +{ + if (data.size() > maxWasmDataLength) + { + return Unexpected(HostFunctionError::DATA_FIELD_TOO_LARGE); + } + data_ = Bytes(data.begin(), data.end()); + return data_->size(); +} + +Expected +WasmHostFunctionsImpl::checkSignature( + Slice const& message, + Slice const& signature, + Slice const& pubkey) +{ + if (!publicKeyType(pubkey)) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + PublicKey const pk(pubkey); + return verify(pk, message, signature); +} + +Expected +WasmHostFunctionsImpl::computeSha512HalfHash(Slice const& data) +{ + auto const hash = sha512Half(data); + return hash; +} + +Expected +WasmHostFunctionsImpl::accountKeylet(AccountID const& account) +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::account(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::ammKeylet(Asset const& issue1, Asset const& issue2) +{ + if (issue1 == issue2) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + // note: this should be removed with the MPT DEX amendment + if (issue1.holds() || issue2.holds()) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + auto const keylet = keylet::amm(issue1, issue2); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::checkKeylet(AccountID const& account, std::uint32_t seq) +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::check(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::credentialKeylet( + AccountID const& subject, + AccountID const& issuer, + Slice const& credentialType) +{ + if (!subject || !issuer) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + if (credentialType.empty() || + credentialType.size() > maxCredentialTypeLength) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + auto const keylet = keylet::credential(subject, issuer, credentialType); + + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::didKeylet(AccountID const& account) +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::did(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::delegateKeylet( + AccountID const& account, + AccountID const& authorize) +{ + if (!account || !authorize) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + if (account == authorize) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto const keylet = keylet::delegate(account, authorize); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::depositPreauthKeylet( + AccountID const& account, + AccountID const& authorize) +{ + if (!account || !authorize) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + if (account == authorize) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto const keylet = keylet::depositPreauth(account, authorize); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::escrowKeylet(AccountID const& account, std::uint32_t seq) +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::escrow(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::lineKeylet( + AccountID const& account1, + AccountID const& account2, + Currency const& currency) +{ + if (!account1 || !account2) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + if (account1 == account2) + return Unexpected(HostFunctionError::INVALID_PARAMS); + if (currency.isZero()) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + auto const keylet = keylet::line(account1, account2, currency); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::mptIssuanceKeylet( + AccountID const& issuer, + std::uint32_t seq) +{ + if (!issuer) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + auto const keylet = keylet::mptIssuance(seq, issuer); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::mptokenKeylet( + MPTID const& mptid, + AccountID const& holder) +{ + if (!mptid) + return Unexpected(HostFunctionError::INVALID_PARAMS); + if (!holder) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + auto const keylet = keylet::mptoken(mptid, holder); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::nftOfferKeylet( + AccountID const& account, + std::uint32_t seq) +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::nftoffer(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::offerKeylet(AccountID const& account, std::uint32_t seq) +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::offer(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::oracleKeylet( + AccountID const& account, + std::uint32_t documentId) +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::oracle(account, documentId); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::paychanKeylet( + AccountID const& account, + AccountID const& destination, + std::uint32_t seq) +{ + if (!account || !destination) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + if (account == destination) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto const keylet = keylet::payChan(account, destination, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::permissionedDomainKeylet( + AccountID const& account, + std::uint32_t seq) +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::permissionedDomain(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::signersKeylet(AccountID const& account) +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::signers(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::ticketKeylet(AccountID const& account, std::uint32_t seq) +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::ticket(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::vaultKeylet(AccountID const& account, std::uint32_t seq) +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::vault(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::getNFT(AccountID const& account, uint256 const& nftId) +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + if (!nftId) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + auto obj = nft::findToken(ctx.view(), account, nftId); + if (!obj) + return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + + auto ouri = obj->at(~sfURI); + if (!ouri) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + Slice const s = ouri->value(); + return Bytes(s.begin(), s.end()); +} + +Expected +WasmHostFunctionsImpl::getNFTIssuer(uint256 const& nftId) +{ + auto const issuer = nft::getIssuer(nftId); + if (!issuer) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + return Bytes{issuer.begin(), issuer.end()}; +} + +Expected +WasmHostFunctionsImpl::getNFTTaxon(uint256 const& nftId) +{ + return nft::toUInt32(nft::getTaxon(nftId)); +} + +Expected +WasmHostFunctionsImpl::getNFTFlags(uint256 const& nftId) +{ + return nft::getFlags(nftId); +} + +Expected +WasmHostFunctionsImpl::getNFTTransferFee(uint256 const& nftId) +{ + return nft::getTransferFee(nftId); +} + +Expected +WasmHostFunctionsImpl::getNFTSerial(uint256 const& nftId) +{ + return nft::getSerial(nftId); +} + +Expected +WasmHostFunctionsImpl::trace( + std::string_view const& msg, + Slice const& data, + bool asHex) +{ +#ifdef DEBUG_OUTPUT + auto j = getJournal().error(); +#else + auto j = getJournal().trace(); +#endif + if (!asHex) + { + j << "WasmTrace[" << leKey.key << "]: " << msg << " " + << std::string_view( + reinterpret_cast(data.data()), data.size()); + } + else + { + std::string hex; + hex.reserve(data.size() * 2); + boost::algorithm::hex( + data.begin(), data.end(), std::back_inserter(hex)); + j << "WasmTrace[" << leKey.key << "]: " << msg << " " << hex; + } + + return msg.size() + data.size() * (asHex ? 2 : 1); +} + +Expected +WasmHostFunctionsImpl::traceNum(std::string_view const& msg, int64_t data) +{ +#ifdef DEBUG_OUTPUT + auto j = getJournal().error(); +#else + auto j = getJournal().trace(); +#endif + j << "WasmTrace[" << leKey.key << "]: " << msg << " " << data; + return msg.size() + sizeof(data); +} + +Expected +WasmHostFunctionsImpl::traceAccount( + std::string_view const& msg, + AccountID const& account) +{ +#ifdef DEBUG_OUTPUT + auto j = getJournal().error(); +#else + auto j = getJournal().trace(); +#endif + + auto const accountStr = toBase58(account); + + j << "WasmTrace[" << leKey.key << "]: " << msg << " " << accountStr; + return msg.size() + accountStr.size(); +} + +Expected +WasmHostFunctionsImpl::traceFloat( + std::string_view const& msg, + Slice const& data) +{ +#ifdef DEBUG_OUTPUT + auto j = getJournal().error(); +#else + auto j = getJournal().trace(); +#endif + auto const s = floatToString(data); + j << "WasmTrace[" << leKey.key << "]: " << msg << " " << s; + return msg.size() + s.size(); +} + +Expected +WasmHostFunctionsImpl::traceAmount( + std::string_view const& msg, + STAmount const& amount) +{ +#ifdef DEBUG_OUTPUT + auto j = getJournal().error(); +#else + auto j = getJournal().trace(); +#endif + auto const amountStr = amount.getFullText(); + j << "WasmTrace[" << leKey.key << "]: " << msg << " " << amountStr; + return msg.size() + amountStr.size(); +} + +Expected +WasmHostFunctionsImpl::floatFromInt(int64_t x, int32_t mode) +{ + return floatFromIntImpl(x, mode); +} + +Expected +WasmHostFunctionsImpl::floatFromUint(uint64_t x, int32_t mode) +{ + return floatFromUintImpl(x, mode); +} + +Expected +WasmHostFunctionsImpl::floatSet( + int64_t mantissa, + int32_t exponent, + int32_t mode) +{ + return floatSetImpl(mantissa, exponent, mode); +} + +Expected +WasmHostFunctionsImpl::floatCompare(Slice const& x, Slice const& y) +{ + return floatCompareImpl(x, y); +} + +Expected +WasmHostFunctionsImpl::floatAdd(Slice const& x, Slice const& y, int32_t mode) +{ + return floatAddImpl(x, y, mode); +} + +Expected +WasmHostFunctionsImpl::floatSubtract( + Slice const& x, + Slice const& y, + int32_t mode) +{ + return floatSubtractImpl(x, y, mode); +} + +Expected +WasmHostFunctionsImpl::floatMultiply( + Slice const& x, + Slice const& y, + int32_t mode) +{ + return floatMultiplyImpl(x, y, mode); +} + +Expected +WasmHostFunctionsImpl::floatDivide(Slice const& x, Slice const& y, int32_t mode) +{ + return floatDivideImpl(x, y, mode); +} + +Expected +WasmHostFunctionsImpl::floatRoot(Slice const& x, int32_t n, int32_t mode) +{ + return floatRootImpl(x, n, mode); +} + +Expected +WasmHostFunctionsImpl::floatPower(Slice const& x, int32_t n, int32_t mode) +{ + return floatPowerImpl(x, n, mode); +} + +Expected +WasmHostFunctionsImpl::floatLog(Slice const& x, int32_t mode) +{ + return floatLogImpl(x, mode); +} + +class Number2 : public Number +{ +protected: + static Bytes const FLOAT_NULL; + + bool good_; + +public: + Number2(Slice const& data) : Number(), good_(false) + { + if (data.size() != 8) + return; + + if (std::ranges::equal(FLOAT_NULL, data)) + { + good_ = true; + return; + } + + uint64_t const v = SerialIter(data).get64(); + if (!(v & STAmount::cIssuedCurrency)) + return; + + int64_t const neg = (v & STAmount::cPositive) ? 1 : -1; + int32_t const e = static_cast((v >> (64 - 10)) & 0xFFull); + if (e < 1 || e > 177) + return; + + int64_t const m = neg * static_cast(v & ((1ull << 54) - 1)); + if (!m) + return; + + Number x(m, e + IOUAmount::minExponent - 1); + *static_cast(this) = x; + good_ = true; + } + + Number2() : Number(), good_(true) + { + } + + Number2(int64_t x) : Number(x), good_(true) + { + } + + Number2(uint64_t x) : Number(0), good_(false) + { + using mtype = std::invoke_result_t; + if (x <= std::numeric_limits::max()) + *this = Number(x); + else + *this = Number(x / 10, 1) + Number(x % 10); + + good_ = true; + } + + Number2(int64_t mantissa, int32_t exponent) + : Number(mantissa, exponent), good_(true) + { + } + + Number2(Number const& n) : Number(n), good_(true) + { + } + + operator bool() const + { + return good_; + } + + Expected + toBytes() const + { + uint64_t v = mantissa() >= 0 ? STAmount::cPositive : 0; + v |= STAmount::cIssuedCurrency; + + uint64_t const absM = mantissa() >= 0 ? mantissa() : -mantissa(); + if (!absM) + { + using etype = + std::invoke_result_t; + if (exponent() != std::numeric_limits::lowest()) + { + return Unexpected( + HostFunctionError:: + FLOAT_COMPUTATION_ERROR); // LCOV_EXCL_LINE + } + return FLOAT_NULL; + } + else if (absM > ((1ull << 54) - 1)) + { + return Unexpected( + HostFunctionError::FLOAT_COMPUTATION_ERROR); // LCOV_EXCL_LINE + } + else if (exponent() > IOUAmount::maxExponent) + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + else if (exponent() < IOUAmount::minExponent) + return FLOAT_NULL; + + int const e = exponent() - IOUAmount::minExponent + 1; //+97 + v |= absM; + v |= ((uint64_t)e) << 54; + + Serializer msg; + msg.add64(v); + auto const data = msg.getData(); + +#ifdef DEBUG_OUTPUT + std::cout << "m: " << std::setw(20) << mantissa() + << ", e: " << std::setw(12) << exponent() << ", hex: "; + std::cout << std::hex << std::uppercase << std::setfill('0'); + for (auto const& c : data) + std::cout << std::setw(2) << (unsigned)c << " "; + std::cout << std::dec << std::setfill(' ') << std::endl; +#endif + + return data; + } +}; + +Bytes const Number2::FLOAT_NULL = + {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +struct SetRound +{ + Number::rounding_mode oldMode_; + bool good_; + + SetRound(int32_t mode) : oldMode_(Number::getround()), good_(false) + { + if (mode < Number::rounding_mode::to_nearest || + mode > Number::rounding_mode::upward) + return; + + Number::setround(static_cast(mode)); + good_ = true; + } + + ~SetRound() + { + Number::setround(oldMode_); + } + + operator bool() const + { + return good_; + } +}; + +std::string +floatToString(Slice const& data) +{ + Number2 const num(data); + if (!num) + { + std::string hex; + hex.reserve(data.size() * 2); + boost::algorithm::hex( + data.begin(), data.end(), std::back_inserter(hex)); + return "Invalid data: " + hex; + } + + auto const s = to_string(num); + return s; +} + +Expected +floatFromIntImpl(int64_t x, int32_t mode) +{ + try + { + SetRound rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + Number2 num(x); + return num.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatFromUintImpl(uint64_t x, int32_t mode) +{ + try + { + SetRound rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + Number2 num(x); + return num.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatSetImpl(int64_t mantissa, int32_t exponent, int32_t mode) +{ + try + { + SetRound rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 num(mantissa, exponent); + return num.toBytes(); + } + catch (...) + { + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); +} + +Expected +floatCompareImpl(Slice const& x, Slice const& y) +{ + try + { + Number2 xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 yy(y); + if (!yy) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + return xx < yy ? 2 : (xx == yy ? 0 : 1); + } + // LCOV_EXCL_START + catch (...) + { + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatAddImpl(Slice const& x, Slice const& y, int32_t mode) +{ + try + { + SetRound rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + Number2 xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 yy(y); + if (!yy) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 res = xx + yy; + + return res.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatSubtractImpl(Slice const& x, Slice const& y, int32_t mode) +{ + try + { + SetRound rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 yy(y); + if (!yy) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 res = xx - yy; + + return res.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatMultiplyImpl(Slice const& x, Slice const& y, int32_t mode) +{ + try + { + SetRound rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 yy(y); + if (!yy) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 res = xx * yy; + + return res.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatDivideImpl(Slice const& x, Slice const& y, int32_t mode) +{ + try + { + SetRound rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 yy(y); + if (!yy) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + Number2 res = xx / yy; + + return res.toBytes(); + } + catch (...) + { + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); +} + +Expected +floatRootImpl(Slice const& x, int32_t n, int32_t mode) +{ + try + { + if (n < 1) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + SetRound rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + Number2 xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + Number2 res(root(xx, n)); + + return res.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatPowerImpl(Slice const& x, int32_t n, int32_t mode) +{ + try + { + if ((n < 0) || (n > IOUAmount::maxExponent)) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + SetRound rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + Number2 xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + if (xx == Number() && !n) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + Number2 res(power(xx, n, 1)); + + return res.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatLogImpl(Slice const& x, int32_t mode) +{ + try + { + SetRound rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + Number2 xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + Number2 res(lg(xx)); + + return res.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/detail/HostFuncWrapper.cpp b/src/xrpld/app/wasm/detail/HostFuncWrapper.cpp new file mode 100644 index 0000000000..adfb220ee3 --- /dev/null +++ b/src/xrpld/app/wasm/detail/HostFuncWrapper.cpp @@ -0,0 +1,2831 @@ +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +using SFieldCRef = std::reference_wrapper; + +static int32_t +setData( + InstanceWrapper const* runtime, + int32_t dst, + int32_t dstSize, + uint8_t const* src, + int32_t srcSize) +{ + if (!srcSize) + return 0; // LCOV_EXCL_LINE + + if (dst < 0 || dstSize < 0 || !src || srcSize < 0) + return HfErrorToInt(HostFunctionError::INVALID_PARAMS); + + if (srcSize > maxWasmDataLength) + return HfErrorToInt(HostFunctionError::DATA_FIELD_TOO_LARGE); + + auto const memory = runtime ? runtime->getMem() : wmem(); + + // LCOV_EXCL_START + if (!memory.s) + return HfErrorToInt(HostFunctionError::NO_MEM_EXPORTED); + // LCOV_EXCL_STOP + if ((int64_t)dst + dstSize > memory.s) + return HfErrorToInt(HostFunctionError::POINTER_OUT_OF_BOUNDS); + if (srcSize > dstSize) + return HfErrorToInt(HostFunctionError::BUFFER_TOO_SMALL); + + memcpy(memory.p + dst, src, srcSize); + + return srcSize; +} + +template +Expected +getDataInt32(IW const* _runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const result = params->data[i].of.i32; + i++; + return result; +} + +template +Expected +getDataInt64(IW const* _runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const result = params->data[i].of.i64; + i++; + return result; +} + +template +Expected +getDataUInt64(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const r = getDataSlice(runtime, params, i); + if (!r) + return Unexpected(r.error()); + if (r->size() != sizeof(uint64_t)) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + return *reinterpret_cast(r->data()); +} + +template +Expected +getDataSField(IW const* _runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const& m = SField::getKnownCodeToField(); + auto const it = m.find(params->data[i].of.i32); + i++; + if (it == m.end()) + { + return Unexpected(HostFunctionError::INVALID_FIELD); + } + return *it->second; +} + +template +Expected +getDataSlice( + IW const* runtime, + wasm_val_vec_t const* params, + int32_t& i, + bool isUpdate = false) +{ + int64_t const ptr = params->data[i].of.i32; + int64_t const size = params->data[i + 1].of.i32; + i += 2; + if (ptr < 0 || size < 0) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + if (!size) + return Slice(); + + if (size > (isUpdate ? maxWasmDataLength : maxWasmParamLength)) + return Unexpected(HostFunctionError::DATA_FIELD_TOO_LARGE); + + auto const memory = runtime ? runtime->getMem() : wmem(); + // LCOV_EXCL_START + if (!memory.s) + return Unexpected(HostFunctionError::NO_MEM_EXPORTED); + // LCOV_EXCL_STOP + + if (ptr + size > memory.s) + return Unexpected(HostFunctionError::POINTER_OUT_OF_BOUNDS); + + Slice data(memory.p + ptr, size); + return data; +} + +template +Expected +getDataUInt256(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + { + return Unexpected(slice.error()); + } + + if (slice->size() != uint256::bytes) + { + return Unexpected(HostFunctionError::INVALID_PARAMS); + } + return uint256::fromVoid(slice->data()); +} + +template +Expected +getDataAccountID(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + { + return Unexpected(slice.error()); + } + + if (slice->size() != AccountID::bytes) + { + return Unexpected(HostFunctionError::INVALID_PARAMS); + } + + return AccountID::fromVoid(slice->data()); +} + +template +static Expected +getDataCurrency(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + { + return Unexpected(slice.error()); + } + + if (slice->size() != Currency::bytes) + { + return Unexpected(HostFunctionError::INVALID_PARAMS); + } + + return Currency::fromVoid(slice->data()); +} + +template +static Expected +getDataAsset(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + { + return Unexpected(slice.error()); + } + + if (slice->size() == MPTID::bytes) + { + auto const mptid = MPTID::fromVoid(slice->data()); + return Asset{mptid}; + } + + if (slice->size() == Currency::bytes) + { + auto const currency = Currency::fromVoid(slice->data()); + auto const issue = Issue{currency, xrpAccount()}; + if (!issue.native()) + return Unexpected(HostFunctionError::INVALID_PARAMS); + return Asset{issue}; + } + + if (slice->size() == (AccountID::bytes + Currency::bytes)) + { + auto const issue = Issue( + Currency::fromVoid(slice->data()), + AccountID::fromVoid(slice->data() + Currency::bytes)); + + if (issue.native()) + return Unexpected(HostFunctionError::INVALID_PARAMS); + return Asset{issue}; + } + + return Unexpected(HostFunctionError::INVALID_PARAMS); +} + +template +Expected +getDataString(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + return Unexpected(slice.error()); + return std::string_view( + reinterpret_cast(slice->data()), slice->size()); +} + +std::nullptr_t +hfResult(wasm_val_vec_t* results, int32_t value) +{ + results->data[0] = WASM_I32_VAL(value); + // results->size = 1; + return nullptr; +} + +std::nullptr_t +hfResult(wasm_val_vec_t* results, HostFunctionError value) +{ + results->data[0] = WASM_I32_VAL(HfErrorToInt(value)); + // results->size = 1; + return nullptr; +} + +template +std::nullptr_t +returnResult( + InstanceWrapper const* runtime, + wasm_val_vec_t const* params, + wasm_val_vec_t* results, + Expected const& res, + int32_t index) +{ + if (!res) + { + return hfResult(results, res.error()); + } + + using t = std::decay_t; + if constexpr (std::is_same_v) + { + return hfResult( + results, + setData( + runtime, + params->data[index].of.i32, + params->data[index + 1].of.i32, + res->data(), + res->size())); + } + else if constexpr (std::is_same_v) + { + return hfResult( + results, + setData( + runtime, + params->data[index].of.i32, + params->data[index + 1].of.i32, + res->data(), + res->size())); + } + else if constexpr (std::is_same_v) + { + return hfResult(results, res.value()); + } + else if constexpr (std::is_same_v) + { + auto const resultValue = res.value(); + return hfResult( + results, + setData( + runtime, + params->data[index].of.i32, + params->data[index + 1].of.i32, + reinterpret_cast(&resultValue), + static_cast(sizeof(resultValue)))); + } + else + { + static_assert( + [] { return false; }(), "Unhandled return type in returnResult"); + } +} + +static inline HostFunctions* +getHF(void* env) +{ + auto const* udata = reinterpret_cast(env); + HostFunctions* hf = reinterpret_cast(udata->first); + return hf; +} + +static inline Expected +checkGas(void* env) +{ + auto const* udata = reinterpret_cast(env); + HostFunctions* hf = reinterpret_cast(udata->first); + + auto const* runtime = reinterpret_cast(hf->getRT()); + if (!runtime) + { + wasm_trap_t* trap = reinterpret_cast( + WasmEngine::instance().newTrap("hf no runtime")); // LCOV_EXCL_LINE + return Unexpected(trap); // LCOV_EXCL_LINE + } + + int64_t const gas = runtime->getGas(); + WasmImportFunc const& impFunc = udata->second; + int64_t const x = gas >= impFunc.gas ? gas - impFunc.gas : 0; + runtime->setGas(x); + if (gas < impFunc.gas) + { + wasm_trap_t* trap = reinterpret_cast( + WasmEngine::instance().newTrap("hf out of gas")); + return Unexpected(trap); + } + + return x; +} + +//---------------------------------------------------------------------------------------------------------------------- +wasm_trap_t* +getLedgerSqn_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + return returnResult(runtime, params, results, hf->getLedgerSqn(), index); +} + +wasm_trap_t* +getParentLedgerTime_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + return returnResult( + runtime, params, results, hf->getParentLedgerTime(), index); +} + +wasm_trap_t* +getParentLedgerHash_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + return returnResult( + runtime, params, results, hf->getParentLedgerHash(), index); +} + +wasm_trap_t* +getBaseFee_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + return returnResult(runtime, params, results, hf->getBaseFee(), index); +} + +wasm_trap_t* +isAmendmentEnabled_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const slice = getDataSlice(runtime, params, index); + if (!slice) + { + return hfResult(results, slice.error()); + } + + if (slice->size() == uint256::bytes) + { + if (auto ret = hf->isAmendmentEnabled(uint256::fromVoid(slice->data())); + *ret == 1) + { + return returnResult(runtime, params, results, ret, index); + } + } + + if (slice->size() > 64) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const str = std::string_view( + reinterpret_cast(slice->data()), slice->size()); + return returnResult( + runtime, params, results, hf->isAmendmentEnabled(str), index); +} + +wasm_trap_t* +cacheLedgerObj_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const id = getDataUInt256(runtime, params, index); + if (!id) + { + return hfResult(results, id.error()); + } + + auto const cache = getDataInt32(runtime, params, index); + if (!cache) + { + return hfResult(results, cache.error()); // LCOV_EXCL_LINE + } + + return returnResult( + runtime, params, results, hf->cacheLedgerObj(*id, *cache), index); +} + +wasm_trap_t* +getTxField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const fname = getDataSField(runtime, params, index); + if (!fname) + { + return hfResult(results, fname.error()); + } + return returnResult( + runtime, params, results, hf->getTxField(*fname), index); +} + +wasm_trap_t* +getCurrentLedgerObjField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const fname = getDataSField(runtime, params, index); + if (!fname) + { + return hfResult(results, fname.error()); + } + + return returnResult( + runtime, params, results, hf->getCurrentLedgerObjField(*fname), index); +} + +wasm_trap_t* +getLedgerObjField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const cache = getDataInt32(runtime, params, index); + if (!cache) + { + return hfResult(results, cache.error()); // LCOV_EXCL_LINE + } + + auto const fname = getDataSField(runtime, params, index); + if (!fname) + { + return hfResult(results, fname.error()); + } + + return returnResult( + runtime, params, results, hf->getLedgerObjField(*cache, *fname), index); +} + +wasm_trap_t* +getTxNestedField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + + return returnResult( + runtime, params, results, hf->getTxNestedField(*bytes), index); +} + +wasm_trap_t* +getCurrentLedgerObjNestedField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + return returnResult( + runtime, + params, + results, + hf->getCurrentLedgerObjNestedField(*bytes), + index); +} + +wasm_trap_t* +getLedgerObjNestedField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const cache = getDataInt32(runtime, params, index); + if (!cache) + { + return hfResult(results, cache.error()); // LCOV_EXCL_LINE + } + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + + return returnResult( + runtime, + params, + results, + hf->getLedgerObjNestedField(*cache, *bytes), + index); +} + +wasm_trap_t* +getTxArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const fname = getDataSField(runtime, params, index); + if (!fname) + { + return hfResult(results, fname.error()); + } + + return returnResult( + runtime, params, results, hf->getTxArrayLen(*fname), index); +} + +wasm_trap_t* +getCurrentLedgerObjArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const fname = getDataSField(runtime, params, index); + if (!fname) + { + return hfResult(results, fname.error()); + } + + return returnResult( + runtime, + params, + results, + hf->getCurrentLedgerObjArrayLen(*fname), + index); +} + +wasm_trap_t* +getLedgerObjArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const cache = getDataInt32(runtime, params, index); + if (!cache) + { + return hfResult(results, cache.error()); // LCOV_EXCL_LINE + } + + auto const fname = getDataSField(runtime, params, index); + if (!fname) + { + return hfResult(results, fname.error()); + } + + return returnResult( + runtime, + params, + results, + hf->getLedgerObjArrayLen(*cache, *fname), + index); +} + +wasm_trap_t* +getTxNestedArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + + return returnResult( + runtime, params, results, hf->getTxNestedArrayLen(*bytes), index); +} + +wasm_trap_t* +getCurrentLedgerObjNestedArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + + return returnResult( + runtime, + params, + results, + hf->getCurrentLedgerObjNestedArrayLen(*bytes), + index); +} +wasm_trap_t* +getLedgerObjNestedArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const cache = getDataInt32(runtime, params, index); + if (!cache) + { + return hfResult(results, cache.error()); // LCOV_EXCL_LINE + } + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + return returnResult( + runtime, + params, + results, + hf->getLedgerObjNestedArrayLen(*cache, *bytes), + index); +} + +wasm_trap_t* +updateData_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const bytes = getDataSlice(runtime, params, index, true); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + + return returnResult( + runtime, params, results, hf->updateData(*bytes), index); +} + +wasm_trap_t* +checkSignature_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const message = getDataSlice(runtime, params, index); + if (!message) + { + return hfResult(results, message.error()); + } + + auto const signature = getDataSlice(runtime, params, index); + if (!signature) + { + return hfResult(results, signature.error()); + } + + auto const pubkey = getDataSlice(runtime, params, index); + if (!pubkey) + { + return hfResult(results, pubkey.error()); + } + + return returnResult( + runtime, + params, + results, + hf->checkSignature(*message, *signature, *pubkey), + index); +} + +wasm_trap_t* +computeSha512HalfHash_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + return returnResult( + runtime, params, results, hf->computeSha512HalfHash(*bytes), index); +} + +wasm_trap_t* +accountKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + return returnResult( + runtime, params, results, hf->accountKeylet(*acc), index); +} + +wasm_trap_t* +ammKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const issue1 = getDataAsset(runtime, params, index); + if (!issue1) + { + return hfResult(results, issue1.error()); + } + + auto const issue2 = getDataAsset(runtime, params, index); + if (!issue2) + { + return hfResult(results, issue2.error()); + } + + return returnResult( + runtime, + params, + results, + hf->ammKeylet(issue1.value(), issue2.value()), + index); +} + +wasm_trap_t* +checkKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const seq = getDataInt32(runtime, params, index); + if (!seq) + { + return hfResult(results, seq.error()); // LCOV_EXCL_LINE + } + + return returnResult( + runtime, params, results, hf->checkKeylet(acc.value(), *seq), index); +} + +wasm_trap_t* +credentialKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const subj = getDataAccountID(runtime, params, index); + if (!subj) + { + return hfResult(results, subj.error()); + } + + auto const iss = getDataAccountID(runtime, params, index); + if (!iss) + { + return hfResult(results, iss.error()); + } + + auto const credType = getDataSlice(runtime, params, index); + if (!credType) + { + return hfResult(results, credType.error()); + } + + return returnResult( + runtime, + params, + results, + hf->credentialKeylet(*subj, *iss, *credType), + index); +} + +wasm_trap_t* +delegateKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const authorize = getDataAccountID(runtime, params, index); + if (!authorize) + { + return hfResult(results, authorize.error()); + } + + return returnResult( + runtime, + params, + results, + hf->delegateKeylet(acc.value(), authorize.value()), + index); +} + +wasm_trap_t* +depositPreauthKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const authorize = getDataAccountID(runtime, params, index); + if (!authorize) + { + return hfResult(results, authorize.error()); + } + + return returnResult( + runtime, + params, + results, + hf->depositPreauthKeylet(acc.value(), authorize.value()), + index); +} + +wasm_trap_t* +didKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + return returnResult( + runtime, params, results, hf->didKeylet(acc.value()), index); +} + +wasm_trap_t* +escrowKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const seq = getDataInt32(runtime, params, index); + if (!seq) + { + return hfResult(results, seq.error()); // LCOV_EXCL_LINE + } + + return returnResult( + runtime, params, results, hf->escrowKeylet(*acc, *seq), index); +} + +wasm_trap_t* +lineKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc1 = getDataAccountID(runtime, params, index); + if (!acc1) + { + return hfResult(results, acc1.error()); + } + + auto const acc2 = getDataAccountID(runtime, params, index); + if (!acc2) + { + return hfResult(results, acc2.error()); + } + + auto const currency = getDataCurrency(runtime, params, index); + if (!currency) + { + return hfResult(results, currency.error()); + } + + return returnResult( + runtime, + params, + results, + hf->lineKeylet(acc1.value(), acc2.value(), currency.value()), + index); +} + +wasm_trap_t* +mptIssuanceKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const seq = getDataInt32(runtime, params, index); + if (!seq) + { + return hfResult(results, seq.error()); // LCOV_EXCL_LINE + } + + return returnResult( + runtime, + params, + results, + hf->mptIssuanceKeylet(acc.value(), seq.value()), + index); +} + +wasm_trap_t* +mptokenKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const slice = getDataSlice(runtime, params, index); + if (!slice) + { + return hfResult(results, slice.error()); + } + + if (slice->size() != MPTID::bytes) + { + return hfResult(results, HostFunctionError::INVALID_PARAMS); + } + auto const mptid = MPTID::fromVoid(slice->data()); + + auto const holder = getDataAccountID(runtime, params, index); + if (!holder) + { + return hfResult(results, holder.error()); + } + + return returnResult( + runtime, + params, + results, + hf->mptokenKeylet(mptid, holder.value()), + index); +} + +wasm_trap_t* +nftOfferKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const seq = getDataInt32(runtime, params, index); + if (!seq) + { + return hfResult(results, seq.error()); // LCOV_EXCL_LINE + } + + return returnResult( + runtime, + params, + results, + hf->nftOfferKeylet(acc.value(), seq.value()), + index); +} + +wasm_trap_t* +offerKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const seq = getDataInt32(runtime, params, index); + if (!seq) + { + return hfResult(results, seq.error()); // LCOV_EXCL_LINE + } + + return returnResult( + runtime, + params, + results, + hf->offerKeylet(acc.value(), seq.value()), + index); +} + +wasm_trap_t* +oracleKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const documentId = getDataInt32(runtime, params, index); + if (!documentId) + { + return hfResult(results, documentId.error()); // LCOV_EXCL_LINE + } + return returnResult( + runtime, params, results, hf->oracleKeylet(*acc, *documentId), index); +} + +wasm_trap_t* +paychanKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const dest = getDataAccountID(runtime, params, index); + if (!dest) + { + return hfResult(results, dest.error()); + } + + auto const seq = getDataInt32(runtime, params, index); + if (!seq) + { + return hfResult(results, seq.error()); // LCOV_EXCL_LINE + } + + return returnResult( + runtime, + params, + results, + hf->paychanKeylet(acc.value(), dest.value(), seq.value()), + index); +} + +wasm_trap_t* +permissionedDomainKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const seq = getDataInt32(runtime, params, index); + if (!seq) + { + return hfResult(results, seq.error()); // LCOV_EXCL_LINE + } + + return returnResult( + runtime, + params, + results, + hf->permissionedDomainKeylet(acc.value(), seq.value()), + index); +} + +wasm_trap_t* +signersKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + return returnResult( + runtime, params, results, hf->signersKeylet(acc.value()), index); +} + +wasm_trap_t* +ticketKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const seq = getDataInt32(runtime, params, index); + if (!seq) + { + return hfResult(results, seq.error()); // LCOV_EXCL_LINE + } + + return returnResult( + runtime, + params, + results, + hf->ticketKeylet(acc.value(), seq.value()), + index); +} + +wasm_trap_t* +vaultKeylet_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const seq = getDataInt32(runtime, params, index); + if (!seq) + { + return hfResult(results, seq.error()); // LCOV_EXCL_LINE + } + + return returnResult( + runtime, + params, + results, + hf->vaultKeylet(acc.value(), seq.value()), + index); +} + +wasm_trap_t* +getNFT_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + { + return hfResult(results, nftId.error()); + } + + return returnResult( + runtime, params, results, hf->getNFT(*acc, *nftId), index); +} + +wasm_trap_t* +getNFTIssuer_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + { + return hfResult(results, nftId.error()); + } + + return returnResult( + runtime, params, results, hf->getNFTIssuer(*nftId), index); +} + +wasm_trap_t* +getNFTTaxon_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + { + return hfResult(results, nftId.error()); + } + + return returnResult( + runtime, params, results, hf->getNFTTaxon(*nftId), index); +} + +wasm_trap_t* +getNFTFlags_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + { + return hfResult(results, nftId.error()); + } + + return returnResult( + runtime, params, results, hf->getNFTFlags(*nftId), index); +} + +wasm_trap_t* +getNFTTransferFee_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + { + return hfResult(results, nftId.error()); + } + + return returnResult( + runtime, params, results, hf->getNFTTransferFee(*nftId), index); +} + +wasm_trap_t* +getNFTSerial_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + { + return hfResult(results, nftId.error()); + } + + return returnResult( + runtime, params, results, hf->getNFTSerial(*nftId), index); +} + +wasm_trap_t* +trace_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + if (params->data[1].of.i32 + params->data[3].of.i32 > maxWasmParamLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const msg = getDataString(runtime, params, index); + if (!msg) + { + return hfResult(results, msg.error()); + } + + auto const data = getDataSlice(runtime, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + auto const asHex = getDataInt32(runtime, params, index); + if (!asHex) + { + return hfResult(results, asHex.error()); // LCOV_EXCL_LINE + } + + return returnResult( + runtime, params, results, hf->trace(*msg, *data, *asHex), index); +} + +wasm_trap_t* +traceNum_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmParamLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const msg = getDataString(runtime, params, index); + if (!msg) + { + return hfResult(results, msg.error()); + } + + auto const number = getDataInt64(runtime, params, index); + if (!number) + { + return hfResult(results, number.error()); // LCOV_EXCL_LINE + } + + return returnResult( + runtime, params, results, hf->traceNum(*msg, *number), index); +} + +wasm_trap_t* +traceAccount_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + if (params->data[1].of.i32 > maxWasmParamLength) + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + + int i = 0; + auto const msg = getDataString(runtime, params, i); + if (!msg) + return hfResult(results, msg.error()); + + auto const account = getDataAccountID(runtime, params, i); + if (!account) + return hfResult(results, account.error()); + + return returnResult( + runtime, params, results, hf->traceAccount(*msg, *account), i); +} + +wasm_trap_t* +traceFloat_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + if (params->data[1].of.i32 > maxWasmParamLength) + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + + int i = 0; + auto const msg = getDataString(runtime, params, i); + if (!msg) + return hfResult(results, msg.error()); + + auto const number = getDataSlice(runtime, params, i); + if (!number) + return hfResult(results, number.error()); + + return returnResult( + runtime, params, results, hf->traceFloat(*msg, *number), i); +} + +wasm_trap_t* +traceAmount_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + if (params->data[1].of.i32 > maxWasmParamLength) + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + + int i = 0; + auto const msg = getDataString(runtime, params, i); + if (!msg) + return hfResult(results, msg.error()); + + auto const amountSliceOpt = getDataSlice(runtime, params, i); + if (!amountSliceOpt) + return hfResult(results, amountSliceOpt.error()); + + auto const amountSlice = amountSliceOpt.value(); + auto serialIter = SerialIter(amountSlice); + + std::optional amount; + try + { + amount = STAmount(serialIter, sfGeneric); + } + catch (std::exception const&) + { + amount = std::nullopt; + } + if (!amount) + return hfResult(results, HostFunctionError::INVALID_PARAMS); + + return returnResult( + runtime, params, results, hf->traceAmount(*msg, *amount), i); +} + +wasm_trap_t* +floatFromInt_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + auto const x = getDataInt64(runtime, params, i); + if (!x) + return hfResult(results, x.error()); // LCOV_EXCL_LINE + + i = 3; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 1; + return returnResult( + runtime, params, results, hf->floatFromInt(*x, *rounding), i); +} + +wasm_trap_t* +floatFromUint_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + auto const x = getDataUInt64(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + i = 4; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 2; + return returnResult( + runtime, params, results, hf->floatFromUint(*x, *rounding), i); +} + +wasm_trap_t* +floatSet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + auto const exp = getDataInt32(runtime, params, i); + if (!exp) + return hfResult(results, exp.error()); // LCOV_EXCL_LINE + + auto const mant = getDataInt64(runtime, params, i); + if (!mant) + return hfResult(results, mant.error()); // LCOV_EXCL_LINE + + i = 4; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 2; + return returnResult( + runtime, params, results, hf->floatSet(*mant, *exp, *rounding), i); +} + +wasm_trap_t* +floatCompare_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const y = getDataSlice(runtime, params, i); + if (!y) + return hfResult(results, y.error()); + + return returnResult(runtime, params, results, hf->floatCompare(*x, *y), i); +} + +wasm_trap_t* +floatAdd_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const y = getDataSlice(runtime, params, i); + if (!y) + return hfResult(results, y.error()); + + i = 6; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 4; + return returnResult( + runtime, params, results, hf->floatAdd(*x, *y, *rounding), i); +} + +wasm_trap_t* +floatSubtract_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const y = getDataSlice(runtime, params, i); + if (!y) + return hfResult(results, y.error()); + + i = 6; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 4; + return returnResult( + runtime, params, results, hf->floatSubtract(*x, *y, *rounding), i); +} + +wasm_trap_t* +floatMultiply_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const y = getDataSlice(runtime, params, i); + if (!y) + return hfResult(results, y.error()); + + i = 6; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 4; + return returnResult( + runtime, params, results, hf->floatMultiply(*x, *y, *rounding), i); +} + +wasm_trap_t* +floatDivide_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const y = getDataSlice(runtime, params, i); + if (!y) + return hfResult(results, y.error()); + + i = 6; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 4; + return returnResult( + runtime, params, results, hf->floatDivide(*x, *y, *rounding), i); +} + +wasm_trap_t* +floatRoot_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const n = getDataInt32(runtime, params, i); + if (!n) + return hfResult(results, n.error()); // LCOV_EXCL_LINE + + i = 5; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 3; + return returnResult( + runtime, params, results, hf->floatRoot(*x, *n, *rounding), i); +} + +wasm_trap_t* +floatPower_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const n = getDataInt32(runtime, params, i); + if (!n) + return hfResult(results, n.error()); // LCOV_EXCL_LINE + + i = 5; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 3; + return returnResult( + runtime, params, results, hf->floatPower(*x, *n, *rounding), i); +} + +wasm_trap_t* +floatLog_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + i = 4; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 2; + return returnResult( + runtime, params, results, hf->floatLog(*x, *rounding), i); +} + +wasm_trap_t* +instanceParam_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const iindex = getDataInt32(rt, params, index); + if (!iindex) + { + return hfResult(results, iindex.error()); + } + + auto const stTypeId = getDataInt32(rt, params, index); + if (!stTypeId) + { + return hfResult(results, stTypeId.error()); + } + + return returnResult( + rt, params, results, hf->instanceParam(*iindex, *stTypeId), index); +} + +wasm_trap_t* +functionParam_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const iindex = getDataInt32(rt, params, index); + if (!iindex) + { + return hfResult(results, iindex.error()); + } + + auto const stTypeId = getDataInt32(rt, params, index); + if (!stTypeId) + { + return hfResult(results, stTypeId.error()); + } + + return returnResult( + rt, params, results, hf->functionParam(*iindex, *stTypeId), index); +} + +wasm_trap_t* +getDataObjectField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + if (params->data[5].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + return returnResult( + rt, params, results, hf->getDataObjectField(*acc, *key), index); +} + +wasm_trap_t* +getDataNestedObjectField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + if (params->data[5].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const nested = getDataString(rt, params, index); + if (!nested) + { + return hfResult(results, nested.error()); + } + + if (params->data[7].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + return returnResult( + rt, + params, + results, + hf->getDataNestedObjectField(*acc, *key, *nested), + index); +} + +wasm_trap_t* +getDataArrayElementField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + auto const elemIndex = getDataInt32(rt, params, index); + if (!elemIndex) + { + return hfResult(results, elemIndex.error()); + } + + if (params->data[6].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + return returnResult( + rt, + params, + results, + hf->getDataArrayElementField(*acc, *elemIndex, *key), + index); +} + +wasm_trap_t* +getDataNestedArrayElementField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + auto const elemIndex = getDataInt32(rt, params, index); + if (!elemIndex) + { + return hfResult(results, elemIndex.error()); + } + + if (params->data[6].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const nested = getDataString(rt, params, index); + if (!nested) + { + return hfResult(results, nested.error()); + } + + if (params->data[8].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + return returnResult( + rt, + params, + results, + hf->getDataNestedArrayElementField(*acc, *key, *elemIndex, *nested), + index); +} + +wasm_trap_t* +setDataObjectField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + if (params->data[5].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const data = getDataSlice(rt, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + SerialIter valueSit(data->data(), data->size()); + STJson::Value const value = STJson::makeValueFromVLWithType(valueSit); + return returnResult( + rt, params, results, hf->setDataObjectField(*acc, *key, value), index); +} + +wasm_trap_t* +setDataNestedObjectField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const nested = getDataString(rt, params, index); + if (!nested) + { + return hfResult(results, nested.error()); + } + + if (params->data[5].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + if (params->data[7].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const data = getDataSlice(rt, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + SerialIter valueSit(data->data(), data->size()); + STJson::Value const value = STJson::makeValueFromVLWithType(valueSit); + return returnResult( + rt, + params, + results, + hf->setDataNestedObjectField(*acc, *nested, *key, value), + index); +} + +wasm_trap_t* +setDataArrayElementField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + auto const elemIndex = getDataInt32(rt, params, index); + if (!elemIndex) + { + return hfResult(results, elemIndex.error()); + } + + if (params->data[6].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const data = getDataSlice(rt, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + SerialIter valueSit(data->data(), data->size()); + STJson::Value const value = STJson::makeValueFromVLWithType(valueSit); + return returnResult( + rt, + params, + results, + hf->setDataArrayElementField(*acc, *elemIndex, *key, value), + index); +} + +wasm_trap_t* +setDataNestedArrayElementField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + auto const elemIndex = getDataInt32(rt, params, index); + if (!elemIndex) + { + return hfResult(results, elemIndex.error()); + } + + if (params->data[6].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const nested = getDataString(rt, params, index); + if (!nested) + { + return hfResult(results, nested.error()); + } + + if (params->data[8].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const data = getDataSlice(rt, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + SerialIter valueSit(data->data(), data->size()); + STJson::Value const value = STJson::makeValueFromVLWithType(valueSit); + return returnResult( + rt, + params, + results, + hf->setDataNestedArrayElementField( + *acc, *key, *elemIndex, *nested, value), + index); +} + +wasm_trap_t* +buildTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const txnType = getDataInt32(rt, params, index); + if (!txnType) + { + return hfResult(results, txnType.error()); + } + + return returnResult(rt, params, results, hf->buildTxn(*txnType), index); +} + +wasm_trap_t* +addTxnField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const txnIndex = getDataInt32(rt, params, index); + if (!txnIndex) + { + return hfResult(results, txnIndex.error()); + } + + auto const fname = getDataSField(rt, params, index); + if (!fname) + { + return hfResult(results, fname.error()); + } + + auto const data = getDataSlice(rt, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + return returnResult( + rt, params, results, hf->addTxnField(*txnIndex, *fname, *data), index); +} + +wasm_trap_t* +emitBuiltTxn_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const txnIndex = getDataInt32(rt, params, index); + if (!txnIndex) + { + return hfResult(results, txnIndex.error()); + } + + return returnResult( + rt, params, results, hf->emitBuiltTxn(*txnIndex), index); +} + +wasm_trap_t* +emitTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const slice = getDataSlice(rt, params, index); + if (!slice) + { + return hfResult(results, slice.error()); + } + + std::shared_ptr stpTrans; + try + { + stpTrans = std::make_shared(SerialIter{*slice}); + } + catch (std::exception& e) + { + std::cout << "Error creating STTx: " << e.what() << std::endl; + return hfResult(results, HostFunctionError::INTERNAL); + } + + return returnResult(rt, params, results, hf->emitTxn(stpTrans), index); +} + +wasm_trap_t* +emitEvent_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const name = getDataString(rt, params, index); + if (!name) + { + return hfResult(results, name.error()); + } + + auto const data = getDataSlice(rt, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + auto parsed = STJson::fromBlob(data->data(), data->size()); + + return returnResult( + rt, params, results, hf->emitEvent(*name, *parsed), index); +} + +// LCOV_EXCL_START +namespace test { + +class MockInstanceWrapper +{ + wmem mem_; + +public: + MockInstanceWrapper(wmem memory) : mem_(memory) + { + } + + // Mock methods to simulate the behavior of InstanceWrapper + wmem + getMem() const + { + return mem_; + } +}; + +bool +testGetDataIncrement() +{ + wasm_val_t values[4]; + + std::array buffer = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; + MockInstanceWrapper runtime(wmem{buffer.data(), buffer.size()}); + + { + // test int32_t + wasm_val_vec_t params = {1, &values[0]}; + + values[0] = WASM_I32_VAL(42); + + int index = 0; + auto const result = getDataInt32(&runtime, ¶ms, index); + if (!result || result.value() != 42 || index != 1) + return false; + } + + { + // test int64_t + wasm_val_vec_t params = {1, &values[0]}; + + values[0] = WASM_I64_VAL(1234); + + int index = 0; + auto const result = getDataInt64(&runtime, ¶ms, index); + if (!result || result.value() != 1234 || index != 1) + return false; + } + + { + // test SFieldCRef + wasm_val_vec_t params = {1, &values[0]}; + + values[0] = WASM_I32_VAL(sfAccount.fieldCode); + + int index = 0; + auto const result = getDataSField(&runtime, ¶ms, index); + if (!result || result.value().get() != sfAccount || index != 1) + return false; + } + + { + // test Slice + wasm_val_vec_t params = {2, &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(3); + + int index = 0; + auto const result = getDataSlice(&runtime, ¶ms, index); + if (!result || result.value() != Slice(buffer.data(), 3) || index != 2) + return false; + } + + { + // test string + wasm_val_vec_t params = {2, &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(5); + + int index = 0; + auto const result = getDataString(&runtime, ¶ms, index); + if (!result || + result.value() != + std::string_view( + reinterpret_cast(buffer.data()), 5) || + index != 2) + return false; + } + + { + // test account + AccountID const id(calcAccountID( + generateKeyPair(KeyType::secp256k1, generateSeed("alice")).first)); + + wasm_val_vec_t params = {2, &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(id.bytes); + memcpy(&buffer[0], id.data(), id.bytes); + + int index = 0; + auto const result = getDataAccountID(&runtime, ¶ms, index); + if (!result || result.value() != id || index != 2) + return false; + } + + { + // test uint256 + + Hash h1 = sha512Half(Slice(buffer.data(), 8)); + wasm_val_vec_t params = {2, &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(h1.bytes); + memcpy(&buffer[0], h1.data(), h1.bytes); + + int index = 0; + auto const result = getDataUInt256(&runtime, ¶ms, index); + if (!result || result.value() != h1 || index != 2) + return false; + } + + { + // test Currency + + Currency const c = xrpCurrency(); + wasm_val_vec_t params = {2, &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(c.bytes); + memcpy(&buffer[0], c.data(), c.bytes); + + int index = 0; + auto const result = getDataCurrency(&runtime, ¶ms, index); + if (!result || result.value() != c || index != 2) + return false; + } + + return true; +} + +} // namespace test +// LCOV_EXCL_STOP + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/detail/WasmVM.cpp b/src/xrpld/app/wasm/detail/WasmVM.cpp new file mode 100644 index 0000000000..5a8d7c1031 --- /dev/null +++ b/src/xrpld/app/wasm/detail/WasmVM.cpp @@ -0,0 +1,294 @@ +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace xrpl { + +static void +setCommonHostFunctions(HostFunctions* hfs, ImportVec& i) +{ + // clang-format off + WASM_IMPORT_FUNC2(i, getLedgerSqn, "get_ledger_sqn", hfs, 60); + WASM_IMPORT_FUNC2(i, getParentLedgerTime, "get_parent_ledger_time", hfs, 60); + WASM_IMPORT_FUNC2(i, getParentLedgerHash, "get_parent_ledger_hash", hfs, 60); + WASM_IMPORT_FUNC2(i, getBaseFee, "get_base_fee", hfs, 60); + WASM_IMPORT_FUNC2(i, isAmendmentEnabled, "amendment_enabled", hfs, 100); + + WASM_IMPORT_FUNC2(i, cacheLedgerObj, "cache_ledger_obj", hfs, 5'000); + WASM_IMPORT_FUNC2(i, getTxField, "get_tx_field", hfs, 70); + WASM_IMPORT_FUNC2(i, getCurrentLedgerObjField, "get_current_ledger_obj_field", hfs, 70); + WASM_IMPORT_FUNC2(i, getLedgerObjField, "get_ledger_obj_field", hfs, 70); + WASM_IMPORT_FUNC2(i, getTxNestedField, "get_tx_nested_field", hfs, 110); + WASM_IMPORT_FUNC2(i, getCurrentLedgerObjNestedField, "get_current_ledger_obj_nested_field", hfs, 110); + WASM_IMPORT_FUNC2(i, getLedgerObjNestedField, "get_ledger_obj_nested_field", hfs, 110); + WASM_IMPORT_FUNC2(i, getTxArrayLen, "get_tx_array_len", hfs, 40); + WASM_IMPORT_FUNC2(i, getCurrentLedgerObjArrayLen, "get_current_ledger_obj_array_len", hfs, 40); + WASM_IMPORT_FUNC2(i, getLedgerObjArrayLen, "get_ledger_obj_array_len", hfs, 40); + WASM_IMPORT_FUNC2(i, getTxNestedArrayLen, "get_tx_nested_array_len", hfs, 70); + WASM_IMPORT_FUNC2(i, getCurrentLedgerObjNestedArrayLen, "get_current_ledger_obj_nested_array_len", hfs, 70); + WASM_IMPORT_FUNC2(i, getLedgerObjNestedArrayLen, "get_ledger_obj_nested_array_len", hfs, 70); + + WASM_IMPORT_FUNC2(i, checkSignature, "check_sig", hfs, 35'000); + WASM_IMPORT_FUNC2(i, computeSha512HalfHash, "compute_sha512_half", hfs, 1'500); + + WASM_IMPORT_FUNC2(i, accountKeylet, "account_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, ammKeylet, "amm_keylet", hfs, 450); + WASM_IMPORT_FUNC2(i, checkKeylet, "check_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, credentialKeylet, "credential_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, delegateKeylet, "delegate_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, depositPreauthKeylet, "deposit_preauth_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, didKeylet, "did_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, escrowKeylet, "escrow_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, lineKeylet, "line_keylet", hfs, 400); + WASM_IMPORT_FUNC2(i, mptIssuanceKeylet, "mpt_issuance_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, mptokenKeylet, "mptoken_keylet", hfs, 500); + WASM_IMPORT_FUNC2(i, nftOfferKeylet, "nft_offer_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, offerKeylet, "offer_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, oracleKeylet, "oracle_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, paychanKeylet, "paychan_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, permissionedDomainKeylet, "permissioned_domain_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, signersKeylet, "signers_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, ticketKeylet, "ticket_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, vaultKeylet, "vault_keylet", hfs, 350); + + WASM_IMPORT_FUNC2(i, getNFT, "get_nft", hfs, 1000); + WASM_IMPORT_FUNC2(i, getNFTIssuer, "get_nft_issuer", hfs, 70); + WASM_IMPORT_FUNC2(i, getNFTTaxon, "get_nft_taxon", hfs, 60); + WASM_IMPORT_FUNC2(i, getNFTFlags, "get_nft_flags", hfs, 60); + WASM_IMPORT_FUNC2(i, getNFTTransferFee, "get_nft_transfer_fee", hfs, 60); + WASM_IMPORT_FUNC2(i, getNFTSerial, "get_nft_serial", hfs, 60); + + WASM_IMPORT_FUNC (i, trace, hfs, 500); + WASM_IMPORT_FUNC2(i, traceNum, "trace_num", hfs, 500); + WASM_IMPORT_FUNC2(i, traceAccount, "trace_account", hfs, 500); + WASM_IMPORT_FUNC2(i, traceFloat, "trace_opaque_float", hfs, 500); + WASM_IMPORT_FUNC2(i, traceAmount, "trace_amount", hfs, 500); + + WASM_IMPORT_FUNC2(i, floatFromInt, "float_from_int", hfs, 100); + WASM_IMPORT_FUNC2(i, floatFromUint, "float_from_uint", hfs, 130); + WASM_IMPORT_FUNC2(i, floatSet, "float_set", hfs, 100); + WASM_IMPORT_FUNC2(i, floatCompare, "float_compare", hfs, 80); + WASM_IMPORT_FUNC2(i, floatAdd, "float_add", hfs, 160); + WASM_IMPORT_FUNC2(i, floatSubtract, "float_subtract", hfs, 160); + WASM_IMPORT_FUNC2(i, floatMultiply, "float_multiply", hfs, 300); + WASM_IMPORT_FUNC2(i, floatDivide, "float_divide", hfs, 300); + WASM_IMPORT_FUNC2(i, floatRoot, "float_root", hfs, 5'500); + WASM_IMPORT_FUNC2(i, floatPower, "float_pow", hfs, 5'500); + WASM_IMPORT_FUNC2(i, floatLog, "float_log", hfs, 12'000); + + WASM_IMPORT_FUNC2(i, instanceParam, "instance_param", hfs, 70); + WASM_IMPORT_FUNC2(i, functionParam, "function_param", hfs, 70); + WASM_IMPORT_FUNC2(i, getDataObjectField, "get_data_object_field", hfs, 70); + WASM_IMPORT_FUNC2(i, getDataNestedObjectField, "get_data_nested_object_field", hfs, 70); + WASM_IMPORT_FUNC2(i, getDataArrayElementField, "get_data_array_element_field", hfs, 70); + WASM_IMPORT_FUNC2(i, getDataNestedArrayElementField, "get_data_nested_array_element_field", hfs, 70); + WASM_IMPORT_FUNC2(i, setDataObjectField, "set_data_object_field", hfs, 70); + WASM_IMPORT_FUNC2(i, setDataNestedObjectField, "set_data_nested_object_field", hfs, 70); + WASM_IMPORT_FUNC2(i, setDataArrayElementField, "set_data_array_element_field", hfs, 70); + WASM_IMPORT_FUNC2(i, setDataNestedArrayElementField, "set_data_nested_array_element_field", hfs, 70); + WASM_IMPORT_FUNC2(i, buildTxn, "build_txn", hfs, 70); + WASM_IMPORT_FUNC2(i, addTxnField, "add_txn_field", hfs, 70); + WASM_IMPORT_FUNC2(i, emitBuiltTxn, "emit_built_txn", hfs, 2'000); + WASM_IMPORT_FUNC2(i, emitTxn, "emit_txn", hfs, 2'000); + WASM_IMPORT_FUNC2(i, emitEvent, "emit_event", hfs, 70); + // clang-format on +} + +ImportVec +createWasmImport(HostFunctions& hfs) +{ + ImportVec i; + + setCommonHostFunctions(&hfs, i); + WASM_IMPORT_FUNC2(i, updateData, "update_data", &hfs, 1000); + + return i; +} + +Expected +runEscrowWasm( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName, + std::vector const& params, + int64_t gasLimit) +{ + // create VM and set cost limit + auto& vm = WasmEngine::instance(); + // vm.initMaxPages(MAX_PAGES); + + auto const ret = vm.run( + wasmCode, + funcName, + params, + createWasmImport(hfs), + &hfs, + gasLimit, + hfs.getJournal()); + + // std::cout << "runEscrowWasm, mod size: " << wasmCode.size() + // << ", gasLimit: " << gasLimit << ", funcName: " << funcName; + + if (!ret) + { +#ifdef DEBUG_OUTPUT + std::cout << ", error: " << ret.error() << std::endl; +#endif + return Unexpected(ret.error()); + } + +#ifdef DEBUG_OUTPUT + std::cout << ", ret: " << ret->result << ", gas spent: " << ret->cost + << std::endl; +#endif + return EscrowResult{ret->result, ret->cost}; +} + +NotTEC +preflightEscrowWasm( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName, + std::vector const& params) +{ + // create VM and set cost limit + auto& vm = WasmEngine::instance(); + // vm.initMaxPages(MAX_PAGES); + + auto const ret = vm.check( + wasmCode, + funcName, + params, + createWasmImport(hfs), + &hfs, + hfs.getJournal()); + + return ret; +} + +// Expected +// runContractWasm( +// Bytes const& wasmCode, +// std::string_view funcName, +// std::vector const& params, +// HostFunctions* hfs, +// int64_t gasLimit, +// beast::Journal j) +// { +// // create VM and set cost limit +// auto& vm = WasmEngine::instance(); +// // vm.initMaxPages(MAX_PAGES); + +// auto const ret = vm.run( +// wasmCode, +// funcName, +// params, +// createWasmImport(hfs), +// &hfs, +// gasLimit, +// hfs->getJournal()); + +// // std::cout << "runContractWasm, mod size: " << wasmCode.size() +// // << ", gasLimit: " << gasLimit << ", funcName: " << funcName; + +// if (!ret) +// { +// #ifdef DEBUG_OUTPUT +// std::cout << ", error: " << ret.error() << std::endl; +// #endif +// return Unexpected(ret.error()); +// } + +// #ifdef DEBUG_OUTPUT +// std::cout << ", ret: " << ret->result << ", gas spent: " << ret->cost +// << std::endl; +// #endif +// return WasmRunResult{ret->result, ret->cost}; +// } + +// NotTEC +// preflightContractWasm( +// Bytes const& wasmCode, +// std::string_view funcName, +// std::vector const& params, +// HostFunctions* hfs, +// beast::Journal j) +// { +// // create VM and set cost limit +// auto& vm = WasmEngine::instance(); +// // vm.initMaxPages(MAX_PAGES); + +// auto const ret = vm.check( +// wasmCode, +// funcName, +// params, +// createWasmImport(hfs), +// hfs->getJournal()); + +// return ret; +// } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +WasmEngine::WasmEngine() : impl(std::make_unique()) +{ +} + +WasmEngine& +WasmEngine::instance() +{ + static WasmEngine e; + return e; +} + +Expected, TER> +WasmEngine::run( + Bytes const& wasmCode, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + HostFunctions* hfs, + int64_t gasLimit, + beast::Journal j) +{ + return impl->run(wasmCode, funcName, params, imports, hfs, gasLimit, j); +} + +NotTEC +WasmEngine::check( + Bytes const& wasmCode, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + HostFunctions* hfs, + beast::Journal j) +{ + return impl->check(wasmCode, funcName, params, imports, hfs, j); +} + +void* +WasmEngine::newTrap(std::string const& msg) +{ + return impl->newTrap(msg); +} + +// LCOV_EXCL_START +beast::Journal +WasmEngine::getJournal() const +{ + return impl->getJournal(); +} +// LCOV_EXCL_STOP + +} // namespace xrpl diff --git a/src/xrpld/app/wasm/detail/WasmiVM.cpp b/src/xrpld/app/wasm/detail/WasmiVM.cpp new file mode 100644 index 0000000000..82c502f63f --- /dev/null +++ b/src/xrpld/app/wasm/detail/WasmiVM.cpp @@ -0,0 +1,966 @@ +#include + +#include + +#include + +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif +// #define SHOW_CALL_TIME 1 + +namespace xrpl { + +namespace { + +void +print_wasm_error(std::string_view msg, wasm_trap_t* trap, beast::Journal jlog) +{ +#ifdef DEBUG_OUTPUT + auto& j = std::cerr; +#else + auto j = jlog.warn(); +#endif + + wasm_byte_vec_t error_message WASM_EMPTY_VEC; + + if (trap) + wasm_trap_message(trap, &error_message); + + if (error_message.size) + { + j << "WASMI Error: " << msg << ", " + << std::string_view(error_message.data, error_message.size - 1); + } + else + j << "WASMI Error: " << msg; + + if (error_message.size) + wasm_byte_vec_delete(&error_message); + + if (trap) + wasm_trap_delete(trap); + +#ifdef DEBUG_OUTPUT + j << std::endl; +#endif +} +// LCOV_EXCL_STOP + +} // namespace + +InstancePtr +InstanceWrapper::init( + StorePtr& s, + ModulePtr& m, + WasmExternVec& expt, + WasmExternVec const& imports, + beast::Journal j) +{ + wasm_trap_t* trap = nullptr; + InstancePtr mi = InstancePtr( + wasm_instance_new(s.get(), m.get(), &imports.vec_, &trap), + &wasm_instance_delete); + + if (!mi || trap) + { + print_wasm_error("can't create instance", trap, j); + throw std::runtime_error("can't create instance"); + } + wasm_instance_exports(mi.get(), &expt.vec_); + return mi; +} + +InstanceWrapper::InstanceWrapper() : instance_(nullptr, &wasm_instance_delete) +{ +} + +// LCOV_EXCL_START +InstanceWrapper::InstanceWrapper(InstanceWrapper&& o) + : instance_(nullptr, &wasm_instance_delete) +{ + *this = std::move(o); +} +// LCOV_EXCL_STOP + +InstanceWrapper::InstanceWrapper( + StorePtr& s, + ModulePtr& m, + WasmExternVec const& imports, + beast::Journal j) + : store_(s.get()), instance_(init(s, m, exports_, imports, j)), j_(j) +{ +} + +InstanceWrapper& +InstanceWrapper::operator=(InstanceWrapper&& o) +{ + if (this == &o) + return *this; // LCOV_EXCL_LINE + + store_ = o.store_; + o.store_ = nullptr; + exports_ = std::move(o.exports_); + instance_ = std::move(o.instance_); + + j_ = o.j_; + + return *this; +} + +InstanceWrapper::operator bool() const +{ + return static_cast(instance_); +} + +FuncInfo +InstanceWrapper::getFunc( + std::string_view funcName, + WasmExporttypeVec const& exportTypes) const +{ + wasm_func_t const* f = nullptr; + wasm_functype_t const* ft = nullptr; + + if (!instance_) + throw std::runtime_error("no instance"); // LCOV_EXCL_LINE + + if (!exportTypes.vec_.size) + throw std::runtime_error("no export"); // LCOV_EXCL_LINE + if (exportTypes.vec_.size != exports_.vec_.size) + throw std::runtime_error("invalid export"); // LCOV_EXCL_LINE + + for (unsigned i = 0; i < exportTypes.vec_.size; ++i) + { + auto const* expType(exportTypes.vec_.data[i]); + + wasm_name_t const* name = wasm_exporttype_name(expType); + wasm_externtype_t const* exnType = wasm_exporttype_type(expType); + if (wasm_externtype_kind(exnType) == WASM_EXTERN_FUNC) + { + if (funcName != std::string_view(name->data, name->size)) + continue; + + auto const* exn(exports_.vec_.data[i]); + if (wasm_extern_kind(exn) != WASM_EXTERN_FUNC) + throw std::runtime_error("invalid export"); // LCOV_EXCL_LINE + + ft = wasm_externtype_as_functype_const(exnType); + f = wasm_extern_as_func_const(exn); + break; + } + } + + if (!f || !ft) + throw std::runtime_error( + "can't find function <" + std::string(funcName) + ">"); + + return {f, ft}; +} + +wmem +InstanceWrapper::getMem() const +{ + if (!instance_) + throw std::runtime_error("no instance"); // LCOV_EXCL_LINE + + wasm_memory_t* mem = nullptr; + for (unsigned i = 0; i < exports_.vec_.size; ++i) + { + auto* e(exports_.vec_.data[i]); + if (wasm_extern_kind(e) == WASM_EXTERN_MEMORY) + { + mem = wasm_extern_as_memory(e); + break; + } + } + + if (!mem) + throw std::runtime_error("no memory exported"); // LCOV_EXCL_LINE + + return { + reinterpret_cast(wasm_memory_data(mem)), + wasm_memory_data_size(mem)}; +} + +std::int64_t +InstanceWrapper::getGas() const +{ + if (!store_) + return -1; // LCOV_EXCL_LINE + std::uint64_t gas = 0; + wasm_store_get_fuel(store_, &gas); + return static_cast(gas); +} + +std::int64_t +InstanceWrapper::setGas(std::int64_t gas) const +{ + if (!store_) + return -1; // LCOV_EXCL_LINE + + if (gas < 0) + gas = std::numeric_limits::max(); + wasmi_error_t* err = + wasm_store_set_fuel(store_, static_cast(gas)); + if (err) + { + // LCOV_EXCL_START + print_wasm_error("Can't set instance gas", nullptr, j_); + wasmi_error_delete(err); + throw std::runtime_error("Can't set instance gas"); + // LCOV_EXCL_STOP + } + + return gas; +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +ModulePtr +ModuleWrapper::init(StorePtr& s, Bytes const& wasmBin, beast::Journal j) +{ + wasm_byte_vec_t const code{wasmBin.size(), (char*)(wasmBin.data())}; + ModulePtr m = + ModulePtr(wasm_module_new(s.get(), &code), &wasm_module_delete); + if (!m) + throw std::runtime_error("can't create module"); + + return m; +} + +// LCOV_EXCL_START +ModuleWrapper::ModuleWrapper() : module_(nullptr, &wasm_module_delete) +{ +} + +ModuleWrapper::ModuleWrapper(ModuleWrapper&& o) + : module_(nullptr, &wasm_module_delete) +{ + *this = std::move(o); +} +// LCOV_EXCL_STOP + +ModuleWrapper::ModuleWrapper( + StorePtr& s, + Bytes const& wasmBin, + bool instantiate, + ImportVec const& imports, + beast::Journal j) + : module_(init(s, wasmBin, j)), j_(j) +{ + wasm_module_exports(module_.get(), &exportTypes_.vec_); + if (instantiate) + { + auto wimports = buildImports(s, imports); + addInstance(s, wimports); + } +} + +// LCOV_EXCL_START +ModuleWrapper& +ModuleWrapper::operator=(ModuleWrapper&& o) +{ + if (this == &o) + return *this; + + module_ = std::move(o.module_); + instanceWrap_ = std::move(o.instanceWrap_); + exportTypes_ = std::move(o.exportTypes_); + j_ = o.j_; + + return *this; +} + +ModuleWrapper::operator bool() const +{ + return instanceWrap_; +} + +// LCOV_EXCL_STOP + +static WasmValtypeVec +makeImpParams(WasmImportFunc const& imp) +{ + auto const paramSize = imp.params.size(); + if (!paramSize) + return {}; + + WasmValtypeVec v(paramSize); + + for (unsigned i = 0; i < paramSize; ++i) + { + auto const vt = imp.params[i]; + switch (vt) + { + case WT_I32: + v.vec_.data[i] = wasm_valtype_new_i32(); + break; + case WT_I64: + v.vec_.data[i] = wasm_valtype_new_i64(); + break; + // LCOV_EXCL_START + default: + throw std::runtime_error("invalid import type"); + // LCOV_EXCL_STOP + } + } + return v; +} + +static WasmValtypeVec +makeImpReturn(WasmImportFunc const& imp) +{ + if (!imp.result) + return {}; // LCOV_EXCL_LINE + + WasmValtypeVec v(1); + switch (*imp.result) + { + case WT_I32: + v.vec_.data[0] = wasm_valtype_new_i32(); + break; + // LCOV_EXCL_START + case WT_I64: + v.vec_.data[0] = wasm_valtype_new_i64(); + break; + default: + throw std::runtime_error("invalid return type"); + // LCOV_EXCL_STOP + } + return v; +} + +WasmExternVec +ModuleWrapper::buildImports(StorePtr& s, ImportVec const& imports) +{ + WasmImporttypeVec importTypes; + wasm_module_imports(module_.get(), &importTypes.vec_); + + if (!importTypes.vec_.size) + return {}; + + WasmExternVec wimports(importTypes.vec_.size); + + unsigned impCnt = 0; + for (unsigned i = 0; i < importTypes.vec_.size; ++i) + { + wasm_importtype_t const* importType = importTypes.vec_.data[i]; + + // wasm_name_t const* mn = wasm_importtype_module(importtype); + // auto modName = std::string_view(mn->data, mn->num_elems); + wasm_name_t const* fn = wasm_importtype_name(importType); + auto fieldName = std::string_view(fn->data, fn->size); + + wasm_externkind_t const itype = + wasm_externtype_kind(wasm_importtype_type(importType)); + if ((itype) != WASM_EXTERN_FUNC) + throw std::runtime_error( + "Invalid import type " + + std::to_string(itype)); // LCOV_EXCL_LINE + + // for multi-module support + // if ((W_ENV != modName) && (W_HOST_LIB != modName)) + // continue; + + bool impSet = false; + for (auto const& obj : imports) + { + auto const& imp = obj.second; + if (imp.name != fieldName) + continue; + + WasmValtypeVec params(makeImpParams(imp)); + WasmValtypeVec results(makeImpReturn(imp)); + + std::unique_ptr + ftype( + wasm_functype_new(¶ms.vec_, &results.vec_), + &wasm_functype_delete); + + params.release(); + results.release(); + + wasm_func_t* func = wasm_func_new_with_env( + s.get(), + ftype.get(), + reinterpret_cast(imp.wrap), + (void*)&obj, + nullptr); + if (!func) + { + // LCOV_EXCL_START + throw std::runtime_error( + "can't create import function " + imp.name); + // LCOV_EXCL_STOP + } + + wimports.vec_.data[i] = wasm_func_as_extern(func); + ++impCnt; + impSet = true; + + break; + } + + if (!impSet) + { + print_wasm_error( + "Import not found: " + std::string(fieldName), nullptr, j_); + } + } + + if (impCnt != importTypes.vec_.size) + { + print_wasm_error( + std::string("Imports not finished: ") + std::to_string(impCnt) + + "/" + std::to_string(importTypes.vec_.size), + nullptr, + j_); + } + + return wimports; +} + +FuncInfo +ModuleWrapper::getFunc(std::string_view funcName) const +{ + return instanceWrap_.getFunc(funcName, exportTypes_); +} + +wmem +ModuleWrapper::getMem() const +{ + return instanceWrap_.getMem(); +} + +InstanceWrapper const& +ModuleWrapper::getInstance(int) const +{ + return instanceWrap_; +} + +int +ModuleWrapper::addInstance(StorePtr& s, WasmExternVec const& imports) +{ + instanceWrap_ = {s, module_, imports, j_}; + return 0; +} + +// int +// my_module_t::delInstance(int i) +// { +// if (i >= mod_inst.size()) +// return -1; +// if (!mod_inst[i]) +// mod_inst[i] = my_mod_inst_t(); +// return i; +// } + +std::int64_t +ModuleWrapper::getGas() +{ + return instanceWrap_ ? instanceWrap_.getGas() : -1; +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// void +// WasmiEngine::clearModules() +// { +// modules.clear(); +// store.reset(); // to free the memory before creating new store +// store = {wasm_store_new(engine.get()), &wasm_store_delete}; +// } + +std::unique_ptr +WasmiEngine::init() +{ + wasm_config_t* config = wasm_config_new(); + if (!config) + return std::unique_ptr{ + nullptr, &wasm_engine_delete}; // LCOV_EXCL_LINE + wasmi_config_consume_fuel_set(config, true); + wasmi_config_ignore_custom_sections_set(config, true); + wasmi_config_wasm_mutable_globals_set(config, false); + wasmi_config_wasm_multi_value_set(config, false); + wasmi_config_wasm_sign_extension_set(config, false); + wasmi_config_wasm_saturating_float_to_int_set(config, false); + wasmi_config_wasm_bulk_memory_set(config, false); + wasmi_config_wasm_reference_types_set(config, false); + wasmi_config_wasm_tail_call_set(config, false); + wasmi_config_wasm_extended_const_set(config, false); + wasmi_config_floats_set(config, false); + + return std::unique_ptr( + wasm_engine_new_with_config(config), &wasm_engine_delete); +} + +WasmiEngine::WasmiEngine() + : engine_(init()), store_(nullptr, &wasm_store_delete) +{ +} + +int +WasmiEngine::addModule( + Bytes const& wasmCode, + bool instantiate, + int64_t gas, + ImportVec const& imports) +{ + moduleWrap_.reset(); + store_.reset(); // to free the memory before creating new store + store_ = { + wasm_store_new_with_memory_max_pages(engine_.get(), MAX_PAGES), + &wasm_store_delete}; + + if (gas < 0) + gas = std::numeric_limits::max(); + wasmi_error_t* err = + wasm_store_set_fuel(store_.get(), static_cast(gas)); + if (err) + { + // LCOV_EXCL_START + print_wasm_error("Error setting gas", nullptr, j_); + wasmi_error_delete(err); + throw std::runtime_error("can't set gas"); + // LCOV_EXCL_STOP + } + + moduleWrap_ = std::make_unique( + store_, wasmCode, instantiate, imports, j_); + + if (!moduleWrap_) + throw std::runtime_error( + "can't create module wrapper"); // LCOV_EXCL_LINE + + return moduleWrap_ ? 0 : -1; +} + +// int +// WasmiEngine::addInstance() +// { +// return module->addInstance(store.get()); +// } + +FuncInfo +WasmiEngine::getFunc(std::string_view funcName) +{ + return moduleWrap_->getFunc(funcName); +} + +std::vector +WasmiEngine::convertParams(std::vector const& params) +{ + std::vector v; + v.reserve(params.size()); + for (auto const& p : params) + { + switch (p.type) + { + case WT_I32: + v.push_back(WASM_I32_VAL(p.of.i32)); + break; + // LCOV_EXCL_START + case WT_I64: + v.push_back(WASM_I64_VAL(p.of.i64)); + break; + // LCOV_EXCL_STOP + case WT_U8V: { + auto const sz = p.of.u8v.sz; + auto const ptr = allocate(sz); + auto mem = getMem(); + memcpy(mem.p + ptr, p.of.u8v.d, sz); + + v.push_back(WASM_I32_VAL(ptr)); + v.push_back(WASM_I32_VAL(sz)); + } + break; + // LCOV_EXCL_START + default: + throw std::runtime_error( + "unknown parameter type: " + std::to_string(p.type)); + break; + // LCOV_EXCL_STOP + } + } + + return v; +} + +int +WasmiEngine::compareParamTypes( + wasm_valtype_vec_t const* ftp, + std::vector const& p) +{ + if (ftp->size != p.size()) + return std::min(ftp->size, p.size()); + + for (unsigned i = 0; i < ftp->size; ++i) + { + auto const t1 = wasm_valtype_kind(ftp->data[i]); + auto const t2 = p[i].kind; + if (t1 != t2) + return i; + } + + return -1; +} + +// LCOV_EXCL_START +void +WasmiEngine::add_param(std::vector& in, int32_t p) +{ + in.emplace_back(); + auto& el(in.back()); + memset(&el, 0, sizeof(el)); + el = WASM_I32_VAL(p); // WASM_I32; +} + +// LCOV_EXCL_STOP + +void +WasmiEngine::add_param(std::vector& in, int64_t p) +{ + in.emplace_back(); + auto& el(in.back()); + el = WASM_I64_VAL(p); +} + +template +WasmiResult +WasmiEngine::call(std::string_view func, Types&&... args) +{ + // Lookup our export function + auto f = getFunc(func); + return call(f, std::forward(args)...); +} + +template +WasmiResult +WasmiEngine::call(FuncInfo const& f, Types&&... args) +{ + std::vector in; + return call(f, in, std::forward(args)...); +} + +#ifdef SHOW_CALL_TIME +static inline uint64_t +usecs() +{ + uint64_t x = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + return x; +} +#endif + +template +WasmiResult +WasmiEngine::call(FuncInfo const& f, std::vector& in) +{ + // wasm_val_t rs[1] = {WASM_I32_VAL(0)}; + WasmiResult ret(NR); + // if (NR) { wasm_val_vec_new_uninitialized(&ret, NR); // + // wasm_val_vec_new(&ret, NR, &rs[0]); // ret = WASM_ARRAY_VEC(rs); } + + wasm_val_vec_t const inv = in.empty() + ? wasm_val_vec_t WASM_EMPTY_VEC + : wasm_val_vec_t{in.size(), in.data()}; + +#ifdef SHOW_CALL_TIME + auto const start = usecs(); +#endif + + wasm_trap_t* trap = wasm_func_call(f.first, &inv, &ret.r.vec_); + +#ifdef SHOW_CALL_TIME + auto const finish = usecs(); + auto const delta_ms = (finish - start) / 1000; + std::cout << "wasm_func_call: " << delta_ms << "ms" << std::endl; +#endif + + if (trap) + { + ret.f = true; + print_wasm_error("failure to call func", trap, j_); + } + + // assert(results[0].kind == WASM_I32); + // if (NR) printf("Result P5: %d\n", ret[0].of.i32); + + return ret; +} + +template +WasmiResult +WasmiEngine::call( + FuncInfo const& f, + std::vector& in, + std::int32_t p, + Types&&... args) +{ + add_param(in, p); + return call(f, in, std::forward(args)...); +} + +template +WasmiResult +WasmiEngine::call( + FuncInfo const& f, + std::vector& in, + std::int64_t p, + Types&&... args) +{ + add_param(in, p); + return call(f, in, std::forward(args)...); +} + +template +WasmiResult +WasmiEngine::call( + FuncInfo const& f, + std::vector& in, + uint8_t const* d, + std::size_t sz, + Types&&... args) +{ + auto const ptr = allocate(sz); + auto mem = getMem(); + memcpy(mem.p + ptr, d, sz); + + add_param(in, ptr); + add_param(in, static_cast(sz)); + return call(f, in, std::forward(args)...); +} + +template +WasmiResult +WasmiEngine::call( + FuncInfo const& f, + std::vector& in, + Bytes const& p, + Types&&... args) +{ + return call(f, in, p.data(), p.size(), std::forward(args)...); +} + +static inline void +checkImports(ImportVec const& imports, HostFunctions* hfs) +{ + for (auto const& obj : imports) + { + if (hfs != obj.first) + Throw("Imports hf unsync"); + } +} + +Expected, TER> +WasmiEngine::run( + Bytes const& wasmCode, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + HostFunctions* hfs, + int64_t gas, + beast::Journal j) +{ + j_ = j; + try + { + checkImports(imports, hfs); + return runHlp(wasmCode, funcName, params, imports, hfs, gas); + } + catch (std::exception const& e) + { + print_wasm_error(std::string("exception: ") + e.what(), nullptr, j_); + } + // LCOV_EXCL_START + catch (...) + { + print_wasm_error(std::string("exception: unknown"), nullptr, j_); + } + // LCOV_EXCL_STOP + return Unexpected(tecFAILED_PROCESSING); +} + +Expected, TER> +WasmiEngine::runHlp( + Bytes const& wasmCode, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + HostFunctions* hfs, + int64_t gas) +{ + // currently only 1 module support, possible parallel UT run + std::lock_guard lg(m_); + + // Create and instantiate the module. + if (!wasmCode.empty()) + { + [[maybe_unused]] int const m = addModule(wasmCode, true, gas, imports); + } + + if (!moduleWrap_ || !moduleWrap_->instanceWrap_) + throw std::runtime_error("no instance"); // LCOV_EXCL_LINE + + if (hfs) + hfs->setRT(&getRT()); + + // Call main + auto const f = getFunc(!funcName.empty() ? funcName : "_start"); + auto const* ftp = wasm_functype_params(f.second); + + // not const because passed directly to VM function (which accept non + // const) + auto p = convertParams(params); + + if (int const comp = compareParamTypes(ftp, p); comp >= 0) + throw std::runtime_error( + "invalid parameter type #" + std::to_string(comp)); + + auto const res = call<1>(f, p); + + if (res.f) + throw std::runtime_error("<" + std::string(funcName) + "> failure"); + else if (!res.r.vec_.size) + throw std::runtime_error( + "<" + std::string(funcName) + + "> return nothing"); // LCOV_EXCL_LINE + + assert(res.r.vec_.data[0].kind == WASM_I32); + if (gas == -1) + gas = std::numeric_limits::max(); + WasmResult const ret{ + res.r.vec_.data[0].of.i32, gas - moduleWrap_->getGas()}; + + // #ifdef DEBUG_OUTPUT + // auto& j = std::cerr; + // #else + // auto j = j_.debug(); + // #endif + // j << "WASMI Res: " << ret.result << " cost: " << ret.cost << std::endl; + + return ret; +} + +NotTEC +WasmiEngine::check( + Bytes const& wasmCode, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + HostFunctions* hfs, + beast::Journal j) +{ + j_ = j; + + try + { + checkImports(imports, hfs); + return checkHlp(wasmCode, funcName, params, imports); + } + catch (std::exception const& e) + { + print_wasm_error(std::string("exception: ") + e.what(), nullptr, j_); + } + // LCOV_EXCL_START + catch (...) + { + print_wasm_error(std::string("exception: unknown"), nullptr, j_); + } + // LCOV_EXCL_STOP + + return temBAD_WASM; +} + +NotTEC +WasmiEngine::checkHlp( + Bytes const& wasmCode, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports) +{ + // currently only 1 module support, possible parallel UT run + std::lock_guard lg(m_); + + // Create and instantiate the module. + if (wasmCode.empty()) + throw std::runtime_error("empty nodule"); + + int const m = addModule(wasmCode, true, -1, imports); + if ((m < 0) || !moduleWrap_ || !moduleWrap_->instanceWrap_) + throw std::runtime_error("no instance"); // LCOV_EXCL_LINE + + // Looking for a func and compare parameter types + auto const f = getFunc(!funcName.empty() ? funcName : "_start"); + auto const* ftp = wasm_functype_params(f.second); + auto const p = convertParams(params); + + if (int const comp = compareParamTypes(ftp, p); comp >= 0) + throw std::runtime_error( + "invalid parameter type #" + std::to_string(comp)); + + return tesSUCCESS; +} + +// LCOV_EXCL_START +std::int64_t +WasmiEngine::getGas() +{ + return moduleWrap_ ? moduleWrap_->getGas() : -1; +} +// LCOV_EXCL_STOP + +wmem +WasmiEngine::getMem() const +{ + return moduleWrap_ ? moduleWrap_->getMem() : wmem(); +} + +InstanceWrapper const& +WasmiEngine::getRT(int m, int i) +{ + if (!moduleWrap_) + throw std::runtime_error("no module"); + return moduleWrap_->getInstance(i); +} + +int32_t +WasmiEngine::allocate(int32_t sz) +{ + auto res = call<1>(W_ALLOC, static_cast(sz)); + + if (res.f || !res.r.vec_.size || (res.r.vec_.data[0].kind != WASM_I32) || + !res.r.vec_.data[0].of.i32) + throw std::runtime_error( + "can't allocate memory, " + std::to_string(sz) + " bytes"); + return res.r.vec_.data[0].of.i32; +} + +wasm_trap_t* +WasmiEngine::newTrap(std::string const& txt) +{ + static char empty[1] = {0}; + wasm_message_t msg = {1, empty}; + + if (!txt.empty()) + wasm_name_new(&msg, txt.size() + 1, txt.c_str()); // include 0 + + wasm_trap_t* trap = wasm_trap_new(store_.get(), &msg); + + if (!txt.empty()) + wasm_byte_vec_delete(&msg); + + return trap; +} + +// LCOV_EXCL_START +beast::Journal +WasmiEngine::getJournal() const +{ + return j_; +} +// LCOV_EXCL_STOP + +} // namespace xrpl diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index cfd260787c..e0bc5ffb2b 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -54,6 +54,15 @@ struct FeeSetup /** The per-owned item reserve requirement in drops. */ XRPAmount owner_reserve{2 * DROPS_PER_XRP}; + /** The compute limit for Feature Extensions. */ + std::uint32_t extension_compute_limit{1'000'000}; + + /** The WASM size limit for Feature Extensions. */ + std::uint32_t extension_size_limit{100'000}; + + /** The price of 1 WASM gas, in micro-drops. */ + std::uint32_t gas_price{1'000'000}; + /* (Remember to update the example cfg files when changing any of these * values.) */ }; diff --git a/src/xrpld/core/detail/Config.cpp b/src/xrpld/core/detail/Config.cpp index e6c61e1471..cbb48d52b1 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -1121,6 +1121,12 @@ setup_FeeVote(Section const& section) setup.account_reserve = temp; if (set(temp, "owner_reserve", section)) setup.owner_reserve = temp; + if (set(temp, "extension_compute_limit", section)) + setup.extension_compute_limit = temp; + if (set(temp, "extension_size_limit", section)) + setup.extension_size_limit = temp; + if (set(temp, "gas_price", section)) + setup.gas_price = temp; } return setup; } diff --git a/src/xrpld/rpc/InfoSub.h b/src/xrpld/rpc/InfoSub.h index 97ff266364..32b8e96d1d 100644 --- a/src/xrpld/rpc/InfoSub.h +++ b/src/xrpld/rpc/InfoSub.h @@ -158,6 +158,11 @@ public: virtual bool unsubConsensus(std::uint64_t uListener) = 0; + virtual bool + subContractEvent(ref ispListener) = 0; + virtual bool + unsubContractEvent(std::uint64_t uListener) = 0; + // VFALCO TODO Remove // This was added for one particular partner, it // "pushes" subscription data to a particular URL. diff --git a/src/xrpld/rpc/detail/Handler.cpp b/src/xrpld/rpc/detail/Handler.cpp index 2000af5e81..5c263bb740 100644 --- a/src/xrpld/rpc/detail/Handler.cpp +++ b/src/xrpld/rpc/detail/Handler.cpp @@ -84,6 +84,7 @@ Handler const handlerArray[]{ {"channel_verify", byRef(&doChannelVerify), Role::USER, NO_CONDITION}, {"connect", byRef(&doConnect), Role::ADMIN, NO_CONDITION}, {"consensus_info", byRef(&doConsensusInfo), Role::ADMIN, NO_CONDITION}, + {"contract_info", byRef(&doContractInfo), Role::USER, NO_CONDITION}, {"deposit_authorized", byRef(&doDepositAuthorized), Role::USER, diff --git a/src/xrpld/rpc/detail/InfoSub.cpp b/src/xrpld/rpc/detail/InfoSub.cpp index a0869b9d96..e7e61e8d46 100644 --- a/src/xrpld/rpc/detail/InfoSub.cpp +++ b/src/xrpld/rpc/detail/InfoSub.cpp @@ -32,6 +32,7 @@ InfoSub::~InfoSub() m_source.unsubValidations(mSeq); m_source.unsubPeerStatus(mSeq); m_source.unsubConsensus(mSeq); + m_source.unsubContractEvent(mSeq); // Use the internal unsubscribe so that it won't call // back to us and modify its own parameter diff --git a/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp b/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp index c58a7ba561..6c6e331f0c 100644 --- a/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp @@ -507,5 +507,42 @@ getOrAcquireLedger(RPC::JsonContext const& context) rpcNOT_READY, "findCreate failed to return an inbound ledger")); } +/** + * @brief Injects JSON describing a ledger entry. + * + * @param jv The JSON value to populate. + * @param sle The ledger entry to describe. + * + * @details + * Populates the provided JSON value with the description of the specified + * ledger entry. If the entry is an account root and contains an email hash, + * adds a 'urlgravatar' field with the corresponding Gravatar URL. + * If the entry is not an account root, sets the 'Invalid' field to true. + */ +void +injectSLE(Json::Value& jv, SLE const& sle) +{ + jv = sle.getJson(JsonOptions::none); + if (sle.getType() == ltACCOUNT_ROOT) + { + if (sle.isFieldPresent(sfEmailHash)) + { + auto const& hash = sle.getFieldH128(sfEmailHash); + Blob const b(hash.begin(), hash.end()); + std::string md5 = strHex(makeSlice(b)); + boost::to_lower(md5); + // VFALCO TODO Give a name and move this constant + // to a more visible location. Also + // shouldn't this be https? + jv[jss::urlgravatar] = + str(boost::format("http://www.gravatar.com/avatar/%s") % md5); + } + } + else + { + jv[jss::Invalid] = true; + } +} + } // namespace RPC } // namespace xrpl diff --git a/src/xrpld/rpc/detail/RPCLedgerHelpers.h b/src/xrpld/rpc/detail/RPCLedgerHelpers.h index 8fe81ef024..5e79a71ec6 100644 --- a/src/xrpld/rpc/detail/RPCLedgerHelpers.h +++ b/src/xrpld/rpc/detail/RPCLedgerHelpers.h @@ -174,6 +174,9 @@ ledgerFromSpecifier( Expected, Json::Value> getOrAcquireLedger(RPC::JsonContext const& context); +void +injectSLE(Json::Value& jv, SLE const& sle); + } // namespace RPC } // namespace xrpl diff --git a/src/xrpld/rpc/handlers/AccountInfo.cpp b/src/xrpld/rpc/handlers/AccountInfo.cpp index 30d16e3099..fbac489f47 100644 --- a/src/xrpld/rpc/handlers/AccountInfo.cpp +++ b/src/xrpld/rpc/handlers/AccountInfo.cpp @@ -15,43 +15,6 @@ namespace xrpl { -/** - * @brief Injects JSON describing a ledger entry. - * - * @param jv The JSON value to populate. - * @param sle The ledger entry to describe. - * - * @details - * Populates the provided JSON value with the description of the specified - * ledger entry. If the entry is an account root and contains an email hash, - * adds a 'urlgravatar' field with the corresponding Gravatar URL. - * If the entry is not an account root, sets the 'Invalid' field to true. - */ -void -injectSLE(Json::Value& jv, SLE const& sle) -{ - jv = sle.getJson(JsonOptions::none); - if (sle.getType() == ltACCOUNT_ROOT) - { - if (sle.isFieldPresent(sfEmailHash)) - { - auto const& hash = sle.getFieldH128(sfEmailHash); - Blob const b(hash.begin(), hash.end()); - std::string md5 = strHex(makeSlice(b)); - boost::to_lower(md5); - // VFALCO TODO Give a name and move this constant - // to a more visible location. Also - // shouldn't this be https? - jv[jss::urlgravatar] = - str(boost::format("http://www.gravatar.com/avatar/%s") % md5); - } - } - else - { - jv[jss::Invalid] = true; - } -} - // { // account: , // ledger_hash : @@ -147,7 +110,7 @@ doAccountInfo(RPC::JsonContext& context) } Json::Value jvAccepted(Json::objectValue); - injectSLE(jvAccepted, *sleAccepted); + RPC::injectSLE(jvAccepted, *sleAccepted); result[jss::account_data] = jvAccepted; Json::Value acctFlags{Json::objectValue}; diff --git a/src/xrpld/rpc/handlers/ContractInfo.cpp b/src/xrpld/rpc/handlers/ContractInfo.cpp new file mode 100644 index 0000000000..e3ac792983 --- /dev/null +++ b/src/xrpld/rpc/handlers/ContractInfo.cpp @@ -0,0 +1,185 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2014 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +// { +// contract_account: , +// function : // optional +// user_account : +// ledger_index : +// } + +Json::Value +doContractInfo(RPC::JsonContext& context) +{ + auto& params = context.params; + + std::string contractAccount; + if (params.isMember(jss::contract_account)) + { + if (!params[jss::contract_account].isString()) + return RPC::invalid_field_error(jss::contract_account); + contractAccount = params[jss::contract_account].asString(); + } + else + return RPC::missing_field_error(jss::contract_account); + + std::string functionName; + if (params.isMember(jss::function)) + { + if (!params[jss::function].isString()) + return RPC::invalid_field_error(jss::function); + functionName = params[jss::function].asString(); + } + + std::string account; + if (params.isMember(jss::account)) + { + if (!params[jss::account].isString()) + return RPC::invalid_field_error(jss::account); + account = params[jss::account].asString(); + } + + std::shared_ptr ledger; + auto result = RPC::lookupLedger(ledger, context); + + if (!ledger) + return result; + + // contract account + auto caid = parseBase58(contractAccount); + if (!caid) + { + RPC::inject_error(rpcACT_MALFORMED, result); + return result; + } + auto const caID{std::move(caid.value())}; + auto const caSle = ledger->read(keylet::account(caID)); + if (!caSle) + { + result[jss::contract_account] = toBase58(caID); + RPC::inject_error(rpcACT_NOT_FOUND, result); + } + + uint256 const contractID = caSle->getFieldH256(sfContractID); + auto const contractSle = ledger->read(keylet::contract(contractID)); + if (!contractSle) + { + result[jss::contract_account] = toBase58(caID); + RPC::inject_error(rpcOBJECT_NOT_FOUND, result); + } + + // contract source + if (!contractSle->at(sfContractHash)) + { + result[jss::contract_account] = toBase58(caID); + RPC::inject_error(rpcUNKNOWN, result); + } + + auto const sourceSle = + ledger->read(keylet::contractSource(contractSle->at(sfContractHash))); + if (!sourceSle) + { + result[jss::contract_account] = toBase58(caID); + RPC::inject_error(rpcOBJECT_NOT_FOUND, result); + } + + result[jss::contract_account] = toBase58(caID); + result[jss::code] = strHex(sourceSle->at(sfContractCode)); + result[jss::hash] = to_string(sourceSle->at(sfContractHash)); + + // lambda to format the functions response: + // name: + // params: [: , : , : ] + auto formatFunctions = [](Json::Value& jv, + std::shared_ptr const& slePtr) { + if (slePtr && slePtr->isFieldPresent(sfFunctions)) + { + auto const& functions = slePtr->getFieldArray(sfFunctions); + for (auto const& function : functions) + { + Json::Value jvFunction(Json::objectValue); + jvFunction[jss::name] = + strHex(function.getFieldVL(sfFunctionName)); + Json::Value jvParams(Json::arrayValue); + for (auto const& param : function.getFieldArray(sfParameters)) + { + Json::Value jvParam(Json::objectValue); + jvParam[jss::flags] = param.getFieldU32(sfParameterFlag); + jvParam[jss::type] = param.getFieldDataType(sfParameterType) + .getInnerTypeString(); + jvParams.append(jvParam); + } + jvFunction[jss::params] = std::move(jvParams); + jv.append(std::move(jvFunction)); + } + } + }; + if (sourceSle->isFieldPresent(sfFunctions)) + formatFunctions(result[jss::functions], sourceSle); + if (contractSle->isFieldPresent(sfURI)) + result[jss::source_code_uri] = strHex(contractSle->at(sfURI)); + + Json::Value jvAccepted(Json::objectValue); + RPC::injectSLE(jvAccepted, *caSle); + result[jss::account_data] = jvAccepted; + + auto const dataSle = ledger->read(keylet::contractData(caID, caID)); + if (dataSle) + result[jss::contract_data] = + dataSle->getFieldJson(sfContractJson).getJson(JsonOptions::none); + + if (!account.empty()) + { + auto id = parseBase58(account); + if (!id) + { + RPC::inject_error(rpcACT_MALFORMED, result); + return result; + } + auto const accountID = id.value(); + if (ledger->exists(keylet::account(accountID))) + { + if (auto dataSle = + ledger->read(keylet::contractData(accountID, caID))) + result[jss::user_data] = dataSle->getFieldJson(sfContractJson) + .getJson(JsonOptions::none); + } + } + + return result; +} + +} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/Handlers.h b/src/xrpld/rpc/handlers/Handlers.h index 773186237b..57f745d25e 100644 --- a/src/xrpld/rpc/handlers/Handlers.h +++ b/src/xrpld/rpc/handlers/Handlers.h @@ -40,6 +40,8 @@ doConnect(RPC::JsonContext&); Json::Value doConsensusInfo(RPC::JsonContext&); Json::Value +doContractInfo(RPC::JsonContext&); +Json::Value doDepositAuthorized(RPC::JsonContext&); Json::Value doFeature(RPC::JsonContext&); diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp index 5b5db72c22..8d694fe077 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -716,6 +716,71 @@ parseXChainOwnedCreateAccountClaimID( return keylet.key; } +static Expected +parseContractSource( + Json::Value const& params, + Json::StaticString const fieldName) +{ + if (!params.isObject()) + { + return parseObjectID(params, fieldName); + } + + auto const id = LedgerEntryHelpers::requiredAccountID( + params, jss::owner, "malformedOwner"); + if (!id) + return Unexpected(id.error()); + + auto const seq = LedgerEntryHelpers::requiredUInt32( + params, jss::seq, "malformedRequest"); + if (!seq) + return Unexpected(seq.error()); + + return keylet::vault(*id, *seq).key; +} + +static Expected +parseContract(Json::Value const& params, Json::StaticString const fieldName) +{ + if (!params.isObject()) + { + return parseObjectID(params, fieldName); + } + + auto const id = LedgerEntryHelpers::requiredAccountID( + params, jss::owner, "malformedOwner"); + if (!id) + return Unexpected(id.error()); + + auto const seq = LedgerEntryHelpers::requiredUInt32( + params, jss::seq, "malformedRequest"); + if (!seq) + return Unexpected(seq.error()); + + return keylet::vault(*id, *seq).key; +} + +static Expected +parseContractData(Json::Value const& params, Json::StaticString const fieldName) +{ + if (!params.isObject()) + { + return parseObjectID(params, fieldName); + } + + auto const id = LedgerEntryHelpers::requiredAccountID( + params, jss::owner, "malformedOwner"); + if (!id) + return Unexpected(id.error()); + + auto const seq = LedgerEntryHelpers::requiredUInt32( + params, jss::seq, "malformedRequest"); + if (!seq) + return Unexpected(seq.error()); + + return keylet::vault(*id, *seq).key; +} + using FunctionType = Expected (*)( Json::Value const&, Json::StaticString const); diff --git a/src/xrpld/rpc/handlers/ServerDefinitions.cpp b/src/xrpld/rpc/handlers/ServerDefinitions.cpp index 9fce4d5a5b..ed55e2c1fb 100644 --- a/src/xrpld/rpc/handlers/ServerDefinitions.cpp +++ b/src/xrpld/rpc/handlers/ServerDefinitions.cpp @@ -75,6 +75,7 @@ ServerDefinitions::translate(std::string const& inp) {"PATHSET", "PathSet"}, {"VL", "Blob"}, {"XCHAIN_BRIDGE", "XChainBridge"}, + {"DATATYPE", "DataType"}, }; if (auto const& it = replacements.find(inp); it != replacements.end()) diff --git a/src/xrpld/rpc/handlers/Subscribe.cpp b/src/xrpld/rpc/handlers/Subscribe.cpp index 018268defb..591285c883 100644 --- a/src/xrpld/rpc/handlers/Subscribe.cpp +++ b/src/xrpld/rpc/handlers/Subscribe.cpp @@ -149,6 +149,10 @@ doSubscribe(RPC::JsonContext& context) { context.netOps.subConsensus(ispSub); } + else if (streamName == "contract_events") + { + context.netOps.subContractEvent(ispSub); + } else { return rpcError(rpcSTREAM_MALFORMED); diff --git a/src/xrpld/rpc/handlers/Unsubscribe.cpp b/src/xrpld/rpc/handlers/Unsubscribe.cpp index bc192510bf..3020a694a2 100644 --- a/src/xrpld/rpc/handlers/Unsubscribe.cpp +++ b/src/xrpld/rpc/handlers/Unsubscribe.cpp @@ -84,6 +84,10 @@ doUnsubscribe(RPC::JsonContext& context) { context.netOps.unsubConsensus(ispSub->getSeq()); } + else if (streamName == "contract_events") + { + context.netOps.unsubContractEvent(ispSub->getSeq()); + } else { return rpcError(rpcSTREAM_MALFORMED);