Compare commits

...
Author SHA1 Message Date
Olek 7bdf5fa8b8 Fix build.md wamr version (#5535) 2025-07-04 00:48:03 +05:30
Olek 65b0b976d9 Sync error codes (#5527)
* Sync error codes
2025-07-02 17:33:39 -04:00
Elliot. a0d275feec chore: Clear CODEOWNERS (#5528) 2025-07-02 10:39:57 -07:00
Mayukha Vadari ece3a8d7be Merge branch 'develop' into develop4 2025-06-30 21:33:30 +05:30
Olek 463acf51b5 preflight checks for wasm (#5517) 2025-06-30 09:34:38 -04:00
Olek 1cd16fab87 Host-functions perf test fixes (#5514) 2025-06-27 09:59:28 -04:00
Olek add55c4f33 Host functions gas cost for wasm_runtime interface (#5500) 2025-06-25 14:04:04 +00:00
Olek 51a9c0ff59 Host function gas cost (#5488)
* Update Wamr to 2.3.1
* Add gas cost per host-function
* Fix windows build
* Fix wasm test
* Add no import test
2025-06-12 15:54:49 -04:00
Mayukha VadariandOlek 6e8a5f0f4e fix: make host function traces easier to use, fix get_NFT bug (#5466)
Co-authored-by: Olek <[email protected]>
2025-06-05 14:24:13 -04:00
Mayukha Vadari 8a33702f26 fix merge issues 2025-06-05 12:38:26 -04:00
Mayukha Vadari a072d49802 Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop3.5 2025-06-05 11:51:53 -04:00
Mayukha Vadari a0aeeb8e07 Merge remote-tracking branch 'upstream/develop' into develop3.5 2025-06-05 11:50:38 -04:00
Olek 383b225690 Fix processing nonexistent field (#5467) 2025-06-04 17:32:11 -04:00
Mayukha Vadari ace2247800 Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop3.5 2025-06-04 14:15:17 -04:00
Olek 6a6fed5dce More hostfunctions (#5451)
* Bug fixes:
- Fix bugs found during schedule table tests
- Add more tests
- Add parameters passing for runEscrowWasm function

* Add new host-functions
 fix wamr logging
 add runtime passing through HF
 fix runEscrowWasm interface

* Improve logs

* Fix logging bug

* Set 4k limit for update_data HF

* allHF wasm module fixes
2025-05-30 19:01:27 -04:00
Mayukha Vadari 1f8aece8cd feat: add a GasUsed parameter to the metadata (#5456) 2025-05-29 16:36:55 -04:00
Mayukha Vadari 6c6f8cd4f9 Merge remote-tracking branch 'upstream/develop' into develop3 2025-05-29 13:05:11 -04:00
Mayukha Vadari fb1311e013 uncomment???? 2025-05-28 14:00:50 -04:00
Mayukha Vadari ce31acf030 debug comments 2025-05-28 13:48:38 -04:00
Mayukha Vadari 31ad5ac63b Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop3 2025-05-27 18:29:41 -04:00
Mayukha Vadari 1ede0bdec4 fix: fix fixtures (#5445) 2025-05-23 17:37:14 -04:00
Mayukha Vadari aef32ead2c better WASM logging to match rippled (#5395)
* basic logging

* pass in Journal

* log level based on journal level

* clean up

* attempt at adding WAMR logging properly

* improve logline

* maybe_unused

* fix

* fix

* fix segfault

* add test
2025-05-23 10:31:02 -04:00
Mayukha Vadari 5b43ec7f73 refactor: switch function name from ready to finish (#5430) 2025-05-20 16:12:19 -04:00
Olek 1e9ff88a00 Fix CI build issues
* Mac build fix
* Windows build fix
* Windows instruction counter fix
2025-05-08 12:39:37 -04:00
Mayukha Vadari bb9bb5f5c5 Merge branch 'ripple/smart-escrow' into develop2 2025-05-01 18:44:06 -04:00
Mayukha Vadari c533abd8b6 Update size and compute cap defaults (#5417) 2025-05-01 18:41:51 -04:00
Olek bb9bc764bc Switch to WAMR (#5416)
* Switch to WAMR
2025-05-01 18:02:06 -04:00
Mayukha Vadari b4b53a6cb7 Merge branch 'ripple/smart-escrow' into develop2 2025-04-29 15:25:54 -04:00
Mayukha Vadari 9c0204906c fix reference fee tests 2025-04-29 15:25:00 -04:00
Mayukha Vadari 4670b373c1 try to fix tests 2025-04-29 14:10:27 -04:00
Mayukha Vadari f03b5883bd More host functions (#5411)
* getNFT

* escrow keylet

* account keylet

* credential keylet

* oracle keylet

* hook everything in

* fix stuff
2025-04-29 12:39:12 -04:00
Mayukha Vadari f8b2fe4dd5 fix imports 2025-04-28 17:43:15 -04:00
Mayukha Vadari be4a0c9c2b Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop2 2025-04-28 17:14:28 -04:00
Mayukha Vadari f37d52d8e9 Set up fees for WASM processing (#5393)
* set up fields

* throw error if allowance is too high

* votable gas price

* fix comments

* hook everything together

* make test less flaky (hopefully)

* fix other tests

* fix some tests

* fix tests

* clean up

* add more tests

* uncomment other tests

* respond to comments

* fix build

* respond to comments
2025-04-24 08:47:13 -04:00
Mayukha Vadari 177cdaf550 Connect votable gas limit into VM (#5360)
* [WIP] add gas limit

* [WIP] host function escrow tests

* finish test

* uncomment out tests
2025-03-25 10:55:33 -04:00
pwang200 1573a443b7 smart escrow devnet 1 host functions (#5353)
* devnet 1 host functions

* clang-format

* fix build issues
2025-03-24 17:07:17 -04:00
911c0466c0 Merge develop into ripple/smart-escrow (#5357)
* Set version to 2.4.0

* refactor: Remove unused and add missing includes (#5293)

The codebase is filled with includes that are unused, and which thus can be removed. At the same time, the files often do not include all headers that contain the definitions used in those files. This change uses clang-format and clang-tidy to clean up the includes, with minor manual intervention to ensure the code compiles on all platforms.

* refactor: Calculate numFeatures automatically (#5324)

Requiring manual updates of numFeatures is an annoying manual process that is easily forgotten, and leads to frequent merge conflicts. This change takes advantage of the `XRPL_FEATURE` and `XRPL_FIX` macros, and adds a new `XRPL_RETIRE` macro to automatically set `numFeatures`.

* refactor: Improve ordering of headers with clang-format (#5343)

Removes all manual header groupings from source and header files by leveraging clang-format options.

* Rename "deadlock" to "stall" in `LoadManager` (#5341)

What the LoadManager class does is stall detection, which is not the same as deadlock detection. In the condition of severe CPU starvation, LoadManager will currently intentionally crash rippled reporting `LogicError: Deadlock detected`. This error message is misleading as the condition being detected is not a deadlock. This change fixes and refactors the code in response.

* Adds hub.xrpl-commons.org as a new Bootstrap Cluster (#5263)

* fix: Error message for ledger_entry rpc (#5344)

Changes the error to `malformedAddress` for `permissioned_domain` in the `ledger_entry` rpc, when the account is not a string. This change makes it more clear to a user what is wrong with their request.

* fix: Handle invalid marker parameter in grpc call (#5317)

The `end_marker` is used to limit the range of ledger entries to fetch. If `end_marker` is less than `marker`, a crash can occur. This change adds an additional check.

* fix: trust line RPC no ripple flag (#5345)

The Trustline RPC `no_ripple` flag gets set depending on `lsfDefaultRipple` flag, which is not a flag of a trustline but of the account root. The `lsfDefaultRipple` flag does not provide any insight if this particular trust line has `lsfLowNoRipple` or `lsfHighNoRipple` flag set, so it should not be used here at all. This change simplifies the logic.

* refactor: Updates Conan dependencies: RocksDB (#5335)

Updates RocksDB to version 9.7.3, the latest version supported in Conan 1.x. A patch for 9.7.4 that fixes a memory leak is included.

* fix: Remove null pointer deref, just do abort (#5338)

This change removes the existing undefined behavior from `LogicError`, so we can be certain that there will be always a stacktrace.

De-referencing a null pointer is an old trick to generate `SIGSEGV`, which would typically also create a stacktrace. However it is also an undefined behaviour and compilers can do something else. A more robust way to create a stacktrace while crashing the program is to use `std::abort`, which we have also used in this location for a long time. If we combine the two, we might not get the expected behaviour - namely, the nullpointer deref followed by `std::abort`, as handled in certain compiler versions may not immediately cause a crash. We have observed stacktrace being wiped instead, and thread put in indeterminate state, then stacktrace created without any useful information.

* chore: Add PR number to payload (#5310)

This PR adds one more payload field to the libXRPL compatibility check workflow - the PR number itself.

* chore: Update link to ripple-binary-codec (#5355)

The link to ripple-binary-codec's definitions.json appears to be outdated. The updated link is also documented here: https://xrpl.org/docs/references/protocol/binary-format#definitions-file

* Prevent consensus from getting stuck in the establish phase (#5277)

- Detects if the consensus process is "stalled". If it is, then we can declare a 
  consensus and end successfully even if we do not have 80% agreement on
  our proposal.
  - "Stalled" is defined as:
    - We have a close time consensus
    - Each disputed transaction is individually stalled:
      - It has been in the final "stuck" 95% requirement for at least 2
        (avMIN_ROUNDS) "inner rounds" of phaseEstablish,
      - and either all of the other trusted proposers or this validator, if proposing,
        have had the same vote(s) for at least 4 (avSTALLED_ROUNDS) "inner
        rounds", and at least 80% of the validators (including this one, if
        appropriate) agree about the vote (whether yes or no).
- If we have been in the establish phase for more than 10x the previous
  consensus establish phase's time, then consensus is considered "expired",
  and we will leave the round, which sends a partial validation (indicating
  that the node is moving on without validating). Two restrictions avoid
  prematurely exiting, or having an extended exit in extreme situations.
  - The 10x time is clamped to be within a range of 15s
    (ledgerMAX_CONSENSUS) to 120s (ledgerABANDON_CONSENSUS).
  - If consensus has not had an opportunity to walk through all avalanche
    states (defined as not going through 8 "inner rounds" of phaseEstablish),
    then ConsensusState::Expired is treated as ConsensusState::No.
- When enough nodes leave the round, any remaining nodes will see they've
  fallen behind, and move on, too, generally before hitting the timeout. Any
  validations or partial validations sent during this time will help the
  consensus process bring the nodes back together.

---------

Co-authored-by: Michael Legleux <[email protected]>
Co-authored-by: Bart <[email protected]>
Co-authored-by: Ed Hennis <[email protected]>
Co-authored-by: Bronek Kozicki <[email protected]>
Co-authored-by: Darius Tumas <[email protected]>
Co-authored-by: Sergey Kuznetsov <[email protected]>
Co-authored-by: cyan317 <[email protected]>
Co-authored-by: Vlad <[email protected]>
Co-authored-by: Alex Kremer <[email protected]>
2025-03-20 16:47:14 -04:00
Mayukha VadariandPeng Wang b6a95f9970 PoC Smart Escrows (#5340)
* wasmedge in unittest

* add WashVM.h and cpp

* accountID comparison (vector<u8>) working

* json decode tx and ledger object with two buffers working

* wasm return a buffer working

* add a failure test case to P2P3

* host function return ledger sqn

* instruction gas and host function gas

* basics

* add scaffold

* add amendment check

* working PoC

* get test working

* fix clang-format

* prototype #2

* p2p3

* [WIP] P4

* P5

* add calculateBaseFee

* add FinishFunction preflight checks (+ tests)

* additional reserve for sfFinishFunction

* higher fees for EscrowFinish

* rename amendment to SmartEscrow

* make fee voting changes, add basic tests

* clean up

* clean up

* clean up

* more cleanup

* add subscribe tests

* add more tests

* undo formatting

* undo formatting

* remove bad comment

* more debugging statements

* fix clang-format

* fix rebase issues

* fix more rebase issues

* more rebase fixes

* add source code for wasm

* respond to comments

* add const

---------

Co-authored-by: Peng Wang <[email protected]>
2025-03-20 14:08:06 -04:00
82 changed files with 19648 additions and 252 deletions

No files matched your search

+38 -38
View File
@@ -1,5 +1,5 @@
---
Language: Cpp
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: false
@@ -19,52 +19,52 @@ AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterClass: true
AfterClass: true
AfterControlStatement: true
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
BreakBeforeBinaryOperators: false
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
ColumnLimit: 80
CommentPragmas: "^ IWYU pragma:"
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
ForEachMacros: [ Q_FOREACH, BOOST_FOREACH ]
IncludeBlocks: Regroup
ForEachMacros: [Q_FOREACH, BOOST_FOREACH]
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<(test)/'
Priority: 0
- Regex: '^<(xrpld)/'
Priority: 1
- Regex: '^<(xrpl)/'
Priority: 2
- Regex: '^<(boost)/'
Priority: 3
- Regex: '^.*/'
Priority: 4
- Regex: '^.*\.h'
Priority: 5
- Regex: '.*'
Priority: 6
IncludeIsMainRegex: '$'
- Regex: "^<(test)/"
Priority: 0
- Regex: "^<(xrpld)/"
Priority: 1
- Regex: "^<(xrpl)/"
Priority: 2
- Regex: "^<(boost)/"
Priority: 3
- Regex: "^.*/"
Priority: 4
- Regex: '^.*\.h'
Priority: 5
- Regex: ".*"
Priority: 6
IncludeIsMainRegex: "$"
IndentCaseLabels: true
IndentFunctionDeclarationAfterType: false
IndentRequiresClause: true
IndentWidth: 4
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 1
@@ -78,20 +78,20 @@ PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: true
ReflowComments: true
RequiresClausePosition: OwnLine
SortIncludes: true
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 8
UseTab: Never
QualifierAlignment: Right
Standard: Cpp11
TabWidth: 8
UseTab: Never
QualifierAlignment: Right
-6
View File
@@ -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
+1
View File
@@ -17,6 +17,7 @@ runs:
conan export external/rocksdb rocksdb/9.7.3@
conan export external/soci soci/4.0.3@
conan export external/nudb nudb/2.0.8@
conan export external/wamr wamr/2.3.1@
- name: add Ripple Conan remote
shell: bash
run: |
+1
View File
@@ -411,6 +411,7 @@ jobs:
conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_DISABLE_CONCEPTS"]' default
conan export external/snappy snappy/1.1.10@
conan export external/soci soci/4.0.3@
conan export external/wamr wamr/2.3.1@
- name: build dependencies
run: |
+11
View File
@@ -204,6 +204,17 @@ It fixes some source files to add missing `#include`s.
conan export --version 2.0.8 external/nudb
```
Export our [Conan recipe for WAMR](./external/wamr).
It add metering and expose some internal structures.
```
# Conan 1.x
conan export external/wamr wamr/2.3.1@
# Conan 2.x
conan export --version 2.3.1 external/wamr
```
### Build and Test
1. Create a build directory and move into it.
+1
View File
@@ -120,6 +120,7 @@ endif()
find_package(nudb REQUIRED)
find_package(date REQUIRED)
find_package(xxHash REQUIRED)
find_package(wamr REQUIRED)
target_link_libraries(ripple_libs INTERFACE
ed25519::ed25519
+33
View File
@@ -1249,6 +1249,39 @@
# Example:
# owner_reserve = 2000000 # 2 XRP
#
# extension_compute_limit = <gas>
#
# The extension compute limit is the maximum amount of gas that can be
# consumed by a single transaction. The gas limit is used to prevent
# transactions from consuming too many resources.
#
# If this parameter is unspecified, 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 = <bytes>
#
# The extension size limit is the maximum size of a WASM extension in
# bytes. The size limit is used to prevent extensions from consuming
# too many resources.
#
# If this parameter is unspecified, rippled will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# extension_size_limit = 100000 # 100 kb
#
# gas_price = <bytes>
#
# The gas price is the conversion between WASM gas and its price in drops.
#
# If this parameter is unspecified, 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
+1
View File
@@ -65,6 +65,7 @@ target_link_libraries(xrpl.imports.main
xrpl.libpb
xxHash::xxhash
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
wamr::wamr
)
include(add_module)
+3
View File
@@ -2,6 +2,7 @@ from conan import ConanFile, __version__ as conan_version
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
import re
class Xrpl(ConanFile):
name = 'xrpl'
@@ -31,6 +32,7 @@ class Xrpl(ConanFile):
'openssl/1.1.1v',
'soci/4.0.3',
'zlib/1.3.1',
'wamr/2.3.1',
]
tool_requires = [
@@ -128,6 +130,7 @@ class Xrpl(ConanFile):
self.folders.generators = 'build/generators'
generators = 'CMakeDeps'
def generate(self):
tc = CMakeToolchain(self)
tc.variables['tests'] = self.options.tests
+6
View File
@@ -0,0 +1,6 @@
patches:
2.3.1:
- patch_description: add metering to iwasm interpreter
patch_file: patches/ripp_metering.patch
patch_type: conan
+92
View File
@@ -0,0 +1,92 @@
from conans import ConanFile, tools
from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps, cmake_layout
from conan.tools.files import (
apply_conandata_patches,
export_conandata_patches,
# get,
)
# import os
required_conan_version = ">=1.55.0"
class WamrConan(ConanFile):
name = "wamr"
version = "2.3.1"
license = "Apache License v2.0"
url = "https://github.com/bytecodealliance/wasm-micro-runtime.git"
description = "Webassembly micro runtime"
package_type = "library"
settings = "os", "compiler", "build_type", "arch"
options = {"shared": [True, False], "fPIC": [True, False]}
default_options = {"shared": False, "fPIC": True}
generators = "CMakeToolchain", "CMakeDeps"
# requires = [("llvm/20.1.1@")]
def export_sources(self):
export_conandata_patches(self)
pass
# def build_requirements(self):
# self.tool_requires("llvm/20.1.1")
def config_options(self):
if self.settings.os == "Windows":
del self.options.fPIC
def layout(self):
cmake_layout(self, src_folder="src")
def source(self):
git = tools.Git()
git.clone(
"https://github.com/bytecodealliance/wasm-micro-runtime.git",
"2a303861cc916dc182b7fecaa0aacc1b797e7ac6",
shallow=True,
)
# get(self, **self.conan_data["sources"][self.version], strip_root=True)
def generate(self):
tc = CMakeToolchain(self)
tc.variables["WAMR_BUILD_INTERP"] = 1
tc.variables["WAMR_BUILD_FAST_INTERP"] = 1
tc.variables["WAMR_BUILD_INSTRUCTION_METERING"] = 1
tc.variables["WAMR_BUILD_AOT"] = 0
tc.variables["WAMR_BUILD_JIT"] = 0
tc.variables["WAMR_BUILD_FAST_JIT"] = 0
tc.variables["WAMR_DISABLE_HW_BOUND_CHECK"] = 1
tc.variables["WAMR_DISABLE_STACK_HW_BOUND_CHECK"] = 1
tc.variables["WAMR_BH_LOG"] = "wamr_log_to_rippled"
# tc.variables["WAMR_BUILD_FAST_JIT"] = 0 if self.settings.os == "Windows" else 1
# ll_dep = self.dependencies["llvm"]
# self.output.info(f"-----------package_folder: {type(ll_dep.__dict__)}")
# tc.variables["LLVM_DIR"] = os.path.join(ll_dep.package_folder, "lib", "cmake", "llvm")
tc.generate()
# This generates "foo-config.cmake" and "bar-config.cmake" in self.generators_folder
deps = CMakeDeps(self)
deps.generate()
def build(self):
apply_conandata_patches(self)
cmake = CMake(self)
cmake.verbose = True
cmake.configure()
cmake.build()
# self.run(f'echo {self.source_folder}')
# Explicit way:
# self.run('cmake %s/hello %s' % (self.source_folder, cmake.command_line))
# self.run("cmake --build . %s" % cmake.build_config)
def package(self):
cmake = CMake(self)
cmake.verbose = True
cmake.install()
def package_info(self):
self.cpp_info.libs = ["iwasm"]
self.cpp_info.names["cmake_find_package"] = "wamr"
self.cpp_info.names["cmake_find_package_multi"] = "wamr"
+724
View File
@@ -0,0 +1,724 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 88a1642b..aeb29912 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-cmake_minimum_required (VERSION 3.14)
+cmake_minimum_required (VERSION 3.20)
option(BUILD_SHARED_LIBS "Build using shared libraries" OFF)
diff --git a/core/iwasm/aot/aot_runtime.c b/core/iwasm/aot/aot_runtime.c
index b2c9ed62..87947a18 100644
--- a/core/iwasm/aot/aot_runtime.c
+++ b/core/iwasm/aot/aot_runtime.c
@@ -5484,7 +5484,7 @@ aot_resolve_import_func(AOTModule *module, AOTImportFunc *import_func)
import_func->func_ptr_linked = wasm_native_resolve_symbol(
import_func->module_name, import_func->func_name,
import_func->func_type, &import_func->signature,
- &import_func->attachment, &import_func->call_conv_raw);
+ &import_func->attachment, NULL, &import_func->call_conv_raw);
#if WASM_ENABLE_MULTI_MODULE != 0
if (!import_func->func_ptr_linked) {
if (!wasm_runtime_is_built_in_module(import_func->module_name)) {
diff --git a/core/iwasm/common/wasm_c_api.c b/core/iwasm/common/wasm_c_api.c
index 269ec577..34eb7c34 100644
--- a/core/iwasm/common/wasm_c_api.c
+++ b/core/iwasm/common/wasm_c_api.c
@@ -3242,10 +3242,20 @@ wasm_func_copy(const wasm_func_t *func)
cloned->func_idx_rt = func->func_idx_rt;
cloned->inst_comm_rt = func->inst_comm_rt;
+ cloned->gas = func->gas;
RETURN_OBJ(cloned, wasm_func_delete)
}
+uint32_t
+wasm_func_set_gas(wasm_func_t *func, uint32_t gas)
+{
+ if(!func) return 0;
+
+ func->gas = gas;
+ return gas;
+}
+
own wasm_functype_t *
wasm_func_type(const wasm_func_t *func)
{
@@ -4998,11 +5008,11 @@ wasm_instance_new_with_args_ex(wasm_store_t *store, const wasm_module_t *module,
goto failed;
}
+ WASMModuleInstance *wasm_module_inst = NULL;
/* create the c-api func import list */
#if WASM_ENABLE_INTERP != 0
if (instance->inst_comm_rt->module_type == Wasm_Module_Bytecode) {
- WASMModuleInstance *wasm_module_inst =
- (WASMModuleInstance *)instance->inst_comm_rt;
+ wasm_module_inst = (WASMModuleInstance *)instance->inst_comm_rt;
p_func_imports = &(wasm_module_inst->c_api_func_imports);
import_func_count = MODULE_INTERP(module)->import_function_count;
}
@@ -5052,6 +5062,13 @@ wasm_instance_new_with_args_ex(wasm_store_t *store, const wasm_module_t *module,
}
bh_assert(func_import->func_ptr_linked);
+ // fill gas
+ if(wasm_module_inst) {
+ WASMFunctionInstance *fi = wasm_module_inst->e->functions + func_host->func_idx_rt;
+ if(fi) fi->gas = func_host->gas;
+ }
+
+
func_import++;
}
@@ -5389,3 +5406,8 @@ wasm_instance_get_wasm_func_exec_time(const wasm_instance_t *instance,
return -1.0;
#endif
}
+
+wasm_exec_env_t wasm_instance_exec_env(const wasm_instance_t *instance)
+{
+ return wasm_runtime_get_exec_env_singleton(instance->inst_comm_rt);
+}
diff --git a/core/iwasm/common/wasm_c_api_internal.h b/core/iwasm/common/wasm_c_api_internal.h
index 49a17a96..19a85980 100644
--- a/core/iwasm/common/wasm_c_api_internal.h
+++ b/core/iwasm/common/wasm_c_api_internal.h
@@ -142,6 +142,10 @@ struct wasm_func_t {
void (*finalizer)(void *);
} cb_env;
} u;
+
+ // gas cost for import func
+ uint32 gas;
+
/*
* an index in both functions runtime instance lists
* of interpreter mode and aot mode
diff --git a/core/iwasm/common/wasm_exec_env.c b/core/iwasm/common/wasm_exec_env.c
index 47752950..d5821c4f 100644
--- a/core/iwasm/common/wasm_exec_env.c
+++ b/core/iwasm/common/wasm_exec_env.c
@@ -86,7 +86,9 @@ wasm_exec_env_create_internal(struct WASMModuleInstanceCommon *module_inst,
#endif
#if WASM_ENABLE_INSTRUCTION_METERING != 0
- exec_env->instructions_to_execute = -1;
+ exec_env->instructions_to_execute = INT64_MAX;
+ for(int i = 0; i < 256; ++i)
+ exec_env->instructions_schedule[i] = 1;
#endif
return exec_env;
diff --git a/core/iwasm/common/wasm_exec_env.h b/core/iwasm/common/wasm_exec_env.h
index 5d80312f..2713a092 100644
--- a/core/iwasm/common/wasm_exec_env.h
+++ b/core/iwasm/common/wasm_exec_env.h
@@ -89,7 +89,8 @@ typedef struct WASMExecEnv {
#if WASM_ENABLE_INSTRUCTION_METERING != 0
/* instructions to execute */
- int instructions_to_execute;
+ int64 instructions_to_execute;
+ int64 instructions_schedule[256];
#endif
#if WASM_ENABLE_FAST_JIT != 0
diff --git a/core/iwasm/common/wasm_native.c b/core/iwasm/common/wasm_native.c
index 060bb2c3..9221c36a 100644
--- a/core/iwasm/common/wasm_native.c
+++ b/core/iwasm/common/wasm_native.c
@@ -180,9 +180,9 @@ native_symbol_cmp(const void *native_symbol1, const void *native_symbol2)
((const NativeSymbol *)native_symbol2)->symbol);
}
-static void *
+static NativeSymbol *
lookup_symbol(NativeSymbol *native_symbols, uint32 n_native_symbols,
- const char *symbol, const char **p_signature, void **p_attachment)
+ const char *symbol)
{
NativeSymbol *native_symbol, key = { 0 };
@@ -190,9 +190,7 @@ lookup_symbol(NativeSymbol *native_symbols, uint32 n_native_symbols,
if ((native_symbol = bsearch(&key, native_symbols, n_native_symbols,
sizeof(NativeSymbol), native_symbol_cmp))) {
- *p_signature = native_symbol->signature;
- *p_attachment = native_symbol->attachment;
- return native_symbol->func_ptr;
+ return native_symbol;
}
return NULL;
@@ -205,25 +203,36 @@ lookup_symbol(NativeSymbol *native_symbols, uint32 n_native_symbols,
void *
wasm_native_resolve_symbol(const char *module_name, const char *field_name,
const WASMFuncType *func_type,
- const char **p_signature, void **p_attachment,
+ const char **p_signature, void **p_attachment, uint32_t *gas,
bool *p_call_conv_raw)
{
NativeSymbolsNode *node, *node_next;
const char *signature = NULL;
void *func_ptr = NULL, *attachment = NULL;
+ NativeSymbol *native_symbol = NULL;
node = g_native_symbols_list;
while (node) {
node_next = node->next;
if (!strcmp(node->module_name, module_name)) {
- if ((func_ptr =
+ if ((native_symbol =
lookup_symbol(node->native_symbols, node->n_native_symbols,
- field_name, &signature, &attachment))
+ field_name))
|| (field_name[0] == '_'
- && (func_ptr = lookup_symbol(
+ && (native_symbol = lookup_symbol(
node->native_symbols, node->n_native_symbols,
- field_name + 1, &signature, &attachment))))
- break;
+ field_name + 1))))
+ {
+ func_ptr = native_symbol->func_ptr;
+ if(func_ptr)
+ {
+ if(gas)
+ *gas = native_symbol->gas;
+ signature = native_symbol->signature;
+ attachment = native_symbol->attachment;
+ break;
+ }
+ }
}
node = node_next;
}
diff --git a/core/iwasm/common/wasm_native.h b/core/iwasm/common/wasm_native.h
index 9a6afee1..0fe4739f 100644
--- a/core/iwasm/common/wasm_native.h
+++ b/core/iwasm/common/wasm_native.h
@@ -52,7 +52,7 @@ wasm_native_lookup_libc_builtin_global(const char *module_name,
void *
wasm_native_resolve_symbol(const char *module_name, const char *field_name,
const WASMFuncType *func_type,
- const char **p_signature, void **p_attachment,
+ const char **p_signature, void **p_attachment, uint32_t *gas,
bool *p_call_conv_raw);
bool
diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c
index dcee0aea..10c516d6 100644
--- a/core/iwasm/common/wasm_runtime_common.c
+++ b/core/iwasm/common/wasm_runtime_common.c
@@ -2288,10 +2288,26 @@ wasm_runtime_access_exce_check_guard_page()
#if WASM_ENABLE_INSTRUCTION_METERING != 0
void
wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
- int instructions_to_execute)
+ int64 instructions_to_execute)
{
+ if(instructions_to_execute == -1)
+ instructions_to_execute = INT64_MAX;
exec_env->instructions_to_execute = instructions_to_execute;
}
+
+int64
+wasm_runtime_get_instruction_count_limit(WASMExecEnv *exec_env)
+{
+ return exec_env->instructions_to_execute;
+}
+
+void
+wasm_runtime_set_instruction_schedule(WASMExecEnv *exec_env,
+ int64 const *instructions_schedule)
+{
+ for(int i = 0; i < 256; ++i)
+ exec_env->instructions_schedule[i] = instructions_schedule[i];
+}
#endif
WASMFuncType *
@@ -7348,7 +7364,7 @@ wasm_runtime_is_import_func_linked(const char *module_name,
const char *func_name)
{
return wasm_native_resolve_symbol(module_name, func_name, NULL, NULL, NULL,
- NULL);
+ NULL, NULL);
}
bool
@@ -7805,13 +7821,14 @@ wasm_runtime_get_module_name(wasm_module_t module)
bool
wasm_runtime_detect_native_stack_overflow(WASMExecEnv *exec_env)
{
+#if WASM_DISABLE_STACK_HW_BOUND_CHECK == 0
uint8 *boundary = exec_env->native_stack_boundary;
RECORD_STACK_USAGE(exec_env, (uint8 *)&boundary);
if (boundary == NULL) {
/* the platform doesn't support os_thread_get_stack_boundary */
return true;
}
-#if defined(OS_ENABLE_HW_BOUND_CHECK) && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0
+#if defined(OS_ENABLE_HW_BOUND_CHECK)
uint32 page_size = os_getpagesize();
uint32 guard_page_count = STACK_OVERFLOW_CHECK_GUARD_PAGE_COUNT;
boundary = boundary + page_size * guard_page_count;
@@ -7821,6 +7838,7 @@ wasm_runtime_detect_native_stack_overflow(WASMExecEnv *exec_env)
"native stack overflow");
return false;
}
+#endif
return true;
}
@@ -7843,7 +7861,7 @@ wasm_runtime_detect_native_stack_overflow_size(WASMExecEnv *exec_env,
boundary = boundary - WASM_STACK_GUARD_SIZE + requested_size;
if ((uint8 *)&boundary < boundary) {
wasm_runtime_set_exception(wasm_runtime_get_module_inst(exec_env),
- "native stack overflow");
+ "native s stack overflow");
return false;
}
return true;
diff --git a/core/iwasm/common/wasm_runtime_common.h b/core/iwasm/common/wasm_runtime_common.h
index 64a6cd79..f4c55e2f 100644
--- a/core/iwasm/common/wasm_runtime_common.h
+++ b/core/iwasm/common/wasm_runtime_common.h
@@ -795,7 +795,14 @@ wasm_runtime_set_native_stack_boundary(WASMExecEnv *exec_env,
/* See wasm_export.h for description */
WASM_RUNTIME_API_EXTERN void
wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
- int instructions_to_execute);
+ int64 instructions_to_execute);
+WASM_RUNTIME_API_EXTERN int64
+wasm_runtime_get_instruction_count_limit(WASMExecEnv *exec_env);
+
+WASM_RUNTIME_API_EXTERN void
+wasm_runtime_set_instruction_schedule(WASMExecEnv *exec_env,
+ int64 const *instructions_schedule);
+
#endif
#if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0
diff --git a/core/iwasm/include/lib_export.h b/core/iwasm/include/lib_export.h
index 0ca668f5..93bcf807 100644
--- a/core/iwasm/include/lib_export.h
+++ b/core/iwasm/include/lib_export.h
@@ -24,6 +24,8 @@ typedef struct NativeSymbol {
/* attachment which can be retrieved in native API by
calling wasm_runtime_get_function_attachment(exec_env) */
void *attachment;
+ // gas cost for import func
+ uint32_t gas;
} NativeSymbol;
/* clang-format off */
diff --git a/core/iwasm/include/wasm_c_api.h b/core/iwasm/include/wasm_c_api.h
index 241a0eec..1141744c 100644
--- a/core/iwasm/include/wasm_c_api.h
+++ b/core/iwasm/include/wasm_c_api.h
@@ -19,8 +19,10 @@
#if defined(_MSC_BUILD)
#if defined(COMPILING_WASM_RUNTIME_API)
#define WASM_API_EXTERN __declspec(dllexport)
-#else
+#elif defined(_DLL)
#define WASM_API_EXTERN __declspec(dllimport)
+#else
+#define WASM_API_EXTERN
#endif
#else
#define WASM_API_EXTERN
@@ -592,6 +594,8 @@ WASM_API_EXTERN size_t wasm_func_result_arity(const wasm_func_t*);
WASM_API_EXTERN own wasm_trap_t* wasm_func_call(
const wasm_func_t*, const wasm_val_vec_t* args, wasm_val_vec_t* results);
+WASM_API_EXTERN own uint32_t wasm_func_set_gas(wasm_func_t*, uint32_t);
+
// Global Instances
@@ -701,6 +705,11 @@ WASM_API_EXTERN double wasm_instance_sum_wasm_exec_time(const wasm_instance_t*);
// func_name. If the function is not found, return 0.
WASM_API_EXTERN double wasm_instance_get_wasm_func_exec_time(const wasm_instance_t*, const char *);
+struct WASMExecEnv;
+typedef struct WASMExecEnv *wasm_exec_env_t;
+
+WASM_API_EXTERN wasm_exec_env_t wasm_instance_exec_env(const wasm_instance_t*);
+
///////////////////////////////////////////////////////////////////////////////
// Convenience
diff --git a/core/iwasm/include/wasm_export.h b/core/iwasm/include/wasm_export.h
index b4ab34be..3fd0949f 100644
--- a/core/iwasm/include/wasm_export.h
+++ b/core/iwasm/include/wasm_export.h
@@ -20,8 +20,10 @@
#if defined(_MSC_BUILD)
#if defined(COMPILING_WASM_RUNTIME_API)
#define WASM_RUNTIME_API_EXTERN __declspec(dllexport)
-#else
+#elif defined(_DLL)
#define WASM_RUNTIME_API_EXTERN __declspec(dllimport)
+#else
+#define WASM_RUNTIME_API_EXTERN
#endif
#elif defined(__GNUC__) || defined(__clang__)
#define WASM_RUNTIME_API_EXTERN __attribute__((visibility("default")))
@@ -1833,7 +1835,14 @@ wasm_runtime_set_native_stack_boundary(wasm_exec_env_t exec_env,
*/
WASM_RUNTIME_API_EXTERN void
wasm_runtime_set_instruction_count_limit(wasm_exec_env_t exec_env,
- int instruction_count);
+ int64_t instruction_count);
+
+WASM_RUNTIME_API_EXTERN int64_t
+wasm_runtime_get_instruction_count_limit(wasm_exec_env_t exec_env);
+
+WASM_RUNTIME_API_EXTERN void
+wasm_runtime_set_instruction_schedule(wasm_exec_env_t exec_env,
+ int64_t const *instructions_schedule);
/**
* Dump runtime memory consumption, including:
diff --git a/core/iwasm/interpreter/wasm.h b/core/iwasm/interpreter/wasm.h
index ddc0b15b..3a707878 100644
--- a/core/iwasm/interpreter/wasm.h
+++ b/core/iwasm/interpreter/wasm.h
@@ -579,6 +579,9 @@ typedef struct WASMFunctionImport {
WASMModule *import_module;
WASMFunction *import_func_linked;
#endif
+ // gas cost for import func
+ uint32 gas;
+
} WASMFunctionImport;
#if WASM_ENABLE_TAGS != 0
diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c
index 1e98b0fa..ae24ff8b 100644
--- a/core/iwasm/interpreter/wasm_interp_classic.c
+++ b/core/iwasm/interpreter/wasm_interp_classic.c
@@ -1569,13 +1569,14 @@ get_global_addr(uint8 *global_data, WASMGlobalInstance *global)
}
#if WASM_ENABLE_INSTRUCTION_METERING != 0
-#define CHECK_INSTRUCTION_LIMIT() \
- if (instructions_left == 0) { \
- wasm_set_exception(module, "instruction limit exceeded"); \
- goto got_exception; \
- } \
- else if (instructions_left > 0) \
- instructions_left--;
+#define CHECK_INSTRUCTION_LIMIT() \
+ do { \
+ instructions_left -= instructions_schedule[opcode]; \
+ if (instructions_left < 0) { \
+ wasm_set_exception(module, "instruction limit exceeded"); \
+ goto got_exception; \
+ } \
+ } while (0)
#else
#define CHECK_INSTRUCTION_LIMIT() (void)0
#endif
@@ -1625,9 +1626,11 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
uint32 cache_index, type_index, param_cell_num, cell_num;
#if WASM_ENABLE_INSTRUCTION_METERING != 0
- int instructions_left = -1;
+ int64 instructions_left = INT64_MAX;
+ int64 const *instructions_schedule = NULL;
if (exec_env) {
instructions_left = exec_env->instructions_to_execute;
+ instructions_schedule = exec_env->instructions_schedule;
}
#endif
@@ -6885,6 +6888,11 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
FREE_FRAME(exec_env, frame);
wasm_exec_env_set_cur_frame(exec_env, prev_frame);
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+ if(exec_env)
+ exec_env->instructions_to_execute = instructions_left;
+#endif
+
if (!prev_frame->ip) {
/* Called from native. */
return;
@@ -6925,6 +6933,12 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
}
#endif
SYNC_ALL_TO_FRAME();
+
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+ if(exec_env)
+ exec_env->instructions_to_execute = instructions_left;
+#endif
+
return;
#if WASM_ENABLE_LABELS_AS_VALUES == 0
diff --git a/core/iwasm/interpreter/wasm_interp_fast.c b/core/iwasm/interpreter/wasm_interp_fast.c
index 4e5edf41..04c39e1f 100644
--- a/core/iwasm/interpreter/wasm_interp_fast.c
+++ b/core/iwasm/interpreter/wasm_interp_fast.c
@@ -106,14 +106,14 @@ typedef float64 CellType_F64;
} while (0)
#if WASM_ENABLE_INSTRUCTION_METERING != 0
-#define CHECK_INSTRUCTION_LIMIT() \
- if (instructions_left == 0) { \
- wasm_set_exception(module, "instruction limit exceeded"); \
- goto got_exception; \
- } \
- else if (instructions_left > 0) \
- instructions_left--;
-
+#define CHECK_INSTRUCTION_LIMIT() \
+ do { \
+ instructions_left -= instructions_schedule[opcode]; \
+ if (instructions_left < 0) { \
+ wasm_set_exception(module, "instruction limit exceeded"); \
+ goto got_exception; \
+ } \
+ } while (0)
#else
#define CHECK_INSTRUCTION_LIMIT() (void)0
#endif
@@ -1454,7 +1454,6 @@ wasm_interp_dump_op_count()
do { \
const void *p_label_addr = *(void **)frame_ip; \
frame_ip += sizeof(void *); \
- CHECK_INSTRUCTION_LIMIT(); \
goto *p_label_addr; \
} while (0)
#else
@@ -1466,7 +1465,6 @@ wasm_interp_dump_op_count()
/* int32 relative offset was emitted in 64-bit target */ \
p_label_addr = label_base + (int32)LOAD_U32_WITH_2U16S(frame_ip); \
frame_ip += sizeof(int32); \
- CHECK_INSTRUCTION_LIMIT(); \
goto *p_label_addr; \
} while (0)
#else
@@ -1477,17 +1475,18 @@ wasm_interp_dump_op_count()
/* uint32 label address was emitted in 32-bit target */ \
p_label_addr = (void *)(uintptr_t)LOAD_U32_WITH_2U16S(frame_ip); \
frame_ip += sizeof(int32); \
- CHECK_INSTRUCTION_LIMIT(); \
goto *p_label_addr; \
} while (0)
#endif
#endif /* end of WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS */
-#define HANDLE_OP_END() FETCH_OPCODE_AND_DISPATCH()
+#define HANDLE_OP_END() CHECK_INSTRUCTION_LIMIT(); FETCH_OPCODE_AND_DISPATCH()
#else /* else of WASM_ENABLE_LABELS_AS_VALUES */
#define HANDLE_OP(opcode) case opcode:
-#define HANDLE_OP_END() continue
+#define HANDLE_OP_END() \
+ CHECK_INSTRUCTION_LIMIT(); \
+ continue
#endif /* end of WASM_ENABLE_LABELS_AS_VALUES */
@@ -1508,6 +1507,25 @@ get_global_addr(uint8 *global_data, WASMGlobalInstance *global)
#endif
}
+static int64 const def_instructions_schedule[256] = {
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
+};
+
static void
wasm_interp_call_func_bytecode(WASMModuleInstance *module,
WASMExecEnv *exec_env,
@@ -1556,9 +1574,11 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
uint8 opcode = 0, local_type, *global_addr;
#if WASM_ENABLE_INSTRUCTION_METERING != 0
- int instructions_left = -1;
+ int64 instructions_left = INT64_MAX;
+ int64 const *instructions_schedule = def_instructions_schedule;
if (exec_env) {
instructions_left = exec_env->instructions_to_execute;
+ instructions_schedule = exec_env->instructions_schedule;
}
#endif
#if !defined(OS_ENABLE_HW_BOUND_CHECK) \
@@ -7694,6 +7714,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
{
wasm_interp_call_func_native(module, exec_env, cur_func,
prev_frame);
+ instructions_left -= cur_func->gas;
}
#if WASM_ENABLE_TAIL_CALL != 0 || WASM_ENABLE_GC != 0
@@ -7806,6 +7827,11 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
FREE_FRAME(exec_env, frame);
wasm_exec_env_set_cur_frame(exec_env, (WASMRuntimeFrame *)prev_frame);
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+ if (exec_env)
+ exec_env->instructions_to_execute = instructions_left;
+#endif
+
if (!prev_frame->ip)
/* Called from native. */
return;
@@ -7834,6 +7860,10 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
got_exception:
SYNC_ALL_TO_FRAME();
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+ if (exec_env)
+ exec_env->instructions_to_execute = instructions_left;
+#endif
return;
#if WASM_ENABLE_LABELS_AS_VALUES == 0
diff --git a/core/iwasm/interpreter/wasm_mini_loader.c b/core/iwasm/interpreter/wasm_mini_loader.c
index e66c08ba..d52e677e 100644
--- a/core/iwasm/interpreter/wasm_mini_loader.c
+++ b/core/iwasm/interpreter/wasm_mini_loader.c
@@ -636,6 +636,7 @@ load_function_import(const uint8 **p_buf, const uint8 *buf_end,
const char *linked_signature = NULL;
void *linked_attachment = NULL;
bool linked_call_conv_raw = false;
+ uint32_t gas = 0;
read_leb_uint32(p, p_end, declare_type_index);
*p_buf = p;
@@ -647,7 +648,7 @@ load_function_import(const uint8 **p_buf, const uint8 *buf_end,
/* check built-in modules */
linked_func = wasm_native_resolve_symbol(
sub_module_name, function_name, declare_func_type, &linked_signature,
- &linked_attachment, &linked_call_conv_raw);
+ &linked_attachment, &gas, &linked_call_conv_raw);
function->module_name = (char *)sub_module_name;
function->field_name = (char *)function_name;
@@ -656,6 +657,7 @@ load_function_import(const uint8 **p_buf, const uint8 *buf_end,
function->signature = linked_signature;
function->attachment = linked_attachment;
function->call_conv_raw = linked_call_conv_raw;
+ function->gas = gas;
return true;
}
diff --git a/core/iwasm/interpreter/wasm_runtime.c b/core/iwasm/interpreter/wasm_runtime.c
index 3cc2afe0..55859d35 100644
--- a/core/iwasm/interpreter/wasm_runtime.c
+++ b/core/iwasm/interpreter/wasm_runtime.c
@@ -168,7 +168,7 @@ wasm_resolve_import_func(const WASMModule *module, WASMFunctionImport *function)
#endif
function->func_ptr_linked = wasm_native_resolve_symbol(
function->module_name, function->field_name, function->func_type,
- &function->signature, &function->attachment, &function->call_conv_raw);
+ &function->signature, &function->attachment, &function->gas, &function->call_conv_raw);
if (function->func_ptr_linked) {
return true;
@@ -820,6 +820,7 @@ functions_instantiate(const WASMModule *module, WASMModuleInstance *module_inst,
function->param_count =
(uint16)function->u.func_import->func_type->param_count;
function->param_types = function->u.func_import->func_type->types;
+ function->gas = import->u.function.gas;
function->local_cell_num = 0;
function->local_count = 0;
function->local_types = NULL;
diff --git a/core/iwasm/interpreter/wasm_runtime.h b/core/iwasm/interpreter/wasm_runtime.h
index 8d38c883..a687ab89 100644
--- a/core/iwasm/interpreter/wasm_runtime.h
+++ b/core/iwasm/interpreter/wasm_runtime.h
@@ -228,6 +228,10 @@ struct WASMFunctionInstance {
WASMFunctionImport *func_import;
WASMFunction *func;
} u;
+
+ // gas cost for import func
+ uint32 gas;
+
#if WASM_ENABLE_MULTI_MODULE != 0
WASMModuleInstance *import_module_inst;
WASMFunctionInstance *import_func_inst;
diff --git a/core/iwasm/libraries/libc-builtin/libc_builtin_wrapper.c b/core/iwasm/libraries/libc-builtin/libc_builtin_wrapper.c
index a68c0749..cafb6915 100644
--- a/core/iwasm/libraries/libc-builtin/libc_builtin_wrapper.c
+++ b/core/iwasm/libraries/libc-builtin/libc_builtin_wrapper.c
@@ -1038,16 +1038,16 @@ print_f64_wrapper(wasm_exec_env_t exec_env, double f64)
/* clang-format off */
#define REG_NATIVE_FUNC(func_name, signature) \
- { #func_name, func_name##_wrapper, signature, NULL }
+ { #func_name, func_name##_wrapper, signature, NULL, 0 }
/* clang-format on */
static NativeSymbol native_symbols_libc_builtin[] = {
REG_NATIVE_FUNC(printf, "($*)i"),
REG_NATIVE_FUNC(sprintf, "($$*)i"),
REG_NATIVE_FUNC(snprintf, "(*~$*)i"),
- { "vprintf", printf_wrapper, "($*)i", NULL },
- { "vsprintf", sprintf_wrapper, "($$*)i", NULL },
- { "vsnprintf", snprintf_wrapper, "(*~$*)i", NULL },
+ { "vprintf", printf_wrapper, "($*)i", NULL, 0 },
+ { "vsprintf", sprintf_wrapper, "($$*)i", NULL, 0 },
+ { "vsnprintf", snprintf_wrapper, "(*~$*)i", NULL, 0 },
REG_NATIVE_FUNC(puts, "($)i"),
REG_NATIVE_FUNC(putchar, "(i)i"),
REG_NATIVE_FUNC(memcmp, "(**~)i"),
diff --git a/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c b/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c
index 6d057a6a..25879f33 100644
--- a/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c
+++ b/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c
@@ -2257,7 +2257,7 @@ wasi_sched_yield(wasm_exec_env_t exec_env)
/* clang-format off */
#define REG_NATIVE_FUNC(func_name, signature) \
- { #func_name, wasi_##func_name, signature, NULL }
+ { #func_name, wasi_##func_name, signature, NULL, 0 }
/* clang-format on */
static NativeSymbol native_symbols_libc_wasi[] = {
diff --git a/core/shared/platform/include/platform_wasi_types.h b/core/shared/platform/include/platform_wasi_types.h
index ac1a95ea..e23b500e 100644
--- a/core/shared/platform/include/platform_wasi_types.h
+++ b/core/shared/platform/include/platform_wasi_types.h
@@ -36,7 +36,11 @@ extern "C" {
#if WASM_ENABLE_UVWASI != 0 || WASM_ENABLE_LIBC_WASI == 0
#define assert_wasi_layout(expr, message) /* nothing */
#else
-#define assert_wasi_layout(expr, message) _Static_assert(expr, message)
+ #ifndef _MSC_VER
+ #define assert_wasi_layout(expr, message) _Static_assert(expr, message)
+ #else
+ #define assert_wasi_layout(expr, message) static_assert(expr, message)
+ #endif
#endif
assert_wasi_layout(_Alignof(int8_t) == 1, "non-wasi data layout");
+2 -2
View File
@@ -451,8 +451,8 @@ mulDivU(Source1 value, Dest mul, Source2 div)
}
using namespace boost::multiprecision;
uint128_t product;
using uint128 = boost::multiprecision::uint128_t;
uint128 product;
product = multiply(
product,
static_cast<std::uint64_t>(value.value()),
+6
View File
@@ -24,6 +24,8 @@
namespace ripple {
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
@@ -34,6 +36,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;
+6
View File
@@ -230,6 +230,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;
+2 -1
View File
@@ -149,11 +149,12 @@ mulRatio(
bool roundUp)
{
using namespace boost::multiprecision;
using int128 = boost::multiprecision::int128_t;
if (!den)
Throw<std::runtime_error>("division by zero");
int128_t const amt128(amt.value());
int128 const amt128(amt.value());
auto const neg = amt.value() < 0;
auto const m = amt128 * num;
auto r = m / den;
+4
View File
@@ -126,6 +126,10 @@ std::uint8_t constexpr vaultStrategyFirstComeFirstServe = 1;
* 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;
/** A ledger index. */
using LedgerIndex = std::uint32_t;
+5
View File
@@ -141,6 +141,8 @@ enum TEMcodes : TERUnderlyingType {
temARRAY_TOO_LARGE,
temBAD_TRANSFER_FEE,
temINVALID_INNER_BATCH,
temBAD_WASM,
};
//------------------------------------------------------------------------------
@@ -185,6 +187,8 @@ enum TEFcodes : TERUnderlyingType {
tefNO_TICKET,
tefNFTOKEN_IS_NOT_TRANSFERABLE,
tefINVALID_LEDGER_FIX_TYPE,
tefNO_WASM,
tefWASM_FIELD_NOT_INCLUDED,
};
//------------------------------------------------------------------------------
@@ -362,6 +366,7 @@ enum TECcodes : TERUnderlyingType {
tecPSEUDO_ACCOUNT = 196,
tecPRECISION_LOSS = 197,
tecNO_DELEGATE_PERMISSION = 198,
tecWASM_REJECTED = 199,
};
//------------------------------------------------------------------------------
+26 -8
View File
@@ -46,10 +46,7 @@ private:
CtorHelper);
public:
TxMeta(
uint256 const& transactionID,
std::uint32_t ledger,
std::optional<uint256> parentBatchId = std::nullopt);
TxMeta(uint256 const& transactionID, std::uint32_t ledger);
TxMeta(uint256 const& txID, std::uint32_t ledger, Blob const&);
TxMeta(uint256 const& txID, std::uint32_t ledger, std::string const&);
TxMeta(uint256 const& txID, std::uint32_t ledger, STObject const&);
@@ -136,7 +133,7 @@ public:
void
setParentBatchId(uint256 const& parentBatchId)
{
mParentBatchId = parentBatchId;
parentBatchId_ = parentBatchId;
}
uint256
@@ -145,13 +142,33 @@ public:
XRPL_ASSERT(
hasParentBatchId(),
"ripple::TxMeta::getParentBatchId : non-null batch id");
return *mParentBatchId;
return *parentBatchId_;
}
bool
hasParentBatchId() const
{
return static_cast<bool>(mParentBatchId);
return static_cast<bool>(parentBatchId_);
}
void
setGasUsed(std::uint32_t const& gasUsed)
{
gasUsed_ = gasUsed;
}
std::uint32_t
getGasUsed() const
{
XRPL_ASSERT(
hasGasUsed(), "ripple::TxMeta::getGasUsed : non-null batch id");
return *gasUsed_;
}
bool
hasGasUsed() const
{
return static_cast<bool>(gasUsed_);
}
private:
@@ -161,7 +178,8 @@ private:
int mResult;
std::optional<STAmount> mDelivered;
std::optional<uint256> mParentBatchId;
std::optional<std::uint32_t> gasUsed_;
std::optional<uint256> parentBatchId_;
STArray mNodes;
};
+2 -1
View File
@@ -286,11 +286,12 @@ mulRatio(
bool roundUp)
{
using namespace boost::multiprecision;
using int128 = boost::multiprecision::int128_t;
if (!den)
Throw<std::runtime_error>("division by zero");
int128_t const amt128(amt.drops());
int128 const amt128(amt.drops());
auto const neg = amt.drops() < 0;
auto const m = amt128 * num;
auto r = m / den;
+1 -1
View File
@@ -35,6 +35,7 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
XRPL_FEATURE(SmartEscrow, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
@@ -43,7 +44,6 @@ XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo
XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegation, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
// Check flags in Credential transactions
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
@@ -319,6 +319,12 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
// New fields
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfGasPrice, soeOPTIONAL},
{sfPreviousTxnID, soeOPTIONAL},
{sfPreviousTxnLgrSeq, soeOPTIONAL},
}))
@@ -348,6 +354,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
{sfCondition, soeOPTIONAL},
{sfCancelAfter, soeOPTIONAL},
{sfFinishAfter, soeOPTIONAL},
{sfFinishFunction, soeOPTIONAL},
{sfData, soeOPTIONAL},
{sfSourceTag, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfOwnerNode, soeREQUIRED},
+7 -1
View File
@@ -114,6 +114,11 @@ TYPED_SFIELD(sfVoteWeight, UINT32, 48)
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
TYPED_SFIELD(sfPermissionValue, UINT32, 52)
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 53)
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 54)
TYPED_SFIELD(sfGasPrice, UINT32, 55)
TYPED_SFIELD(sfComputationAllowance, UINT32, 56)
TYPED_SFIELD(sfGasUsed, UINT32, 57)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -233,7 +238,7 @@ 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)
@@ -275,6 +280,7 @@ 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)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
@@ -44,11 +44,13 @@ TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegatable, ({
/** This transaction type creates an escrow object. */
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegatable, ({
{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. */
@@ -58,6 +60,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::delegatable, ({
{sfFulfillment, soeOPTIONAL},
{sfCondition, soeOPTIONAL},
{sfCredentialIDs, soeOPTIONAL},
{sfComputationAllowance, soeOPTIONAL},
}))
@@ -545,6 +548,9 @@ TRANSACTION(ttFEE, 101, SetFee, Delegation::notDelegatable, ({
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfGasPrice, soeOPTIONAL},
}))
/** This system-generated transaction type is used to update the network's negative UNL
+3
View File
@@ -268,6 +268,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
+1 -2
View File
@@ -1052,8 +1052,7 @@ parseArray(
Json::Value const objectFields(json[i][objectName]);
std::stringstream ss;
ss << json_name << "."
<< "[" << i << "]." << objectName;
ss << json_name << "." << "[" << i << "]." << objectName;
auto ret = parseObject(
ss.str(), objectFields, nameField, depth + 1, error);
+4
View File
@@ -78,6 +78,10 @@ STValidation::validationFormat()
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
// featureSmartEscrow
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfGasPrice, soeOPTIONAL},
};
// clang-format on
+4
View File
@@ -128,6 +128,7 @@ transResults()
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(tecNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
MAKE_ERROR(tecWASM_REJECTED, "The custom WASM code that was run rejected your transaction."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -151,6 +152,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."),
@@ -220,6 +223,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."),
+8 -7
View File
@@ -56,9 +56,10 @@ TxMeta::TxMeta(
if (obj.isFieldPresent(sfDeliveredAmount))
setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount));
if (obj.isFieldPresent(sfParentBatchID))
setParentBatchId(obj.getFieldH256(sfParentBatchID));
if (obj.isFieldPresent(sfGasUsed))
setGasUsed(obj.getFieldU32(sfGasUsed));
}
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
@@ -82,6 +83,9 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
if (obj.isFieldPresent(sfParentBatchID))
setParentBatchId(obj.getFieldH256(sfParentBatchID));
if (obj.isFieldPresent(sfGasUsed))
setGasUsed(obj.getFieldU32(sfGasUsed));
}
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, Blob const& vec)
@@ -97,15 +101,11 @@ TxMeta::TxMeta(
{
}
TxMeta::TxMeta(
uint256 const& transactionID,
std::uint32_t ledger,
std::optional<uint256> parentBatchId)
TxMeta::TxMeta(uint256 const& transactionID, std::uint32_t ledger)
: mTransactionID(transactionID)
, mLedger(ledger)
, mIndex(static_cast<std::uint32_t>(-1))
, mResult(255)
, mParentBatchId(parentBatchId)
, mNodes(sfAffectedNodes)
{
mNodes.reserve(32);
@@ -241,9 +241,10 @@ TxMeta::getAsObject() const
metaData.emplace_back(mNodes);
if (hasDeliveredAmount())
metaData.setFieldAmount(sfDeliveredAmount, getDeliveredAmount());
if (hasParentBatchId())
metaData.setFieldH256(sfParentBatchID, getParentBatchId());
if (hasGasUsed())
metaData.setFieldU32(sfGasUsed, getGasUsed());
return metaData;
}
+659 -2
View File
@@ -17,8 +17,10 @@
*/
//==============================================================================
#include <test/app/wasm_fixtures/fixtures.h>
#include <test/jtx.h>
#include <xrpld/app/misc/WasmVM.h>
#include <xrpld/app/tx/applySteps.h>
#include <xrpld/ledger/Dir.h>
@@ -1551,7 +1553,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";
@@ -1693,6 +1695,655 @@ struct Escrow_test : public beast::unit_test::suite
}
}
void
testCreateFinishFunctionPreflight()
{
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
// #[no_mangle]
// pub fn finish() -> bool {
// unsafe { host_lib::getLedgerSqn() >= 5}
// }
static auto wasmHex = ledgerSqnHex;
{
// featureSmartEscrow disabled
Env env(*this, supported_amendments() - 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();
}
{
// FinishFunction > max length
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
cfg->FEES.extension_size_limit = 10; // 10 bytes
return cfg;
}));
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();
}
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
cfg->START_UP = Config::FRESH;
return cfg;
}));
XRPAmount const txnFees = env.current()->fees().base * 10 + 1000;
// 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() + 100s),
fee(txnFees));
env.close();
}
{
// FinishFunction + Condition + CancelAfter
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 100s),
escrow::condition(escrow::cb1),
fee(txnFees));
env.close();
}
{
// FinishFunction + FinishAfter + CancelAfter
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 100s),
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() + 100s),
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() + 100s),
fee(txnFees),
ter(temMALFORMED));
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()
{
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
// #[no_mangle]
// pub fn finish() -> bool {
// unsafe { host_lib::getLedgerSqn() >= 5}
// }
static auto wasmHex = ledgerSqnHex;
{
// featureSmartEscrow disabled
Env env(*this, supported_amendments() - featureSmartEscrow);
env.fund(XRP(5000), alice, carol);
XRPAmount const txnFees = env.current()->fees().base + 1000;
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<Config> cfg) {
cfg->FEES.extension_compute_limit = 1'000; // in gas
return cfg;
}));
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));
}
{
uint32_t const allowance = 10'000;
Env env(*this);
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
XRPAmount const createFee = env.current()->fees().base + 1000;
// Escrow with FinishFunction + Condition
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
env(escrowCreate,
escrow::finish_function(reqNonexistentField),
escrow::condition(escrow::cb1),
escrow::cancel_time(env.now() + 100s),
fee(createFee));
env.close();
env.close();
// Rejected as wasm code request nonexistent memo field
XRPAmount const txnFees = env.current()->fees().base * 34 + 1000;
env(escrow::finish(alice, alice, seq),
escrow::condition(escrow::cb1),
escrow::fulfillment(escrow::fb1),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
}
Env env(*this);
// 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 + 1000;
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));
}
{
// 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),
escrow::comp_allowance(allowance),
ter(tefNO_WASM));
}
}
void
testFinishFunction()
{
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
// #[no_mangle]
// pub fn finish() -> bool {
// unsafe { host_lib::getLedgerSqn() >= 5}
// }
static auto wasmHex = ledgerSqnHex;
std::uint32_t constexpr allowance = 4;
{
// basic FinishFunction situation
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env(escrow::finish(alice, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env(escrow::finish(alice, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
env(escrow::finish(alice, alice, seq),
fee(txnFees),
escrow::comp_allowance(allowance),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
{
// FinishFunction + Condition
Env env(*this);
env.fund(XRP(5000), alice, carol);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto const seq = env.seq(alice);
// create escrow
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
XRPAmount const createFee = env.current()->fees().base + 1000;
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::condition(escrow::cb1),
escrow::cancel_time(env.now() + 100s),
fee(createFee));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - createFee));
env.require(balance(carol, XRP(5000)));
XRPAmount const txnFees =
env.current()->fees().base * 34 + 1000;
// no fulfillment provided, function fails
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
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(txnFees),
ter(tecWASM_REJECTED));
env.close();
// no fulfillment provided, function succeeds
env(escrow::finish(alice, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
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(txnFees),
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(txnFees),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
{
// FinishFunction + FinishAfter
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
auto const ts = env.now() + 97s;
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::finish_time(ts),
escrow::cancel_time(env.now() + 1000s),
fee(txnFees));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
// finish time hasn't passed, function fails
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(4),
fee(txnFees + 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(4),
fee(txnFees + 2),
ter(tecNO_PERMISSION));
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(4),
fee(txnFees + 1),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
{
// FinishFunction + FinishAfter #2
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::finish_time(env.now() + 2s),
escrow::cancel_time(env.now() + 100s),
fee(txnFees));
// Don't close the ledger here
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
// finish time hasn't passed, function fails
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tecNO_PERMISSION));
env.close();
// finish time has passed, function fails
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
// finish time has passed, function succeeds, tx succeeds
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
}
void
testAllHostFunctions()
{
testcase("Test all host functions");
using namespace jtx;
using namespace std::chrono;
// TODO: create wasm module for all host functions
static auto wasmHex = allHostFunctionsHex;
// let sender = get_tx_account_id();
// let owner = get_current_escrow_account_id();
// let dest = get_current_escrow_destination();
// let dest_balance = get_account_balance(dest);
// let escrow_data = get_current_escrow_data();
// let ed_str = String::from_utf8(escrow_data).unwrap();
// let threshold_balance = ed_str.parse::<u64>().unwrap();
// let pl_time = host_lib::getParentLedgerTime();
// let e_time = get_current_escrow_finish_after();
// sender == owner && dest_balance <= threshold_balance &&
// pl_time >= e_time
Account const alice{"alice"};
Account const carol{"carol"};
{
// basic FinishFunction situation
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow::create(alice, carol, 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
fee(txnFees));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
// TODO: figure out why this can't be 2412
auto const allowance = 3'600;
// FinishAfter time hasn't passed
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tecNO_PERMISSION));
env.close();
// tx sender not escrow creator (alice)
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
// destination balance is too high
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
// reduce the destination balance
env(pay(carol, alice, XRP(4500)));
env.close();
// tx sender not escrow creator (alice)
env(escrow::finish(carol, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
env(escrow::finish(alice, alice, seq),
escrow::comp_allowance(allowance),
fee(txnFees),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == 487);
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
}
void
testWithFeats(FeatureBitset features)
{
@@ -1708,6 +2359,12 @@ struct Escrow_test : public beast::unit_test::suite
testConsequences(features);
testEscrowWithTickets(features);
testCredentials(features);
testCreateFinishFunctionPreflight();
testFinishWasmFailures();
testFinishFunction();
// TODO: Update module with new host functions
testAllHostFunctions();
}
public:
@@ -1718,7 +2375,7 @@ public:
FeatureBitset const all{supported_amendments()};
testWithFeats(all);
testWithFeats(all - featureTokenEscrow);
}
};
};
BEAST_DEFINE_TESTSUITE(Escrow, app, ripple);
+46 -4
View File
@@ -37,36 +37,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);
@@ -74,6 +100,12 @@ class FeeVote_test : public beast::unit_test::suite
setup.account_reserve == static_cast<std::uint32_t>(-1234567));
BEAST_EXPECT(
setup.owner_reserve == static_cast<std::uint32_t>(-1234));
BEAST_EXPECT(
setup.extension_compute_limit ==
static_cast<std::uint32_t>(-100));
BEAST_EXPECT(
setup.extension_size_limit == static_cast<std::uint32_t>(-200));
BEAST_EXPECT(setup.gas_price == static_cast<std::uint32_t>(-300));
}
{
auto const big64 = std::to_string(
@@ -84,12 +116,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);
}
}
+8 -1
View File
@@ -50,6 +50,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) {
@@ -116,9 +122,10 @@ struct PseudoTx_test : public beast::unit_test::suite
{
using namespace test::jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const xrpFees{featureXRPFees};
testPrevented(all - featureXRPFees - featureSmartEscrow);
testPrevented(all - featureXRPFees);
testPrevented(all - featureSmartEscrow);
testPrevented(all);
testAllowed();
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+62
View File
@@ -0,0 +1,62 @@
//------------------------------------------------------------------------------
/*
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
// TODO: consider moving these to separate files (and figure out the build)
#include <string>
extern std::string const ledgerSqnHex;
extern std::string const allHostFunctionsHex;
extern std::string const deepRecursionHex;
extern std::string const tx_js;
extern std::string const lo_js;
extern std::string const lo_js2;
extern std::string const fib32Hex;
extern std::string const fib64Hex;
extern std::string const b58Hex;
extern std::string const sha512PureHex;
extern std::string const checkJsonHex;
extern std::string const compareJsonHex;
extern std::string const zkProofHex;
extern std::string const sp1_wasm;
extern std::string const xrplStdExampleHex;
extern std::string const hostFunctions2Hex;
extern std::string const reqNonexistentField;
extern std::string const hfPerfTest;
extern std::string const opcCallPerfTest;
@@ -68,8 +68,8 @@ class ByzantineFailureSim_test : public beast::unit_test::suite
for (TrustGraph<Peer*>::ForkInfo const& fi :
sim.trustGraph.forkablePairs(0.8))
{
std::cout << "Can fork " << PeerGroup{fi.unlA} << " "
<< " " << PeerGroup{fi.unlB} << " overlap " << fi.overlap
std::cout << "Can fork " << PeerGroup{fi.unlA} << " " << " "
<< PeerGroup{fi.unlB} << " overlap " << fi.overlap
<< " required " << fi.required << "\n";
};
+5 -7
View File
@@ -220,9 +220,8 @@ public:
getList_ = [blob = blob, sig, manifest, version](int interval) {
// Build the contents of a version 1 format UNL file
std::stringstream l;
l << "{\"blob\":\"" << blob << "\""
<< ",\"signature\":\"" << sig << "\""
<< ",\"manifest\":\"" << manifest << "\""
l << "{\"blob\":\"" << blob << "\"" << ",\"signature\":\"" << sig
<< "\"" << ",\"manifest\":\"" << manifest << "\""
<< ",\"refresh_interval\": " << interval
<< ",\"version\":" << version << '}';
return l.str();
@@ -257,15 +256,14 @@ public:
std::stringstream l;
for (auto const& info : blobInfo)
{
l << "{\"blob\":\"" << info.blob << "\""
<< ",\"signature\":\"" << info.signature << "\"},";
l << "{\"blob\":\"" << info.blob << "\"" << ",\"signature\":\""
<< info.signature << "\"},";
}
std::string blobs = l.str();
blobs.pop_back();
l.str(std::string());
l << "{\"blobs_v2\": [ " << blobs << "],\"manifest\":\"" << manifest
<< "\""
<< ",\"refresh_interval\": " << interval
<< "\"" << ",\"refresh_interval\": " << interval
<< ",\"version\":" << (version + 1) << '}';
return l.str();
};
+70
View File
@@ -175,6 +175,76 @@ public:
}
};
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 <size_t N>
explicit finish_function(std::array<std::uint8_t, N> 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 <size_t N>
explicit data(std::array<std::uint8_t, N> 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
+5
View File
@@ -33,9 +33,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;
+2 -3
View File
@@ -897,9 +897,8 @@ protected:
printPeers(std::string const& msg, std::uint16_t validator = 0)
{
auto peers = network_.overlay().getPeers(network_.validator(validator));
std::cout << msg << " "
<< "num peers " << (int)network_.overlay().getNumPeers()
<< std::endl;
std::cout << msg << " " << "num peers "
<< (int)network_.overlay().getNumPeers() << std::endl;
for (auto& [k, v] : peers)
std::cout << k << ":" << (int)std::get<reduce_relay::PeerState>(v)
<< " ";
+18 -2
View File
@@ -522,6 +522,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;
};
@@ -1578,14 +1594,14 @@ public:
{
using namespace test::jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const xrpFees{featureXRPFees};
testServer();
testLedger();
testTransactions_APIv1();
testTransactions_APIv2();
testManifests();
testValidations(all - xrpFees);
testValidations(all - featureXRPFees - featureSmartEscrow);
testValidations(all - featureSmartEscrow);
testValidations(all);
testSubErrors(true);
testSubErrors(false);
+44 -5
View File
@@ -221,6 +221,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);
}
@@ -612,6 +621,7 @@ Ledger::setup()
{
bool oldFees = false;
bool newFees = false;
bool extensionFees = false;
{
auto const baseFee = sle->at(~sfBaseFee);
auto const reserveBase = sle->at(~sfReserveBase);
@@ -629,6 +639,7 @@ Ledger::setup()
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
auto const reserveIncrementXRP =
sle->at(~sfReserveIncrementDrops);
auto assign = [&ret](
XRPAmount& dest,
std::optional<STAmount> const& src) {
@@ -645,12 +656,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<std::uint32_t> 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&)
@@ -669,15 +703,20 @@ Ledger::setup()
void
Ledger::defaultFees(Config const& config)
{
XRPL_ASSERT(
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0,
"ripple::Ledger::defaultFees : zero fees");
assert(
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0 &&
fees_.extensionComputeLimit == 0 && fees_.extensionSizeLimit == 0 &&
fees_.gasPrice == 0);
if (fees_.base == 0)
fees_.base = config.FEES.reference_fee;
if (fees_.reserve == 0)
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<SLE>
+28 -30
View File
@@ -225,30 +225,30 @@ log_metadata_difference(
{
JLOG(j.debug()) << "MISMATCH on TX " << tx
<< ": Different result and index!";
JLOG(j.debug()) << " Built:"
<< " Result: " << builtMetaData->getResult()
<< " Index: " << builtMetaData->getIndex();
JLOG(j.debug()) << " Valid:"
<< " Result: " << validMetaData->getResult()
<< " Index: " << validMetaData->getIndex();
JLOG(j.debug())
<< " Built:" << " Result: " << builtMetaData->getResult()
<< " Index: " << builtMetaData->getIndex();
JLOG(j.debug())
<< " Valid:" << " Result: " << validMetaData->getResult()
<< " Index: " << validMetaData->getIndex();
}
else if (result_diff)
{
JLOG(j.debug())
<< "MISMATCH on TX " << tx << ": Different result!";
JLOG(j.debug()) << " Built:"
<< " Result: " << builtMetaData->getResult();
JLOG(j.debug()) << " Valid:"
<< " Result: " << validMetaData->getResult();
JLOG(j.debug())
<< " Built:" << " Result: " << builtMetaData->getResult();
JLOG(j.debug())
<< " Valid:" << " Result: " << validMetaData->getResult();
}
else if (index_diff)
{
JLOG(j.debug())
<< "MISMATCH on TX " << tx << ": Different index!";
JLOG(j.debug()) << " Built:"
<< " Index: " << builtMetaData->getIndex();
JLOG(j.debug()) << " Valid:"
<< " Index: " << validMetaData->getIndex();
JLOG(j.debug())
<< " Built:" << " Index: " << builtMetaData->getIndex();
JLOG(j.debug())
<< " Valid:" << " Index: " << validMetaData->getIndex();
}
}
else
@@ -267,12 +267,12 @@ log_metadata_difference(
JLOG(j.debug()) << "MISMATCH on TX " << tx
<< ": Different result and nodes!";
JLOG(j.debug())
<< " Built:"
<< " Result: " << builtMetaData->getResult() << " Nodes:\n"
<< " Built:" << " Result: " << builtMetaData->getResult()
<< " Nodes:\n"
<< builtNodes.getJson(JsonOptions::none);
JLOG(j.debug())
<< " Valid:"
<< " Result: " << validMetaData->getResult() << " Nodes:\n"
<< " Valid:" << " Result: " << validMetaData->getResult()
<< " Nodes:\n"
<< validNodes.getJson(JsonOptions::none);
}
else if (index_diff)
@@ -280,23 +280,21 @@ log_metadata_difference(
JLOG(j.debug()) << "MISMATCH on TX " << tx
<< ": Different index and nodes!";
JLOG(j.debug())
<< " Built:"
<< " Index: " << builtMetaData->getIndex() << " Nodes:\n"
<< " Built:" << " Index: " << builtMetaData->getIndex()
<< " Nodes:\n"
<< builtNodes.getJson(JsonOptions::none);
JLOG(j.debug())
<< " Valid:"
<< " Index: " << validMetaData->getIndex() << " Nodes:\n"
<< " Valid:" << " Index: " << validMetaData->getIndex()
<< " Nodes:\n"
<< validNodes.getJson(JsonOptions::none);
}
else // nodes_diff
{
JLOG(j.debug())
<< "MISMATCH on TX " << tx << ": Different nodes!";
JLOG(j.debug()) << " Built:"
<< " Nodes:\n"
JLOG(j.debug()) << " Built:" << " Nodes:\n"
<< builtNodes.getJson(JsonOptions::none);
JLOG(j.debug()) << " Valid:"
<< " Nodes:\n"
JLOG(j.debug()) << " Valid:" << " Nodes:\n"
<< validNodes.getJson(JsonOptions::none);
}
}
@@ -353,10 +351,10 @@ LedgerHistory::handleMismatch(
if (!builtLedger || !validLedger)
{
JLOG(j_.error()) << "MISMATCH cannot be analyzed:"
<< " builtLedger: " << to_string(built) << " -> "
<< builtLedger << " validLedger: " << to_string(valid)
<< " -> " << validLedger;
JLOG(j_.error()) << "MISMATCH cannot be analyzed:" << " builtLedger: "
<< to_string(built) << " -> " << builtLedger
<< " validLedger: " << to_string(valid) << " -> "
<< validLedger;
return;
}
@@ -100,8 +100,8 @@ TimeoutCounter::invokeOnTimer()
if (!progress_)
{
++timeouts_;
JLOG(journal_.debug()) << "Timeout(" << timeouts_ << ") "
<< " acquiring " << hash_;
JLOG(journal_.debug())
<< "Timeout(" << timeouts_ << ") " << " acquiring " << hash_;
onTimer(false, sl);
}
else
+2 -2
View File
@@ -447,8 +447,8 @@ GRPCServerImpl::handleRpcs()
if (!ok)
{
JLOG(journal_.debug()) << "Request listener cancelled. "
<< "Destroying object";
JLOG(journal_.debug())
<< "Request listener cancelled. " << "Destroying object";
erase(ptr);
}
else
+3 -3
View File
@@ -384,9 +384,9 @@ changeSpotPriceQuality(
{
JLOG(j.error())
<< "changeSpotPriceQuality failed: " << to_string(pool.in)
<< " " << to_string(pool.out) << " "
<< " " << quality << " " << tfee << " "
<< to_string(amounts.in) << " " << to_string(amounts.out);
<< " " << to_string(pool.out) << " " << " " << quality
<< " " << tfee << " " << to_string(amounts.in) << " "
<< to_string(amounts.out);
Throw<std::runtime_error>("changeSpotPriceQuality failed");
}
else
+79 -6
View File
@@ -28,10 +28,10 @@ namespace ripple {
namespace detail {
template <typename value_type>
class VotableValue
{
private:
using value_type = XRPAmount;
value_type const current_; // The current setting
value_type const target_; // The setting we want
std::map<value_type, int> voteMap_;
@@ -66,8 +66,9 @@ public:
getVotes() const;
};
auto
VotableValue::getVotes() const -> std::pair<value_type, bool>
template <typename value_type>
std::pair<value_type, bool>
VotableValue<value_type>::getVotes() const
{
value_type ourVote = current_;
int weight = 0;
@@ -190,6 +191,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
@@ -212,11 +240,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<STValidation> const& val,
detail::VotableValue& value,
detail::VotableValue<XRPAmount>& value,
SF_AMOUNT const& xrpField) {
if (auto const field = ~val->at(~xrpField);
field && field->native())
@@ -245,7 +284,7 @@ FeeVoteImpl::doVoting(
else
{
auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue& value,
detail::VotableValue<XRPAmount>& value,
auto const& valueField) {
if (auto const field = val->at(~valueField))
{
@@ -276,6 +315,30 @@ FeeVoteImpl::doVoting(
doVote(val, incReserveVote, sfReserveIncrement);
}
}
if (rules.enabled(featureSmartEscrow))
{
auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue<std::uint32_t>& 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
@@ -284,11 +347,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->info().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 << "/"
@@ -316,6 +383,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();
+46 -2
View File
@@ -2467,6 +2467,18 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> 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};
@@ -2916,12 +2928,22 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
l[jss::seq] = Json::UInt(lpClosed->info().seq);
l[jss::hash] = to_string(lpClosed->info().hash);
bool const smartEscrowEnabled =
m_ledgerMaster.getValidatedLedger()->rules().enabled(
featureSmartEscrow);
if (!human)
{
l[jss::base_fee] = baseFee.jsonClipped();
l[jss::reserve_base] =
lpClosed->fees().accountReserve(0).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->info().closeTime.time_since_epoch().count());
}
@@ -2931,6 +2953,13 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
l[jss::reserve_base_xrp] =
lpClosed->fees().accountReserve(0).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)
@@ -3121,6 +3150,14 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
lpAccepted->fees().accountReserve(0).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());
@@ -3491,8 +3528,8 @@ NetworkOPsImp::pubAccountTransaction(
}
JLOG(m_journal.trace())
<< "pubAccountTransaction: "
<< "proposed=" << iProposed << ", accepted=" << iAccepted;
<< "pubAccountTransaction: " << "proposed=" << iProposed
<< ", accepted=" << iAccepted;
if (!notify.empty() || !accountHistoryNotify.empty())
{
@@ -4172,6 +4209,13 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult)
jvResult[jss::reserve_base] =
lpClosed->fees().accountReserve(0).jsonClipped();
jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
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())
File diff suppressed because it is too large. Load diff
+327
View File
@@ -0,0 +1,327 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#pragma once
#include <xrpld/app/misc/WasmVM.h>
#include <wasm_c_api.h>
#include <wasm_export.h>
namespace ripple {
struct WamrResult
{
wasm_val_vec_t r;
bool f; // failure flag
WamrResult(unsigned N = 0) : r{0, nullptr, 0, 0, nullptr}, f(false)
{
if (N)
wasm_val_vec_new_uninitialized(&r, N);
}
~WamrResult()
{
if (r.size)
wasm_val_vec_delete(&r);
}
WamrResult(WamrResult const&) = delete;
WamrResult&
operator=(WamrResult const&) = delete;
WamrResult(WamrResult&& o)
{
*this = std::move(o);
}
WamrResult&
operator=(WamrResult&& o)
{
r = o.r;
o.r = {0, nullptr, 0, 0, nullptr};
f = o.f;
o.f = false;
return *this;
}
// operator wasm_val_vec_t &() {return r;}
};
using ModulePtr = std::unique_ptr<wasm_module_t, decltype(&wasm_module_delete)>;
using InstancePtr =
std::unique_ptr<wasm_instance_t, decltype(&wasm_instance_delete)>;
using FuncInfo = std::pair<wasm_func_t const*, wasm_functype_t const*>;
struct InstanceWrapper
{
wasm_extern_vec_t exports_;
InstancePtr instance_;
wasm_exec_env_t execEnv_ = nullptr;
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
private:
static InstancePtr
init(
wasm_store_t* s,
wasm_module_t* m,
int32_t maxPages,
wasm_extern_vec_t* expt,
wasm_extern_vec_t const& imports,
beast::Journal j);
public:
InstanceWrapper();
InstanceWrapper(InstanceWrapper&& o);
InstanceWrapper&
operator=(InstanceWrapper&& o);
InstanceWrapper(
wasm_store_t* s,
wasm_module_t* m,
int32_t maxPages,
int64_t gas,
wasm_extern_vec_t const& imports,
beast::Journal j);
~InstanceWrapper();
operator bool() const;
FuncInfo
getFunc(
std::string_view funcName,
wasm_exporttype_vec_t const& export_types) const;
wmem
getMem() const;
std::int64_t
getGas() const;
};
struct ModuleWrapper
{
ModulePtr module_;
InstanceWrapper instanceWrap_;
wasm_exporttype_vec_t exportTypes_;
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
private:
static ModulePtr
init(wasm_store_t* s, Bytes const& wasmBin, beast::Journal j);
public:
ModuleWrapper();
ModuleWrapper(ModuleWrapper&& o);
ModuleWrapper&
operator=(ModuleWrapper&& o);
ModuleWrapper(
wasm_store_t* s,
Bytes const& wasmBin,
bool instantiate,
int32_t maxPages,
int64_t gas,
std::vector<WasmImportFunc> const& imports,
beast::Journal j);
~ModuleWrapper();
operator bool() const;
FuncInfo
getFunc(std::string_view funcName) const;
wmem
getMem() const;
InstanceWrapper const&
getInstance(int i = 0) const;
int
addInstance(
wasm_store_t* s,
int32_t maxPages,
int64_t gas,
wasm_extern_vec_t const& imports = WASM_EMPTY_VEC);
std::int64_t
getGas();
private:
static void
makeImpParams(wasm_valtype_vec_t& v, WasmImportFunc const& imp);
static void
makeImpReturn(wasm_valtype_vec_t& v, WasmImportFunc const& imp);
wasm_extern_vec_t
buildImports(wasm_store_t* s, std::vector<WasmImportFunc> const& imports);
};
class WamrEngine
{
std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)> engine_;
std::unique_ptr<wasm_store_t, decltype(&wasm_store_delete)> store_;
std::unique_ptr<ModuleWrapper> moduleWrap_;
std::int32_t defMaxPages_ = -1;
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
std::mutex m_; // 1 instance mutex
public:
WamrEngine();
~WamrEngine() = default;
Expected<WasmResult<int32_t>, TER>
run(Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params,
std::vector<WasmImportFunc> const& imports,
HostFunctions* hfs,
int64_t gas,
beast::Journal j);
NotTEC
check(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params,
std::vector<WasmImportFunc> const& imports,
beast::Journal j);
std::int32_t
initMaxPages(std::int32_t def);
std::int64_t
getGas();
// Host functions helper functionality
wasm_trap_t*
newTrap(std::string_view 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<WasmResult<int32_t>, TER>
runHlp(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params,
std::vector<WasmImportFunc> const& imports,
HostFunctions* hfs,
int64_t gas);
NotTEC
checkHlp(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params,
std::vector<WasmImportFunc> const& imports);
int
addModule(
Bytes const& wasmCode,
bool instantiate,
int64_t gas,
std::vector<WasmImportFunc> const& imports);
void
clearModules();
// int addInstance();
int32_t
runFunc(std::string_view const funcName, int32_t p);
int32_t
makeModule(
Bytes const& wasmCode,
wasm_extern_vec_t const& imports = WASM_EMPTY_VEC);
FuncInfo
getFunc(std::string_view funcName);
std::vector<wasm_val_t>
convertParams(std::vector<WasmParam> const& params);
static int
compareParamTypes(
wasm_valtype_vec_t const* ftp,
std::vector<wasm_val_t> const& p);
static void
add_param(std::vector<wasm_val_t>& in, int32_t p);
static void
add_param(std::vector<wasm_val_t>& in, int64_t p);
template <int NR, class... Types>
inline WamrResult
call(std::string_view func, Types&&... args);
template <int NR, class... Types>
inline WamrResult
call(FuncInfo const& f, Types&&... args);
template <int NR, class... Types>
inline WamrResult
call(FuncInfo const& f, std::vector<wasm_val_t>& in);
template <int NR, class... Types>
inline WamrResult
call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
std::int32_t p,
Types&&... args);
template <int NR, class... Types>
inline WamrResult
call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
std::int64_t p,
Types&&... args);
template <int NR, class... Types>
inline WamrResult
call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
uint8_t const* d,
std::size_t sz,
Types&&... args);
template <int NR, class... Types>
inline WamrResult
call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
Bytes const& p,
Types&&... args);
};
} // namespace ripple
+228
View File
@@ -0,0 +1,228 @@
//------------------------------------------------------------------------------
/*
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 <xrpld/app/misc/WasmParamsHelper.h>
#include <xrpl/basics/Expected.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/TER.h>
namespace ripple {
enum HostFunctionErrors : int32_t {
HF_ERR_INTERNAL = -1,
HF_ERR_FIELD_NOT_FOUND = -2,
HF_ERR_BUFFER_TOO_SMALL = -3,
HF_ERR_NO_ARRAY = -4,
HF_ERR_NOT_LEAF_FIELD = -5,
HF_ERR_LOCATOR_MALFORMED = -6,
HF_ERR_SLOT_OUT_RANGE = -7,
HF_ERR_SLOTS_FULL = -8,
HF_ERR_EMPTY_SLOT = -9,
HF_ERR_LEDGER_OBJ_NOT_FOUND = -10,
HF_ERR_DECODING = -11,
HF_ERR_DATA_FIELD_TOO_LARGE = -12,
HF_ERR_POINTER_OUT_OF_BOUNDS = -13,
HF_ERR_NO_MEM_EXPORTED = -14,
HF_ERR_INVALID_PARAMS = -15,
HF_ERR_INVALID_ACCOUNT = -16,
HF_ERR_INVALID_FIELD = -17,
HF_ERR_INDEX_OUT_OF_BOUNDS = -18,
};
struct HostFunctions
{
virtual void
setRT(void const*)
{
}
virtual void const*
getRT() const
{
return nullptr;
}
virtual beast::Journal
getJournal()
{
return beast::Journal{beast::Journal::getNullSink()};
}
virtual int32_t
getLedgerSqn()
{
return 1;
}
virtual int32_t
getParentLedgerTime()
{
return 1;
}
virtual Hash
getParentLedgerHash()
{
return {};
}
virtual int32_t
cacheLedgerObj(Keylet const& keylet, int32_t cacheIdx)
{
return HF_ERR_INTERNAL;
}
virtual Expected<Bytes, int32_t>
getTxField(SField const& fname)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
getCurrentLedgerObjField(SField const& fname)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
getLedgerObjField(int32_t cacheIdx, SField const& fname)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
getTxNestedField(Slice const& locator)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
getCurrentLedgerObjNestedField(Slice const& locator)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual int32_t
getTxArrayLen(SField const& fname)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
getCurrentLedgerObjArrayLen(SField const& fname)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
getTxNestedArrayLen(Slice const& locator)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
getCurrentLedgerObjNestedArrayLen(Slice const& locator)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
updateData(Bytes const& data)
{
return HF_ERR_INTERNAL;
}
virtual Hash
computeSha512HalfHash(Bytes const& data)
{
return Hash{};
}
virtual Expected<Bytes, int32_t>
accountKeylet(AccountID const& account)
{
return Bytes{};
}
virtual Expected<Bytes, int32_t>
credentialKeylet(
AccountID const& subject,
AccountID const& issuer,
Bytes const& credentialType)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
escrowKeylet(AccountID const& account, std::uint32_t seq)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
oracleKeylet(AccountID const& account, std::uint32_t docId)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
getNFT(AccountID const& account, uint256 const& nftId)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual int32_t
trace(std::string const& msg, Bytes const& data, bool asHex)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
traceNum(std::string const& msg, int64_t data)
{
return HF_ERR_INTERNAL;
}
virtual ~HostFunctions() = default;
};
} // namespace ripple
+515
View File
@@ -0,0 +1,515 @@
//------------------------------------------------------------------------------
/*
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 <xrpld/app/misc/WasmHostFuncImpl.h>
#include <xrpld/app/tx/detail/NFTokenUtils.h>
#include <xrpl/protocol/STBitString.h>
#include <xrpl/protocol/digest.h>
#ifdef _DEBUG
// #define DEBUG_OUTPUT 1
// #define DEBUG_OUTPUT_WAMR 1
#endif
namespace ripple {
int32_t
WasmHostFunctionsImpl::getLedgerSqn()
{
return ctx.view().seq();
}
int32_t
WasmHostFunctionsImpl::getParentLedgerTime()
{
return ctx.view().parentCloseTime().time_since_epoch().count();
}
Hash
WasmHostFunctionsImpl::getParentLedgerHash()
{
return ctx.view().info().parentHash;
}
int32_t
WasmHostFunctionsImpl::cacheLedgerObj(Keylet const& keylet, int32_t cacheIdx)
{
if (cacheIdx < 0 || cacheIdx > MAX_CACHE)
return HF_ERR_SLOT_OUT_RANGE;
if (!cacheIdx)
{
for (cacheIdx = 0; cacheIdx < MAX_CACHE; ++cacheIdx)
if (!cache[cacheIdx])
break;
}
else
--cacheIdx;
if (cacheIdx >= MAX_CACHE)
return HF_ERR_SLOTS_FULL;
cache[cacheIdx] = ctx.view().read(keylet);
return cache[cacheIdx] ? cacheIdx + 1 : HF_ERR_LEDGER_OBJ_NOT_FOUND;
}
static Expected<Bytes, int32_t>
getAnyFieldData(STBase const* obj)
{
// auto const& fname = obj.getFName();
if (!obj)
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
auto const stype = obj->getSType();
switch (stype)
{
case STI_UNKNOWN:
case STI_NOTPRESENT:
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
break;
case STI_OBJECT:
case STI_ARRAY:
return Unexpected(HF_ERR_NOT_LEAF_FIELD);
break;
case STI_ACCOUNT: {
auto const& super(static_cast<STAccount const*>(obj));
auto const& data = super->value();
return Bytes{data.begin(), data.end()};
}
break;
case STI_AMOUNT: {
auto const& super(static_cast<STAmount const*>(obj));
int64_t const data = super->xrp().drops();
auto const* b = reinterpret_cast<uint8_t const*>(&data);
auto const* e = reinterpret_cast<uint8_t const*>(&data + 1);
return Bytes{b, e};
}
break;
case STI_VL: {
auto const& super(static_cast<STBlob const*>(obj));
auto const& data = super->value();
return Bytes{data.begin(), data.end()};
}
break;
case STI_UINT256: {
auto const& super(static_cast<STBitString<256> const*>(obj));
auto const& data = super->value();
return Bytes{data.begin(), data.end()};
}
break;
case STI_UINT32: {
auto const& super(
static_cast<STInteger<std::uint32_t> const*>(obj));
std::uint32_t const data = super->value();
auto const* b = reinterpret_cast<uint8_t const*>(&data);
auto const* e = reinterpret_cast<uint8_t const*>(&data + 1);
return Bytes{b, e};
}
break;
default:
break;
}
Serializer msg;
obj->add(msg);
return msg.getData();
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getTxField(SField const& fname)
{
return getAnyFieldData(ctx.tx.peekAtPField(fname));
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getCurrentLedgerObjField(SField const& fname)
{
auto const sle = ctx.view().read(leKey);
if (!sle)
return Unexpected(HF_ERR_LEDGER_OBJ_NOT_FOUND);
return getAnyFieldData(sle->peekAtPField(fname));
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getLedgerObjField(int32_t cacheIdx, SField const& fname)
{
--cacheIdx;
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
return Unexpected(HF_ERR_SLOT_OUT_RANGE);
if (!cache[cacheIdx])
return Unexpected(HF_ERR_EMPTY_SLOT);
return getAnyFieldData(cache[cacheIdx]->peekAtPField(fname));
}
static inline bool
noField(STBase const* field)
{
return !field || (STI_NOTPRESENT == field->getSType()) ||
(STI_UNKNOWN == field->getSType());
}
static Expected<STBase const*, int32_t>
locateField(STObject const& obj, Slice const& loc)
{
if (loc.empty() || (loc.size() & 3)) // must be multiple of 4
return Unexpected(HF_ERR_LOCATOR_MALFORMED);
int32_t const* l = reinterpret_cast<int32_t const*>(loc.data());
int32_t const sz = loc.size() / 4;
STBase const* field = nullptr;
auto const& m = SField::getKnownCodeToField();
{
int32_t const c = l[0];
auto const it = m.find(c);
if (it == m.end())
return Unexpected(HF_ERR_INVALID_FIELD);
auto const& fname(*it->second);
field = obj.peekAtPField(fname);
if (noField(field))
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
}
for (int i = 1; i < sz; ++i)
{
int32_t const c = l[i];
if (STI_ARRAY == field->getSType())
{
auto const* arr = static_cast<STArray const*>(field);
if (c >= arr->size())
return Unexpected(HF_ERR_INDEX_OUT_OF_BOUNDS);
field = &(arr->operator[](c));
}
else if (STI_OBJECT == field->getSType())
{
auto const* o = static_cast<STObject const*>(field);
auto const it = m.find(c);
if (it == m.end())
return Unexpected(HF_ERR_INVALID_FIELD);
auto const& fname(*it->second);
field = o->peekAtPField(fname);
}
else // simple field must be the last one
{
return Unexpected(HF_ERR_LOCATOR_MALFORMED);
}
if (noField(field))
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
}
return field;
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getTxNestedField(Slice const& locator)
{
auto const r = locateField(ctx.tx, locator);
if (!r)
return Unexpected(r.error());
return getAnyFieldData(r.value());
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getCurrentLedgerObjNestedField(Slice const& locator)
{
auto const sle = ctx.view().read(leKey);
if (!sle)
return Unexpected(HF_ERR_LEDGER_OBJ_NOT_FOUND);
auto const r = locateField(*sle, locator);
if (!r)
return Unexpected(r.error());
return getAnyFieldData(r.value());
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getLedgerObjNestedField(
int32_t cacheIdx,
Slice const& locator)
{
--cacheIdx;
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
return Unexpected(HF_ERR_SLOT_OUT_RANGE);
if (!cache[cacheIdx])
return Unexpected(HF_ERR_EMPTY_SLOT);
auto const r = locateField(*cache[cacheIdx], locator);
if (!r)
return Unexpected(r.error());
return getAnyFieldData(r.value());
}
int32_t
WasmHostFunctionsImpl::getTxArrayLen(SField const& fname)
{
if (fname.fieldType != STI_ARRAY)
return HF_ERR_NO_ARRAY;
auto const* field = ctx.tx.peekAtPField(fname);
if (noField(field))
return HF_ERR_FIELD_NOT_FOUND;
if (field->getSType() != STI_ARRAY)
return HF_ERR_NO_ARRAY;
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
}
int32_t
WasmHostFunctionsImpl::getCurrentLedgerObjArrayLen(SField const& fname)
{
if (fname.fieldType != STI_ARRAY)
return HF_ERR_NO_ARRAY;
auto const sle = ctx.view().read(leKey);
if (!sle)
return HF_ERR_LEDGER_OBJ_NOT_FOUND;
auto const* field = sle->peekAtPField(fname);
if (noField(field))
return HF_ERR_FIELD_NOT_FOUND;
if (field->getSType() != STI_ARRAY)
return HF_ERR_NO_ARRAY;
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
}
int32_t
WasmHostFunctionsImpl::getLedgerObjArrayLen(
int32_t cacheIdx,
SField const& fname)
{
if (fname.fieldType != STI_ARRAY)
return HF_ERR_NO_ARRAY;
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
return HF_ERR_SLOT_OUT_RANGE;
if (!cache[cacheIdx])
return HF_ERR_EMPTY_SLOT;
auto const* field = cache[cacheIdx]->peekAtPField(fname);
if (noField(field))
return HF_ERR_FIELD_NOT_FOUND;
if (field->getSType() != STI_ARRAY)
return HF_ERR_NO_ARRAY;
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
}
int32_t
WasmHostFunctionsImpl::getTxNestedArrayLen(Slice const& locator)
{
auto const r = locateField(ctx.tx, locator);
if (!r)
return r.error();
auto const* field = r.value();
if (field->getSType() != STI_ARRAY)
return HF_ERR_NO_ARRAY;
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
}
int32_t
WasmHostFunctionsImpl::getCurrentLedgerObjNestedArrayLen(Slice const& locator)
{
auto const sle = ctx.view().read(leKey);
if (!sle)
return HF_ERR_LEDGER_OBJ_NOT_FOUND;
auto const r = locateField(*sle, locator);
if (!r)
return r.error();
auto const* field = r.value();
if (field->getSType() != STI_ARRAY)
return HF_ERR_NO_ARRAY;
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
}
int32_t
WasmHostFunctionsImpl::getLedgerObjNestedArrayLen(
int32_t cacheIdx,
Slice const& locator)
{
--cacheIdx;
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
return HF_ERR_SLOT_OUT_RANGE;
if (!cache[cacheIdx])
return HF_ERR_EMPTY_SLOT;
auto const r = locateField(*cache[cacheIdx], locator);
if (!r)
return r.error();
auto const* field = r.value();
if (field->getSType() != STI_ARRAY)
return HF_ERR_NO_ARRAY;
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
}
int32_t
WasmHostFunctionsImpl::updateData(Bytes const& data)
{
auto sle = ctx.view().peek(leKey);
if (!sle)
return HF_ERR_LEDGER_OBJ_NOT_FOUND;
sle->setFieldVL(sfData, data);
ctx.view().update(sle);
return 0;
}
Hash
WasmHostFunctionsImpl::computeSha512HalfHash(Bytes const& data)
{
auto const hash = sha512Half(data);
return hash;
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::accountKeylet(AccountID const& account)
{
if (!account)
return Unexpected(HF_ERR_INVALID_ACCOUNT);
auto const keylet = keylet::account(account);
return Bytes{keylet.key.begin(), keylet.key.end()};
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::credentialKeylet(
AccountID const& subject,
AccountID const& issuer,
Bytes const& credentialType)
{
if (!subject || !issuer)
return Unexpected(HF_ERR_INVALID_ACCOUNT);
if (credentialType.empty() ||
credentialType.size() > maxCredentialTypeLength)
return Unexpected(HF_ERR_INVALID_PARAMS);
auto const keylet =
keylet::credential(subject, issuer, makeSlice(credentialType));
return Bytes{keylet.key.begin(), keylet.key.end()};
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::escrowKeylet(AccountID const& account, std::uint32_t seq)
{
if (!account)
return Unexpected(HF_ERR_INVALID_ACCOUNT);
auto const keylet = keylet::escrow(account, seq);
return Bytes{keylet.key.begin(), keylet.key.end()};
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::oracleKeylet(
AccountID const& account,
std::uint32_t documentId)
{
if (!account)
return Unexpected(HF_ERR_INVALID_ACCOUNT);
auto const keylet = keylet::oracle(account, documentId);
return Bytes{keylet.key.begin(), keylet.key.end()};
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getNFT(AccountID const& account, uint256 const& nftId)
{
if (!account)
return Unexpected(HF_ERR_INVALID_ACCOUNT);
if (!nftId)
return Unexpected(HF_ERR_INVALID_PARAMS);
auto obj = nft::findToken(ctx.view(), account, nftId);
if (!obj)
return Unexpected(HF_ERR_LEDGER_OBJ_NOT_FOUND);
auto ouri = obj->at(~sfURI);
if (!ouri)
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
Slice const s = ouri->value();
return Bytes(s.begin(), s.end());
}
int32_t
WasmHostFunctionsImpl::trace(
std::string const& msg,
Bytes const& data,
bool asHex)
{
#ifdef DEBUG_OUTPUT
auto j = ctx.journal.error();
#else
auto j = ctx.journal.trace();
#endif
if (!asHex)
j << "WAMR TRACE (" << leKey.key << "): " << msg << " - "
<< std::string_view(
reinterpret_cast<char const*>(data.data()), data.size());
else
{
auto const hex =
boost::algorithm::hex(std::string(data.begin(), data.end()));
j << "WAMR DEV TRACE (" << leKey.key << "): " << msg << " - " << hex;
}
return msg.size() + data.size() * (asHex ? 2 : 1);
}
int32_t
WasmHostFunctionsImpl::traceNum(std::string const& msg, int64_t data)
{
#ifdef DEBUG_OUTPUT
auto j = ctx.journal.error();
#else
auto j = ctx.journal.trace();
#endif
j << "WAMR DEV TRACE (" << leKey.key << "): " << msg << " - " << data;
return msg.size() + sizeof(data);
}
} // namespace ripple
+139
View File
@@ -0,0 +1,139 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#pragma once
#include <xrpld/app/misc/WasmHostFunc.h>
#include <xrpld/app/tx/detail/ApplyContext.h>
namespace ripple {
class WasmHostFunctionsImpl : public HostFunctions
{
ApplyContext& ctx;
Keylet leKey;
static int constexpr MAX_CACHE = 256;
std::array<std::shared_ptr<SLE const>, MAX_CACHE> cache;
void const* rt_ = nullptr;
public:
WasmHostFunctionsImpl(ApplyContext& ctx, Keylet const& leKey)
: ctx(ctx), leKey(leKey)
{
}
virtual void
setRT(void const* rt) override
{
rt_ = rt;
}
virtual void const*
getRT() const override
{
return rt_;
}
beast::Journal
getJournal() override
{
return ctx.journal;
}
int32_t
getLedgerSqn() override;
int32_t
getParentLedgerTime() override;
Hash
getParentLedgerHash() override;
int32_t
cacheLedgerObj(Keylet const& keylet, int32_t cacheIdx) override;
Expected<Bytes, int32_t>
getTxField(SField const& fname) override;
Expected<Bytes, int32_t>
getCurrentLedgerObjField(SField const& fname) override;
Expected<Bytes, int32_t>
getLedgerObjField(int32_t cacheIdx, SField const& fname) override;
Expected<Bytes, int32_t>
getTxNestedField(Slice const& locator) override;
Expected<Bytes, int32_t>
getCurrentLedgerObjNestedField(Slice const& locator) override;
Expected<Bytes, int32_t>
getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) override;
int32_t
getTxArrayLen(SField const& fname) override;
int32_t
getCurrentLedgerObjArrayLen(SField const& fname) override;
int32_t
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) override;
int32_t
getTxNestedArrayLen(Slice const& locator) override;
int32_t
getCurrentLedgerObjNestedArrayLen(Slice const& locator) override;
int32_t
getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) override;
int32_t
updateData(Bytes const& data) override;
Hash
computeSha512HalfHash(Bytes const& data) override;
Expected<Bytes, int32_t>
accountKeylet(AccountID const& account) override;
Expected<Bytes, int32_t>
credentialKeylet(
AccountID const& subject,
AccountID const& issuer,
Bytes const& credentialType) override;
Expected<Bytes, int32_t>
escrowKeylet(AccountID const& account, std::uint32_t seq) override;
Expected<Bytes, int32_t>
oracleKeylet(AccountID const& account, std::uint32_t documentId) override;
Expected<Bytes, int32_t>
getNFT(AccountID const& account, uint256 const& nftId) override;
int32_t
trace(std::string const& msg, Bytes const& data, bool asHex) override;
int32_t
traceNum(std::string const& msg, int64_t data) override;
};
} // namespace ripple
+764
View File
@@ -0,0 +1,764 @@
//------------------------------------------------------------------------------
/*
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 <xrpld/app/misc/WamrVM.h>
#include <xrpld/app/misc/WasmHostFunc.h>
#include <xrpld/app/misc/WasmHostFuncWrapper.h>
#include <xrpld/app/tx/detail/NFTokenUtils.h>
#include <xrpl/protocol/digest.h>
namespace ripple {
static int32_t
setData(
InstanceWrapper const* rt,
int32_t dst,
int32_t dsz,
uint8_t const* src,
int32_t ssz)
{
if (!ssz)
return 0;
if (dst < 0 || dsz < 0 || !src || ssz < 0)
return HF_ERR_INVALID_PARAMS;
auto mem = rt ? rt->getMem() : wmem();
if (!mem.s)
return HF_ERR_NO_MEM_EXPORTED;
if (dst + dsz > mem.s)
return HF_ERR_POINTER_OUT_OF_BOUNDS;
if (ssz > dsz)
return HF_ERR_BUFFER_TOO_SMALL;
memcpy(mem.p + dst, src, ssz);
return ssz;
}
static Expected<Bytes, int32_t>
getData(InstanceWrapper const* rt, int32_t src, int32_t ssz)
{
if (src < 0 || ssz <= 0)
return Unexpected(HF_ERR_INVALID_PARAMS);
auto mem = rt ? rt->getMem() : wmem();
if (!mem.s)
return Unexpected(HF_ERR_NO_MEM_EXPORTED);
if (src + ssz > mem.s)
return Unexpected(HF_ERR_POINTER_OUT_OF_BOUNDS);
Bytes data(mem.p + src, mem.p + src + ssz);
return data;
}
static Expected<AccountID, int32_t>
getDataAccount(InstanceWrapper const* rt, int32_t ptr, int32_t sz)
{
auto const r = getData(rt, ptr, sz);
if (!r || (r->size() != AccountID::bytes))
return Unexpected(HF_ERR_INVALID_PARAMS);
return AccountID::fromVoid(r->data());
}
static Expected<std::string, int32_t>
getDataString(InstanceWrapper const* rt, int32_t src, int32_t ssz)
{
if (src < 0 || ssz <= 0)
return Unexpected(HF_ERR_INVALID_PARAMS);
auto mem = rt ? rt->getMem() : wmem();
if (!mem.s)
return Unexpected(HF_ERR_NO_MEM_EXPORTED);
if (src + ssz > mem.s)
return Unexpected(HF_ERR_POINTER_OUT_OF_BOUNDS);
std::string data(mem.p + src, mem.p + src + ssz);
return data;
}
#define RET(x) \
results->data[0] = WASM_I32_VAL(x); \
results->num_elems = 1; \
return nullptr;
wasm_trap_t*
getLedgerSqnOld_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
// auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
int32_t const sqn = hf->getLedgerSqn();
RET(sqn);
}
wasm_trap_t*
getLedgerSqn_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
int32_t const sqn = hf->getLedgerSqn();
RET(setData(
rt,
params->data[0].of.i32,
params->data[1].of.i32,
reinterpret_cast<uint8_t const*>(&sqn),
static_cast<int32_t>(sizeof(sqn))));
}
wasm_trap_t*
getParentLedgerTime_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
int32_t const ltime = hf->getParentLedgerTime();
RET(setData(
rt,
params->data[0].of.i32,
params->data[1].of.i32,
reinterpret_cast<uint8_t const*>(&ltime),
static_cast<int32_t>(sizeof(ltime))));
}
wasm_trap_t*
getParentLedgerHash_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
Hash const hash = hf->getParentLedgerHash();
RET(setData(
rt,
params->data[0].of.i32,
params->data[1].of.i32,
hash.data(),
static_cast<int32_t>(hash.size())));
}
wasm_trap_t*
cacheLedgerObj_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
if (r->size() != uint256::bytes)
{
RET(HF_ERR_INVALID_PARAMS);
}
uint256 const key(uint256::fromVoid(r->data()));
int32_t const idx =
hf->cacheLedgerObj(keylet::unchecked(key), params->data[2].of.i32);
RET(idx);
}
wasm_trap_t*
getTxField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const& m = SField::getKnownCodeToField();
auto const it = m.find(params->data[0].of.i32);
if (it == m.end())
{
RET(HF_ERR_INVALID_FIELD);
}
auto const& fname(*it->second);
auto fieldData = hf->getTxField(fname);
if (!fieldData)
{
RET(fieldData.error());
}
RET(setData(
rt,
params->data[1].of.i32,
params->data[2].of.i32,
fieldData->data(),
fieldData->size()));
}
wasm_trap_t*
getCurrentLedgerObjField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const& m = SField::getKnownCodeToField();
auto const it = m.find(params->data[0].of.i32);
if (it == m.end())
{
RET(HF_ERR_INVALID_FIELD);
}
auto const& fname(*it->second);
auto fieldData = hf->getCurrentLedgerObjField(fname);
if (!fieldData)
{
RET(fieldData.error());
}
RET(setData(
rt,
params->data[1].of.i32,
params->data[2].of.i32,
fieldData->data(),
fieldData->size()));
}
wasm_trap_t*
getLedgerObjField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const& m = SField::getKnownCodeToField();
auto const it = m.find(params->data[1].of.i32);
if (it == m.end())
{
RET(HF_ERR_INVALID_FIELD);
}
auto const& fname(*it->second);
auto fieldData = hf->getLedgerObjField(params->data[0].of.i32, fname);
if (!fieldData)
{
RET(fieldData.error());
}
RET(setData(
rt,
params->data[2].of.i32,
params->data[3].of.i32,
fieldData->data(),
fieldData->size()));
}
wasm_trap_t*
getTxNestedField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
auto fieldData = hf->getTxNestedField(makeSlice(r.value()));
if (!fieldData)
{
RET(fieldData.error());
}
RET(setData(
rt,
params->data[2].of.i32,
params->data[3].of.i32,
fieldData->data(),
fieldData->size()));
}
wasm_trap_t*
getCurrentLedgerObjNestedField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
auto fieldData = hf->getCurrentLedgerObjNestedField(makeSlice(r.value()));
if (!fieldData)
{
RET(fieldData.error());
}
RET(setData(
rt,
params->data[2].of.i32,
params->data[3].of.i32,
fieldData->data(),
fieldData->size()));
}
wasm_trap_t*
getLedgerObjNestedField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[1].of.i32, params->data[2].of.i32);
if (!r)
{
RET(r.error());
}
auto fieldData = hf->getLedgerObjNestedField(
params->data[0].of.i32, makeSlice(r.value()));
if (!fieldData)
{
RET(fieldData.error());
}
RET(setData(
rt,
params->data[3].of.i32,
params->data[4].of.i32,
fieldData->data(),
fieldData->size()));
}
wasm_trap_t*
getTxArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
// auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const& m = SField::getKnownCodeToField();
auto const it = m.find(params->data[0].of.i32);
if (it == m.end())
{
RET(HF_ERR_INVALID_FIELD);
}
auto const& fname(*it->second);
int32_t sz = hf->getTxArrayLen(fname);
RET(sz);
}
wasm_trap_t*
getCurrentLedgerObjArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
// auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const& m = SField::getKnownCodeToField();
auto const it = m.find(params->data[0].of.i32);
if (it == m.end())
{
RET(HF_ERR_INVALID_FIELD);
}
auto const& fname(*it->second);
int32_t sz = hf->getCurrentLedgerObjArrayLen(fname);
RET(sz);
}
wasm_trap_t*
getLedgerObjArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
// auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const& m = SField::getKnownCodeToField();
auto const it = m.find(params->data[1].of.i32);
if (it == m.end())
{
RET(HF_ERR_INVALID_FIELD);
}
auto const& fname(*it->second);
int32_t sz = hf->getLedgerObjArrayLen(params->data[0].of.i32, fname);
RET(sz);
}
wasm_trap_t*
getTxNestedArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
int32_t sz = hf->getTxNestedArrayLen(makeSlice(r.value()));
RET(sz);
}
wasm_trap_t*
getCurrentLedgerObjNestedArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
int32_t sz = hf->getCurrentLedgerObjNestedArrayLen(makeSlice(r.value()));
RET(sz);
}
wasm_trap_t*
getLedgerObjNestedArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[1].of.i32, params->data[2].of.i32);
if (!r)
{
RET(r.error());
}
int32_t sz = hf->getLedgerObjNestedArrayLen(
params->data[0].of.i32, makeSlice(r.value()));
RET(sz);
}
wasm_trap_t*
updateData_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
if (params->data[1].of.i32 > maxWasmDataLength)
{
RET(HF_ERR_DATA_FIELD_TOO_LARGE)
}
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
RET(hf->updateData(r.value()));
}
wasm_trap_t*
computeSha512HalfHash_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
auto const hash = hf->computeSha512HalfHash(r.value());
RET(setData(
rt,
params->data[2].of.i32,
params->data[3].of.i32,
hash.data(),
hash.size()));
}
wasm_trap_t*
accountKeylet_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const acc =
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!acc)
{
RET(acc.error());
}
auto const k = hf->accountKeylet(acc.value());
if (!k)
{
RET(k.error());
}
RET(setData(
rt,
params->data[2].of.i32,
params->data[3].of.i32,
k->data(),
k->size()));
}
wasm_trap_t*
credentialKeylet_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const subject =
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!subject)
{
RET(subject.error());
}
auto const issuer =
getDataAccount(rt, params->data[2].of.i32, params->data[3].of.i32);
if (!issuer)
{
RET(issuer.error());
}
auto const credType =
getData(rt, params->data[4].of.i32, params->data[5].of.i32);
if (!credType)
{
RET(credType.error());
}
auto const k =
hf->credentialKeylet(subject.value(), issuer.value(), credType.value());
if (!k)
{
RET(k.error());
}
RET(setData(
rt,
params->data[6].of.i32,
params->data[7].of.i32,
k->data(),
k->size()));
}
wasm_trap_t*
escrowKeylet_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const acc =
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!acc)
{
RET(acc.error());
}
auto const k = hf->escrowKeylet(acc.value(), params->data[2].of.i32);
if (!k)
{
RET(k.error());
}
RET(setData(
rt,
params->data[3].of.i32,
params->data[4].of.i32,
k->data(),
k->size()));
}
wasm_trap_t*
oracleKeylet_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const acc =
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!acc)
{
RET(acc.error());
}
auto const k = hf->oracleKeylet(acc.value(), params->data[2].of.i32);
if (!k)
{
RET(k.error());
}
RET(setData(
rt,
params->data[3].of.i32,
params->data[4].of.i32,
k->data(),
k->size()));
}
wasm_trap_t*
getNFT_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const acc =
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!acc)
{
RET(acc.error());
}
auto const nftRaw =
getData(rt, params->data[2].of.i32, params->data[3].of.i32);
if (!nftRaw)
{
RET(nftRaw.error());
}
if (nftRaw->size() != uint256::bytes)
{
hf->getJournal().trace()
<< "WAMR getNFT: Invalid NFT data size: " << nftRaw->size()
<< ", expected " << (uint256::bytes);
RET(HF_ERR_INVALID_PARAMS);
}
uint256 const ntfId(uint256::fromVoid(nftRaw->data()));
auto const nftURI = hf->getNFT(acc.value(), ntfId);
if (!nftURI)
{
RET(nftURI.error());
}
RET(setData(
rt,
params->data[4].of.i32,
params->data[5].of.i32,
nftURI->data(),
nftURI->size()));
}
wasm_trap_t*
trace_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const msg =
getDataString(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!msg)
{
RET(msg.error());
}
auto const data =
getData(rt, params->data[2].of.i32, params->data[3].of.i32);
if (!data)
{
RET(data.error());
}
auto const e = hf->trace(msg.value(), data.value(), params->data[4].of.i32);
RET(e);
}
wasm_trap_t*
traceNum_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const msg =
getDataString(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!msg)
{
RET(msg.error());
}
auto const e = hf->traceNum(msg.value(), params->data[2].of.i64);
RET(e);
}
} // namespace ripple
+222
View File
@@ -0,0 +1,222 @@
//------------------------------------------------------------------------------
/*
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 <xrpld/app/misc/WamrVM.h>
namespace ripple {
using getLedgerSqnOld_proto = int32_t();
wasm_trap_t*
getLedgerSqnOld_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getLedgerSqn_proto = int32_t(uint8_t*, 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(uint8_t*, 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 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 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 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 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 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 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 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);
} // namespace ripple
+272
View File
@@ -0,0 +1,272 @@
//------------------------------------------------------------------------------
/*
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 <xrpl/basics/base_uint.h>
#include <boost/function_types/function_arity.hpp>
#include <boost/function_types/parameter_types.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/mpl/vector.hpp>
#include <optional>
#include <string>
#include <vector>
namespace bft = boost::function_types;
namespace ripple {
using Bytes = std::vector<std::uint8_t>;
using Hash = ripple::uint256;
struct wmem
{
std::uint8_t* p = nullptr;
std::size_t s = 0;
};
template <typename T>
struct WasmResult
{
T result;
int64_t cost;
};
typedef WasmResult<bool> EscrowResult;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
enum WasmTypes { WT_I32, WT_I64, WT_F32, WT_F64, WT_U8V };
struct WasmImportFunc
{
std::string name;
std::optional<WasmTypes> result;
std::vector<WasmTypes> params;
void* udata = nullptr;
// wasm_func_callback_with_env_t
void* wrap = nullptr;
uint32_t gas = 0;
};
#define WASM_IMPORT_FUNC(v, f, ...) \
WasmImpFunc<f##_proto>( \
v, #f, reinterpret_cast<void*>(&f##_wrap), ##__VA_ARGS__)
#define WASM_IMPORT_FUNC2(v, f, n, ...) \
WasmImpFunc<f##_proto>( \
v, n, reinterpret_cast<void*>(&f##_wrap), ##__VA_ARGS__)
template <int N, int C, typename mpl>
void
WasmImpArgs(WasmImportFunc& e)
{
if constexpr (N < C)
{
using at = typename boost::mpl::at_c<mpl, N>::type;
if constexpr (std::is_pointer_v<at>)
e.params.push_back(WT_I32);
else if constexpr (std::is_same_v<at, std::int32_t>)
e.params.push_back(WT_I32);
else if constexpr (std::is_same_v<at, std::int64_t>)
e.params.push_back(WT_I64);
else if constexpr (std::is_same_v<at, float>)
e.params.push_back(WT_F32);
else if constexpr (std::is_same_v<at, double>)
e.params.push_back(WT_F64);
else
static_assert(std::is_pointer_v<at>, "Unsupported argument type");
return WasmImpArgs<N + 1, C, mpl>(e);
}
return;
}
template <typename rt>
void
WasmImpRet(WasmImportFunc& e)
{
if constexpr (std::is_pointer_v<rt>)
e.result = WT_I32;
else if constexpr (std::is_same_v<rt, std::int32_t>)
e.result = WT_I32;
else if constexpr (std::is_same_v<rt, std::int64_t>)
e.result = WT_I64;
else if constexpr (std::is_same_v<rt, float>)
e.result = WT_F32;
else if constexpr (std::is_same_v<rt, double>)
e.result = WT_F64;
else if constexpr (std::is_void_v<rt>)
e.result.reset();
#if (defined(__GNUC__) && (__GNUC__ >= 14)) || \
((defined(__clang_major__)) && (__clang_major__ >= 18))
else
static_assert(false, "Unsupported return type");
#endif
}
template <typename F>
void
WasmImpFuncHelper(WasmImportFunc& e)
{
using rt = typename bft::result_type<F>::type;
using pt = typename bft::parameter_types<F>::type;
// typename boost::mpl::at_c<mpl, N>::type
WasmImpRet<rt>(e);
WasmImpArgs<0, bft::function_arity<F>::value, pt>(e);
// WasmImpWrap(e, std::forward<F>(f));
}
template <typename F>
void
WasmImpFunc(
std::vector<WasmImportFunc>& v,
std::string_view imp_name,
void* f_wrap,
void* data = nullptr,
uint32_t gas = 0)
{
WasmImportFunc e;
e.name = imp_name;
e.udata = data;
e.wrap = f_wrap;
e.gas = gas;
WasmImpFuncHelper<F>(e);
v.push_back(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 <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, std::int32_t p, Types&&... args)
{
v.push_back({.type = WT_I32, .of = {.i32 = p}});
wasmParamsHlp(v, std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, std::int64_t p, Types&&... args)
{
v.push_back({.type = WT_I64, .of = {.i64 = p}});
wasmParamsHlp(v, std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, float p, Types&&... args)
{
v.push_back({.type = WT_F32, .of = {.f32 = p}});
wasmParamsHlp(v, std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, double p, Types&&... args)
{
v.push_back({.type = WT_F64, .of = {.f64 = p}});
wasmParamsHlp(v, std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(
std::vector<WasmParam>& 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<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, Bytes const& p, Types&&... args)
{
wasmParamsHlp(
v,
p.data(),
static_cast<std::int32_t>(p.size()),
std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(
std::vector<WasmParam>& v,
std::string_view const& p,
Types&&... args)
{
wasmParamsHlp(
v,
reinterpret_cast<std::uint8_t const*>(p.data()),
static_cast<std::int32_t>(p.size()),
std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, std::string const& p, Types&&... args)
{
wasmParamsHlp(
v,
reinterpret_cast<std::uint8_t const*>(p.c_str()),
static_cast<std::int32_t>(p.size()),
std::forward<Types>(args)...);
}
inline void
wasmParamsHlp(std::vector<WasmParam>& v)
{
return;
}
template <class... Types>
inline std::vector<WasmParam>
wasmParams(Types&&... args)
{
std::vector<WasmParam> v;
v.reserve(sizeof...(args));
wasmParamsHlp(v, std::forward<Types>(args)...);
return v;
}
} // namespace ripple
+216
View File
@@ -0,0 +1,216 @@
//------------------------------------------------------------------------------
/*
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 <xrpld/app/misc/WamrVM.h>
#include <xrpld/app/misc/WasmHostFunc.h>
#include <xrpld/app/misc/WasmHostFuncWrapper.h>
#include <xrpl/basics/Log.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <memory>
namespace ripple {
static std::vector<WasmImportFunc>
createImports(HostFunctions* hfs)
{
std::vector<WasmImportFunc> imports;
if (hfs)
{
// TODO: remove after escrow_test wasm module will be updated
WASM_IMPORT_FUNC2(imports, getLedgerSqnOld, "getLedgerSqn", hfs);
WASM_IMPORT_FUNC2(imports, getLedgerSqn, "get_ledger_sqn", hfs);
WASM_IMPORT_FUNC2(
imports, getParentLedgerTime, "get_parent_ledger_time", hfs);
WASM_IMPORT_FUNC2(
imports, getParentLedgerHash, "get_parent_ledger_hash", hfs);
WASM_IMPORT_FUNC2(imports, cacheLedgerObj, "cache_ledger_obj", hfs);
WASM_IMPORT_FUNC2(imports, getTxField, "get_tx_field", hfs);
WASM_IMPORT_FUNC2(
imports,
getCurrentLedgerObjField,
"get_current_ledger_obj_field",
hfs);
WASM_IMPORT_FUNC2(
imports, getLedgerObjField, "get_ledger_obj_field", hfs);
WASM_IMPORT_FUNC2(
imports, getTxNestedField, "get_tx_nested_field", hfs);
WASM_IMPORT_FUNC2(
imports,
getCurrentLedgerObjNestedField,
"get_current_ledger_obj_nested_field",
hfs);
WASM_IMPORT_FUNC2(
imports,
getLedgerObjNestedField,
"get_ledger_obj_nested_field",
hfs);
WASM_IMPORT_FUNC2(imports, getTxArrayLen, "get_tx_array_len", hfs);
WASM_IMPORT_FUNC2(
imports,
getCurrentLedgerObjArrayLen,
"get_current_ledger_obj_array_len",
hfs);
WASM_IMPORT_FUNC2(
imports, getLedgerObjArrayLen, "get_ledger_obj_array_len", hfs);
WASM_IMPORT_FUNC2(
imports, getTxNestedArrayLen, "get_tx_nested_array_len", hfs);
WASM_IMPORT_FUNC2(
imports,
getCurrentLedgerObjNestedArrayLen,
"get_current_ledger_obj_nested_array_len",
hfs);
WASM_IMPORT_FUNC2(
imports,
getLedgerObjNestedArrayLen,
"get_ledger_obj_nested_array_len",
hfs);
WASM_IMPORT_FUNC2(imports, updateData, "update_data", hfs);
WASM_IMPORT_FUNC2(
imports, computeSha512HalfHash, "compute_sha512_half", hfs);
WASM_IMPORT_FUNC2(imports, accountKeylet, "account_keylet", hfs);
WASM_IMPORT_FUNC2(imports, credentialKeylet, "credential_keylet", hfs);
WASM_IMPORT_FUNC2(imports, escrowKeylet, "escrow_keylet", hfs);
WASM_IMPORT_FUNC2(imports, oracleKeylet, "oracle_keylet", hfs);
WASM_IMPORT_FUNC2(imports, getNFT, "get_NFT", hfs);
WASM_IMPORT_FUNC(imports, trace, hfs);
WASM_IMPORT_FUNC2(imports, traceNum, "trace_num", hfs);
}
return imports;
}
Expected<EscrowResult, TER>
runEscrowWasm(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> 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,
createImports(hfs),
hfs,
gasLimit,
hfs ? hfs->getJournal() : j);
// std::cout << "runEscrowWasm, mod size: " << wasmCode.size()
// << ", gasLimit: " << gasLimit << ", funcName: " << funcName;
if (!ret.has_value())
{
// std::cout << ", error: " << ret.error() << std::endl;
return Unexpected<TER>(ret.error());
}
// std::cout << ", ret: " << ret->result << ", gas spent: " << ret->cost
// << std::endl;
return EscrowResult{ret->result > 0, ret->cost};
}
NotTEC
preflightEscrowWasm(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> 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,
createImports(hfs),
hfs ? hfs->getJournal() : j);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
WasmEngine::WasmEngine() : impl(std::make_unique<WamrEngine>())
{
}
WasmEngine&
WasmEngine::instance()
{
static WasmEngine e;
return e;
}
Expected<WasmResult<int32_t>, TER>
WasmEngine::run(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params,
std::vector<WasmImportFunc> 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<WasmParam> const& params,
std::vector<WasmImportFunc> const& imports,
beast::Journal j)
{
return impl->check(wasmCode, funcName, params, imports, j);
}
std::int32_t
WasmEngine::initMaxPages(std::int32_t def)
{
return impl->initMaxPages(def);
}
void*
WasmEngine::newTrap(std::string_view msg)
{
return impl->newTrap(msg);
}
beast::Journal
WasmEngine::getJournal() const
{
return impl->getJournal();
}
} // namespace ripple
+106
View File
@@ -0,0 +1,106 @@
//------------------------------------------------------------------------------
/*
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 <xrpld/app/misc/WasmHostFunc.h>
#include <string_view>
namespace ripple {
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";
uint32_t const MAX_PAGES = 128; // 8MB = 64KB*128
class WamrEngine;
class WasmEngine
{
std::unique_ptr<WamrEngine> 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<WasmResult<int32_t>, TER>
run(Bytes const& wasmCode,
std::string_view funcName = {},
std::vector<WasmParam> const& params = {},
std::vector<WasmImportFunc> 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<WasmParam> const& params = {},
std::vector<WasmImportFunc> const& imports = {},
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
std::int32_t
initMaxPages(std::int32_t def);
// Host functions helper functionality
void*
newTrap(std::string_view msg = {});
beast::Journal
getJournal() const;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expected<EscrowResult, TER>
runEscrowWasm(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params = {},
HostFunctions* hfs = nullptr,
int64_t gasLimit = -1,
beast::Journal j = beast::Journal(beast::Journal::getNullSink()));
NotTEC
preflightEscrowWasm(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params = {},
HostFunctions* hfs = nullptr,
beast::Journal j = beast::Journal(beast::Journal::getNullSink()));
} // namespace ripple
+2 -2
View File
@@ -150,8 +150,8 @@ ammLPHolds(
}
amount.setIssuer(ammAccount);
JLOG(j.trace()) << "ammLPHolds:"
<< " lpAccount=" << to_string(lpAccount)
JLOG(j.trace()) << "ammLPHolds:" << " lpAccount="
<< to_string(lpAccount)
<< " amount=" << amount.getFullText();
}
+5 -5
View File
@@ -1504,11 +1504,11 @@ TxQ::accept(Application& app, OpenView& view)
}
else
{
JLOG(j_.debug()) << "Queued transaction " << candidateIter->txID
<< " failed with " << transToken(txnResult)
<< ". Leave in queue."
<< " Applied: " << didApply
<< ". Flags: " << candidateIter->flags;
JLOG(j_.debug())
<< "Queued transaction " << candidateIter->txID
<< " failed with " << transToken(txnResult)
<< ". Leave in queue." << " Applied: " << didApply
<< ". Flags: " << candidateIter->flags;
if (account.retryPenalty && candidateIter->retriesRemaining > 2)
candidateIter->retriesRemaining = 1;
else
+1 -2
View File
@@ -238,8 +238,7 @@ Pathfinder::findPaths(
mSource = STPathElement(account, mSrcCurrency, issuer);
auto issuerString =
mSrcIssuer ? to_string(*mSrcIssuer) : std::string("none");
JLOG(j_.trace()) << "findPaths>"
<< " mSrcAccount=" << mSrcAccount
JLOG(j_.trace()) << "findPaths>" << " mSrcAccount=" << mSrcAccount
<< " mDstAccount=" << mDstAccount
<< " mDstAmount=" << mDstAmount.getFullText()
<< " mSrcCurrency=" << mSrcCurrency
+1 -2
View File
@@ -190,8 +190,7 @@ protected:
logStringImpl(char const* name) const
{
std::ostringstream ostr;
ostr << name << ": "
<< "\ninIss: " << book_.in.account
ostr << name << ": " << "\ninIss: " << book_.in.account
<< "\noutIss: " << book_.out.account
<< "\ninCur: " << book_.in.currency
<< "\noutCur: " << book_.out.currency;
+1 -2
View File
@@ -205,8 +205,7 @@ protected:
logStringImpl(char const* name) const
{
std::ostringstream ostr;
ostr << name << ": "
<< "\nSrc: " << src_ << "\nDst: " << dst_;
ostr << name << ": " << "\nSrc: " << src_ << "\nDst: " << dst_;
return ostr.str();
}
@@ -132,8 +132,7 @@ protected:
logStringImpl(char const* name) const
{
std::ostringstream ostr;
ostr << name << ": "
<< "\nAcc: " << acc_;
ostr << name << ": " << "\nAcc: " << acc_;
return ostr.str();
}
+2 -2
View File
@@ -238,8 +238,8 @@ rangeCheckedCast(C c)
/* This should never happen */
UNREACHABLE("ripple::rangeCheckedCast : domain error");
JLOG(debugLog().error())
<< "rangeCheckedCast domain error:"
<< " value = " << c << " min = " << std::numeric_limits<T>::lowest()
<< "rangeCheckedCast domain error:" << " value = " << c
<< " min = " << std::numeric_limits<T>::lowest()
<< " max: " << std::numeric_limits<T>::max();
}
+7
View File
@@ -106,6 +106,13 @@ public:
view_->deliver(amount);
}
/** Sets the gas used in the metadata */
void
setGasUsed(std::uint32_t const& gasUsed)
{
view_->setGasUsed(gasUsed);
}
/** Discard changes and start fresh. */
void
discard();
+45 -42
View File
@@ -212,8 +212,8 @@ Batch::preflight(PreflightContext const& ctx)
if (flags & tfBatchMask)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
<< "invalid flags.";
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId << "]:" << "invalid flags.";
return temINVALID_FLAG;
}
@@ -221,23 +221,24 @@ Batch::preflight(PreflightContext const& ctx)
flags &
(tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent)) != 1)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
<< "too many flags.";
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId << "]:" << "too many flags.";
return temINVALID_FLAG;
}
auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions);
if (rawTxns.size() <= 1)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
<< "txns array must have at least 2 entries.";
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]:" << "txns array must have at least 2 entries.";
return temARRAY_EMPTY;
}
if (rawTxns.size() > maxBatchTxCount)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
<< "txns array exceeds 8 entries.";
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
<< "]:" << "txns array exceeds 8 entries.";
return temARRAY_TOO_LARGE;
}
@@ -252,50 +253,53 @@ Batch::preflight(PreflightContext const& ctx)
auto const hash = stx.getTransactionID();
if (!uniqueHashes.emplace(hash).second)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "duplicate Txn found. "
<< "txID: " << hash;
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]: " << "duplicate Txn found. " << "txID: " << hash;
return temREDUNDANT;
}
if (stx.getFieldU16(sfTransactionType) == ttBATCH)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "batch cannot have an inner batch txn. "
<< "txID: " << hash;
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]: " << "batch cannot have an inner batch txn. "
<< "txID: " << hash;
return temINVALID;
}
if (!(stx.getFlags() & tfInnerBatchTxn))
{
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId << "]: "
<< "inner txn must have the tfInnerBatchTxn flag. "
<< "BatchTrace[" << parentBatchId
<< "]: " << "inner txn must have the tfInnerBatchTxn flag. "
<< "txID: " << hash;
return temINVALID_FLAG;
}
if (stx.isFieldPresent(sfTxnSignature))
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "inner txn cannot include TxnSignature. "
<< "txID: " << hash;
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]: " << "inner txn cannot include TxnSignature. "
<< "txID: " << hash;
return temBAD_SIGNATURE;
}
if (stx.isFieldPresent(sfSigners))
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "inner txn cannot include Signers. "
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
<< "]: " << "inner txn cannot include Signers. "
<< "txID: " << hash;
return temBAD_SIGNER;
}
if (!stx.getSigningPubKey().empty())
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "inner txn SigningPubKey must be empty. "
<< "txID: " << hash;
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]: " << "inner txn SigningPubKey must be empty. "
<< "txID: " << hash;
return temBAD_REGKEY;
}
@@ -304,10 +308,10 @@ Batch::preflight(PreflightContext const& ctx)
ctx.app, ctx.rules, parentBatchId, stx, tapBATCH, ctx.j);
preflightResult.ter != tesSUCCESS)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "inner txn preflight failed: "
<< transHuman(preflightResult.ter) << " "
<< "txID: " << hash;
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]: " << "inner txn preflight failed: "
<< transHuman(preflightResult.ter) << " " << "txID: " << hash;
return temINVALID_INNER_BATCH;
}
@@ -315,8 +319,8 @@ Batch::preflight(PreflightContext const& ctx)
if (auto const fee = stx.getFieldAmount(sfFee);
!fee.native() || fee.xrp() != beast::zero)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "inner txn must have a fee of 0. "
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
<< "]: " << "inner txn must have a fee of 0. "
<< "txID: " << hash;
return temBAD_FEE;
}
@@ -353,8 +357,7 @@ Batch::preflight(PreflightContext const& ctx)
{
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId << "]: "
<< "duplicate sequence found: "
<< "txID: " << hash;
<< "duplicate sequence found: " << "txID: " << hash;
return temREDUNDANT;
}
}
@@ -366,8 +369,7 @@ Batch::preflight(PreflightContext const& ctx)
{
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId << "]: "
<< "duplicate ticket found: "
<< "txID: " << hash;
<< "duplicate ticket found: " << "txID: " << hash;
return temREDUNDANT;
}
}
@@ -393,8 +395,8 @@ Batch::preflight(PreflightContext const& ctx)
// Check that the batch signers array is not too large.
if (signers.size() > maxBatchTxCount)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "signers array exceeds 8 entries.";
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
<< "]: " << "signers array exceeds 8 entries.";
return temARRAY_TOO_LARGE;
}
@@ -417,8 +419,8 @@ Batch::preflight(PreflightContext const& ctx)
if (!batchSigners.insert(signerAccount).second)
{
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId << "]: "
<< "duplicate signer found: " << signerAccount;
<< "BatchTrace[" << parentBatchId
<< "]: " << "duplicate signer found: " << signerAccount;
return temREDUNDANT;
}
@@ -426,8 +428,9 @@ Batch::preflight(PreflightContext const& ctx)
// Remove it if it does, as it can be crossed off the list.
if (requiredSigners.erase(signerAccount) == 0)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "no account signature for inner txn.";
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]: " << "no account signature for inner txn.";
return temBAD_SIGNER;
}
}
@@ -447,8 +450,8 @@ Batch::preflight(PreflightContext const& ctx)
if (!requiredSigners.empty())
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "invalid batch signers.";
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
<< "]: " << "invalid batch signers.";
return temBAD_SIGNER;
}
return tesSUCCESS;
+4 -4
View File
@@ -35,8 +35,8 @@ CancelOffer::preflight(PreflightContext const& ctx)
if (uTxFlags & tfUniversalMask)
{
JLOG(ctx.j.trace()) << "Malformed transaction: "
<< "Invalid flags set.";
JLOG(ctx.j.trace())
<< "Malformed transaction: " << "Invalid flags set.";
return temINVALID_FLAG;
}
@@ -63,8 +63,8 @@ CancelOffer::preclaim(PreclaimContext const& ctx)
if ((*sle)[sfSequence] <= offerSequence)
{
JLOG(ctx.j.trace()) << "Malformed transaction: "
<< "Sequence " << offerSequence << " is invalid.";
JLOG(ctx.j.trace()) << "Malformed transaction: " << "Sequence "
<< offerSequence << " is invalid.";
return temBAD_SEQUENCE;
}
+20
View File
@@ -130,6 +130,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:
@@ -377,6 +391,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);
+216 -28
View File
@@ -19,6 +19,8 @@
#include <xrpld/app/misc/CredentialHelpers.h>
#include <xrpld/app/misc/HashRouter.h>
#include <xrpld/app/misc/WasmHostFuncImpl.h>
#include <xrpld/app/misc/WasmVM.h>
#include <xrpld/app/tx/detail/Escrow.h>
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
#include <xrpld/conditions/Condition.h>
@@ -34,6 +36,8 @@
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h>
#include <algorithm>
// During an EscrowFinish, the transaction must specify both
// a condition and a fulfillment. We track whether that
// fulfillment matches and validates the condition.
@@ -118,9 +122,28 @@ escrowCreatePreflightHelper<MPTIssue>(PreflightContext const& ctx)
return tesSUCCESS;
}
XRPAmount
EscrowCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount txnFees{Transactor::calculateBaseFee(view, tx)};
if (tx.isFieldPresent(sfFinishFunction))
{
// TODO: make this fee increase based on the extra compute run
txnFees += 1000;
}
return txnFees;
}
NotTEC
EscrowCreate::preflight(PreflightContext const& ctx)
{
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
!ctx.rules.enabled(featureSmartEscrow))
{
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
return temDISABLED;
}
if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG;
@@ -157,14 +180,23 @@ 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;
if (ctx.rules.enabled(fix1571))
{
// 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])
@@ -189,6 +221,26 @@ EscrowCreate::preflight(PreflightContext const& ctx)
return temDISABLED;
}
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;
auto const re = preflightEscrowWasm(code, "finish", {}, &mock);
if (!isTesSuccess(re))
{
JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM";
return re;
}
}
return preflight2(ctx);
}
@@ -537,6 +589,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(featureTokenEscrow) && !isXRP(amount))
{
@@ -599,7 +653,12 @@ EscrowCreate::doApply()
}
// increment owner count
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
// TODO: determine actual reserve based on FinishFunction size
adjustOwnerCount(
ctx_.view(),
sle,
ctx_.tx.isFieldPresent(sfFinishFunction) ? 2 : 1,
ctx_.journal);
ctx_.view().update(sle);
return tesSUCCESS;
}
@@ -634,6 +693,13 @@ EscrowFinish::preflight(PreflightContext const& ctx)
!ctx.rules.enabled(featureCredentials))
return temDISABLED;
if (ctx.tx.isFieldPresent(sfComputationAllowance) &&
!ctx.rules.enabled(featureSmartEscrow))
{
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
return temDISABLED;
}
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
@@ -643,7 +709,10 @@ EscrowFinish::preflight(PreflightContext const& ctx)
// If you specify a condition, then you must also specify
// a fulfillment.
if (static_cast<bool>(cb) != static_cast<bool>(fb))
{
JLOG(ctx.j.debug()) << "Condition != Fulfillment";
return temMALFORMED;
}
// Verify the transaction signature. If it doesn't work
// then don't do any more work.
@@ -672,6 +741,16 @@ EscrowFinish::preflight(PreflightContext const& ctx)
}
}
if (auto const allowance = ctx.tx[~sfComputationAllowance]; allowance)
{
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); !isTesSuccess(err))
return err;
@@ -687,7 +766,10 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
{
extraFee += view.fees().base * (32 + (fb->size() / 16));
}
if (auto const allowance = tx[~sfComputationAllowance]; allowance)
{
extraFee += (*allowance) * view.fees().gasPrice / MICRO_DROPS_PER_DROP;
}
return Transactor::calculateBaseFee(view, tx) + extraFee;
}
@@ -766,25 +848,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(
[&]<typename T>(T const&) {
return escrowFinishPreclaimHelper<T>(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(
[&]<typename T>(T const&) {
return escrowFinishPreclaimHelper<T>(
ctx, dest, amount);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
}
}
}
return tesSUCCESS;
@@ -1016,7 +1125,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;
@@ -1031,11 +1141,17 @@ EscrowFinish::doApply()
// Too soon: can't execute before the finish time
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
{
JLOG(j_.debug()) << "Too soon";
return tecNO_PERMISSION;
}
// Too late: can't execute after the cancel time
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
{
JLOG(j_.debug()) << "Too late";
return tecNO_PERMISSION;
}
}
else
{
@@ -1043,13 +1159,35 @@ EscrowFinish::doApply()
if ((*slep)[~sfFinishAfter] &&
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
(*slep)[sfFinishAfter])
{
JLOG(j_.debug()) << "Too soon?";
return tecNO_PERMISSION;
}
// Too late?
if ((*slep)[~sfCancelAfter] &&
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
(*slep)[sfCancelAfter])
{
JLOG(j_.debug()) << "Too late?";
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 (ctx_.view().rules().enabled(featureDepositAuth))
{
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
!isTesSuccess(err))
return err;
}
}
// Check cryptocondition fulfillment
@@ -1099,17 +1237,59 @@ 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(featureDepositAuth))
if (!ctx_.view().rules().enabled(featureSmartEscrow))
{
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
!isTesSuccess(err))
return err;
// NOTE: Escrow payments cannot be used to fund accounts.
if (!sled)
return tecNO_DST;
if (ctx_.view().rules().enabled(featureDepositAuth))
{
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
!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<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
std::string funcName("finish");
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, funcName, {}, &ledgerDataProvider, allowance);
JLOG(j_.trace()) << "Escrow WASM ran";
if (re.has_value())
{
auto reValue = re.value().result;
// TODO: better error handling for this conversion
ctx_.setGasUsed(static_cast<uint32_t>(re.value().cost));
JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue)
<< ", cost: " << re.value().cost;
if (!reValue)
{
// ctx_.view().update(slep);
return tecWASM_REJECTED;
}
}
else
{
JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error());
return re.error();
}
}
AccountID const account = (*slep)[sfAccount];
@@ -1184,7 +1364,11 @@ EscrowFinish::doApply()
// Adjust source owner count
auto const sle = ctx_.view().peek(keylet::account(account));
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
adjustOwnerCount(
ctx_.view(),
sle,
slep->isFieldPresent(sfFinishFunction) ? -2 : -1,
ctx_.journal);
ctx_.view().update(sle);
// Remove escrow from ledger
@@ -1395,7 +1579,11 @@ EscrowCancel::doApply()
}
}
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
adjustOwnerCount(
ctx_.view(),
sle,
slep->isFieldPresent(sfFinishFunction) ? -2 : -1,
ctx_.journal);
ctx_.view().update(sle);
// Remove escrow from ledger
+3
View File
@@ -36,6 +36,9 @@ public:
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static NotTEC
preflight(PreflightContext const& ctx);
+35 -2
View File
@@ -964,6 +964,22 @@ removeExpiredCredentials(
}
}
static void
modifyWasmDataFields(
ApplyView& view,
std::vector<std::pair<uint256, Blob>> 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,
@@ -1121,6 +1137,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);
@@ -1133,13 +1150,16 @@ Transactor::operator()()
std::vector<uint256> removedTrustLines;
std::vector<uint256> expiredNFTokenOffers;
std::vector<uint256> expiredCredentials;
std::vector<std::pair<uint256, Blob>> 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,
@@ -1148,7 +1168,9 @@ Transactor::operator()()
doNFTokenOffers,
&expiredNFTokenOffers,
doCredentials,
&expiredCredentials](
&expiredCredentials,
doWasmData,
&modifiedWasmObjects](
uint256 const& index,
bool isDelete,
std::shared_ptr<SLE const> const& before,
@@ -1183,6 +1205,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)));
}
});
}
@@ -1212,6 +1241,10 @@ Transactor::operator()()
removeExpiredCredentials(
view(), expiredCredentials, ctx_.app.journal("View"));
if (result == tecWASM_REJECTED)
modifyWasmDataFields(
view(), modifiedWasmObjects, ctx_.app.journal("View"));
applied = isTecClaim(result);
}
+6 -8
View File
@@ -1121,8 +1121,8 @@ Consensus<Adaptor>::checkLedger(std::unique_ptr<std::stringstream> const& clog)
auto netLgr =
adaptor_.getPrevLedger(prevLedgerID_, previousLedger_, mode_.get());
CLOG(clog) << "network ledgerid " << netLgr << ", "
<< "previous ledger " << prevLedgerID_ << ". ";
CLOG(clog) << "network ledgerid " << netLgr << ", " << "previous ledger "
<< prevLedgerID_ << ". ";
if (netLgr != prevLedgerID_)
{
@@ -1213,8 +1213,7 @@ Consensus<Adaptor>::phaseOpen(std::unique_ptr<std::stringstream> const& clog)
adaptor_.parms().ledgerIDLE_INTERVAL,
2 * previousLedger_.closeTimeResolution());
CLOG(clog) << "idle interval set to " << idleInterval.count()
<< "ms based on "
<< "ledgerIDLE_INTERVAL: "
<< "ms based on " << "ledgerIDLE_INTERVAL: "
<< adaptor_.parms().ledgerIDLE_INTERVAL.count()
<< ", previous ledger close time resolution: "
<< previousLedger_.closeTimeResolution().count() << "ms. ";
@@ -1262,8 +1261,7 @@ Consensus<Adaptor>::shouldPause(
<< "roundTime: " << result_->roundTime.read().count() << ", "
<< "max consensus time: " << parms.ledgerMAX_CONSENSUS.count() << ", "
<< "validators: " << totalValidators << ", "
<< "laggards: " << laggards << ", "
<< "offline: " << offline << ", "
<< "laggards: " << laggards << ", " << "offline: " << offline << ", "
<< "quorum: " << quorum << ")";
if (!ahead || !laggards || !totalValidators || !adaptor_.validator() ||
@@ -1624,8 +1622,8 @@ Consensus<Adaptor>::updateOurPositions(
if (!haveCloseTimeConsensus_)
{
JLOG(j_.debug())
<< "No CT consensus:"
<< " Proposers:" << currPeerPositions_.size()
<< "No CT consensus:" << " Proposers:"
<< currPeerPositions_.size()
<< " Mode:" << to_string(mode_.get())
<< " Thresh:" << threshConsensus
<< " Pos:" << consensusCloseTime.time_since_epoch().count();
+9
View File
@@ -73,6 +73,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.) */
};
+6
View File
@@ -1140,6 +1140,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;
}
+7
View File
@@ -75,6 +75,12 @@ public:
deliver_ = amount;
}
void
setGasUsed(std::uint32_t const& gasUsed)
{
gasUsed_ = gasUsed;
}
/** Get the number of modified entries
*/
std::size_t
@@ -93,6 +99,7 @@ public:
private:
std::optional<STAmount> deliver_;
std::optional<std::uint32_t> gasUsed_;
};
} // namespace ripple
+6 -2
View File
@@ -117,6 +117,7 @@ ApplyStateTable::apply(
TER ter,
std::optional<STAmount> const& deliver,
std::optional<uint256 const> const& parentBatchId,
std::optional<std::uint32_t> const& gasUsed,
bool isDryRun,
beast::Journal j)
{
@@ -127,11 +128,14 @@ ApplyStateTable::apply(
std::optional<TxMeta> metadata;
if (!to.open() || isDryRun)
{
TxMeta meta(tx.getTransactionID(), to.seq(), parentBatchId);
TxMeta meta(tx.getTransactionID(), to.seq());
if (deliver)
meta.setDeliveredAmount(*deliver);
if (parentBatchId)
meta.setParentBatchId(*parentBatchId);
if (gasUsed)
meta.setGasUsed(*gasUsed);
Mods newMod;
for (auto& item : items_)
{
@@ -73,6 +73,7 @@ public:
TER ter,
std::optional<STAmount> const& deliver,
std::optional<uint256 const> const& parentBatchId,
std::optional<std::uint32_t> const& gasUsed,
bool isDryRun,
beast::Journal j);
+2 -1
View File
@@ -35,7 +35,8 @@ 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_, isDryRun, j);
}
std::size_t
+2 -4
View File
@@ -458,8 +458,7 @@ accountHolds(
amount.clear(Issue{currency, issuer});
}
JLOG(j.trace()) << "accountHolds:"
<< " account=" << to_string(account)
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(account)
<< " amount=" << amount.getFullText();
return view.balanceHook(account, issuer, amount);
@@ -640,8 +639,7 @@ xrpLiquid(
STAmount const amount =
(balance < reserve) ? STAmount{0} : balance - reserve;
JLOG(j.trace()) << "accountHolds:"
<< " account=" << to_string(id)
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id)
<< " amount=" << amount.getFullText()
<< " fullBalance=" << fullBalance.getFullText()
<< " balance=" << balance.getFullText()
+2 -2
View File
@@ -1272,8 +1272,8 @@ PeerImp::handleTransaction(
{
// If we've never been in synch, there's nothing we can do
// with a transaction
JLOG(p_journal_.debug()) << "Ignoring incoming transaction: "
<< "Need network ledger";
JLOG(p_journal_.debug())
<< "Ignoring incoming transaction: " << "Need network ledger";
return;
}
+3 -3
View File
@@ -1132,9 +1132,9 @@ public:
}
else
{
JLOG(m_journal.error()) << beast::leftw(18) << "Logic failed "
<< "'" << source->name() << "' fetch, "
<< results.error.message();
JLOG(m_journal.error())
<< beast::leftw(18) << "Logic failed " << "'" << source->name()
<< "' fetch, " << results.error.message();
}
}