mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-18 09:52:31 +00:00
Compare commits
268 Commits
gregtatcam
...
ripple/se/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36fb84926e | ||
|
|
56decaf852 | ||
|
|
1d9fe9dbaf | ||
|
|
8cc2169939 | ||
|
|
a4dfdaf77a | ||
|
|
826f613ad8 | ||
|
|
1259c1d5ca | ||
|
|
34522bc668 | ||
|
|
d2641d85bd | ||
|
|
75f66bd9fe | ||
|
|
7cd71cb659 | ||
|
|
9917f96166 | ||
|
|
e1cc82587b | ||
|
|
2cc9439fde | ||
|
|
52af9582e2 | ||
|
|
46e88dc732 | ||
|
|
bc24f2e211 | ||
|
|
7a7c993b15 | ||
|
|
9733ca8f91 | ||
|
|
18d5e3e226 | ||
|
|
b30b4e1d65 | ||
|
|
d435893602 | ||
|
|
00b0cf50f6 | ||
|
|
22054a573d | ||
|
|
7ef256499c | ||
|
|
1338062be7 | ||
|
|
4fc1778ec8 | ||
|
|
65322d9e78 | ||
|
|
63bd5fc4ee | ||
|
|
82e7f7eeec | ||
|
|
c5598a4284 | ||
|
|
63d09f9c51 | ||
|
|
0deb6bcadf | ||
|
|
d606f88e84 | ||
|
|
9b013b559b | ||
|
|
1d4a3c00b8 | ||
|
|
4b34102e8e | ||
|
|
d006433579 | ||
|
|
a7ab8ee923 | ||
|
|
e0073a4402 | ||
|
|
2930ef217f | ||
|
|
9dbb301699 | ||
|
|
531e8b6ebd | ||
|
|
90397e1a52 | ||
|
|
888ca2e6d9 | ||
|
|
51458a92e2 | ||
|
|
b6514b680f | ||
|
|
913e4b919e | ||
|
|
196e6a1b27 | ||
|
|
27468ddbcf | ||
|
|
bce5d91e45 | ||
|
|
654338fa66 | ||
|
|
864d88a3c2 | ||
|
|
9c25d18851 | ||
|
|
3a825a41e1 | ||
|
|
a9ebf786c6 | ||
|
|
5afe8cc321 | ||
|
|
bc5ec3c962 | ||
|
|
7004d216ed | ||
|
|
1775251e90 | ||
|
|
61bcb7621f | ||
|
|
a3f71b1774 | ||
|
|
4df7d1a4bb | ||
|
|
00eeab6d44 | ||
|
|
125df7a425 | ||
|
|
b08bcf5d21 | ||
|
|
dc413aef0c | ||
|
|
77dfd56ace | ||
|
|
953b9a3500 | ||
|
|
1d9ec84350 | ||
|
|
0392846a17 | ||
|
|
5148098079 | ||
|
|
1b4a564369 | ||
|
|
fd524c4be9 | ||
|
|
495dda7f58 | ||
|
|
9c3c0280b1 | ||
|
|
f73d8a6cf2 | ||
|
|
4fe508cb92 | ||
|
|
6728ab52b7 | ||
|
|
eb2d44d442 | ||
|
|
77673663ca | ||
|
|
c1381f8ddd | ||
|
|
bd16f7989d | ||
|
|
65f9cf80c0 | ||
|
|
cd46b5d999 | ||
|
|
de55a5ebfc | ||
|
|
2ec4a1114e | ||
|
|
13707dda05 | ||
|
|
ba03a8a9d2 | ||
|
|
7c8279ec83 | ||
|
|
f625fb993a | ||
|
|
0418ffb26a | ||
|
|
b2627039f6 | ||
|
|
8f97ec3bde | ||
|
|
e85e7b1b1a | ||
|
|
69c61b2235 | ||
|
|
6ae0e860ff | ||
|
|
c077e7f073 | ||
|
|
803a344c65 | ||
|
|
4eb34f381a | ||
|
|
72fffb6e51 | ||
|
|
f7ee580f01 | ||
|
|
122d405750 | ||
|
|
c1c1b4ea67 | ||
|
|
977caea0a5 | ||
|
|
d7ed6d6512 | ||
|
|
f1f2e2629f | ||
|
|
917c610f96 | ||
|
|
317e533d81 | ||
|
|
4160677878 | ||
|
|
4621e4eda3 | ||
|
|
df98db1452 | ||
|
|
981ac7abf4 | ||
|
|
673476ef1b | ||
|
|
8bc6f9cd70 | ||
|
|
ba5debfecd | ||
|
|
f4a27c9b6d | ||
|
|
fd1cb318e3 | ||
|
|
43c80edaf4 | ||
|
|
8c3544a58c | ||
|
|
ed5139d4e3 | ||
|
|
42494dd4cf | ||
|
|
ce84cc8b44 | ||
|
|
9a9a7aab01 | ||
|
|
209a1a6ffa | ||
|
|
384b3608d7 | ||
|
|
fc35a9f9c8 | ||
|
|
c5e50aa221 | ||
|
|
074b1f00d5 | ||
|
|
7a9d245950 | ||
|
|
1809fe07f2 | ||
|
|
409c67494a | ||
|
|
c626b6403a | ||
|
|
81cbc91927 | ||
|
|
e1513570df | ||
|
|
1c812a6c4d | ||
|
|
0724927799 | ||
|
|
d83ec96848 | ||
|
|
f0d0739528 | ||
|
|
375dd50b35 | ||
|
|
419d53ec4c | ||
|
|
d4d70d5675 | ||
|
|
6ab15f8377 | ||
|
|
91f3d51f3d | ||
|
|
9ed60b45f8 | ||
|
|
d5c53dcfd2 | ||
|
|
103379836a | ||
|
|
e94321fb41 | ||
|
|
bbc28b3b1c | ||
|
|
843e981c8a | ||
|
|
5aab274b7a | ||
|
|
2c30e41191 | ||
|
|
8ea5106b0b | ||
|
|
f57f67a8ae | ||
|
|
397bc8781e | ||
|
|
a98269f049 | ||
|
|
8bb8c2e38b | ||
|
|
b66bc47ca9 | ||
|
|
0e9c7458bb | ||
|
|
36ecd3b52b | ||
|
|
1d89940653 | ||
|
|
1a1a6806ec | ||
|
|
1977df9c2e | ||
|
|
9d1f51b01a | ||
|
|
6c95548df5 | ||
|
|
69ab39d658 | ||
|
|
b9eb66eecc | ||
|
|
827ecc6e3a | ||
|
|
881087dd3d | ||
|
|
90e0bbd0fc | ||
|
|
b57df290de | ||
|
|
8a403f1241 | ||
|
|
6d2640871d | ||
|
|
27ac30208d | ||
|
|
c145598ff9 | ||
|
|
50e5608d86 | ||
|
|
e40a4df777 | ||
|
|
7a7b96107c | ||
|
|
500bb68831 | ||
|
|
95d78a8600 | ||
|
|
53eb0f60bc | ||
|
|
41205ae928 | ||
|
|
c33b0ae463 | ||
|
|
16087c9680 | ||
|
|
56bc6d58f6 | ||
|
|
ef5d335e09 | ||
|
|
25c3060fef | ||
|
|
ce9f0b38a4 | ||
|
|
35f7cbf772 | ||
|
|
578413859c | ||
|
|
0db564d261 | ||
|
|
427b7ea104 | ||
|
|
3195eb16b2 | ||
|
|
7bf6878b4b | ||
|
|
eed280d169 | ||
|
|
0bc1a115ff | ||
|
|
334bcfa5ef | ||
|
|
106dea4559 | ||
|
|
3ffdcf8114 | ||
|
|
4021a7eb28 | ||
|
|
0690fda0f1 | ||
|
|
d0cc48c6d3 | ||
|
|
d66e3c949e | ||
|
|
aebec5378c | ||
|
|
0c65a386b5 | ||
|
|
67e2c1b563 | ||
|
|
29f5430881 | ||
|
|
209ee25c32 | ||
|
|
101f285bcd | ||
|
|
af6beb1d7c | ||
|
|
ce19c13059 | ||
|
|
286dc6322b | ||
|
|
c9346cd40d | ||
|
|
43caa1ef29 | ||
|
|
1c5683ec78 | ||
|
|
9bee155d59 | ||
|
|
f34b05f4de | ||
|
|
97ce25f4ce | ||
|
|
9e14c14a26 | ||
|
|
fe601308e7 | ||
|
|
c507880d8f | ||
|
|
3f8328bbf8 | ||
|
|
51f1be7f5b | ||
|
|
c10a5f9ef6 | ||
|
|
3c141de695 | ||
|
|
f57b855d74 | ||
|
|
da2b9455f2 | ||
|
|
86525d8583 | ||
|
|
51ee06429b | ||
|
|
cb622488c0 | ||
|
|
32f971fec6 | ||
|
|
ca85d09f02 | ||
|
|
8dea76baa4 | ||
|
|
299fbe04c4 | ||
|
|
57fc1df7d7 | ||
|
|
e59f5f3b01 | ||
|
|
c8b06e7de1 | ||
|
|
eaba76f9e6 | ||
|
|
cb702cc238 | ||
|
|
c3fd52c177 | ||
|
|
b69b4a0a4a | ||
|
|
50d6072a73 | ||
|
|
d24cd50e61 | ||
|
|
737fab5471 | ||
|
|
5a6c4e8ae0 | ||
|
|
9f5875158c | ||
|
|
c3dc33c861 | ||
|
|
7420f47658 | ||
|
|
6be8f2124c | ||
|
|
edfed06001 | ||
|
|
1c646dba91 | ||
|
|
6781068058 | ||
|
|
cfe57c1dfe | ||
|
|
c34d09a971 | ||
|
|
ebd90c4742 | ||
|
|
ba52d34828 | ||
|
|
1b6312afb3 | ||
|
|
bf32dc2e72 | ||
|
|
a15d65f7a2 | ||
|
|
2de8488855 | ||
|
|
129aa4bfaa | ||
|
|
b1d70db63b | ||
|
|
f03c3aafe4 | ||
|
|
51a9f106d1 | ||
|
|
bfc048e3fe | ||
|
|
83418644f7 | ||
|
|
dbc9dd5bfc | ||
|
|
45ab15d4b5 |
32
.clang-tidy
32
.clang-tidy
@@ -10,26 +10,26 @@ Checks: "-*,
|
||||
bugprone-chained-comparison,
|
||||
bugprone-compare-pointer-to-member-virtual-function,
|
||||
bugprone-copy-constructor-init,
|
||||
bugprone-crtp-constructor-accessibility,
|
||||
# bugprone-crtp-constructor-accessibility, # has issues
|
||||
bugprone-dangling-handle,
|
||||
bugprone-dynamic-static-initializers,
|
||||
bugprone-empty-catch,
|
||||
# bugprone-empty-catch, # has issues
|
||||
bugprone-fold-init-type,
|
||||
bugprone-forward-declaration-namespace,
|
||||
bugprone-inaccurate-erase,
|
||||
bugprone-inc-dec-in-conditions,
|
||||
bugprone-incorrect-enable-if,
|
||||
bugprone-incorrect-roundings,
|
||||
bugprone-infinite-loop,
|
||||
bugprone-integer-division,
|
||||
# bugprone-forward-declaration-namespace, # has issues
|
||||
# bugprone-inaccurate-erase,
|
||||
# bugprone-inc-dec-in-conditions,
|
||||
# bugprone-incorrect-enable-if,
|
||||
# bugprone-incorrect-roundings,
|
||||
# bugprone-infinite-loop,
|
||||
# bugprone-integer-division,
|
||||
bugprone-lambda-function-name,
|
||||
bugprone-macro-parentheses,
|
||||
# bugprone-macro-parentheses, # has issues
|
||||
bugprone-macro-repeated-side-effects,
|
||||
bugprone-misplaced-operator-in-strlen-in-alloc,
|
||||
bugprone-misplaced-pointer-arithmetic-in-alloc,
|
||||
bugprone-misplaced-widening-cast,
|
||||
bugprone-move-forwarding-reference,
|
||||
bugprone-multi-level-implicit-pointer-conversion,
|
||||
# bugprone-multi-level-implicit-pointer-conversion, # has issues
|
||||
bugprone-multiple-new-in-one-expression,
|
||||
bugprone-multiple-statement-macro,
|
||||
bugprone-no-escape,
|
||||
@@ -39,13 +39,13 @@ Checks: "-*,
|
||||
bugprone-pointer-arithmetic-on-polymorphic-object,
|
||||
bugprone-posix-return,
|
||||
bugprone-redundant-branch-condition,
|
||||
bugprone-reserved-identifier,
|
||||
bugprone-return-const-ref-from-parameter,
|
||||
# bugprone-reserved-identifier, # has issues
|
||||
# bugprone-return-const-ref-from-parameter, # has issues
|
||||
bugprone-shared-ptr-array-mismatch,
|
||||
bugprone-signal-handler,
|
||||
bugprone-signed-char-misuse,
|
||||
bugprone-sizeof-container,
|
||||
bugprone-sizeof-expression,
|
||||
# bugprone-sizeof-expression, # has issues
|
||||
bugprone-spuriously-wake-up-functions,
|
||||
bugprone-standalone-empty,
|
||||
bugprone-string-constructor,
|
||||
@@ -62,7 +62,7 @@ Checks: "-*,
|
||||
bugprone-suspicious-string-compare,
|
||||
bugprone-suspicious-stringview-data-usage,
|
||||
bugprone-swapped-arguments,
|
||||
bugprone-switch-missing-default-case,
|
||||
# bugprone-switch-missing-default-case, # has issues
|
||||
bugprone-terminating-continue,
|
||||
bugprone-throw-keyword-missing,
|
||||
bugprone-too-small-loop-variable,
|
||||
@@ -73,7 +73,7 @@ Checks: "-*,
|
||||
bugprone-unhandled-self-assignment,
|
||||
bugprone-unique-ptr-array-mismatch,
|
||||
bugprone-unsafe-functions,
|
||||
bugprone-use-after-move, # has issues
|
||||
# bugprone-use-after-move, # has issues
|
||||
bugprone-unused-raii,
|
||||
bugprone-unused-return-value,
|
||||
bugprone-unused-local-non-trivial-variable,
|
||||
|
||||
@@ -17,6 +17,7 @@ repos:
|
||||
args: [--maxkb=400, --enforce-all]
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: mixed-line-ending
|
||||
- id: check-merge-conflict
|
||||
args: [--assume-in-merge]
|
||||
|
||||
@@ -37,7 +38,6 @@ repos:
|
||||
rev: c2bc67fe8f8f549cc489e00ba8b45aa18ee713b1 # frozen: v3.8.1
|
||||
hooks:
|
||||
- id: prettier
|
||||
args: [--end-of-line=auto]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: ea488cebbfd88a5f50b8bd95d5c829d0bb76feb8 # frozen: 26.1.0
|
||||
|
||||
@@ -93,6 +93,7 @@ find_package(OpenSSL REQUIRED)
|
||||
find_package(secp256k1 REQUIRED)
|
||||
find_package(SOCI REQUIRED)
|
||||
find_package(SQLite3 REQUIRED)
|
||||
find_package(wasmi REQUIRED)
|
||||
find_package(xxHash REQUIRED)
|
||||
|
||||
target_link_libraries(
|
||||
|
||||
@@ -1272,6 +1272,39 @@
|
||||
# Example:
|
||||
# owner_reserve = 2000000 # 2 XRP
|
||||
#
|
||||
# extension_compute_limit = <gas>
|
||||
#
|
||||
# The extension compute limit is the maximum amount of gas that can be
|
||||
# consumed by a single transaction. The gas limit is used to prevent
|
||||
# transactions from consuming too many resources.
|
||||
#
|
||||
# If this parameter is unspecified, xrpld will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# extension_compute_limit = 1000000 # 1 million gas
|
||||
#
|
||||
# extension_size_limit = <bytes>
|
||||
#
|
||||
# The extension size limit is the maximum size of a WASM extension in
|
||||
# bytes. The size limit is used to prevent extensions from consuming
|
||||
# too many resources.
|
||||
#
|
||||
# If this parameter is unspecified, xrpld will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# extension_size_limit = 100000 # 100 kb
|
||||
#
|
||||
# gas_price = <micro-drops>
|
||||
#
|
||||
# The gas price is the conversion between WASM gas and its price in drops.
|
||||
#
|
||||
# If this parameter is unspecified, xrpld will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# gas_price = 1000000 # 1 drop per gas
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
# 9. Misc Settings
|
||||
|
||||
@@ -67,6 +67,7 @@ target_link_libraries(
|
||||
Xrpl::opts
|
||||
Xrpl::syslibs
|
||||
secp256k1::secp256k1
|
||||
wasmi::wasmi
|
||||
xrpl.libpb
|
||||
xxHash::xxhash
|
||||
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"requires": [
|
||||
"zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1774439233.809",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
|
||||
"wasmi/1.0.9#1fecdab9b90c96698eb35ea99ca4f5cb%1772227278.324",
|
||||
"sqlite3/3.51.0#66aa11eabd0e34954c5c1c061ad44abe%1774467355.988",
|
||||
"soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231",
|
||||
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878",
|
||||
@@ -11,7 +12,7 @@
|
||||
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1774398111.888",
|
||||
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12",
|
||||
"openssl/3.6.1#e6399de266349245a4542fc5f6c71552%1774458290.139",
|
||||
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1774883011.384",
|
||||
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1775040983.408",
|
||||
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
|
||||
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492",
|
||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03",
|
||||
|
||||
@@ -35,6 +35,7 @@ class Xrpl(ConanFile):
|
||||
"openssl/3.6.1",
|
||||
"secp256k1/0.7.1",
|
||||
"soci/4.0.3",
|
||||
"wasmi/1.0.9",
|
||||
"zlib/1.3.1",
|
||||
]
|
||||
|
||||
@@ -220,6 +221,7 @@ class Xrpl(ConanFile):
|
||||
"soci::soci",
|
||||
"secp256k1::secp256k1",
|
||||
"sqlite3::sqlite",
|
||||
"wasmi::wasmi",
|
||||
"xxhash::xxhash",
|
||||
"zlib::zlib",
|
||||
]
|
||||
|
||||
@@ -7,6 +7,8 @@ ignorePaths:
|
||||
- cmake/**
|
||||
- LICENSE.md
|
||||
- .clang-tidy
|
||||
- src/test/app/wasm_fixtures/**/*.wat
|
||||
- src/test/app/wasm_fixtures/*.c
|
||||
language: en
|
||||
allowCompoundWords: true # TODO (#6334)
|
||||
ignoreRandomStrings: true
|
||||
@@ -64,6 +66,7 @@ words:
|
||||
- Britto
|
||||
- Btrfs
|
||||
- canonicality
|
||||
- cdylib
|
||||
- changespq
|
||||
- checkme
|
||||
- choco
|
||||
@@ -254,6 +257,7 @@ words:
|
||||
- STATSDCOLLECTOR
|
||||
- stissue
|
||||
- stnum
|
||||
- stnumber
|
||||
- stobj
|
||||
- stobject
|
||||
- stpath
|
||||
|
||||
@@ -296,7 +296,7 @@ set(T& target, std::string const& name, Section const& section)
|
||||
if ((found_and_valid = val.has_value()))
|
||||
target = *val;
|
||||
}
|
||||
catch (boost::bad_lexical_cast const&) // NOLINT(bugprone-empty-catch)
|
||||
catch (boost::bad_lexical_cast&)
|
||||
{
|
||||
}
|
||||
return found_and_valid;
|
||||
@@ -330,7 +330,7 @@ get(Section const& section, std::string const& name, T const& defaultValue = T{}
|
||||
{
|
||||
return section.value_or<T>(name, defaultValue);
|
||||
}
|
||||
catch (boost::bad_lexical_cast const&) // NOLINT(bugprone-empty-catch)
|
||||
catch (boost::bad_lexical_cast&)
|
||||
{
|
||||
}
|
||||
return defaultValue;
|
||||
@@ -345,7 +345,7 @@ get(Section const& section, std::string const& name, char const* defaultValue)
|
||||
if (val.has_value())
|
||||
return *val;
|
||||
}
|
||||
catch (boost::bad_lexical_cast const&) // NOLINT(bugprone-empty-catch)
|
||||
catch (boost::bad_lexical_cast&)
|
||||
{
|
||||
}
|
||||
return defaultValue;
|
||||
|
||||
@@ -112,6 +112,7 @@ private:
|
||||
return c;
|
||||
}
|
||||
|
||||
public:
|
||||
CountedObject() noexcept
|
||||
{
|
||||
getCounter().increment();
|
||||
@@ -125,13 +126,10 @@ private:
|
||||
CountedObject&
|
||||
operator=(CountedObject const&) noexcept = default;
|
||||
|
||||
public:
|
||||
~CountedObject() noexcept
|
||||
{
|
||||
getCounter().decrement();
|
||||
}
|
||||
|
||||
friend Object;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -226,7 +226,7 @@ private:
|
||||
// expensive argument lists if the stream is not active.
|
||||
#ifndef JLOG
|
||||
#define JLOG(x) \
|
||||
if (!(x)) \
|
||||
if (!x) \
|
||||
{ \
|
||||
} \
|
||||
else \
|
||||
@@ -235,7 +235,7 @@ private:
|
||||
|
||||
#ifndef CLOG
|
||||
#define CLOG(ss) \
|
||||
if (!(ss)) \
|
||||
if (!ss) \
|
||||
; \
|
||||
else \
|
||||
*ss
|
||||
|
||||
@@ -381,6 +381,9 @@ public:
|
||||
friend Number
|
||||
root2(Number f);
|
||||
|
||||
friend Number
|
||||
log10(Number const&, int);
|
||||
|
||||
// Thread local rounding control. Default is to_nearest
|
||||
enum rounding_mode { to_nearest, towards_zero, downward, upward };
|
||||
static rounding_mode
|
||||
@@ -729,6 +732,10 @@ abs(Number x) noexcept
|
||||
Number
|
||||
power(Number const& f, unsigned n);
|
||||
|
||||
// logarithm with base 10
|
||||
Number
|
||||
log10(Number const& value, int iterations = 50);
|
||||
|
||||
// Returns f^(1/d)
|
||||
// Uses Newton–Raphson iterations until the result stops changing
|
||||
// to find the root of the polynomial g(x) = x^d - f
|
||||
|
||||
@@ -60,7 +60,7 @@ class SlabAllocator
|
||||
{
|
||||
// Use memcpy to avoid unaligned UB
|
||||
// (will optimize to equivalent code)
|
||||
std::memcpy(data, static_cast<void const*>(&l_), sizeof(std::uint8_t*));
|
||||
std::memcpy(data, &l_, sizeof(std::uint8_t*));
|
||||
l_ = data;
|
||||
data += item;
|
||||
}
|
||||
@@ -102,7 +102,7 @@ class SlabAllocator
|
||||
{
|
||||
// Use memcpy to avoid unaligned UB
|
||||
// (will optimize to equivalent code)
|
||||
std::memcpy(static_cast<void*>(&l_), ret, sizeof(std::uint8_t*));
|
||||
std::memcpy(&l_, ret, sizeof(std::uint8_t*));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ class SlabAllocator
|
||||
|
||||
// Use memcpy to avoid unaligned UB
|
||||
// (will optimize to equivalent code)
|
||||
std::memcpy(ptr, static_cast<void const*>(&l_), sizeof(std::uint8_t*));
|
||||
std::memcpy(ptr, &l_, sizeof(std::uint8_t*));
|
||||
l_ = ptr;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ struct aged_associative_container_extract_t<false>
|
||||
Value const&
|
||||
operator()(Value const& value) const
|
||||
{
|
||||
return value; // NOLINT(bugprone-return-const-ref-from-parameter)
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -257,8 +257,7 @@ private:
|
||||
|
||||
config_t(config_t&& other)
|
||||
: KeyValueCompare(std::move(other.key_compare()))
|
||||
, beast::detail::empty_base_optimization<ElementAllocator>(std::move(
|
||||
static_cast<beast::detail::empty_base_optimization<ElementAllocator>&>(other)))
|
||||
, beast::detail::empty_base_optimization<ElementAllocator>(std::move(other))
|
||||
, clock(other.clock)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -35,11 +35,9 @@ struct CopyConst<T const, U>
|
||||
template <typename T, typename Tag>
|
||||
class ListNode
|
||||
{
|
||||
ListNode() = default;
|
||||
|
||||
private:
|
||||
using value_type = T;
|
||||
|
||||
friend T;
|
||||
friend class List<T, Tag>;
|
||||
|
||||
template <typename>
|
||||
|
||||
@@ -203,8 +203,7 @@ template <class Hasher, class T>
|
||||
inline std::enable_if_t<is_contiguously_hashable<T, Hasher>::value>
|
||||
hash_append(Hasher& h, T const& t) noexcept
|
||||
{
|
||||
// NOLINTNEXTLINE(bugprone-sizeof-expression)
|
||||
h(static_cast<void const*>(std::addressof(t)), sizeof(t));
|
||||
h(std::addressof(t), sizeof(t));
|
||||
}
|
||||
|
||||
template <class Hasher, class T>
|
||||
|
||||
@@ -53,9 +53,8 @@ is_white(char c)
|
||||
case '\t':
|
||||
case '\v':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class FwdIter>
|
||||
|
||||
@@ -118,18 +118,18 @@ private:
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
void
|
||||
reporter<Unused>::suite_results::add(case_results const& r)
|
||||
reporter<_>::suite_results::add(case_results const& r)
|
||||
{
|
||||
++cases;
|
||||
total += r.total;
|
||||
failed += r.failed;
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
void
|
||||
reporter<Unused>::results::add(suite_results const& r)
|
||||
reporter<_>::results::add(suite_results const& r)
|
||||
{
|
||||
++suites;
|
||||
total += r.total;
|
||||
@@ -160,13 +160,13 @@ reporter<Unused>::results::add(suite_results const& r)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Unused>
|
||||
reporter<Unused>::reporter(std::ostream& os) : os_(os)
|
||||
template <class _>
|
||||
reporter<_>::reporter(std::ostream& os) : os_(os)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
reporter<Unused>::~reporter()
|
||||
template <class _>
|
||||
reporter<_>::~reporter()
|
||||
{
|
||||
if (results_.top.size() > 0)
|
||||
{
|
||||
@@ -180,9 +180,9 @@ reporter<Unused>::~reporter()
|
||||
<< amount{results_.failed, "failure"} << std::endl;
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
std::string
|
||||
reporter<Unused>::fmtdur(typename clock_type::duration const& d)
|
||||
reporter<_>::fmtdur(typename clock_type::duration const& d)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto const ms = duration_cast<milliseconds>(d);
|
||||
@@ -193,46 +193,46 @@ reporter<Unused>::fmtdur(typename clock_type::duration const& d)
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
void
|
||||
reporter<Unused>::on_suite_begin(suite_info const& info)
|
||||
reporter<_>::on_suite_begin(suite_info const& info)
|
||||
{
|
||||
suite_results_ = suite_results{info.full_name()};
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
void
|
||||
reporter<Unused>::on_suite_end()
|
||||
reporter<_>::on_suite_end()
|
||||
{
|
||||
results_.add(suite_results_);
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
void
|
||||
reporter<Unused>::on_case_begin(std::string const& name)
|
||||
reporter<_>::on_case_begin(std::string const& name)
|
||||
{
|
||||
case_results_ = case_results(name);
|
||||
os_ << suite_results_.name << (case_results_.name.empty() ? "" : (" " + case_results_.name))
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
void
|
||||
reporter<Unused>::on_case_end()
|
||||
reporter<_>::on_case_end()
|
||||
{
|
||||
suite_results_.add(case_results_);
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
void
|
||||
reporter<Unused>::on_pass()
|
||||
reporter<_>::on_pass()
|
||||
{
|
||||
++case_results_.total;
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
void
|
||||
reporter<Unused>::on_fail(std::string const& reason)
|
||||
reporter<_>::on_fail(std::string const& reason)
|
||||
{
|
||||
++case_results_.failed;
|
||||
++case_results_.total;
|
||||
@@ -240,9 +240,9 @@ reporter<Unused>::on_fail(std::string const& reason)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
void
|
||||
reporter<Unused>::on_log(std::string const& s)
|
||||
reporter<_>::on_log(std::string const& s)
|
||||
{
|
||||
os_ << s;
|
||||
}
|
||||
|
||||
@@ -145,9 +145,9 @@ public:
|
||||
void
|
||||
insert(case_results&& r)
|
||||
{
|
||||
cont().emplace_back(std::move(r));
|
||||
total_ += r.tests.total();
|
||||
failed_ += r.tests.failed();
|
||||
cont().emplace_back(std::move(r));
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -36,7 +36,7 @@ make_reason(String const& reason, char const* file, int line)
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class Thread;
|
||||
class thread;
|
||||
|
||||
enum abort_t { no_abort_on_fail, abort_on_fail };
|
||||
|
||||
@@ -295,7 +295,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Thread;
|
||||
friend class thread;
|
||||
|
||||
static suite**
|
||||
p_this_suite()
|
||||
@@ -538,7 +538,7 @@ suite::run(runner& r)
|
||||
{
|
||||
run();
|
||||
}
|
||||
catch (abort_exception const&) // NOLINT(bugprone-empty-catch)
|
||||
catch (abort_exception const&)
|
||||
{
|
||||
// ends the suite
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace beast {
|
||||
namespace unit_test {
|
||||
|
||||
/** Replacement for std::thread that handles exceptions in unit tests. */
|
||||
class Thread
|
||||
class thread
|
||||
{
|
||||
private:
|
||||
suite* s_ = nullptr;
|
||||
@@ -24,17 +24,17 @@ public:
|
||||
using id = std::thread::id;
|
||||
using native_handle_type = std::thread::native_handle_type;
|
||||
|
||||
Thread() = default;
|
||||
Thread(Thread const&) = delete;
|
||||
Thread&
|
||||
operator=(Thread const&) = delete;
|
||||
thread() = default;
|
||||
thread(thread const&) = delete;
|
||||
thread&
|
||||
operator=(thread const&) = delete;
|
||||
|
||||
Thread(Thread&& other) : s_(other.s_), t_(std::move(other.t_))
|
||||
thread(thread&& other) : s_(other.s_), t_(std::move(other.t_))
|
||||
{
|
||||
}
|
||||
|
||||
Thread&
|
||||
operator=(Thread&& other)
|
||||
thread&
|
||||
operator=(thread&& other)
|
||||
{
|
||||
s_ = other.s_;
|
||||
t_ = std::move(other.t_);
|
||||
@@ -42,10 +42,10 @@ public:
|
||||
}
|
||||
|
||||
template <class F, class... Args>
|
||||
explicit Thread(suite& s, F&& f, Args&&... args) : s_(&s)
|
||||
explicit thread(suite& s, F&& f, Args&&... args) : s_(&s)
|
||||
{
|
||||
std::function<void(void)> b = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
|
||||
t_ = std::thread(&Thread::run, this, std::move(b));
|
||||
t_ = std::thread(&thread::run, this, std::move(b));
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
swap(Thread& other)
|
||||
swap(thread& other)
|
||||
{
|
||||
std::swap(s_, other.s_);
|
||||
std::swap(t_, other.t_);
|
||||
@@ -94,7 +94,7 @@ private:
|
||||
{
|
||||
f();
|
||||
}
|
||||
catch (suite::abort_exception const&) // NOLINT(bugprone-empty-catch)
|
||||
catch (suite::abort_exception const&)
|
||||
{
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
|
||||
@@ -43,15 +43,15 @@ private:
|
||||
murmurhash3(result_type x);
|
||||
};
|
||||
|
||||
template <class Unused>
|
||||
xor_shift_engine<Unused>::xor_shift_engine(result_type val)
|
||||
template <class _>
|
||||
xor_shift_engine<_>::xor_shift_engine(result_type val)
|
||||
{
|
||||
seed(val);
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
void
|
||||
xor_shift_engine<Unused>::seed(result_type seed)
|
||||
xor_shift_engine<_>::seed(result_type seed)
|
||||
{
|
||||
if (seed == 0)
|
||||
throw std::domain_error("invalid seed");
|
||||
@@ -59,9 +59,9 @@ xor_shift_engine<Unused>::seed(result_type seed)
|
||||
s_[1] = murmurhash3(s_[0]);
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
auto
|
||||
xor_shift_engine<Unused>::operator()() -> result_type
|
||||
xor_shift_engine<_>::operator()() -> result_type
|
||||
{
|
||||
result_type s1 = s_[0];
|
||||
result_type const s0 = s_[1];
|
||||
@@ -70,9 +70,9 @@ xor_shift_engine<Unused>::operator()() -> result_type
|
||||
return (s_[1] = (s1 ^ s0 ^ (s1 >> 17) ^ (s0 >> 26))) + s0;
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
template <class _>
|
||||
auto
|
||||
xor_shift_engine<Unused>::murmurhash3(result_type x) -> result_type
|
||||
xor_shift_engine<_>::murmurhash3(result_type x) -> result_type
|
||||
{
|
||||
x ^= x >> 33;
|
||||
x *= 0xff51afd7ed558ccdULL;
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -16,7 +11,6 @@
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -719,94 +713,4 @@ adjustFracByTokens(
|
||||
STAmount const& tokens,
|
||||
Number const& frac);
|
||||
|
||||
/** Get AMM pool balances.
|
||||
*/
|
||||
std::pair<STAmount, STAmount>
|
||||
ammPoolHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& ammAccountID,
|
||||
Asset const& asset1,
|
||||
Asset const& asset2,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal const j);
|
||||
|
||||
/** Get AMM pool and LP token balances. If both optIssue are
|
||||
* provided then they are used as the AMM token pair issues.
|
||||
* Otherwise the missing issues are fetched from ammSle.
|
||||
*/
|
||||
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
|
||||
ammHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
std::optional<Asset> const& optAsset1,
|
||||
std::optional<Asset> const& optAsset2,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal const j);
|
||||
|
||||
/** Get the balance of LP tokens.
|
||||
*/
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
Asset const& asset1,
|
||||
Asset const& asset2,
|
||||
AccountID const& ammAccount,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j);
|
||||
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j);
|
||||
|
||||
/** Get AMM trading fee for the given account. The fee is discounted
|
||||
* if the account is the auction slot owner or one of the slot's authorized
|
||||
* accounts.
|
||||
*/
|
||||
std::uint16_t
|
||||
getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account);
|
||||
|
||||
/** Returns total amount held by AMM for the given token.
|
||||
*/
|
||||
STAmount
|
||||
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const& asset);
|
||||
|
||||
/** Delete trustlines to AMM. If all trustlines are deleted then
|
||||
* AMM object and account are deleted. Otherwise tecINCOMPLETE is returned.
|
||||
*/
|
||||
TER
|
||||
deleteAMMAccount(Sandbox& view, Asset const& asset, Asset const& asset2, beast::Journal j);
|
||||
|
||||
/** Initialize Auction and Voting slots and set the trading/discounted fee.
|
||||
*/
|
||||
void
|
||||
initializeFeeAuctionVote(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account,
|
||||
Asset const& lptAsset,
|
||||
std::uint16_t tfee);
|
||||
|
||||
/** Return true if the Liquidity Provider is the only AMM provider, false
|
||||
* otherwise. Return tecINTERNAL if encountered an unexpected condition,
|
||||
* for instance Liquidity Provider has more than one LPToken trustline.
|
||||
*/
|
||||
Expected<bool, TER>
|
||||
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount);
|
||||
|
||||
/** Due to rounding, the LPTokenBalance of the last LP might
|
||||
* not match the LP's trustline balance. If it's within the tolerance,
|
||||
* update LPTokenBalance to match the LP's trustline balance.
|
||||
*/
|
||||
Expected<bool, TER>
|
||||
verifyAndAdjustLPTokenBalance(
|
||||
Sandbox& sb,
|
||||
STAmount const& lpTokens,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
108
include/xrpl/ledger/helpers/AMMUtils.h
Normal file
108
include/xrpl/ledger/helpers/AMMUtils.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class ReadView;
|
||||
class ApplyView;
|
||||
class Sandbox;
|
||||
class NetClock;
|
||||
|
||||
/** Get AMM pool balances.
|
||||
*/
|
||||
std::pair<STAmount, STAmount>
|
||||
ammPoolHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& ammAccountID,
|
||||
Asset const& asset1,
|
||||
Asset const& asset2,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal const j);
|
||||
|
||||
/** Get AMM pool and LP token balances. If both optIssue are
|
||||
* provided then they are used as the AMM token pair issues.
|
||||
* Otherwise the missing issues are fetched from ammSle.
|
||||
*/
|
||||
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
|
||||
ammHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
std::optional<Asset> const& optAsset1,
|
||||
std::optional<Asset> const& optAsset2,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal const j);
|
||||
|
||||
/** Get the balance of LP tokens.
|
||||
*/
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
Asset const& asset1,
|
||||
Asset const& asset2,
|
||||
AccountID const& ammAccount,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j);
|
||||
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j);
|
||||
|
||||
/** Get AMM trading fee for the given account. The fee is discounted
|
||||
* if the account is the auction slot owner or one of the slot's authorized
|
||||
* accounts.
|
||||
*/
|
||||
std::uint16_t
|
||||
getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account);
|
||||
|
||||
/** Returns total amount held by AMM for the given token.
|
||||
*/
|
||||
STAmount
|
||||
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const& asset);
|
||||
|
||||
/** Delete trustlines to AMM. If all trustlines are deleted then
|
||||
* AMM object and account are deleted. Otherwise tecIMPCOMPLETE is returned.
|
||||
*/
|
||||
TER
|
||||
deleteAMMAccount(Sandbox& view, Asset const& asset, Asset const& asset2, beast::Journal j);
|
||||
|
||||
/** Initialize Auction and Voting slots and set the trading/discounted fee.
|
||||
*/
|
||||
void
|
||||
initializeFeeAuctionVote(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account,
|
||||
Asset const& lptAsset,
|
||||
std::uint16_t tfee);
|
||||
|
||||
/** Return true if the Liquidity Provider is the only AMM provider, false
|
||||
* otherwise. Return tecINTERNAL if encountered an unexpected condition,
|
||||
* for instance Liquidity Provider has more than one LPToken trustline.
|
||||
*/
|
||||
Expected<bool, TER>
|
||||
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount);
|
||||
|
||||
/** Due to rounding, the LPTokenBalance of the last LP might
|
||||
* not match the LP's trustline balance. If it's within the tolerance,
|
||||
* update LPTokenBalance to match the LP's trustline balance.
|
||||
*/
|
||||
Expected<bool, TER>
|
||||
verifyAndAdjustLPTokenBalance(
|
||||
Sandbox& sb,
|
||||
STAmount const& lpTokens,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -121,15 +121,6 @@ canTransfer(
|
||||
[[nodiscard]] TER
|
||||
canTrade(ReadView const& view, Asset const& asset);
|
||||
|
||||
/** Convenience to combine canTrade/Transfer. Returns tesSUCCESS if Asset is Issue.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canMPTTradeAndTransfer(
|
||||
ReadView const& v,
|
||||
Asset const& asset,
|
||||
AccountID const& from,
|
||||
AccountID const& to);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (MPT-specific)
|
||||
@@ -236,4 +227,17 @@ issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue);
|
||||
void
|
||||
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// MPT DEX
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/* Return true if a transaction is allowed for the specified MPT/account. The
|
||||
* function checks MPTokenIssuance and MPToken objects flags to determine if the
|
||||
* transaction is allowed.
|
||||
*/
|
||||
TER
|
||||
checkMPTTxAllowed(ReadView const& v, TxType tx, Asset const& asset, AccountID const& accountID);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -20,6 +20,10 @@ removeTokenOffersWithLimit(
|
||||
Keylet const& directory,
|
||||
std::size_t maxDeletableOffers);
|
||||
|
||||
/** Returns tesSUCCESS if NFToken has few enough offers that it can be burned */
|
||||
TER
|
||||
notTooManyOffers(ReadView const& view, uint256 const& nftokenID);
|
||||
|
||||
/** Finds the specified token in the owner's token directory. */
|
||||
std::optional<STObject>
|
||||
findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID);
|
||||
|
||||
@@ -54,15 +54,9 @@ enum class AuthType { StrongAuth, WeakAuth, Legacy };
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, Asset const& asset);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkGlobalFrozen(ReadView const& view, Asset const& asset);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
|
||||
@@ -134,7 +134,7 @@ public:
|
||||
{
|
||||
lowest_layer().shutdown(plain_socket::shutdown_both);
|
||||
}
|
||||
catch (boost::system::system_error const& e)
|
||||
catch (boost::system::system_error& e)
|
||||
{
|
||||
ec = e.code();
|
||||
}
|
||||
|
||||
@@ -138,11 +138,9 @@ forApiVersions(Fn const& fn, Args&&... args)
|
||||
{
|
||||
constexpr auto size = maxVer + 1 - minVer;
|
||||
[&]<std::size_t... offset>(std::index_sequence<offset...>) {
|
||||
// NOLINTBEGIN(bugprone-use-after-move)
|
||||
(((void)fn(
|
||||
std::integral_constant<unsigned int, minVer + offset>{}, std::forward<Args>(args)...)),
|
||||
...);
|
||||
// NOLINTEND(bugprone-use-after-move)
|
||||
}(std::make_index_sequence<size>{});
|
||||
}
|
||||
|
||||
|
||||
@@ -125,12 +125,10 @@ namespace detail {
|
||||
#pragma push_macro("XRPL_RETIRE_FIX")
|
||||
#undef XRPL_RETIRE_FIX
|
||||
|
||||
// NOLINTBEGIN(bugprone-macro-parentheses)
|
||||
#define XRPL_FEATURE(name, supported, vote) +1
|
||||
#define XRPL_FIX(name, supported, vote) +1
|
||||
#define XRPL_RETIRE_FEATURE(name) +1
|
||||
#define XRPL_RETIRE_FIX(name) +1
|
||||
// NOLINTEND(bugprone-macro-parentheses)
|
||||
|
||||
// This value SHOULD be equal to the number of amendments registered in
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
|
||||
@@ -8,6 +8,9 @@ namespace xrpl {
|
||||
// This was the reference fee units used in the old fee calculation.
|
||||
inline constexpr std::uint32_t FEE_UNITS_DEPRECATED = 10;
|
||||
|
||||
// Number of micro-drops in one drop.
|
||||
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
|
||||
@@ -24,6 +27,15 @@ struct Fees
|
||||
/** @brief Additional XRP reserve required per owned ledger object. */
|
||||
XRPAmount increment{0};
|
||||
|
||||
/** @brief Compute limit for Feature Extensions (instructions). */
|
||||
std::uint32_t extensionComputeLimit{0};
|
||||
|
||||
/** @brief Size limit for Feature Extensions (bytes). */
|
||||
std::uint32_t extensionSizeLimit{0};
|
||||
|
||||
/** @brief Price of WASM gas (micro-drops). */
|
||||
std::uint32_t gasPrice{0};
|
||||
|
||||
explicit Fees() = default;
|
||||
Fees(Fees const&) = default;
|
||||
Fees&
|
||||
|
||||
@@ -74,12 +74,10 @@ public:
|
||||
|
||||
Derived classes will load the object with all the known formats.
|
||||
*/
|
||||
private:
|
||||
KnownFormats() : name_(beast::type_name<Derived>())
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
/** Destroy the known formats object.
|
||||
|
||||
The defined formats are deleted.
|
||||
@@ -183,7 +181,6 @@ private:
|
||||
|
||||
boost::container::flat_map<std::string, Item const*> names_;
|
||||
boost::container::flat_map<KeyType, Item const*> types_;
|
||||
friend Derived;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -211,7 +211,7 @@ enum LedgerEntryType : std::uint16_t {
|
||||
// lsfRequireDestTag = 0x00020000,
|
||||
// ...
|
||||
// };
|
||||
#define TO_VALUE(name, value) name = (value),
|
||||
#define TO_VALUE(name, value) name = value,
|
||||
#define NULL_NAME(name, values) values
|
||||
#define NULL_OUTPUT(name, value)
|
||||
enum LedgerSpecificFlags : std::uint32_t { XMACRO(NULL_NAME, TO_VALUE, NULL_OUTPUT) };
|
||||
|
||||
@@ -20,7 +20,7 @@ enum GranularPermissionType : std::uint32_t {
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
|
||||
#define PERMISSION(type, txType, value) type = (value),
|
||||
#define PERMISSION(type, txType, value) type = value,
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
|
||||
@@ -251,6 +251,12 @@ std::uint8_t constexpr vaultMaximumIOUScale = 18;
|
||||
* another vault; counted from 0 */
|
||||
std::uint8_t constexpr maxAssetCheckDepth = 5;
|
||||
|
||||
/** Maximum length of a Data field in Escrow object that can be updated by WASM code. */
|
||||
std::size_t constexpr maxWasmDataLength = 4 * 1024; // 4KB
|
||||
|
||||
/** Maximum length of parameters passed from WASM code to host functions. */
|
||||
std::size_t constexpr maxWasmParamLength = 1024; // 1KB
|
||||
|
||||
/** A ledger index. */
|
||||
using LedgerIndex = std::uint32_t;
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class STCurrency;
|
||||
#pragma push_macro("TO_MAP")
|
||||
#undef TO_MAP
|
||||
|
||||
#define TO_ENUM(name, value) name = (value),
|
||||
#define TO_ENUM(name, value) name = value,
|
||||
#define TO_MAP(name, value) {#name, value},
|
||||
|
||||
enum SerializedTypeID { XMACRO(TO_ENUM) };
|
||||
|
||||
@@ -401,7 +401,7 @@ amountFromJsonNoThrow(STAmount& result, Json::Value const& jvSource);
|
||||
inline STAmount const&
|
||||
toSTAmount(STAmount const& a)
|
||||
{
|
||||
return a; // NOLINT(bugprone-return-const-ref-from-parameter)
|
||||
return a;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -1188,7 +1188,6 @@ STObject::getFieldByConstRef(SField const& field, V const& empty) const
|
||||
SerializedTypeID const id = rf->getSType();
|
||||
|
||||
if (id == STI_NOTPRESENT)
|
||||
// NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter)
|
||||
return empty; // optional field not present
|
||||
|
||||
T const* cf = dynamic_cast<T const*>(rf);
|
||||
|
||||
@@ -121,7 +121,9 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temARRAY_TOO_LARGE,
|
||||
temBAD_TRANSFER_FEE,
|
||||
temINVALID_INNER_BATCH,
|
||||
|
||||
temBAD_MPT,
|
||||
temBAD_WASM,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -344,6 +346,10 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecLIMIT_EXCEEDED = 195,
|
||||
tecPSEUDO_ACCOUNT = 196,
|
||||
tecPRECISION_LOSS = 197,
|
||||
// DEPRECATED: This error code tecNO_DELEGATE_PERMISSION is reserved for
|
||||
// backward compatibility with historical data on non-prod networks, can be
|
||||
// reclaimed after those networks reset.
|
||||
tecNO_DELEGATE_PERMISSION = 198,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -235,7 +235,7 @@ XMACRO(NULL_NAME, TO_VALUE, NULL_OUTPUT, NULL_MASK_ADJ)
|
||||
// The mask adjustment (maskAdj) allows adding flags back to the mask, making them invalid.
|
||||
// For example, Batch uses MASK_ADJ(tfInnerBatchTxn) to reject tfInnerBatchTxn on outer Batch.
|
||||
#define TO_MASK(name, values, maskAdj) \
|
||||
inline constexpr FlagValue tf##name##Mask = ~(tfUniversal values) | (maskAdj);
|
||||
inline constexpr FlagValue tf##name##Mask = ~(tfUniversal values) | maskAdj;
|
||||
#define VALUE_TO_MASK(name, value) | name
|
||||
#define MASK_ADJ_TO_MASK(value) value
|
||||
XMACRO(TO_MASK, VALUE_TO_MASK, VALUE_TO_MASK, MASK_ADJ_TO_MASK)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FEATURE(SmartEscrow, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(MPTokensV2, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Security3_1_3, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -304,6 +304,11 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// Smart Escrow fields
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
|
||||
{sfPreviousTxnID, soeOPTIONAL},
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
@@ -113,6 +113,9 @@ TYPED_SFIELD(sfInterestRate, UINT32, 65) // 1/10 basis points (bi
|
||||
TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 69)
|
||||
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 70)
|
||||
TYPED_SFIELD(sfGasPrice, UINT32, 71)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
|
||||
@@ -1110,6 +1110,10 @@ TRANSACTION(ttFEE, 101, SetFee,
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// Smart Escrow fields
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the network's negative UNL
|
||||
|
||||
@@ -146,7 +146,7 @@ getOptional(Json::Value const& v, xrpl::SField const& field)
|
||||
{
|
||||
return getOrThrow<T>(v, field);
|
||||
}
|
||||
catch (...) // NOLINT(bugprone-empty-catch)
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -248,6 +248,9 @@ JSS(expected_date); // out: any (warnings)
|
||||
JSS(expected_date_UTC); // out: any (warnings)
|
||||
JSS(expected_ledger_size); // out: TxQ
|
||||
JSS(expiration); // out: AccountOffers, AccountChannels, ValidatorList, amm_info
|
||||
JSS(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
|
||||
|
||||
@@ -213,6 +213,78 @@ public:
|
||||
return this->sle_->isFieldPresent(sfReserveIncrementDrops);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfExtensionComputeLimit (soeOPTIONAL)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getExtensionComputeLimit() const
|
||||
{
|
||||
if (hasExtensionComputeLimit())
|
||||
return this->sle_->at(sfExtensionComputeLimit);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfExtensionComputeLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasExtensionComputeLimit() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfExtensionComputeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfExtensionSizeLimit (soeOPTIONAL)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getExtensionSizeLimit() const
|
||||
{
|
||||
if (hasExtensionSizeLimit())
|
||||
return this->sle_->at(sfExtensionSizeLimit);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfExtensionSizeLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasExtensionSizeLimit() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfExtensionSizeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasPrice (soeOPTIONAL)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasPrice() const
|
||||
{
|
||||
if (hasGasPrice())
|
||||
return this->sle_->at(sfGasPrice);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasPrice is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasPrice() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfGasPrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnID (soeOPTIONAL)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -373,6 +445,39 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfExtensionComputeLimit (soeOPTIONAL)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setExtensionComputeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfExtensionComputeLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfExtensionSizeLimit (soeOPTIONAL)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setExtensionSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfExtensionSizeLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasPrice (soeOPTIONAL)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasPrice] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnID (soeOPTIONAL)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -254,6 +254,84 @@ public:
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfReserveIncrementDrops);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfExtensionComputeLimit (soeOPTIONAL)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getExtensionComputeLimit() const
|
||||
{
|
||||
if (hasExtensionComputeLimit())
|
||||
{
|
||||
return this->tx_->at(sfExtensionComputeLimit);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfExtensionComputeLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasExtensionComputeLimit() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfExtensionComputeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfExtensionSizeLimit (soeOPTIONAL)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getExtensionSizeLimit() const
|
||||
{
|
||||
if (hasExtensionSizeLimit())
|
||||
{
|
||||
return this->tx_->at(sfExtensionSizeLimit);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfExtensionSizeLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasExtensionSizeLimit() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfExtensionSizeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasPrice (soeOPTIONAL)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasPrice() const
|
||||
{
|
||||
if (hasGasPrice())
|
||||
{
|
||||
return this->tx_->at(sfGasPrice);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasPrice is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasPrice() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGasPrice);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -384,6 +462,39 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfExtensionComputeLimit (soeOPTIONAL)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setExtensionComputeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfExtensionComputeLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfExtensionSizeLimit (soeOPTIONAL)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setExtensionSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfExtensionSizeLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasPrice (soeOPTIONAL)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasPrice] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the SetFee wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -34,7 +34,6 @@ protected:
|
||||
boost::asio::strand<boost::asio::executor> strand_;
|
||||
|
||||
public:
|
||||
// NOLINTNEXTLINE(bugprone-crtp-constructor-accessibility)
|
||||
BasePeer(
|
||||
Port const& port,
|
||||
Handler& handler,
|
||||
|
||||
@@ -392,7 +392,7 @@ BaseWSPeer<Handler, Impl>::cancel_timer()
|
||||
{
|
||||
timer_.cancel();
|
||||
}
|
||||
catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch)
|
||||
catch (boost::system::system_error const&)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
@@ -399,8 +399,7 @@ using InvariantChecks = std::tuple<
|
||||
ValidLoanBroker,
|
||||
ValidLoan,
|
||||
ValidVault,
|
||||
ValidMPTPayment,
|
||||
ValidMPTTransfer>;
|
||||
ValidMPTPayment>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
@@ -56,42 +56,4 @@ public:
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
class ValidMPTTransfer
|
||||
{
|
||||
struct Value
|
||||
{
|
||||
std::optional<std::uint64_t> amtBefore;
|
||||
std::optional<std::uint64_t> amtAfter;
|
||||
};
|
||||
// MPTID: {holder: Value}
|
||||
hash_map<uint192, hash_map<AccountID, Value>> amount_;
|
||||
// Deleted MPToken
|
||||
// MPToken key: true if MPTAuthorized is set
|
||||
hash_map<uint256, bool> deletedAuthorized_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Check whether a holder is authorized to send or receive an MPToken.
|
||||
*
|
||||
* Deleted MPToken SLEs are no longer present in the view by the time
|
||||
* finalize() runs, so their authorization state is captured during
|
||||
* visitEntry() and stored in deletedAuthorized_. For deleted MPTokens,
|
||||
* returns true if reqAuth is false or lsfMPTAuthorized was set at deletion.
|
||||
* For existing MPTokens, returns the result of requireAuth()
|
||||
*/
|
||||
bool
|
||||
isAuthorized(
|
||||
ReadView const& view,
|
||||
MPTID const& mptid,
|
||||
AccountID const& holder,
|
||||
bool requireAuth) const;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/tx/transactors/dex/AMMContext.h>
|
||||
|
||||
@@ -220,7 +220,6 @@ struct FlowDebugInfo
|
||||
write_list(amts, get_val, delim);
|
||||
};
|
||||
auto writeIntList = [&write_list](std::vector<size_t> const& vals, char delim = ';') {
|
||||
// NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter)
|
||||
auto get_val = [](size_t const& v) -> size_t const& { return v; };
|
||||
write_list(vals, get_val);
|
||||
};
|
||||
|
||||
@@ -429,10 +429,8 @@ toStrands(
|
||||
template <StepAmount TIn, StepAmount TOut, class TDerived>
|
||||
struct StepImp : public Step
|
||||
{
|
||||
private:
|
||||
explicit StepImp() = default;
|
||||
|
||||
public:
|
||||
std::pair<EitherAmount, EitherAmount>
|
||||
rev(PaymentSandbox& sb,
|
||||
ApplyView& afView,
|
||||
@@ -472,7 +470,6 @@ public:
|
||||
{
|
||||
return get<TIn>(lhs) == get<TIn>(rhs);
|
||||
}
|
||||
friend TDerived;
|
||||
};
|
||||
/// @endcond
|
||||
|
||||
|
||||
558
include/xrpl/tx/wasm/HostFunc.h
Normal file
558
include/xrpl/tx/wasm/HostFunc.h
Normal file
@@ -0,0 +1,558 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/tx/wasm/ParamsHelper.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
enum class HostFunctionError : int32_t {
|
||||
INTERNAL = -1,
|
||||
FIELD_NOT_FOUND = -2,
|
||||
BUFFER_TOO_SMALL = -3,
|
||||
NO_ARRAY = -4,
|
||||
NOT_LEAF_FIELD = -5,
|
||||
LOCATOR_MALFORMED = -6,
|
||||
SLOT_OUT_RANGE = -7,
|
||||
SLOTS_FULL = -8,
|
||||
EMPTY_SLOT = -9,
|
||||
LEDGER_OBJ_NOT_FOUND = -10,
|
||||
DECODING = -11,
|
||||
DATA_FIELD_TOO_LARGE = -12,
|
||||
POINTER_OUT_OF_BOUNDS = -13,
|
||||
NO_MEM_EXPORTED = -14,
|
||||
INVALID_PARAMS = -15,
|
||||
INVALID_ACCOUNT = -16,
|
||||
INVALID_FIELD = -17,
|
||||
INDEX_OUT_OF_BOUNDS = -18,
|
||||
FLOAT_INPUT_MALFORMED = -19,
|
||||
FLOAT_COMPUTATION_ERROR = -20,
|
||||
NO_RUNTIME = -21,
|
||||
OUT_OF_GAS = -22,
|
||||
};
|
||||
|
||||
using FloatPair = std::pair<int64_t, int32_t>;
|
||||
|
||||
inline int32_t
|
||||
HfErrorToInt(HostFunctionError e)
|
||||
{
|
||||
return static_cast<int32_t>(e);
|
||||
}
|
||||
|
||||
namespace wasm_float {
|
||||
|
||||
std::string
|
||||
floatToString(Slice const& data);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromIntImpl(int64_t x, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromUintImpl(uint64_t x, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTAmountImpl(STAmount const& x, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTNumberImpl(STNumber const& x, int32_t mode);
|
||||
|
||||
Expected<int64_t, HostFunctionError>
|
||||
floatToIntImpl(Slice const& x, int32_t mode);
|
||||
|
||||
Expected<FloatPair, HostFunctionError>
|
||||
floatToMantissaAndExponentImpl(Slice const& x);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatNegateImpl(Slice const& x);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatAbsImpl(Slice const& x);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatSetImpl(int64_t mantissa, int32_t exponent, int32_t mode);
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
floatCompareImpl(Slice const& x, Slice const& y);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatAddImpl(Slice const& x, Slice const& y, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatSubtractImpl(Slice const& x, Slice const& y, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatMultiplyImpl(Slice const& x, Slice const& y, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatDivideImpl(Slice const& x, Slice const& y, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatRootImpl(Slice const& x, int32_t n, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatPowerImpl(Slice const& x, int32_t n, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatLogImpl(Slice const& x, int32_t mode);
|
||||
|
||||
} // namespace wasm_float
|
||||
|
||||
// Intended to work only through wasm runtime. Don't call them directly, except with unit tests
|
||||
struct HostFunctions
|
||||
{
|
||||
beast::Journal j_;
|
||||
|
||||
HostFunctions(beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) : j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
virtual void
|
||||
setRT(void*)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void*
|
||||
getRT() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
beast::Journal
|
||||
getJournal() const
|
||||
{
|
||||
return j_;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
checkSelf() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual Expected<std::uint32_t, HostFunctionError>
|
||||
getLedgerSqn() const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<std::uint32_t, HostFunctionError>
|
||||
getParentLedgerTime() const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Hash, HostFunctionError>
|
||||
getParentLedgerHash() const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<uint32_t, HostFunctionError>
|
||||
getBaseFee() const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(uint256 const& amendmentId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(std::string_view const& amendmentName) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx)
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getTxField(SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjField(SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjField(int32_t cacheIdx, SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getTxNestedField(Slice const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjNestedField(Slice const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getTxArrayLen(SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjArrayLen(SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getTxNestedArrayLen(Slice const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjNestedArrayLen(Slice const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
updateData(Slice const& data)
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Hash, HostFunctionError>
|
||||
computeSha512HalfHash(Slice const& data) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
accountKeylet(AccountID const& account) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
ammKeylet(Asset const& issue1, Asset const& issue2) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
checkKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
|
||||
const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
didKeylet(AccountID const& account) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
delegateKeylet(AccountID const& account, AccountID const& authorize) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
escrowKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
lineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
mptokenKeylet(MPTID const& mptid, AccountID const& holder) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
nftOfferKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
offerKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
oracleKeylet(AccountID const& account, std::uint32_t docId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
paychanKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
signersKeylet(AccountID const& account) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
ticketKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
vaultKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getNFT(AccountID const& account, uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getNFTIssuer(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTTaxon(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getNFTFlags(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getNFTTransferFee(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTSerial(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
trace(std::string_view const& msg, Slice const& data, bool asHex) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
traceNum(std::string_view const& msg, int64_t data) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
traceAccount(std::string_view const& msg, AccountID const& account) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
traceFloat(std::string_view const& msg, Slice const& data) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
traceAmount(std::string_view const& msg, STAmount const& amount) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromInt(int64_t x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromUint(uint64_t x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromSTAmount(STAmount const& x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromSTNumber(STNumber const& x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int64_t, HostFunctionError>
|
||||
floatToInt(Slice const& x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<FloatPair, HostFunctionError>
|
||||
floatToMantissaAndExponent(Slice const& x) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatNegate(Slice const& x) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatAbs(Slice const& x) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatSet(int64_t mantissa, int32_t exponent, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
floatCompare(Slice const& x, Slice const& y) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatAdd(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatDivide(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatRoot(Slice const& x, int32_t n, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatPower(Slice const& x, int32_t n, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatLog(Slice const& x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
|
||||
virtual ~HostFunctions() = default;
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
305
include/xrpl/tx/wasm/HostFuncImpl.h
Normal file
305
include/xrpl/tx/wasm/HostFuncImpl.h
Normal file
@@ -0,0 +1,305 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Intended to work only through wasm runtime. Don't call them directly, except with unit tests
|
||||
class WasmHostFunctionsImpl : public HostFunctions
|
||||
{
|
||||
ApplyContext& ctx_;
|
||||
|
||||
Keylet leKey_;
|
||||
mutable std::optional<std::shared_ptr<SLE const>> currentLedgerObj_;
|
||||
|
||||
static int constexpr MAX_CACHE = 256;
|
||||
std::array<std::shared_ptr<SLE const>, MAX_CACHE> cache_;
|
||||
|
||||
std::optional<Bytes> data_;
|
||||
|
||||
void* rt_ = nullptr;
|
||||
|
||||
Expected<std::shared_ptr<SLE const>, HostFunctionError>
|
||||
getCurrentLedgerObj() const
|
||||
{
|
||||
if (!currentLedgerObj_)
|
||||
currentLedgerObj_ = ctx_.view().read(leKey_);
|
||||
if (*currentLedgerObj_)
|
||||
return *currentLedgerObj_;
|
||||
return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND);
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
normalizeCacheIndex(int32_t cacheIdx) const
|
||||
{
|
||||
--cacheIdx;
|
||||
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
|
||||
return Unexpected(HostFunctionError::SLOT_OUT_RANGE);
|
||||
if (!cache_[cacheIdx])
|
||||
return Unexpected(HostFunctionError::EMPTY_SLOT);
|
||||
return cacheIdx;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void
|
||||
log(std::string_view const& msg, F&& dataFn) const
|
||||
{
|
||||
#ifdef DEBUG_OUTPUT
|
||||
auto& j = std::cerr;
|
||||
#else
|
||||
if (!getJournal().active(beast::severities::kTrace))
|
||||
return;
|
||||
auto j = getJournal().trace();
|
||||
#endif
|
||||
j << "WasmTrace[" << to_short_string(leKey_.key) << "]: " << msg << " " << dataFn();
|
||||
|
||||
#ifdef DEBUG_OUTPUT
|
||||
j << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
WasmHostFunctionsImpl(ApplyContext& ct, Keylet const& leKey)
|
||||
: HostFunctions(ct.journal), ctx_(ct), leKey_(leKey)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void
|
||||
setRT(void* rt) override
|
||||
{
|
||||
rt_ = rt;
|
||||
}
|
||||
|
||||
virtual void*
|
||||
getRT() const override
|
||||
{
|
||||
return rt_;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
checkSelf() const override
|
||||
{
|
||||
return !currentLedgerObj_ && !data_ &&
|
||||
std::ranges::find_if(cache_, [](auto& p) { return !!p; }) == cache_.end();
|
||||
}
|
||||
|
||||
std::optional<Bytes> const&
|
||||
getData() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getLedgerSqn() const override;
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getParentLedgerTime() const override;
|
||||
|
||||
Expected<Hash, HostFunctionError>
|
||||
getParentLedgerHash() const override;
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getBaseFee() const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(uint256 const& amendmentId) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(std::string_view const& amendmentName) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getTxField(SField const& fname) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjField(SField const& fname) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjField(int32_t cacheIdx, SField const& fname) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getTxNestedField(Slice const& locator) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjNestedField(Slice const& locator) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getTxArrayLen(SField const& fname) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjArrayLen(SField const& fname) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getTxNestedArrayLen(Slice const& locator) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjNestedArrayLen(Slice const& locator) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
updateData(Slice const& data) override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey)
|
||||
const override;
|
||||
|
||||
Expected<Hash, HostFunctionError>
|
||||
computeSha512HalfHash(Slice const& data) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
accountKeylet(AccountID const& account) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
ammKeylet(Asset const& issue1, Asset const& issue2) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
checkKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
|
||||
const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
didKeylet(AccountID const& account) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
delegateKeylet(AccountID const& account, AccountID const& authorize) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
escrowKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
lineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency)
|
||||
const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
mptokenKeylet(MPTID const& mptid, AccountID const& holder) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
nftOfferKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
offerKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
oracleKeylet(AccountID const& account, std::uint32_t docId) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
paychanKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq)
|
||||
const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
signersKeylet(AccountID const& account) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
ticketKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
vaultKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getNFT(AccountID const& account, uint256 const& nftId) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getNFTIssuer(uint256 const& nftId) const override;
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTTaxon(uint256 const& nftId) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getNFTFlags(uint256 const& nftId) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getNFTTransferFee(uint256 const& nftId) const override;
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTSerial(uint256 const& nftId) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
trace(std::string_view const& msg, Slice const& data, bool asHex) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceNum(std::string_view const& msg, int64_t data) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceAccount(std::string_view const& msg, AccountID const& account) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceFloat(std::string_view const& msg, Slice const& data) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceAmount(std::string_view const& msg, STAmount const& amount) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromInt(int64_t x, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromUint(uint64_t x, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTAmount(STAmount const& x, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTNumber(STNumber const& x, int32_t mode) const override;
|
||||
|
||||
Expected<int64_t, HostFunctionError>
|
||||
floatToInt(Slice const& x, int32_t mode) const override;
|
||||
|
||||
Expected<FloatPair, HostFunctionError>
|
||||
floatToMantissaAndExponent(Slice const& x) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatNegate(Slice const& x) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatAbs(Slice const& x) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatSet(int64_t mantissa, int32_t exponent, int32_t mode) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
floatCompare(Slice const& x, Slice const& y) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatAdd(Slice const& x, Slice const& y, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatDivide(Slice const& x, Slice const& y, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatRoot(Slice const& x, int32_t n, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatPower(Slice const& x, int32_t n, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatLog(Slice const& x, int32_t mode) const override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
332
include/xrpl/tx/wasm/HostFuncWrapper.h
Normal file
332
include/xrpl/tx/wasm/HostFuncWrapper.h
Normal file
@@ -0,0 +1,332 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/ParamsHelper.h>
|
||||
|
||||
#include <wasm.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
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 getBaseFee_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getBaseFee_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using isAmendmentEnabled_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
isAmendmentEnabled_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using cacheLedgerObj_proto = int32_t(uint8_t const*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
cacheLedgerObj_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getTxField_proto = int32_t(int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getTxField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getCurrentLedgerObjField_proto = int32_t(int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getCurrentLedgerObjField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getLedgerObjField_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getLedgerObjField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getTxNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getTxNestedField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getCurrentLedgerObjNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getCurrentLedgerObjNestedField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getLedgerObjNestedField_proto = int32_t(int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getLedgerObjNestedField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getTxArrayLen_proto = int32_t(int32_t);
|
||||
wasm_trap_t*
|
||||
getTxArrayLen_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getCurrentLedgerObjArrayLen_proto = int32_t(int32_t);
|
||||
wasm_trap_t*
|
||||
getCurrentLedgerObjArrayLen_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getLedgerObjArrayLen_proto = int32_t(int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
getLedgerObjArrayLen_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getTxNestedArrayLen_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
getTxNestedArrayLen_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getCurrentLedgerObjNestedArrayLen_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
getCurrentLedgerObjNestedArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getLedgerObjNestedArrayLen_proto = int32_t(int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
getLedgerObjNestedArrayLen_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using updateData_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
updateData_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using checkSignature_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
checkSignature_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using computeSha512HalfHash_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
computeSha512HalfHash_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using accountKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
accountKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using ammKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
ammKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using checkKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
checkKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using credentialKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t*
|
||||
credentialKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using delegateKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
delegateKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using depositPreauthKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
depositPreauthKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using didKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
didKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using escrowKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
escrowKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using lineKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t*
|
||||
lineKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using mptIssuanceKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
mptIssuanceKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using mptokenKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
mptokenKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using nftOfferKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
nftOfferKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using offerKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
offerKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using oracleKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
oracleKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using paychanKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t*
|
||||
paychanKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using permissionedDomainKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
permissionedDomainKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using signersKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
signersKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using ticketKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
ticketKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using vaultKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
vaultKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getNFT_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getNFT_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getNFTIssuer_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getNFTIssuer_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getNFTTaxon_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getNFTTaxon_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getNFTFlags_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
getNFTFlags_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getNFTTransferFee_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
getNFTTransferFee_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using getNFTSerial_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getNFTSerial_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using trace_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
trace_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using traceNum_proto = int32_t(uint8_t const*, int32_t, int64_t);
|
||||
wasm_trap_t*
|
||||
traceNum_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using traceAccount_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
traceAccount_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using traceFloat_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
traceFloat_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using traceAmount_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
traceAmount_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatFromInt_proto = int32_t(int64_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatFromInt_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatFromUint_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatFromUint_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatFromSTAmount_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatFromSTAmount_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatFromSTNumber_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatFromSTNumber_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatToInt_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatToInt_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatToMantissaAndExponent_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
floatToMantissaAndExponent_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatNegate_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
floatNegate_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatAbs_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
floatAbs_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatSet_proto = int32_t(int32_t, int64_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatSet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatCompare_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
floatCompare_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatAdd_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatAdd_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatSubtract_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatSubtract_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatMultiply_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatMultiply_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatDivide_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatDivide_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatRoot_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatRoot_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatPower_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatPower_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using floatLog_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
floatLog_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
} // namespace xrpl
|
||||
245
include/xrpl/tx/wasm/ParamsHelper.h
Normal file
245
include/xrpl/tx/wasm/ParamsHelper.h
Normal file
@@ -0,0 +1,245 @@
|
||||
#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 xrpl {
|
||||
|
||||
using Bytes = std::vector<std::uint8_t>;
|
||||
using Hash = xrpl::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<int32_t> EscrowResult;
|
||||
|
||||
struct WasmRuntimeWrapper
|
||||
{
|
||||
virtual ~WasmRuntimeWrapper() = default;
|
||||
|
||||
virtual wmem
|
||||
getMem() = 0;
|
||||
|
||||
virtual std::int64_t
|
||||
getGas() = 0;
|
||||
|
||||
virtual std::int64_t
|
||||
setGas(std::int64_t gas) = 0;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum WasmTypes { WT_I32, WT_I64, 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;
|
||||
};
|
||||
|
||||
typedef std::pair<void*, WasmImportFunc> WasmUserData;
|
||||
typedef std::vector<WasmUserData> ImportVec;
|
||||
|
||||
#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
|
||||
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_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(
|
||||
ImportVec& v,
|
||||
std::string_view imp_name,
|
||||
void* f_wrap,
|
||||
void* data = nullptr,
|
||||
uint32_t gas = 0)
|
||||
{
|
||||
WasmImportFunc e;
|
||||
e.name = imp_name;
|
||||
e.wrap = f_wrap;
|
||||
e.gas = gas;
|
||||
WasmImpFuncHelper<F>(e);
|
||||
v.push_back(std::make_pair(data, std::move(e)));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct WasmParamVec
|
||||
{
|
||||
std::uint8_t const* d = nullptr;
|
||||
std::int32_t sz = 0;
|
||||
};
|
||||
|
||||
struct WasmParam
|
||||
{
|
||||
WasmTypes type = WT_I32;
|
||||
union
|
||||
{
|
||||
std::int32_t i32;
|
||||
std::int64_t i64 = 0;
|
||||
float f32;
|
||||
double f64;
|
||||
WasmParamVec u8v;
|
||||
} of;
|
||||
};
|
||||
|
||||
template <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)...);
|
||||
}
|
||||
|
||||
// We are not supporting float/double for now
|
||||
// Leaving this code here so that it is easier to add later if needed
|
||||
// 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)...);
|
||||
// }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
template <typename T, size_t size = sizeof(T)>
|
||||
inline constexpr T
|
||||
adjustWasmEndianessHlp(T x)
|
||||
{
|
||||
static_assert(std::is_integral<T>::value, "Only integral types");
|
||||
if constexpr (size > 1)
|
||||
{
|
||||
using U = std::make_unsigned<T>::type;
|
||||
U u = static_cast<U>(x);
|
||||
U const low = (u & 0xFF) << ((size - 1) << 3);
|
||||
u = adjustWasmEndianessHlp<U, size - 1>(u >> 8);
|
||||
return static_cast<T>(low | u);
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
template <typename T, size_t size = sizeof(T)>
|
||||
inline constexpr T
|
||||
adjustWasmEndianess(T x)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
static_assert(std::is_integral<T>::value, "Only integral types");
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
{
|
||||
return adjustWasmEndianessHlp(x);
|
||||
}
|
||||
return x;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
189
include/xrpl/tx/wasm/README.md
Normal file
189
include/xrpl/tx/wasm/README.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# WASM Module for Programmable Escrows
|
||||
|
||||
This module provides WebAssembly (WASM) execution capabilities for programmable
|
||||
escrows on the XRP Ledger. When an escrow is finished, the WASM code runs to
|
||||
determine whether the escrow conditions are met, enabling custom programmable
|
||||
logic for escrow release conditions.
|
||||
|
||||
For the full specification, see
|
||||
[XLS-0102: WASM VM](https://xls.xrpl.org/xls/XLS-0102-wasm-vm.html).
|
||||
|
||||
## Architecture
|
||||
|
||||
The module follows a layered architecture:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ WasmEngine (WasmVM.h) │
|
||||
│ runEscrowWasm(), preflightEscrowWasm() │
|
||||
│ Host function registration │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ WasmiEngine (WasmiVM.h) │
|
||||
│ Low-level wasmi interpreter integration │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ HostFuncWrapper │ HostFuncImpl │
|
||||
│ C-style WASM bridges │ C++ implementations │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ HostFunc (Interface) │
|
||||
│ Abstract base class for host functions │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
- **`WasmVM.h` / `detail/WasmVM.cpp`** - High-level facade providing:
|
||||
- `WasmEngine` singleton that wraps the underlying WASM interpreter
|
||||
- `runEscrowWasm()` - Execute WASM code for escrow finish
|
||||
- `preflightEscrowWasm()` - Validate WASM code during preflight
|
||||
- `createWasmImport()` - Register all host functions
|
||||
|
||||
- **`WasmiVM.h` / `detail/WasmiVM.cpp`** - Low-level integration with the
|
||||
[wasmi](https://github.com/wasmi-labs/wasmi) WebAssembly interpreter:
|
||||
- `WasmiEngine` - Manages WASM modules, instances, and execution
|
||||
- Memory management and gas metering
|
||||
- Function invocation and result handling
|
||||
|
||||
- **`HostFunc.h`** - Abstract `HostFunctions` base class defining the interface
|
||||
for all callable host functions. Each method returns
|
||||
`Expected<T, HostFunctionError>`.
|
||||
|
||||
- **`HostFuncImpl.h` / `detail/HostFuncImpl*.cpp`** - Concrete
|
||||
`WasmHostFunctionsImpl` class that implements host functions with access to
|
||||
`ApplyContext` for ledger state queries. Implementation split across files:
|
||||
- `HostFuncImpl.cpp` - Core utilities (updateData, checkSignature, etc.)
|
||||
- `HostFuncImplFloat.cpp` - Float/number arithmetic operations
|
||||
- `HostFuncImplGetter.cpp` - Field access (transaction, ledger objects)
|
||||
- `HostFuncImplKeylet.cpp` - Keylet construction functions
|
||||
- `HostFuncImplLedgerHeader.cpp` - Ledger header info access
|
||||
- `HostFuncImplNFT.cpp` - NFT-related queries
|
||||
- `HostFuncImplTrace.cpp` - Debugging/tracing functions
|
||||
|
||||
- **`HostFuncWrapper.h` / `detail/HostFuncWrapper.cpp`** - C-style wrapper
|
||||
functions that bridge WASM calls to C++ `HostFunctions` methods. Each host
|
||||
function has:
|
||||
- A `_proto` type alias defining the function signature
|
||||
- A `_wrap` function that extracts parameters and calls the implementation
|
||||
|
||||
- **`ParamsHelper.h`** - Utilities for WASM parameter handling:
|
||||
- `WASM_IMPORT_FUNC` / `WASM_IMPORT_FUNC2` macros for registration
|
||||
- `wasmParams()` helper for building parameter vectors
|
||||
- Type conversion between WASM and C++ types
|
||||
|
||||
## Host Functions
|
||||
|
||||
Host functions allow WASM code to interact with the XRP Ledger. They are
|
||||
organized into categories:
|
||||
|
||||
- **Ledger Information** - Access ledger sequence, timestamps, hashes, fees
|
||||
- **Transaction & Ledger Object Access** - Read fields from the transaction
|
||||
and ledger objects (including the current escrow object)
|
||||
- **Keylet Construction** - Build keylets to look up various ledger object types
|
||||
- **Cryptography** - Signature verification and hashing
|
||||
- **Float Arithmetic** - Mathematical operations for amount calculations
|
||||
- **NFT Operations** - Query NFT properties
|
||||
- **Tracing/Debugging** - Log messages for debugging
|
||||
|
||||
For the complete list of available host functions, their WASM names, and gas
|
||||
costs, see the [XLS-0102 specification](https://xls.xrpl.org/xls/XLS-0102-wasm-vm.html)
|
||||
or `detail/WasmVM.cpp` where they are registered via `WASM_IMPORT_FUNC2` macros.
|
||||
For method signatures, see `HostFunc.h`.
|
||||
|
||||
## Gas Model
|
||||
|
||||
Each host function has an associated gas cost. The gas cost is specified when
|
||||
registering the function in `detail/WasmVM.cpp`:
|
||||
|
||||
```cpp
|
||||
WASM_IMPORT_FUNC2(i, getLedgerSqn, "get_ledger_sqn", hfs, 60);
|
||||
// ^^ gas cost
|
||||
```
|
||||
|
||||
WASM execution is metered, and if the gas limit is exceeded, execution fails.
|
||||
|
||||
## Entry Point
|
||||
|
||||
The WASM module must export a function with the name defined by
|
||||
`ESCROW_FUNCTION_NAME` (currently `"finish"`). This function:
|
||||
|
||||
- Takes no parameters (or parameters passed via host function calls)
|
||||
- Returns an `int32_t`:
|
||||
- `1` (or positive): Escrow conditions are met, allow finish
|
||||
- `0` (or negative): Escrow conditions are not met, reject finish
|
||||
|
||||
## Adding a New Host Function
|
||||
|
||||
To add a new host function, follow these steps:
|
||||
|
||||
### 1. Add to HostFunc.h (Base Class)
|
||||
|
||||
Add a virtual method declaration with a default implementation that returns an
|
||||
error:
|
||||
|
||||
```cpp
|
||||
virtual Expected<ReturnType, HostFunctionError>
|
||||
myNewFunction(ParamType1 param1, ParamType2 param2)
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Add to HostFuncImpl.h (Declaration)
|
||||
|
||||
Add the method override declaration in `WasmHostFunctionsImpl`:
|
||||
|
||||
```cpp
|
||||
Expected<ReturnType, HostFunctionError>
|
||||
myNewFunction(ParamType1 param1, ParamType2 param2) override;
|
||||
```
|
||||
|
||||
### 3. Implement in detail/HostFuncImpl\*.cpp
|
||||
|
||||
Add the implementation in the appropriate file:
|
||||
|
||||
```cpp
|
||||
Expected<ReturnType, HostFunctionError>
|
||||
WasmHostFunctionsImpl::myNewFunction(ParamType1 param1, ParamType2 param2)
|
||||
{
|
||||
// Implementation using ctx (ApplyContext) for ledger access
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Add Wrapper to HostFuncWrapper.h
|
||||
|
||||
Add the prototype and wrapper declaration:
|
||||
|
||||
```cpp
|
||||
using myNewFunction_proto = int32_t(uint8_t const*, int32_t, ...);
|
||||
wasm_trap_t*
|
||||
myNewFunction_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
```
|
||||
|
||||
### 5. Implement Wrapper in detail/HostFuncWrapper.cpp
|
||||
|
||||
Implement the C-style wrapper that bridges WASM to C++:
|
||||
|
||||
```cpp
|
||||
wasm_trap_t*
|
||||
myNewFunction_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
|
||||
{
|
||||
// Extract parameters from params
|
||||
// Call hfs->myNewFunction(...)
|
||||
// Set results and return
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Register in WasmVM.cpp
|
||||
|
||||
Add the function registration in `setCommonHostFunctions()` or
|
||||
`createWasmImport()`:
|
||||
|
||||
```cpp
|
||||
WASM_IMPORT_FUNC2(i, myNewFunction, "my_new_function", hfs, 100);
|
||||
// ^^ WASM name ^^ gas cost
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> New host functions MUST be amendment-gated in `WasmVM.cpp`.
|
||||
> Wrap the registration in an amendment check to ensure the function is only
|
||||
> available after the corresponding amendment is enabled on the network.
|
||||
90
include/xrpl/tx/wasm/WasmVM.h
Normal file
90
include/xrpl/tx/wasm/WasmVM.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
static std::string_view const W_ENV = "env";
|
||||
static std::string_view const W_HOST_LIB = "host_lib";
|
||||
static std::string_view const W_MEM = "memory";
|
||||
static std::string_view const W_STORE = "store";
|
||||
static std::string_view const W_LOAD = "load";
|
||||
static std::string_view const W_SIZE = "size";
|
||||
static std::string_view const W_ALLOC = "allocate";
|
||||
static std::string_view const W_DEALLOC = "deallocate";
|
||||
static std::string_view const W_PROC_EXIT = "proc_exit";
|
||||
|
||||
static std::string_view const ESCROW_FUNCTION_NAME = "finish";
|
||||
|
||||
uint32_t const MAX_PAGES = 128; // 8MB = 64KB*128
|
||||
|
||||
class WasmiEngine;
|
||||
|
||||
class WasmEngine
|
||||
{
|
||||
std::unique_ptr<WasmiEngine> 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,
|
||||
HostFunctions& hfs,
|
||||
int64_t gasLimit,
|
||||
std::string_view funcName = {},
|
||||
std::vector<WasmParam> const& params = {},
|
||||
ImportVec const& imports = {},
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
NotTEC
|
||||
check(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params = {},
|
||||
ImportVec const& imports = {},
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
// Host functions helper functionality
|
||||
void*
|
||||
newTrap(std::string const& txt = std::string());
|
||||
|
||||
beast::Journal
|
||||
getJournal() const;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ImportVec
|
||||
createWasmImport(HostFunctions& hfs);
|
||||
|
||||
Expected<EscrowResult, TER>
|
||||
runEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gasLimit,
|
||||
std::string_view funcName = ESCROW_FUNCTION_NAME,
|
||||
std::vector<WasmParam> const& params = {});
|
||||
|
||||
NotTEC
|
||||
preflightEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName = ESCROW_FUNCTION_NAME,
|
||||
std::vector<WasmParam> const& params = {});
|
||||
|
||||
} // namespace xrpl
|
||||
363
include/xrpl/tx/wasm/WasmiVM.h
Normal file
363
include/xrpl/tx/wasm/WasmiVM.h
Normal file
@@ -0,0 +1,363 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/WasmVM.h>
|
||||
|
||||
#include <wasm.h>
|
||||
#include <wasmi.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <class T, void (*Create)(T*, size_t), void (*Destroy)(T*)>
|
||||
class WasmVec
|
||||
{
|
||||
using TD = std::remove_pointer_t<decltype(T::data)>;
|
||||
T vec_;
|
||||
|
||||
public:
|
||||
WasmVec(size_t s = 0) : vec_ WASM_EMPTY_VEC
|
||||
{
|
||||
if (s > 0)
|
||||
Create(&vec_, s); // zeroes memory
|
||||
}
|
||||
|
||||
~WasmVec()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
WasmVec(WasmVec const&) = delete;
|
||||
WasmVec&
|
||||
operator=(WasmVec const&) = delete;
|
||||
|
||||
WasmVec(WasmVec&& other) noexcept : vec_ WASM_EMPTY_VEC
|
||||
{
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
WasmVec&
|
||||
operator=(WasmVec&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
clear();
|
||||
vec_ = other.vec_;
|
||||
other.vec_ = WASM_EMPTY_VEC;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
clear()
|
||||
{
|
||||
Destroy(&vec_); // call destructor for every elements too
|
||||
vec_ = WASM_EMPTY_VEC;
|
||||
}
|
||||
|
||||
T
|
||||
release()
|
||||
{
|
||||
T result = vec_;
|
||||
vec_ = WASM_EMPTY_VEC;
|
||||
return result;
|
||||
}
|
||||
|
||||
T*
|
||||
get()
|
||||
{
|
||||
return &vec_;
|
||||
}
|
||||
|
||||
T const*
|
||||
get() const
|
||||
{
|
||||
return &vec_;
|
||||
}
|
||||
|
||||
TD&
|
||||
operator[](size_t i)
|
||||
{
|
||||
if (i >= vec_.size)
|
||||
Throw<std::runtime_error>("Out of bound");
|
||||
return vec_.data[i];
|
||||
}
|
||||
|
||||
TD const&
|
||||
operator[](size_t i) const
|
||||
{
|
||||
if (i >= vec_.size)
|
||||
Throw<std::runtime_error>("Out of bound");
|
||||
return vec_.data[i];
|
||||
}
|
||||
|
||||
size_t
|
||||
size() const
|
||||
{
|
||||
return vec_.size;
|
||||
}
|
||||
|
||||
bool
|
||||
empty() const
|
||||
{
|
||||
return vec_.size == 0u;
|
||||
}
|
||||
};
|
||||
|
||||
using WasmValtypeVec =
|
||||
WasmVec<wasm_valtype_vec_t, &wasm_valtype_vec_new_uninitialized, &wasm_valtype_vec_delete>;
|
||||
using WasmValVec = WasmVec<wasm_val_vec_t, &wasm_val_vec_new_uninitialized, &wasm_val_vec_delete>;
|
||||
using WasmExternVec =
|
||||
WasmVec<wasm_extern_vec_t, &wasm_extern_vec_new_uninitialized, &wasm_extern_vec_delete>;
|
||||
using WasmExporttypeVec = WasmVec<
|
||||
wasm_exporttype_vec_t,
|
||||
&wasm_exporttype_vec_new_uninitialized,
|
||||
&wasm_exporttype_vec_delete>;
|
||||
using WasmImporttypeVec = WasmVec<
|
||||
wasm_importtype_vec_t,
|
||||
&wasm_importtype_vec_new_uninitialized,
|
||||
&wasm_importtype_vec_delete>;
|
||||
|
||||
struct WasmiResult
|
||||
{
|
||||
WasmValVec r;
|
||||
bool f{false}; // failure flag
|
||||
|
||||
WasmiResult(unsigned N = 0) : r(N)
|
||||
{
|
||||
}
|
||||
|
||||
~WasmiResult() = default;
|
||||
WasmiResult(WasmiResult&& o) = default;
|
||||
WasmiResult&
|
||||
operator=(WasmiResult&& o) = default;
|
||||
};
|
||||
|
||||
using ModulePtr = std::unique_ptr<wasm_module_t, decltype(&wasm_module_delete)>;
|
||||
using InstancePtr = std::unique_ptr<wasm_instance_t, decltype(&wasm_instance_delete)>;
|
||||
using EnginePtr = std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)>;
|
||||
using StorePtr = std::unique_ptr<wasm_store_t, decltype(&wasm_store_delete)>;
|
||||
|
||||
using FuncInfo = std::pair<wasm_func_t const*, wasm_functype_t const*>;
|
||||
|
||||
struct InstanceWrapper
|
||||
{
|
||||
wasm_store_t* store_ = nullptr;
|
||||
WasmExternVec exports_;
|
||||
mutable int memIdx_ = -1;
|
||||
InstancePtr instance_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
private:
|
||||
static InstancePtr
|
||||
init(
|
||||
StorePtr& s,
|
||||
ModulePtr& m,
|
||||
WasmExternVec& expt,
|
||||
WasmExternVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
public:
|
||||
InstanceWrapper();
|
||||
|
||||
InstanceWrapper(InstanceWrapper&& o);
|
||||
|
||||
InstanceWrapper&
|
||||
operator=(InstanceWrapper&& o);
|
||||
|
||||
InstanceWrapper(StorePtr& s, ModulePtr& m, WasmExternVec const& imports, beast::Journal j);
|
||||
|
||||
~InstanceWrapper() = default;
|
||||
|
||||
operator bool() const;
|
||||
|
||||
FuncInfo
|
||||
getFunc(std::string_view funcName, WasmExporttypeVec const& exportTypes) const;
|
||||
|
||||
wmem
|
||||
getMem() const;
|
||||
|
||||
std::int64_t
|
||||
getGas() const;
|
||||
|
||||
std::int64_t
|
||||
setGas(std::int64_t) const;
|
||||
};
|
||||
|
||||
struct ModuleWrapper
|
||||
{
|
||||
ModulePtr module_;
|
||||
InstanceWrapper instanceWrap_;
|
||||
WasmExporttypeVec exportTypes_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
private:
|
||||
static ModulePtr
|
||||
init(StorePtr& s, Bytes const& wasmBin, beast::Journal j);
|
||||
|
||||
public:
|
||||
ModuleWrapper();
|
||||
ModuleWrapper(ModuleWrapper&& o);
|
||||
ModuleWrapper&
|
||||
operator=(ModuleWrapper&& o);
|
||||
ModuleWrapper(
|
||||
StorePtr& s,
|
||||
Bytes const& wasmBin,
|
||||
bool instantiate,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
~ModuleWrapper() = default;
|
||||
|
||||
operator bool() const;
|
||||
|
||||
FuncInfo
|
||||
getFunc(std::string_view funcName) const;
|
||||
|
||||
wasm_functype_t*
|
||||
getFuncType(std::string_view funcName) const;
|
||||
|
||||
wmem
|
||||
getMem() const;
|
||||
|
||||
InstanceWrapper&
|
||||
getInstance(int i = 0);
|
||||
|
||||
int
|
||||
addInstance(StorePtr& s, WasmExternVec const& imports);
|
||||
|
||||
std::int64_t
|
||||
getGas() const;
|
||||
|
||||
private:
|
||||
WasmExternVec
|
||||
buildImports(StorePtr& s, ImportVec const& imports) const;
|
||||
};
|
||||
|
||||
class WasmiEngine
|
||||
{
|
||||
EnginePtr engine_;
|
||||
StorePtr store_;
|
||||
std::unique_ptr<ModuleWrapper> moduleWrap_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
std::mutex m_; // 1 instance mutex
|
||||
|
||||
public:
|
||||
WasmiEngine();
|
||||
~WasmiEngine() = default;
|
||||
|
||||
static EnginePtr
|
||||
init();
|
||||
|
||||
Expected<WasmResult<int32_t>, TER>
|
||||
run(Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gas,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
NotTEC
|
||||
check(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
std::int64_t
|
||||
getGas() const;
|
||||
|
||||
// Host functions helper functionality
|
||||
wasm_trap_t*
|
||||
newTrap(std::string const& msg);
|
||||
|
||||
beast::Journal
|
||||
getJournal() const;
|
||||
|
||||
private:
|
||||
InstanceWrapper&
|
||||
getRT(int m = 0, int i = 0) const;
|
||||
|
||||
wmem
|
||||
getMem() const;
|
||||
|
||||
Expected<WasmResult<int32_t>, TER>
|
||||
runHlp(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gas,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports);
|
||||
|
||||
NotTEC
|
||||
checkHlp(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports);
|
||||
|
||||
int
|
||||
addModule(Bytes const& wasmCode, bool instantiate, ImportVec const& imports, int64_t gas);
|
||||
void
|
||||
clearModules();
|
||||
|
||||
// int addInstance();
|
||||
|
||||
int32_t
|
||||
runFunc(std::string_view const funcName, int32_t p);
|
||||
|
||||
int32_t
|
||||
makeModule(Bytes const& wasmCode, WasmExternVec const& imports = {});
|
||||
|
||||
FuncInfo
|
||||
getFunc(std::string_view funcName) const;
|
||||
|
||||
static std::vector<wasm_val_t>
|
||||
convertParams(std::vector<WasmParam> const& params);
|
||||
|
||||
static int
|
||||
compareParamTypes(wasm_valtype_vec_t const* ftp, std::vector<wasm_val_t> const& p);
|
||||
|
||||
static void
|
||||
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 WasmiResult
|
||||
call(std::string_view func, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in, std::int32_t p, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in, std::int64_t p, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(
|
||||
FuncInfo const& f,
|
||||
std::vector<wasm_val_t>& in,
|
||||
uint8_t const* d,
|
||||
int32_t sz,
|
||||
Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in, Bytes const& p, Types&&... args);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -945,6 +945,82 @@ power(Number const& f, unsigned n)
|
||||
return r;
|
||||
}
|
||||
|
||||
// Series expansion method approximation of ln(x)
|
||||
static Number
|
||||
ln(Number const& x, int iterations = 50)
|
||||
{
|
||||
Number const N0(0);
|
||||
Number const N2(2, 0);
|
||||
Number const N05(5, -1);
|
||||
Number const LN2(693'147'180'559'945'309ll, -18);
|
||||
|
||||
if (x <= 0)
|
||||
{
|
||||
throw std::runtime_error("Not a positive value");
|
||||
}
|
||||
if (x == 1)
|
||||
{
|
||||
return N0;
|
||||
}
|
||||
|
||||
int exponent = 0;
|
||||
Number mantissa = x;
|
||||
|
||||
while (mantissa >= N2)
|
||||
{
|
||||
mantissa /= 2;
|
||||
exponent += 1;
|
||||
}
|
||||
while (mantissa < N05)
|
||||
{
|
||||
mantissa *= 2;
|
||||
exponent -= 1;
|
||||
}
|
||||
|
||||
Number z = (mantissa - 1) / (mantissa + 1);
|
||||
Number const zz = z * z;
|
||||
Number sum;
|
||||
|
||||
for (int i = 1; i <= iterations; ++i)
|
||||
{
|
||||
sum = sum + z / ((2 * i) - 1);
|
||||
z = z * zz;
|
||||
}
|
||||
|
||||
return 2 * sum + exponent * LN2;
|
||||
}
|
||||
|
||||
Number
|
||||
log10(Number const& x, int iterations)
|
||||
{
|
||||
Number const N0(0);
|
||||
Number const LN10(2'302'585'092'994'045'684ll, -18);
|
||||
|
||||
if (x <= 0)
|
||||
{
|
||||
throw std::runtime_error("Not a positive value");
|
||||
}
|
||||
if (x == 1)
|
||||
{
|
||||
return N0;
|
||||
}
|
||||
|
||||
if (x <= Number(10))
|
||||
{
|
||||
auto const r = ln(x, iterations) / LN10;
|
||||
return r;
|
||||
}
|
||||
|
||||
// (1 <= normalX < 10)
|
||||
// x = normalX * 10^norm
|
||||
// ln(x) = ln(normalX * 10^norm) = ln(normalX) + norm * ln(10)
|
||||
int const diffExp = Number::mantissaLog() + x.exponent_;
|
||||
Number const normalX = x / Number(1, diffExp);
|
||||
auto const lnX = ln(normalX, iterations) + diffExp * LN10;
|
||||
auto const lgX = lnX / LN10;
|
||||
return lgX;
|
||||
}
|
||||
|
||||
// Returns f^(1/d)
|
||||
// Uses Newton–Raphson iterations until the result stops changing
|
||||
// to find the non-negative root of the polynomial g(x) = x^d - f
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl {
|
||||
namespace detail {
|
||||
|
||||
@@ -376,14 +374,14 @@ ApplyStateTable::erase(ReadView const& base, std::shared_ptr<SLE> const& sle)
|
||||
{
|
||||
auto const iter = items_.find(sle->key());
|
||||
if (iter == items_.end())
|
||||
Throw<std::logic_error>("ApplyStateTable::erase: missing key");
|
||||
LogicError("ApplyStateTable::erase: missing key");
|
||||
auto& item = iter->second;
|
||||
if (item.second != sle)
|
||||
Throw<std::logic_error>("ApplyStateTable::erase: unknown SLE");
|
||||
LogicError("ApplyStateTable::erase: unknown SLE");
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::erase:
|
||||
Throw<std::logic_error>("ApplyStateTable::erase: double erase");
|
||||
LogicError("ApplyStateTable::erase: double erase");
|
||||
break;
|
||||
case Action::insert:
|
||||
items_.erase(iter);
|
||||
@@ -407,7 +405,7 @@ ApplyStateTable::rawErase(ReadView const& base, std::shared_ptr<SLE> const& sle)
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::erase:
|
||||
Throw<std::logic_error>("ApplyStateTable::rawErase: double erase");
|
||||
LogicError("ApplyStateTable::rawErase: double erase");
|
||||
break;
|
||||
case Action::insert:
|
||||
items_.erase(result.first);
|
||||
@@ -438,11 +436,11 @@ ApplyStateTable::insert(ReadView const& base, std::shared_ptr<SLE> const& sle)
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::cache:
|
||||
Throw<std::logic_error>("ApplyStateTable::insert: already cached");
|
||||
LogicError("ApplyStateTable::insert: already cached");
|
||||
case Action::insert:
|
||||
Throw<std::logic_error>("ApplyStateTable::insert: already inserted");
|
||||
LogicError("ApplyStateTable::insert: already inserted");
|
||||
case Action::modify:
|
||||
Throw<std::logic_error>("ApplyStateTable::insert: already modified");
|
||||
LogicError("ApplyStateTable::insert: already modified");
|
||||
case Action::erase:
|
||||
break;
|
||||
}
|
||||
@@ -468,7 +466,7 @@ ApplyStateTable::replace(ReadView const& base, std::shared_ptr<SLE> const& sle)
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::erase:
|
||||
Throw<std::logic_error>("ApplyStateTable::replace: already erased");
|
||||
LogicError("ApplyStateTable::replace: already erased");
|
||||
case Action::cache:
|
||||
item.first = Action::modify;
|
||||
break;
|
||||
@@ -484,14 +482,14 @@ ApplyStateTable::update(ReadView const& base, std::shared_ptr<SLE> const& sle)
|
||||
{
|
||||
auto const iter = items_.find(sle->key());
|
||||
if (iter == items_.end())
|
||||
Throw<std::logic_error>("ApplyStateTable::update: missing key");
|
||||
LogicError("ApplyStateTable::update: missing key");
|
||||
auto& item = iter->second;
|
||||
if (item.second != sle)
|
||||
Throw<std::logic_error>("ApplyStateTable::update: unknown SLE");
|
||||
LogicError("ApplyStateTable::update: unknown SLE");
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::erase:
|
||||
Throw<std::logic_error>("ApplyStateTable::update: erased");
|
||||
LogicError("ApplyStateTable::update: erased");
|
||||
break;
|
||||
case Action::cache:
|
||||
item.first = Action::modify;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -41,8 +40,10 @@ findPreviousPage(ApplyView& view, Keylet const& directory, SLE::ref start)
|
||||
{
|
||||
node = view.peek(keylet::page(directory, page));
|
||||
if (!node)
|
||||
Throw<std::logic_error>(
|
||||
"Directory chain: root back-pointer broken."); // LCOV_EXCL_LINE
|
||||
{ // LCOV_EXCL_START
|
||||
LogicError("Directory chain: root back-pointer broken.");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
auto indexes = node->getFieldV256(sfIndexes);
|
||||
@@ -61,20 +62,21 @@ insertKey(
|
||||
if (preserveOrder)
|
||||
{
|
||||
if (std::find(indexes.begin(), indexes.end(), key) != indexes.end())
|
||||
Throw<std::logic_error>("dirInsert: double insertion"); // LCOV_EXCL_LINE
|
||||
LogicError("dirInsert: double insertion"); // LCOV_EXCL_LINE
|
||||
|
||||
indexes.push_back(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We can't be sure if this page is already sorted because it may be a
|
||||
// legacy page we haven't yet touched. Take the time to sort it.
|
||||
// We can't be sure if this page is already sorted because
|
||||
// it may be a legacy page we haven't yet touched. Take
|
||||
// the time to sort it.
|
||||
std::sort(indexes.begin(), indexes.end());
|
||||
|
||||
auto pos = std::lower_bound(indexes.begin(), indexes.end(), key);
|
||||
|
||||
if (pos != indexes.end() && key == *pos)
|
||||
Throw<std::logic_error>("dirInsert: double insertion"); // LCOV_EXCL_LINE
|
||||
LogicError("dirInsert: double insertion"); // LCOV_EXCL_LINE
|
||||
|
||||
indexes.insert(pos, key);
|
||||
}
|
||||
@@ -127,7 +129,8 @@ insertPage(
|
||||
node->setFieldH256(sfRootIndex, directory.key);
|
||||
node->setFieldV256(sfIndexes, indexes);
|
||||
|
||||
// Save some space by not specifying the value 0 since it's the default.
|
||||
// Save some space by not specifying the value 0 since
|
||||
// it's the default.
|
||||
if (page != 1)
|
||||
node->setFieldU64(sfIndexPrevious, page - 1);
|
||||
XRPL_ASSERT_PARTS(!nextPage, "xrpl::directory::insertPage", "nextPage has default value");
|
||||
@@ -196,24 +199,28 @@ ApplyView::emptyDirDelete(Keylet const& directory)
|
||||
auto nextPage = node->getFieldU64(sfIndexNext);
|
||||
|
||||
if (nextPage == rootPage && prevPage != rootPage)
|
||||
Throw<std::logic_error>("Directory chain: fwd link broken"); // LCOV_EXCL_LINE
|
||||
LogicError("Directory chain: fwd link broken"); // LCOV_EXCL_LINE
|
||||
|
||||
if (prevPage == rootPage && nextPage != rootPage)
|
||||
Throw<std::logic_error>("Directory chain: rev link broken"); // LCOV_EXCL_LINE
|
||||
LogicError("Directory chain: rev link broken"); // LCOV_EXCL_LINE
|
||||
|
||||
// Older versions of the code would, in some cases, allow the last page to
|
||||
// be empty. Remove such pages:
|
||||
// Older versions of the code would, in some cases, allow the last
|
||||
// page to be empty. Remove such pages:
|
||||
if (nextPage == prevPage && nextPage != rootPage)
|
||||
{
|
||||
auto last = peek(keylet::page(directory, nextPage));
|
||||
|
||||
if (!last)
|
||||
Throw<std::logic_error>("Directory chain: fwd link broken."); // LCOV_EXCL_LINE
|
||||
{ // LCOV_EXCL_START
|
||||
LogicError("Directory chain: fwd link broken.");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (!last->getFieldV256(sfIndexes).empty())
|
||||
return false;
|
||||
|
||||
// Update the first page's linked list and mark it as updated.
|
||||
// Update the first page's linked list and
|
||||
// mark it as updated.
|
||||
node->setFieldU64(sfIndexNext, rootPage);
|
||||
node->setFieldU64(sfIndexPrevious, rootPage);
|
||||
update(node);
|
||||
@@ -221,7 +228,8 @@ ApplyView::emptyDirDelete(Keylet const& directory)
|
||||
// And erase the empty last page:
|
||||
erase(last);
|
||||
|
||||
// Make sure our local values reflect the updated information:
|
||||
// Make sure our local values reflect the
|
||||
// updated information:
|
||||
nextPage = rootPage;
|
||||
prevPage = rootPage;
|
||||
}
|
||||
@@ -261,33 +269,46 @@ ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const&
|
||||
return true;
|
||||
}
|
||||
|
||||
// The current page is now empty; check if it can be deleted, and, if so,
|
||||
// whether the entire directory can now be removed.
|
||||
// The current page is now empty; check if it can be
|
||||
// deleted, and, if so, whether the entire directory
|
||||
// can now be removed.
|
||||
auto prevPage = node->getFieldU64(sfIndexPrevious);
|
||||
auto nextPage = node->getFieldU64(sfIndexNext);
|
||||
|
||||
// The first page is the directory's root node and is treated specially: it
|
||||
// can never be deleted even if it is empty, unless we plan on removing the
|
||||
// entire directory.
|
||||
// The first page is the directory's root node and is
|
||||
// treated specially: it can never be deleted even if
|
||||
// it is empty, unless we plan on removing the entire
|
||||
// directory.
|
||||
if (page == rootPage)
|
||||
{
|
||||
if (nextPage == page && prevPage != page)
|
||||
Throw<std::logic_error>("Directory chain: fwd link broken"); // LCOV_EXCL_LINE
|
||||
{ // LCOV_EXCL_START
|
||||
LogicError("Directory chain: fwd link broken");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (prevPage == page && nextPage != page)
|
||||
Throw<std::logic_error>("Directory chain: rev link broken"); // LCOV_EXCL_LINE
|
||||
{ // LCOV_EXCL_START
|
||||
LogicError("Directory chain: rev link broken");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// Older versions of the code would, in some cases, allow the last page
|
||||
// to be empty. Remove such pages if we stumble on them:
|
||||
// Older versions of the code would, in some cases,
|
||||
// allow the last page to be empty. Remove such
|
||||
// pages if we stumble on them:
|
||||
if (nextPage == prevPage && nextPage != page)
|
||||
{
|
||||
auto last = peek(keylet::page(directory, nextPage));
|
||||
if (!last)
|
||||
Throw<std::logic_error>("Directory chain: fwd link broken."); // LCOV_EXCL_LINE
|
||||
{ // LCOV_EXCL_START
|
||||
LogicError("Directory chain: fwd link broken.");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (last->getFieldV256(sfIndexes).empty())
|
||||
{
|
||||
// Update the first page's linked list and mark it as updated.
|
||||
// Update the first page's linked list and
|
||||
// mark it as updated.
|
||||
node->setFieldU64(sfIndexNext, page);
|
||||
node->setFieldU64(sfIndexPrevious, page);
|
||||
update(node);
|
||||
@@ -295,7 +316,8 @@ ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const&
|
||||
// And erase the empty last page:
|
||||
erase(last);
|
||||
|
||||
// Make sure our local values reflect the updated information:
|
||||
// Make sure our local values reflect the
|
||||
// updated information:
|
||||
nextPage = page;
|
||||
prevPage = page;
|
||||
}
|
||||
@@ -313,24 +335,25 @@ ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const&
|
||||
|
||||
// This can never happen for nodes other than the root:
|
||||
if (nextPage == page)
|
||||
Throw<std::logic_error>("Directory chain: fwd link broken"); // LCOV_EXCL_LINE
|
||||
LogicError("Directory chain: fwd link broken"); // LCOV_EXCL_LINE
|
||||
|
||||
if (prevPage == page)
|
||||
Throw<std::logic_error>("Directory chain: rev link broken"); // LCOV_EXCL_LINE
|
||||
LogicError("Directory chain: rev link broken"); // LCOV_EXCL_LINE
|
||||
|
||||
// This node isn't the root, so it can either be in the middle of the list,
|
||||
// or at the end. Unlink it first and then check if that leaves the list
|
||||
// with only a root:
|
||||
// This node isn't the root, so it can either be in the
|
||||
// middle of the list, or at the end. Unlink it first
|
||||
// and then check if that leaves the list with only a
|
||||
// root:
|
||||
auto prev = peek(keylet::page(directory, prevPage));
|
||||
if (!prev)
|
||||
Throw<std::logic_error>("Directory chain: fwd link broken."); // LCOV_EXCL_LINE
|
||||
LogicError("Directory chain: fwd link broken."); // LCOV_EXCL_LINE
|
||||
// Fix previous to point to its new next.
|
||||
prev->setFieldU64(sfIndexNext, nextPage);
|
||||
update(prev);
|
||||
|
||||
auto next = peek(keylet::page(directory, nextPage));
|
||||
if (!next)
|
||||
Throw<std::logic_error>("Directory chain: rev link broken."); // LCOV_EXCL_LINE
|
||||
LogicError("Directory chain: rev link broken."); // LCOV_EXCL_LINE
|
||||
// Fix next to point to its new previous.
|
||||
next->setFieldU64(sfIndexPrevious, prevPage);
|
||||
update(next);
|
||||
@@ -338,12 +361,13 @@ ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const&
|
||||
// The page is no longer linked. Delete it.
|
||||
erase(node);
|
||||
|
||||
// Check whether the next page is the last page and, if so, whether it's
|
||||
// empty. If it is, delete it.
|
||||
// Check whether the next page is the last page and, if
|
||||
// so, whether it's empty. If it is, delete it.
|
||||
if (nextPage != rootPage && next->getFieldU64(sfIndexNext) == rootPage &&
|
||||
next->getFieldV256(sfIndexes).empty())
|
||||
{
|
||||
// Since next doesn't point to the root, it can't be pointing to prev.
|
||||
// Since next doesn't point to the root, it
|
||||
// can't be pointing to prev.
|
||||
erase(next);
|
||||
|
||||
// The previous page is now the last page:
|
||||
@@ -353,16 +377,18 @@ ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const&
|
||||
// And the root points to the last page:
|
||||
auto root = peek(keylet::page(directory, rootPage));
|
||||
if (!root)
|
||||
Throw<std::logic_error>("Directory chain: root link broken."); // LCOV_EXCL_LINE
|
||||
|
||||
{ // LCOV_EXCL_START
|
||||
LogicError("Directory chain: root link broken.");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
root->setFieldU64(sfIndexPrevious, prevPage);
|
||||
update(root);
|
||||
|
||||
nextPage = rootPage;
|
||||
}
|
||||
|
||||
// If we're not keeping the root, then check to see if it's left empty.
|
||||
// If so, delete it as well.
|
||||
// If we're not keeping the root, then check to see if
|
||||
// it's left empty. If so, delete it as well.
|
||||
if (!keepRoot && nextPage == rootPage && prevPage == rootPage)
|
||||
{
|
||||
if (prev->getFieldV256(sfIndexes).empty())
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -170,6 +169,12 @@ Ledger::Ledger(
|
||||
sle->at(sfReserveIncrement) = *f;
|
||||
sle->at(sfReferenceFeeUnits) = FEE_UNITS_DEPRECATED;
|
||||
}
|
||||
if (std::find(amendments.begin(), amendments.end(), featureSmartEscrow) != amendments.end())
|
||||
{
|
||||
sle->at(sfExtensionComputeLimit) = fees.extensionComputeLimit;
|
||||
sle->at(sfExtensionSizeLimit) = fees.extensionSizeLimit;
|
||||
sle->at(sfGasPrice) = fees.gasPrice;
|
||||
}
|
||||
rawInsert(sle);
|
||||
}
|
||||
|
||||
@@ -462,14 +467,14 @@ void
|
||||
Ledger::rawErase(std::shared_ptr<SLE> const& sle)
|
||||
{
|
||||
if (!stateMap_.delItem(sle->key()))
|
||||
Throw<std::logic_error>("Ledger::rawErase: key not found");
|
||||
LogicError("Ledger::rawErase: key not found");
|
||||
}
|
||||
|
||||
void
|
||||
Ledger::rawErase(uint256 const& key)
|
||||
{
|
||||
if (!stateMap_.delItem(key))
|
||||
Throw<std::logic_error>("Ledger::rawErase: key not found");
|
||||
LogicError("Ledger::rawErase: key not found");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -479,7 +484,7 @@ Ledger::rawInsert(std::shared_ptr<SLE> const& sle)
|
||||
sle->add(ss);
|
||||
if (!stateMap_.addGiveItem(
|
||||
SHAMapNodeType::tnACCOUNT_STATE, make_shamapitem(sle->key(), ss.slice())))
|
||||
Throw<std::logic_error>("Ledger::rawInsert: key already exists");
|
||||
LogicError("Ledger::rawInsert: key already exists");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -489,7 +494,7 @@ Ledger::rawReplace(std::shared_ptr<SLE> const& sle)
|
||||
sle->add(ss);
|
||||
if (!stateMap_.updateGiveItem(
|
||||
SHAMapNodeType::tnACCOUNT_STATE, make_shamapitem(sle->key(), ss.slice())))
|
||||
Throw<std::logic_error>("Ledger::rawReplace: key not found");
|
||||
LogicError("Ledger::rawReplace: key not found");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -505,7 +510,7 @@ Ledger::rawTxInsert(
|
||||
s.addVL(txn->peekData());
|
||||
s.addVL(metaData->peekData());
|
||||
if (!txMap_.addGiveItem(SHAMapNodeType::tnTRANSACTION_MD, make_shamapitem(key, s.slice())))
|
||||
Throw<std::logic_error>("duplicate_tx: " + to_string(key));
|
||||
LogicError("duplicate_tx: " + to_string(key));
|
||||
}
|
||||
|
||||
uint256
|
||||
@@ -523,7 +528,7 @@ Ledger::rawTxInsertWithHash(
|
||||
auto item = make_shamapitem(key, s.slice());
|
||||
auto hash = sha512Half(HashPrefix::txNode, item->slice(), item->key());
|
||||
if (!txMap_.addGiveItem(SHAMapNodeType::tnTRANSACTION_MD, std::move(item)))
|
||||
Throw<std::logic_error>("duplicate_tx: " + to_string(key));
|
||||
LogicError("duplicate_tx: " + to_string(key));
|
||||
|
||||
return hash;
|
||||
}
|
||||
@@ -553,6 +558,7 @@ Ledger::setup()
|
||||
{
|
||||
bool oldFees = false;
|
||||
bool newFees = false;
|
||||
bool extensionFees = false;
|
||||
{
|
||||
auto const baseFee = sle->at(~sfBaseFee);
|
||||
auto const reserveBase = sle->at(~sfReserveBase);
|
||||
@@ -569,6 +575,7 @@ Ledger::setup()
|
||||
auto const baseFeeXRP = sle->at(~sfBaseFeeDrops);
|
||||
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
|
||||
auto const reserveIncrementXRP = sle->at(~sfReserveIncrementDrops);
|
||||
|
||||
auto assign = [&ret](XRPAmount& dest, std::optional<STAmount> const& src) {
|
||||
if (src)
|
||||
{
|
||||
@@ -587,6 +594,22 @@ 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
|
||||
@@ -597,6 +620,12 @@ Ledger::setup()
|
||||
// Can't populate the new fees before the amendment is enabled
|
||||
ret = false;
|
||||
}
|
||||
if (!rules_.enabled(featureSmartEscrow) && extensionFees)
|
||||
{
|
||||
// Can't populate the extension fees before the amendment is
|
||||
// enabled
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SHAMapMissingNode const&)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class OpenView::txs_iter_impl : public txs_type::iter_base
|
||||
@@ -249,7 +247,7 @@ OpenView::rawTxInsert(
|
||||
auto const result = txs_.emplace(
|
||||
std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(txn, metaData));
|
||||
if (!result.second)
|
||||
Throw<std::logic_error>("rawTxInsert: duplicate TX id: " + to_string(key));
|
||||
LogicError("rawTxInsert: duplicate TX id: " + to_string(key));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/ledger/detail/RawStateTable.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl {
|
||||
namespace detail {
|
||||
|
||||
@@ -243,7 +241,7 @@ RawStateTable::erase(std::shared_ptr<SLE> const& sle)
|
||||
switch (item.action)
|
||||
{
|
||||
case Action::erase:
|
||||
Throw<std::logic_error>("RawStateTable::erase: already erased");
|
||||
LogicError("RawStateTable::erase: already erased");
|
||||
break;
|
||||
case Action::insert:
|
||||
items_.erase(result.first);
|
||||
@@ -272,10 +270,10 @@ RawStateTable::insert(std::shared_ptr<SLE> const& sle)
|
||||
item.sle = sle;
|
||||
break;
|
||||
case Action::insert:
|
||||
Throw<std::logic_error>("RawStateTable::insert: already inserted");
|
||||
LogicError("RawStateTable::insert: already inserted");
|
||||
break;
|
||||
case Action::replace:
|
||||
Throw<std::logic_error>("RawStateTable::insert: already exists");
|
||||
LogicError("RawStateTable::insert: already exists");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -293,7 +291,7 @@ RawStateTable::replace(std::shared_ptr<SLE> const& sle)
|
||||
switch (item.action)
|
||||
{
|
||||
case Action::erase:
|
||||
Throw<std::logic_error>("RawStateTable::replace: was erased");
|
||||
LogicError("RawStateTable::replace: was erased");
|
||||
break;
|
||||
case Action::insert:
|
||||
case Action::replace:
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
//
|
||||
#include <xrpl/ledger/View.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -378,535 +376,4 @@ adjustFracByTokens(
|
||||
return tokens / lptAMMBalance;
|
||||
}
|
||||
|
||||
std::pair<STAmount, STAmount>
|
||||
ammPoolHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& ammAccountID,
|
||||
Asset const& asset1,
|
||||
Asset const& asset2,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal const j)
|
||||
{
|
||||
auto const assetInBalance =
|
||||
accountHolds(view, ammAccountID, asset1, freezeHandling, authHandling, j);
|
||||
auto const assetOutBalance =
|
||||
accountHolds(view, ammAccountID, asset2, freezeHandling, authHandling, j);
|
||||
return std::make_pair(assetInBalance, assetOutBalance);
|
||||
}
|
||||
|
||||
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
|
||||
ammHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
std::optional<Asset> const& optAsset1,
|
||||
std::optional<Asset> const& optAsset2,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal const j)
|
||||
{
|
||||
auto const assets = [&]() -> std::optional<std::pair<Asset, Asset>> {
|
||||
auto const asset1 = ammSle[sfAsset];
|
||||
auto const asset2 = ammSle[sfAsset2];
|
||||
if (optAsset1 && optAsset2)
|
||||
{
|
||||
if (invalidAMMAssetPair(
|
||||
*optAsset1, *optAsset2, std::make_optional(std::make_pair(asset1, asset2))))
|
||||
{
|
||||
// This error can only be hit if the AMM is corrupted
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.debug()) << "ammHolds: Invalid optAsset1 or optAsset2 " << *optAsset1 << " "
|
||||
<< *optAsset2;
|
||||
return std::nullopt;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return std::make_optional(std::make_pair(*optAsset1, *optAsset2));
|
||||
}
|
||||
auto const singleAsset = [&asset1, &asset2, &j](
|
||||
Asset checkIssue,
|
||||
char const* label) -> std::optional<std::pair<Asset, Asset>> {
|
||||
if (checkIssue == asset1)
|
||||
{
|
||||
return std::make_optional(std::make_pair(asset1, asset2));
|
||||
}
|
||||
if (checkIssue == asset2)
|
||||
{
|
||||
return std::make_optional(std::make_pair(asset2, asset1));
|
||||
}
|
||||
// Unreachable unless AMM corrupted.
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.debug()) << "ammHolds: Invalid " << label << " " << checkIssue;
|
||||
return std::nullopt;
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
if (optAsset1)
|
||||
{
|
||||
return singleAsset(*optAsset1, "optAsset1");
|
||||
}
|
||||
if (optAsset2)
|
||||
{
|
||||
// Cannot have Amount2 without Amount.
|
||||
return singleAsset(*optAsset2, "optAsset2"); // LCOV_EXCL_LINE
|
||||
}
|
||||
return std::make_optional(std::make_pair(asset1, asset2));
|
||||
}();
|
||||
if (!assets)
|
||||
return Unexpected(tecAMM_INVALID_TOKENS);
|
||||
auto const [amount1, amount2] = ammPoolHolds(
|
||||
view,
|
||||
ammSle.getAccountID(sfAccount),
|
||||
assets->first,
|
||||
assets->second,
|
||||
freezeHandling,
|
||||
authHandling,
|
||||
j);
|
||||
return std::make_tuple(amount1, amount2, ammSle[sfLPTokenBalance]);
|
||||
}
|
||||
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
Asset const& asset1,
|
||||
Asset const& asset2,
|
||||
AccountID const& ammAccount,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j)
|
||||
{
|
||||
// This function looks similar to `accountHolds`. However, it only checks if
|
||||
// a LPToken holder has enough balance. On the other hand, `accountHolds`
|
||||
// checks if the underlying assets of LPToken are frozen with the
|
||||
// fixFrozenLPTokenTransfer amendment
|
||||
|
||||
auto const currency = ammLPTCurrency(asset1, asset2);
|
||||
STAmount amount;
|
||||
|
||||
auto const sle = view.read(keylet::line(lpAccount, ammAccount, currency));
|
||||
if (!sle)
|
||||
{
|
||||
amount.clear(Issue{currency, ammAccount});
|
||||
JLOG(j.trace()) << "ammLPHolds: no SLE "
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
else if (isFrozen(view, lpAccount, currency, ammAccount))
|
||||
{
|
||||
amount.clear(Issue{currency, ammAccount});
|
||||
JLOG(j.trace()) << "ammLPHolds: frozen currency "
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = sle->getFieldAmount(sfBalance);
|
||||
if (lpAccount > ammAccount)
|
||||
{
|
||||
// Put balance in account terms.
|
||||
amount.negate();
|
||||
}
|
||||
amount.get<Issue>().account = ammAccount;
|
||||
|
||||
JLOG(j.trace()) << "ammLPHolds:"
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
|
||||
return view.balanceHookIOU(lpAccount, ammAccount, amount);
|
||||
}
|
||||
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j)
|
||||
{
|
||||
return ammLPHolds(view, ammSle[sfAsset], ammSle[sfAsset2], ammSle[sfAccount], lpAccount, j);
|
||||
}
|
||||
|
||||
std::uint16_t
|
||||
getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
XRPL_ASSERT(
|
||||
!view.rules().enabled(fixInnerObjTemplate) || ammSle.isFieldPresent(sfAuctionSlot),
|
||||
"xrpl::getTradingFee : auction present");
|
||||
if (ammSle.isFieldPresent(sfAuctionSlot))
|
||||
{
|
||||
auto const& auctionSlot = safe_downcast<STObject const&>(ammSle.peekAtField(sfAuctionSlot));
|
||||
// Not expired
|
||||
if (auto const expiration = auctionSlot[~sfExpiration];
|
||||
duration_cast<seconds>(view.header().parentCloseTime.time_since_epoch()).count() <
|
||||
expiration)
|
||||
{
|
||||
if (auctionSlot[~sfAccount] == account)
|
||||
return auctionSlot[sfDiscountedFee];
|
||||
if (auctionSlot.isFieldPresent(sfAuthAccounts))
|
||||
{
|
||||
for (auto const& acct : auctionSlot.getFieldArray(sfAuthAccounts))
|
||||
{
|
||||
if (acct[~sfAccount] == account)
|
||||
return auctionSlot[sfDiscountedFee];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ammSle[sfTradingFee];
|
||||
}
|
||||
|
||||
STAmount
|
||||
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const& asset)
|
||||
{
|
||||
// Get the actual AMM balance without factoring in the balance hook
|
||||
return asset.visit(
|
||||
[&](MPTIssue const& issue) {
|
||||
if (auto const sle = view.read(keylet::mptoken(issue, ammAccountID));
|
||||
sle && !isFrozen(view, ammAccountID, issue))
|
||||
return STAmount{issue, (*sle)[sfMPTAmount]};
|
||||
return STAmount{asset};
|
||||
},
|
||||
[&](Issue const& issue) {
|
||||
if (isXRP(issue))
|
||||
{
|
||||
if (auto const sle = view.read(keylet::account(ammAccountID)))
|
||||
return (*sle)[sfBalance];
|
||||
}
|
||||
else if (
|
||||
auto const sle =
|
||||
view.read(keylet::line(ammAccountID, issue.account, issue.currency));
|
||||
sle && !isFrozen(view, ammAccountID, issue.currency, issue.account))
|
||||
{
|
||||
STAmount amount = (*sle)[sfBalance];
|
||||
if (ammAccountID > issue.account)
|
||||
amount.negate();
|
||||
amount.get<Issue>().account = issue.account;
|
||||
return amount;
|
||||
}
|
||||
return STAmount{asset};
|
||||
});
|
||||
}
|
||||
|
||||
static TER
|
||||
deleteAMMTrustLines(
|
||||
Sandbox& sb,
|
||||
AccountID const& ammAccountID,
|
||||
std::uint16_t maxTrustlinesToDelete,
|
||||
beast::Journal j)
|
||||
{
|
||||
return cleanupOnAccountDelete(
|
||||
sb,
|
||||
keylet::ownerDir(ammAccountID),
|
||||
[&](LedgerEntryType nodeType,
|
||||
uint256 const&,
|
||||
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
|
||||
// Skip AMM and MPToken
|
||||
if (nodeType == ltAMM || nodeType == ltMPTOKEN)
|
||||
return {tesSUCCESS, SkipEntry::Yes};
|
||||
|
||||
if (nodeType == ltRIPPLE_STATE)
|
||||
{
|
||||
// Trustlines must have zero balance
|
||||
if (sleItem->getFieldAmount(sfBalance) != beast::zero)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting trustline with "
|
||||
"non-zero balance.";
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return {deleteAMMTrustLine(sb, sleItem, ammAccountID, j), SkipEntry::No};
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType;
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
},
|
||||
j,
|
||||
maxTrustlinesToDelete);
|
||||
}
|
||||
|
||||
static TER
|
||||
deleteAMMMPTokens(Sandbox& sb, AccountID const& ammAccountID, beast::Journal j)
|
||||
{
|
||||
return cleanupOnAccountDelete(
|
||||
sb,
|
||||
keylet::ownerDir(ammAccountID),
|
||||
[&](LedgerEntryType nodeType,
|
||||
uint256 const&,
|
||||
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
|
||||
// Skip AMM
|
||||
if (nodeType == ltAMM)
|
||||
return {tesSUCCESS, SkipEntry::Yes};
|
||||
|
||||
if (nodeType == ltMPTOKEN)
|
||||
{
|
||||
// MPT must have zero balance
|
||||
if (sleItem->getFieldU64(sfMPTAmount) != 0 ||
|
||||
(*sleItem)[~sfLockedAmount].value_or(0) != 0)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting MPT with "
|
||||
"non-zero balance.";
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return {deleteAMMMPToken(sb, sleItem, ammAccountID, j), SkipEntry::No};
|
||||
}
|
||||
if (nodeType == ltRIPPLE_STATE)
|
||||
{
|
||||
// Trustlines should have been deleted
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: trustlines should have been deleted";
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType;
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
},
|
||||
j,
|
||||
3); // At most two MPToken plus AMM object
|
||||
}
|
||||
|
||||
TER
|
||||
deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Journal j)
|
||||
{
|
||||
auto ammSle = sb.peek(keylet::amm(asset, asset2));
|
||||
if (!ammSle)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: AMM object does not exist " << asset << " " << asset2;
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const ammAccountID = (*ammSle)[sfAccount];
|
||||
auto sleAMMRoot = sb.peek(keylet::account(ammAccountID));
|
||||
if (!sleAMMRoot)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist "
|
||||
<< to_string(ammAccountID);
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (auto const ter = deleteAMMTrustLines(sb, ammAccountID, maxDeletableAMMTrustLines, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Delete AMM's MPTokens only if all trustlines are deleted. If trustlines
|
||||
// are not deleted then AMM can be re-created with Deposit and
|
||||
// AMM's MPToken(s) must exist.
|
||||
if (auto const ter = deleteAMMMPTokens(sb, ammAccountID, j); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
auto const ownerDirKeylet = keylet::ownerDir(ammAccountID);
|
||||
if (!sb.dirRemove(ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(), false))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: failed to remove dir link";
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
if (sb.exists(ownerDirKeylet) && !sb.emptyDirDelete(ownerDirKeylet))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: cannot delete root dir node of "
|
||||
<< toBase58(ammAccountID);
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
sb.erase(ammSle);
|
||||
sb.erase(sleAMMRoot);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
initializeFeeAuctionVote(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account,
|
||||
Asset const& lptAsset,
|
||||
std::uint16_t tfee)
|
||||
{
|
||||
auto const& rules = view.rules();
|
||||
// AMM creator gets the voting slot.
|
||||
STArray voteSlots;
|
||||
STObject voteEntry = STObject::makeInnerObject(sfVoteEntry);
|
||||
if (tfee != 0)
|
||||
voteEntry.setFieldU16(sfTradingFee, tfee);
|
||||
voteEntry.setFieldU32(sfVoteWeight, VOTE_WEIGHT_SCALE_FACTOR);
|
||||
voteEntry.setAccountID(sfAccount, account);
|
||||
voteSlots.push_back(voteEntry);
|
||||
ammSle->setFieldArray(sfVoteSlots, voteSlots);
|
||||
// AMM creator gets the auction slot for free.
|
||||
// AuctionSlot is created on AMMCreate and updated on AMMDeposit
|
||||
// when AMM is in an empty state
|
||||
if (rules.enabled(fixInnerObjTemplate) && !ammSle->isFieldPresent(sfAuctionSlot))
|
||||
{
|
||||
STObject auctionSlot = STObject::makeInnerObject(sfAuctionSlot);
|
||||
ammSle->set(std::move(auctionSlot));
|
||||
}
|
||||
STObject& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
|
||||
auctionSlot.setAccountID(sfAccount, account);
|
||||
// current + sec in 24h
|
||||
auto const expiration = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
view.header().parentCloseTime.time_since_epoch())
|
||||
.count() +
|
||||
TOTAL_TIME_SLOT_SECS;
|
||||
auctionSlot.setFieldU32(sfExpiration, expiration);
|
||||
auctionSlot.setFieldAmount(sfPrice, STAmount{lptAsset, 0});
|
||||
// Set the fee
|
||||
if (tfee != 0)
|
||||
{
|
||||
ammSle->setFieldU16(sfTradingFee, tfee);
|
||||
}
|
||||
else if (ammSle->isFieldPresent(sfTradingFee))
|
||||
{
|
||||
ammSle->makeFieldAbsent(sfTradingFee); // LCOV_EXCL_LINE
|
||||
}
|
||||
if (auto const dfee = tfee / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION)
|
||||
{
|
||||
auctionSlot.setFieldU16(sfDiscountedFee, dfee);
|
||||
}
|
||||
else if (auctionSlot.isFieldPresent(sfDiscountedFee))
|
||||
{
|
||||
auctionSlot.makeFieldAbsent(sfDiscountedFee); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
Expected<bool, TER>
|
||||
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount)
|
||||
{
|
||||
// Liquidity Provider (LP) must have one LPToken trustline
|
||||
std::uint8_t nLPTokenTrustLines = 0;
|
||||
// AMM account has at most two IOU (pool tokens, not LPToken) trustlines.
|
||||
// One or both trustlines could be to the LP if LP is the issuer,
|
||||
// or a different account if LP is not an issuer. For instance,
|
||||
// if AMM has two tokens USD and EUR and LP is not the issuer of the tokens
|
||||
// then the trustlines are between AMM account and the issuer.
|
||||
// There is one LPToken trustline for each LP. Only remaining LP has
|
||||
// exactly one LPToken trustlines and at most two IOU trustline for each
|
||||
// pool token. One or both tokens could be MPT.
|
||||
std::uint8_t nIOUTrustLines = 0;
|
||||
// There are at most two MPT objects, one for each side of the pool.
|
||||
std::uint8_t nMPT = 0;
|
||||
// There is only one AMM object
|
||||
bool hasAMM = false;
|
||||
// AMM LP has at most three trustlines, at most two MPTs, and only one
|
||||
// AMM object must exist. If there are more than four objects then
|
||||
// it's either an error or there are more than one LP. Ten pages should
|
||||
// be sufficient to include four objects.
|
||||
std::uint8_t limit = 10;
|
||||
auto const root = keylet::ownerDir(ammIssue.account);
|
||||
auto currentIndex = root;
|
||||
|
||||
// Iterate over AMM owner directory objects.
|
||||
while (limit-- >= 1)
|
||||
{
|
||||
auto const ownerDir = view.read(currentIndex);
|
||||
if (!ownerDir)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
for (auto const& key : ownerDir->getFieldV256(sfIndexes))
|
||||
{
|
||||
auto const sle = view.read(keylet::child(key));
|
||||
if (!sle)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
auto const entryType = sle->getFieldU16(sfLedgerEntryType);
|
||||
// Only one AMM object
|
||||
if (entryType == ltAMM)
|
||||
{
|
||||
if (hasAMM)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
hasAMM = true;
|
||||
continue;
|
||||
}
|
||||
if (entryType == ltMPTOKEN)
|
||||
{
|
||||
++nMPT;
|
||||
continue;
|
||||
}
|
||||
if (entryType != ltRIPPLE_STATE)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
auto const lowLimit = sle->getFieldAmount(sfLowLimit);
|
||||
auto const highLimit = sle->getFieldAmount(sfHighLimit);
|
||||
auto const isLPTrustline =
|
||||
lowLimit.getIssuer() == lpAccount || highLimit.getIssuer() == lpAccount;
|
||||
auto const isLPTokenTrustline =
|
||||
lowLimit.asset() == ammIssue || highLimit.asset() == ammIssue;
|
||||
|
||||
// Liquidity Provider trustline
|
||||
if (isLPTrustline)
|
||||
{
|
||||
// LPToken trustline
|
||||
if (isLPTokenTrustline)
|
||||
{
|
||||
// LP has exactly one LPToken trustline
|
||||
if (++nLPTokenTrustLines > 1)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
// AMM account has at most two IOU trustlines
|
||||
else if (++nIOUTrustLines > 2)
|
||||
{
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
// Another Liquidity Provider LPToken trustline
|
||||
else if (isLPTokenTrustline)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// AMM account has at most two IOU trustlines
|
||||
else if (++nIOUTrustLines > 2)
|
||||
{
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
|
||||
if (uNodeNext == 0)
|
||||
{
|
||||
if (nLPTokenTrustLines != 1 || (nIOUTrustLines == 0 && nMPT == 0) ||
|
||||
(nIOUTrustLines > 2 || nMPT > 2) || (nIOUTrustLines + nMPT) > 2)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
return true;
|
||||
}
|
||||
currentIndex = keylet::page(root, uNodeNext);
|
||||
}
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
Expected<bool, TER>
|
||||
verifyAndAdjustLPTokenBalance(
|
||||
Sandbox& sb,
|
||||
STAmount const& lpTokens,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account)
|
||||
{
|
||||
auto const res = isOnlyLiquidityProvider(sb, lpTokens.get<Issue>(), account);
|
||||
if (!res.has_value())
|
||||
{
|
||||
return Unexpected<TER>(res.error());
|
||||
}
|
||||
|
||||
if (res.value())
|
||||
{
|
||||
if (withinRelativeDistance(
|
||||
lpTokens, ammSle->getFieldAmount(sfLPTokenBalance), Number{1, -3}))
|
||||
{
|
||||
ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
|
||||
sb.update(ammSle);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Unexpected<TER>(tecAMM_INVALID_TOKENS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
542
src/libxrpl/ledger/helpers/AMMUtils.cpp
Normal file
542
src/libxrpl/ledger/helpers/AMMUtils.cpp
Normal file
@@ -0,0 +1,542 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::pair<STAmount, STAmount>
|
||||
ammPoolHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& ammAccountID,
|
||||
Asset const& asset1,
|
||||
Asset const& asset2,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal const j)
|
||||
{
|
||||
auto const assetInBalance =
|
||||
accountHolds(view, ammAccountID, asset1, freezeHandling, authHandling, j);
|
||||
auto const assetOutBalance =
|
||||
accountHolds(view, ammAccountID, asset2, freezeHandling, authHandling, j);
|
||||
return std::make_pair(assetInBalance, assetOutBalance);
|
||||
}
|
||||
|
||||
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
|
||||
ammHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
std::optional<Asset> const& optAsset1,
|
||||
std::optional<Asset> const& optAsset2,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal const j)
|
||||
{
|
||||
auto const assets = [&]() -> std::optional<std::pair<Asset, Asset>> {
|
||||
auto const asset1 = ammSle[sfAsset];
|
||||
auto const asset2 = ammSle[sfAsset2];
|
||||
if (optAsset1 && optAsset2)
|
||||
{
|
||||
if (invalidAMMAssetPair(
|
||||
*optAsset1, *optAsset2, std::make_optional(std::make_pair(asset1, asset2))))
|
||||
{
|
||||
// This error can only be hit if the AMM is corrupted
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.debug()) << "ammHolds: Invalid optAsset1 or optAsset2 " << *optAsset1 << " "
|
||||
<< *optAsset2;
|
||||
return std::nullopt;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return std::make_optional(std::make_pair(*optAsset1, *optAsset2));
|
||||
}
|
||||
auto const singleAsset = [&asset1, &asset2, &j](
|
||||
Asset checkIssue,
|
||||
char const* label) -> std::optional<std::pair<Asset, Asset>> {
|
||||
if (checkIssue == asset1)
|
||||
{
|
||||
return std::make_optional(std::make_pair(asset1, asset2));
|
||||
}
|
||||
if (checkIssue == asset2)
|
||||
{
|
||||
return std::make_optional(std::make_pair(asset2, asset1));
|
||||
}
|
||||
// Unreachable unless AMM corrupted.
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.debug()) << "ammHolds: Invalid " << label << " " << checkIssue;
|
||||
return std::nullopt;
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
if (optAsset1)
|
||||
{
|
||||
return singleAsset(*optAsset1, "optAsset1");
|
||||
}
|
||||
if (optAsset2)
|
||||
{
|
||||
// Cannot have Amount2 without Amount.
|
||||
return singleAsset(*optAsset2, "optAsset2"); // LCOV_EXCL_LINE
|
||||
}
|
||||
return std::make_optional(std::make_pair(asset1, asset2));
|
||||
}();
|
||||
if (!assets)
|
||||
return Unexpected(tecAMM_INVALID_TOKENS);
|
||||
auto const [amount1, amount2] = ammPoolHolds(
|
||||
view,
|
||||
ammSle.getAccountID(sfAccount),
|
||||
assets->first,
|
||||
assets->second,
|
||||
freezeHandling,
|
||||
authHandling,
|
||||
j);
|
||||
return std::make_tuple(amount1, amount2, ammSle[sfLPTokenBalance]);
|
||||
}
|
||||
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
Asset const& asset1,
|
||||
Asset const& asset2,
|
||||
AccountID const& ammAccount,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j)
|
||||
{
|
||||
// This function looks similar to `accountHolds`. However, it only checks if
|
||||
// a LPToken holder has enough balance. On the other hand, `accountHolds`
|
||||
// checks if the underlying assets of LPToken are frozen with the
|
||||
// fixFrozenLPTokenTransfer amendment
|
||||
|
||||
auto const currency = ammLPTCurrency(asset1, asset2);
|
||||
STAmount amount;
|
||||
|
||||
auto const sle = view.read(keylet::line(lpAccount, ammAccount, currency));
|
||||
if (!sle)
|
||||
{
|
||||
amount.clear(Issue{currency, ammAccount});
|
||||
JLOG(j.trace()) << "ammLPHolds: no SLE "
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
else if (isFrozen(view, lpAccount, currency, ammAccount))
|
||||
{
|
||||
amount.clear(Issue{currency, ammAccount});
|
||||
JLOG(j.trace()) << "ammLPHolds: frozen currency "
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = sle->getFieldAmount(sfBalance);
|
||||
if (lpAccount > ammAccount)
|
||||
{
|
||||
// Put balance in account terms.
|
||||
amount.negate();
|
||||
}
|
||||
amount.get<Issue>().account = ammAccount;
|
||||
|
||||
JLOG(j.trace()) << "ammLPHolds:"
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
|
||||
return view.balanceHookIOU(lpAccount, ammAccount, amount);
|
||||
}
|
||||
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j)
|
||||
{
|
||||
return ammLPHolds(view, ammSle[sfAsset], ammSle[sfAsset2], ammSle[sfAccount], lpAccount, j);
|
||||
}
|
||||
|
||||
std::uint16_t
|
||||
getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
XRPL_ASSERT(
|
||||
!view.rules().enabled(fixInnerObjTemplate) || ammSle.isFieldPresent(sfAuctionSlot),
|
||||
"xrpl::getTradingFee : auction present");
|
||||
if (ammSle.isFieldPresent(sfAuctionSlot))
|
||||
{
|
||||
auto const& auctionSlot = safe_downcast<STObject const&>(ammSle.peekAtField(sfAuctionSlot));
|
||||
// Not expired
|
||||
if (auto const expiration = auctionSlot[~sfExpiration];
|
||||
duration_cast<seconds>(view.header().parentCloseTime.time_since_epoch()).count() <
|
||||
expiration)
|
||||
{
|
||||
if (auctionSlot[~sfAccount] == account)
|
||||
return auctionSlot[sfDiscountedFee];
|
||||
if (auctionSlot.isFieldPresent(sfAuthAccounts))
|
||||
{
|
||||
for (auto const& acct : auctionSlot.getFieldArray(sfAuthAccounts))
|
||||
{
|
||||
if (acct[~sfAccount] == account)
|
||||
return auctionSlot[sfDiscountedFee];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ammSle[sfTradingFee];
|
||||
}
|
||||
|
||||
STAmount
|
||||
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const& asset)
|
||||
{
|
||||
// Get the actual AMM balance without factoring in the balance hook
|
||||
return asset.visit(
|
||||
[&](MPTIssue const& issue) {
|
||||
if (auto const sle = view.read(keylet::mptoken(issue, ammAccountID));
|
||||
sle && !isFrozen(view, ammAccountID, issue))
|
||||
return STAmount{issue, (*sle)[sfMPTAmount]};
|
||||
return STAmount{asset};
|
||||
},
|
||||
[&](Issue const& issue) {
|
||||
if (isXRP(issue))
|
||||
{
|
||||
if (auto const sle = view.read(keylet::account(ammAccountID)))
|
||||
return (*sle)[sfBalance];
|
||||
}
|
||||
else if (
|
||||
auto const sle =
|
||||
view.read(keylet::line(ammAccountID, issue.account, issue.currency));
|
||||
sle && !isFrozen(view, ammAccountID, issue.currency, issue.account))
|
||||
{
|
||||
STAmount amount = (*sle)[sfBalance];
|
||||
if (ammAccountID > issue.account)
|
||||
amount.negate();
|
||||
amount.get<Issue>().account = issue.account;
|
||||
return amount;
|
||||
}
|
||||
return STAmount{asset};
|
||||
});
|
||||
}
|
||||
|
||||
static TER
|
||||
deleteAMMTrustLines(
|
||||
Sandbox& sb,
|
||||
AccountID const& ammAccountID,
|
||||
std::uint16_t maxTrustlinesToDelete,
|
||||
beast::Journal j)
|
||||
{
|
||||
return cleanupOnAccountDelete(
|
||||
sb,
|
||||
keylet::ownerDir(ammAccountID),
|
||||
[&](LedgerEntryType nodeType,
|
||||
uint256 const&,
|
||||
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
|
||||
// Skip AMM and MPToken
|
||||
if (nodeType == ltAMM || nodeType == ltMPTOKEN)
|
||||
return {tesSUCCESS, SkipEntry::Yes};
|
||||
|
||||
if (nodeType == ltRIPPLE_STATE)
|
||||
{
|
||||
// Trustlines must have zero balance
|
||||
if (sleItem->getFieldAmount(sfBalance) != beast::zero)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting trustline with "
|
||||
"non-zero balance.";
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return {deleteAMMTrustLine(sb, sleItem, ammAccountID, j), SkipEntry::No};
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType;
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
},
|
||||
j,
|
||||
maxTrustlinesToDelete);
|
||||
}
|
||||
|
||||
static TER
|
||||
deleteAMMMPTokens(Sandbox& sb, AccountID const& ammAccountID, beast::Journal j)
|
||||
{
|
||||
return cleanupOnAccountDelete(
|
||||
sb,
|
||||
keylet::ownerDir(ammAccountID),
|
||||
[&](LedgerEntryType nodeType,
|
||||
uint256 const&,
|
||||
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
|
||||
// Skip AMM
|
||||
if (nodeType == ltAMM)
|
||||
return {tesSUCCESS, SkipEntry::Yes};
|
||||
|
||||
if (nodeType == ltMPTOKEN)
|
||||
{
|
||||
// MPT must have zero balance
|
||||
if (sleItem->getFieldU64(sfMPTAmount) != 0 ||
|
||||
(*sleItem)[~sfLockedAmount].value_or(0) != 0)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting MPT with "
|
||||
"non-zero balance.";
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return {deleteAMMMPToken(sb, sleItem, ammAccountID, j), SkipEntry::No};
|
||||
}
|
||||
if (nodeType == ltRIPPLE_STATE)
|
||||
{
|
||||
// Trustlines should have been deleted
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: trustlines should have been deleted";
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType;
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
},
|
||||
j,
|
||||
3); // At most two MPToken plus AMM object
|
||||
}
|
||||
|
||||
TER
|
||||
deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Journal j)
|
||||
{
|
||||
auto ammSle = sb.peek(keylet::amm(asset, asset2));
|
||||
if (!ammSle)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: AMM object does not exist " << asset << " " << asset2;
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const ammAccountID = (*ammSle)[sfAccount];
|
||||
auto sleAMMRoot = sb.peek(keylet::account(ammAccountID));
|
||||
if (!sleAMMRoot)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist "
|
||||
<< to_string(ammAccountID);
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (auto const ter = deleteAMMTrustLines(sb, ammAccountID, maxDeletableAMMTrustLines, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Delete AMM's MPTokens only if all trustlines are deleted. If trustlines
|
||||
// are not deleted then AMM can be re-created with Deposit and
|
||||
// AMM's MPToken(s) must exist.
|
||||
if (auto const ter = deleteAMMMPTokens(sb, ammAccountID, j); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
auto const ownerDirKeylet = keylet::ownerDir(ammAccountID);
|
||||
if (!sb.dirRemove(ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(), false))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: failed to remove dir link";
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
if (sb.exists(ownerDirKeylet) && !sb.emptyDirDelete(ownerDirKeylet))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: cannot delete root dir node of "
|
||||
<< toBase58(ammAccountID);
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
sb.erase(ammSle);
|
||||
sb.erase(sleAMMRoot);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
initializeFeeAuctionVote(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account,
|
||||
Asset const& lptAsset,
|
||||
std::uint16_t tfee)
|
||||
{
|
||||
auto const& rules = view.rules();
|
||||
// AMM creator gets the voting slot.
|
||||
STArray voteSlots;
|
||||
STObject voteEntry = STObject::makeInnerObject(sfVoteEntry);
|
||||
if (tfee != 0)
|
||||
voteEntry.setFieldU16(sfTradingFee, tfee);
|
||||
voteEntry.setFieldU32(sfVoteWeight, VOTE_WEIGHT_SCALE_FACTOR);
|
||||
voteEntry.setAccountID(sfAccount, account);
|
||||
voteSlots.push_back(voteEntry);
|
||||
ammSle->setFieldArray(sfVoteSlots, voteSlots);
|
||||
// AMM creator gets the auction slot for free.
|
||||
// AuctionSlot is created on AMMCreate and updated on AMMDeposit
|
||||
// when AMM is in an empty state
|
||||
if (rules.enabled(fixInnerObjTemplate) && !ammSle->isFieldPresent(sfAuctionSlot))
|
||||
{
|
||||
STObject auctionSlot = STObject::makeInnerObject(sfAuctionSlot);
|
||||
ammSle->set(std::move(auctionSlot));
|
||||
}
|
||||
STObject& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
|
||||
auctionSlot.setAccountID(sfAccount, account);
|
||||
// current + sec in 24h
|
||||
auto const expiration = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
view.header().parentCloseTime.time_since_epoch())
|
||||
.count() +
|
||||
TOTAL_TIME_SLOT_SECS;
|
||||
auctionSlot.setFieldU32(sfExpiration, expiration);
|
||||
auctionSlot.setFieldAmount(sfPrice, STAmount{lptAsset, 0});
|
||||
// Set the fee
|
||||
if (tfee != 0)
|
||||
{
|
||||
ammSle->setFieldU16(sfTradingFee, tfee);
|
||||
}
|
||||
else if (ammSle->isFieldPresent(sfTradingFee))
|
||||
{
|
||||
ammSle->makeFieldAbsent(sfTradingFee); // LCOV_EXCL_LINE
|
||||
}
|
||||
if (auto const dfee = tfee / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION)
|
||||
{
|
||||
auctionSlot.setFieldU16(sfDiscountedFee, dfee);
|
||||
}
|
||||
else if (auctionSlot.isFieldPresent(sfDiscountedFee))
|
||||
{
|
||||
auctionSlot.makeFieldAbsent(sfDiscountedFee); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
Expected<bool, TER>
|
||||
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount)
|
||||
{
|
||||
// Liquidity Provider (LP) must have one LPToken trustline
|
||||
std::uint8_t nLPTokenTrustLines = 0;
|
||||
// AMM account has at most two IOU (pool tokens, not LPToken) trustlines.
|
||||
// One or both trustlines could be to the LP if LP is the issuer,
|
||||
// or a different account if LP is not an issuer. For instance,
|
||||
// if AMM has two tokens USD and EUR and LP is not the issuer of the tokens
|
||||
// then the trustlines are between AMM account and the issuer.
|
||||
// There is one LPToken trustline for each LP. Only remaining LP has
|
||||
// exactly one LPToken trustlines and at most two IOU trustline for each
|
||||
// pool token. One or both tokens could be MPT.
|
||||
std::uint8_t nIOUTrustLines = 0;
|
||||
// There are at most two MPT objects, one for each side of the pool.
|
||||
std::uint8_t nMPT = 0;
|
||||
// There is only one AMM object
|
||||
bool hasAMM = false;
|
||||
// AMM LP has at most three trustlines, at most two MPTs, and only one
|
||||
// AMM object must exist. If there are more than four objects then
|
||||
// it's either an error or there are more than one LP. Ten pages should
|
||||
// be sufficient to include four objects.
|
||||
std::uint8_t limit = 10;
|
||||
auto const root = keylet::ownerDir(ammIssue.account);
|
||||
auto currentIndex = root;
|
||||
|
||||
// Iterate over AMM owner directory objects.
|
||||
while (limit-- >= 1)
|
||||
{
|
||||
auto const ownerDir = view.read(currentIndex);
|
||||
if (!ownerDir)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
for (auto const& key : ownerDir->getFieldV256(sfIndexes))
|
||||
{
|
||||
auto const sle = view.read(keylet::child(key));
|
||||
if (!sle)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
auto const entryType = sle->getFieldU16(sfLedgerEntryType);
|
||||
// Only one AMM object
|
||||
if (entryType == ltAMM)
|
||||
{
|
||||
if (hasAMM)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
hasAMM = true;
|
||||
continue;
|
||||
}
|
||||
if (entryType == ltMPTOKEN)
|
||||
{
|
||||
++nMPT;
|
||||
continue;
|
||||
}
|
||||
if (entryType != ltRIPPLE_STATE)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
auto const lowLimit = sle->getFieldAmount(sfLowLimit);
|
||||
auto const highLimit = sle->getFieldAmount(sfHighLimit);
|
||||
auto const isLPTrustline =
|
||||
lowLimit.getIssuer() == lpAccount || highLimit.getIssuer() == lpAccount;
|
||||
auto const isLPTokenTrustline =
|
||||
lowLimit.asset() == ammIssue || highLimit.asset() == ammIssue;
|
||||
|
||||
// Liquidity Provider trustline
|
||||
if (isLPTrustline)
|
||||
{
|
||||
// LPToken trustline
|
||||
if (isLPTokenTrustline)
|
||||
{
|
||||
// LP has exactly one LPToken trustline
|
||||
if (++nLPTokenTrustLines > 1)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
// AMM account has at most two IOU trustlines
|
||||
else if (++nIOUTrustLines > 2)
|
||||
{
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
// Another Liquidity Provider LPToken trustline
|
||||
else if (isLPTokenTrustline)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// AMM account has at most two IOU trustlines
|
||||
else if (++nIOUTrustLines > 2)
|
||||
{
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
|
||||
if (uNodeNext == 0)
|
||||
{
|
||||
if (nLPTokenTrustLines != 1 || (nIOUTrustLines == 0 && nMPT == 0) ||
|
||||
(nIOUTrustLines > 2 || nMPT > 2) || (nIOUTrustLines + nMPT) > 2)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
return true;
|
||||
}
|
||||
currentIndex = keylet::page(root, uNodeNext);
|
||||
}
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
Expected<bool, TER>
|
||||
verifyAndAdjustLPTokenBalance(
|
||||
Sandbox& sb,
|
||||
STAmount const& lpTokens,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account)
|
||||
{
|
||||
auto const res = isOnlyLiquidityProvider(sb, lpTokens.get<Issue>(), account);
|
||||
if (!res.has_value())
|
||||
{
|
||||
return Unexpected<TER>(res.error());
|
||||
}
|
||||
|
||||
if (res.value())
|
||||
{
|
||||
if (withinRelativeDistance(
|
||||
lpTokens, ammSle->getFieldAmount(sfLPTokenBalance), Number{1, -3}))
|
||||
{
|
||||
ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
|
||||
sb.update(ammSle);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Unexpected<TER>(tecAMM_INVALID_TOKENS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -154,7 +153,7 @@ getPseudoAccountFields()
|
||||
if (!ar)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
Throw<std::logic_error>(
|
||||
LogicError(
|
||||
"xrpl::getPseudoAccountFields : unable to find account root "
|
||||
"ledger format");
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
@@ -495,22 +495,6 @@ canTrade(ReadView const& view, Asset const& asset)
|
||||
});
|
||||
}
|
||||
|
||||
TER
|
||||
canMPTTradeAndTransfer(
|
||||
ReadView const& view,
|
||||
Asset const& asset,
|
||||
AccountID const& from,
|
||||
AccountID const& to)
|
||||
{
|
||||
if (!asset.holds<MPTIssue>())
|
||||
return tesSUCCESS;
|
||||
|
||||
if (auto const ter = canTrade(view, asset); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return canTransfer(view, asset, from, to);
|
||||
}
|
||||
|
||||
TER
|
||||
lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount, beast::Journal j)
|
||||
{
|
||||
@@ -878,4 +862,65 @@ issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amo
|
||||
view.issuerSelfDebitHookMPT(issue, amount, available);
|
||||
}
|
||||
|
||||
static TER
|
||||
checkMPTAllowed(ReadView const& view, TxType txType, Asset const& asset, AccountID const& accountID)
|
||||
{
|
||||
if (!asset.holds<MPTIssue>())
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const& issuanceID = asset.get<MPTIssue>().getMptID();
|
||||
auto const validTx = txType == ttAMM_CREATE || txType == ttAMM_DEPOSIT ||
|
||||
txType == ttAMM_WITHDRAW || txType == ttOFFER_CREATE || txType == ttCHECK_CREATE ||
|
||||
txType == ttCHECK_CASH || txType == ttPAYMENT;
|
||||
XRPL_ASSERT(validTx, "xrpl::checkMPTAllowed : all MPT tx or DEX");
|
||||
if (!validTx)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& issuer = asset.getIssuer();
|
||||
if (!view.exists(keylet::account(issuer)))
|
||||
return tecNO_ISSUER; // LCOV_EXCL_LINE
|
||||
|
||||
auto const issuanceKey = keylet::mptIssuance(issuanceID);
|
||||
auto const issuanceSle = view.read(issuanceKey);
|
||||
if (!issuanceSle)
|
||||
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
|
||||
|
||||
auto const flags = issuanceSle->getFlags();
|
||||
|
||||
if ((flags & lsfMPTLocked) != 0u)
|
||||
return tecLOCKED; // LCOV_EXCL_LINE
|
||||
// Offer crossing and Payment
|
||||
if ((flags & lsfMPTCanTrade) == 0)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (accountID != issuer)
|
||||
{
|
||||
if ((flags & lsfMPTCanTransfer) == 0)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const mptSle = view.read(keylet::mptoken(issuanceKey.key, accountID));
|
||||
// Allow to succeed since some tx create MPToken if it doesn't exist.
|
||||
// Tx's have their own check for missing MPToken.
|
||||
if (!mptSle)
|
||||
return tesSUCCESS;
|
||||
|
||||
if (mptSle->isFlag(lsfMPTLocked))
|
||||
return tecLOCKED;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
checkMPTTxAllowed(
|
||||
ReadView const& view,
|
||||
TxType txType,
|
||||
Asset const& asset,
|
||||
AccountID const& accountID)
|
||||
{
|
||||
// use isDEXAllowed for payment/offer crossing
|
||||
XRPL_ASSERT(txType != ttPAYMENT, "xrpl::checkMPTTxAllowed : not payment");
|
||||
return checkMPTAllowed(view, txType, asset, accountID);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -607,6 +607,33 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t
|
||||
return deletedOffersCount;
|
||||
}
|
||||
|
||||
TER
|
||||
notTooManyOffers(ReadView const& view, uint256 const& nftokenID)
|
||||
{
|
||||
std::size_t totalOffers = 0;
|
||||
|
||||
{
|
||||
Dir const buys(view, keylet::nft_buys(nftokenID));
|
||||
for (auto iter = buys.begin(); iter != buys.end(); iter.next_page())
|
||||
{
|
||||
totalOffers += iter.page_size();
|
||||
if (totalOffers > maxDeletableTokenOfferEntries)
|
||||
return tefTOO_BIG;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Dir const sells(view, keylet::nft_sells(nftokenID));
|
||||
for (auto iter = sells.begin(); iter != sells.end(); iter.next_page())
|
||||
{
|
||||
totalOffers += iter.page_size();
|
||||
if (totalOffers > maxDeletableTokenOfferEntries)
|
||||
return tefTOO_BIG;
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
deleteTokenOffer(ApplyView& view, std::shared_ptr<SLE> const& offer)
|
||||
{
|
||||
|
||||
@@ -40,14 +40,6 @@ isGlobalFrozen(ReadView const& view, Asset const& asset)
|
||||
[&](MPTIssue const& issue) { return isGlobalFrozen(view, issue); });
|
||||
}
|
||||
|
||||
TER
|
||||
checkGlobalFrozen(ReadView const& view, Asset const& asset)
|
||||
{
|
||||
if (isGlobalFrozen(view, asset))
|
||||
return asset.holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
{
|
||||
@@ -55,14 +47,6 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const&
|
||||
[&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value());
|
||||
}
|
||||
|
||||
TER
|
||||
checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
{
|
||||
if (isIndividualFrozen(view, account, asset))
|
||||
return asset.holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
|
||||
{
|
||||
|
||||
@@ -67,11 +67,6 @@ Permission::Permission()
|
||||
#pragma pop_macro("PERMISSION")
|
||||
};
|
||||
|
||||
XRPL_ASSERT(
|
||||
txFeatureMap_.size() == delegableTx_.size(),
|
||||
"xrpl::Permission : txFeatureMap_ and delegableTx_ must have same "
|
||||
"size");
|
||||
|
||||
for ([[maybe_unused]] auto const& permission : granularPermissionMap_)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
|
||||
@@ -58,6 +58,10 @@ STValidation::validationFormat()
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// featureSmartEscrow
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -199,6 +199,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."),
|
||||
@@ -215,7 +216,6 @@ transResults()
|
||||
MAKE_ERROR(terNO_AMM, "AMM doesn't exist for the asset pair."),
|
||||
MAKE_ERROR(terADDRESS_COLLISION, "Failed to allocate an unique account address."),
|
||||
MAKE_ERROR(terNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
|
||||
MAKE_ERROR(terLOCKED, "Fund is locked."),
|
||||
|
||||
MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."),
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -369,141 +369,4 @@ ValidMPTPayment::finalize(
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ValidMPTTransfer::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
// Record the before/after MPTAmount for each (issuanceID, account) pair
|
||||
// so finalize() can determine whether a transfer actually occurred.
|
||||
auto update = [&](SLE const& sle, bool isBefore) {
|
||||
if (sle.getType() == ltMPTOKEN)
|
||||
{
|
||||
auto const issuanceID = sle[sfMPTokenIssuanceID];
|
||||
auto const account = sle[sfAccount];
|
||||
auto const amount = sle[sfMPTAmount];
|
||||
if (isBefore)
|
||||
{
|
||||
amount_[issuanceID][account].amtBefore = amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
amount_[issuanceID][account].amtAfter = amount;
|
||||
}
|
||||
if (isDelete && isBefore)
|
||||
{
|
||||
deletedAuthorized_[sle.key()] = sle.isFlag(lsfMPTAuthorized);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (before)
|
||||
update(*before, true);
|
||||
|
||||
if (after)
|
||||
update(*after, false);
|
||||
}
|
||||
|
||||
bool
|
||||
ValidMPTTransfer::isAuthorized(
|
||||
ReadView const& view,
|
||||
MPTID const& mptid,
|
||||
AccountID const& holder,
|
||||
bool reqAuth) const
|
||||
{
|
||||
auto const key = keylet::mptoken(mptid, holder);
|
||||
auto const it = deletedAuthorized_.find(key.key);
|
||||
if (it != deletedAuthorized_.end())
|
||||
return !reqAuth || it->second;
|
||||
return isTesSuccess(requireAuth(view, MPTIssue{mptid}, holder));
|
||||
}
|
||||
|
||||
bool
|
||||
ValidMPTTransfer::finalize(
|
||||
STTx const& tx,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (hasPrivilege(tx, overrideFreeze))
|
||||
return true;
|
||||
|
||||
// DEX transactions (AMM[Create,Deposit,Withdraw], cross-currency payments, offer creates) are
|
||||
// subject to the MPTCanTrade flag in addition to the standard transfer rules.
|
||||
// A payment is only DEX if it is a cross-currency payment.
|
||||
auto const isDEX = [&tx] {
|
||||
auto const txnType = tx.getTxnType();
|
||||
if (txnType == ttPAYMENT)
|
||||
{
|
||||
// A payment is cross-currency (and thus DEX) only if SendMax is present
|
||||
// and its asset differs from the destination asset.
|
||||
auto const amount = tx[sfAmount];
|
||||
return tx[~sfSendMax].value_or(amount).asset() != amount.asset();
|
||||
}
|
||||
return txnType == ttAMM_CREATE || txnType == ttAMM_DEPOSIT || txnType == ttAMM_WITHDRAW ||
|
||||
txnType == ttOFFER_CREATE;
|
||||
}();
|
||||
|
||||
// Only enforce once MPTokensV2 is enabled to preserve consensus with non-V2 nodes.
|
||||
// Log invariant failure error even if MPTokensV2 is disabled.
|
||||
auto const enforce = !view.rules().enabled(featureMPTokensV2);
|
||||
|
||||
for (auto const& [mptID, values] : amount_)
|
||||
{
|
||||
std::uint16_t senders = 0;
|
||||
std::uint16_t receivers = 0;
|
||||
bool invalidTransfer = false;
|
||||
auto const sleIssuance = view.read(keylet::mptIssuance(mptID));
|
||||
if (!sleIssuance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const canTransfer = sleIssuance->isFlag(lsfMPTCanTransfer);
|
||||
auto const canTrade = sleIssuance->isFlag(lsfMPTCanTrade);
|
||||
auto const reqAuth = sleIssuance->isFlag(lsfMPTRequireAuth);
|
||||
|
||||
for (auto const& [account, value] : values)
|
||||
{
|
||||
// Classify each account as a sender or receiver based on whether their MPTAmount
|
||||
// decreased or increased. Count new MPToken holders (no amtBefore) as receivers.
|
||||
// Skip deleted MPToken holders (amtAfter is nullopt); deletion requires zero balance.
|
||||
if (value.amtAfter.has_value() && value.amtBefore.value_or(0) != *value.amtAfter)
|
||||
{
|
||||
if (!value.amtBefore.has_value() || *value.amtAfter > *value.amtBefore)
|
||||
{
|
||||
++receivers;
|
||||
}
|
||||
else
|
||||
{
|
||||
++senders;
|
||||
}
|
||||
|
||||
// Check once: if any involved account is frozen, the whole
|
||||
// issuance transfer is considered frozen. Only need to check for
|
||||
// frozen if there is a transfer of funds.
|
||||
if (!invalidTransfer &&
|
||||
(isFrozen(view, account, MPTIssue{mptID}) ||
|
||||
!isAuthorized(view, mptID, account, reqAuth)))
|
||||
{
|
||||
invalidTransfer = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// A transfer between holders has occurred (senders > 0 && receivers > 0).
|
||||
// Fail if the issuance is frozen, does not permit transfers, or — for
|
||||
// DEX transactions — does not permit trading.
|
||||
if ((invalidTransfer || !canTransfer || (isDEX && !canTrade)) && senders > 0 &&
|
||||
receivers > 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: invalid MPToken transfer between holders";
|
||||
return enforce;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
|
||||
@@ -243,10 +243,9 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
return tecLOCKED;
|
||||
}
|
||||
|
||||
if (auto const err = canTransfer(ctx.view, issue, srcId, dstId);
|
||||
!isTesSuccess(err))
|
||||
if (auto const err = canTrade(ctx.view, value.asset()); !isTesSuccess(err))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "MPT transfer is disabled.";
|
||||
JLOG(ctx.j.warn()) << "MPT DEX is not allowed.";
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,10 +94,10 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// The currency may not be globally frozen
|
||||
AccountID const& issuerId{sendMax.getIssuer()};
|
||||
if (auto const ter = checkGlobalFrozen(ctx.view, sendMax.asset()); !isTesSuccess(ter))
|
||||
if (isGlobalFrozen(ctx.view, sendMax.asset()))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Creating a check for frozen or locked asset";
|
||||
return ter;
|
||||
JLOG(ctx.j.warn()) << "Creating a check for frozen asset";
|
||||
return sendMax.asset().holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
|
||||
}
|
||||
auto const err = sendMax.asset().visit(
|
||||
[&](Issue const& issue) -> std::optional<TER> {
|
||||
@@ -136,21 +136,9 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
},
|
||||
[&](MPTIssue const& issue) -> std::optional<TER> {
|
||||
if (srcId != issuerId && isFrozen(ctx.view, srcId, issue))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Creating a check for locked MPT.";
|
||||
return tecLOCKED;
|
||||
}
|
||||
if (dstId != issuerId && isFrozen(ctx.view, dstId, issue))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Creating a check for locked MPT.";
|
||||
return tecLOCKED;
|
||||
}
|
||||
if (auto const ter = canTransfer(ctx.view, issue, srcId, dstId);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "MPT transfer is disabled.";
|
||||
return ter;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
});
|
||||
@@ -164,7 +152,7 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
return tecEXPIRED;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
return canTrade(ctx.view, ctx.tx[sfSendMax].asset());
|
||||
}
|
||||
|
||||
TER
|
||||
|
||||
@@ -6,7 +6,7 @@ NotTEC
|
||||
checkTxPermission(std::shared_ptr<SLE const> const& delegate, STTx const& tx)
|
||||
{
|
||||
if (!delegate)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
return terNO_DELEGATE_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
auto const permissionArray = delegate->getFieldArray(sfPermissions);
|
||||
auto const txPermission = tx.getTxnType() + 1;
|
||||
@@ -28,7 +28,7 @@ loadGranularPermission(
|
||||
std::unordered_set<GranularPermissionType>& granularPermissions)
|
||||
{
|
||||
if (!delegate)
|
||||
return;
|
||||
return; // LCOV_EXCL_LINE
|
||||
|
||||
auto const permissionArray = delegate->getFieldArray(sfPermissions);
|
||||
for (auto const& permission : permissionArray)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -105,9 +106,7 @@ AMMClawback::preclaim(PreclaimContext const& ctx)
|
||||
// permission
|
||||
if (((issuerFlagsIn & lsfAllowTrustLineClawback) == 0u) ||
|
||||
((issuerFlagsIn & lsfNoFreeze) != 0u))
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
auto const checkClawAsset = [&](Asset const asset) -> bool {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
@@ -94,16 +95,11 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
// Globally or individually frozen
|
||||
if (auto const ter = checkFrozen(ctx.view, accountID, amount.asset()); !isTesSuccess(ter))
|
||||
|
||||
if (isFrozen(ctx.view, accountID, amount.asset()) ||
|
||||
isFrozen(ctx.view, accountID, amount2.asset()))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Instance: involves frozen or locked asset.";
|
||||
return ter;
|
||||
}
|
||||
if (auto const ter = checkFrozen(ctx.view, accountID, amount2.asset()); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Instance: involves frozen or locked asset.";
|
||||
return ter;
|
||||
JLOG(ctx.j.debug()) << "AMM Instance: involves frozen asset.";
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
auto noDefaultRipple = [](ReadView const& view, Asset const& asset) {
|
||||
@@ -170,10 +166,10 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
|
||||
return terADDRESS_COLLISION;
|
||||
}
|
||||
|
||||
if (auto const ter = canMPTTradeAndTransfer(ctx.view, amount.asset(), accountID, accountID);
|
||||
if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_CREATE, amount.asset(), accountID);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
if (auto const ter = canMPTTradeAndTransfer(ctx.view, amount2.asset(), accountID, accountID);
|
||||
if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_CREATE, amount2.asset(), accountID);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
@@ -243,12 +244,12 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (auto const ter = checkFrozen(ctx.view, accountID, asset); !isTesSuccess(ter))
|
||||
if (isFrozen(ctx.view, accountID, asset))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: account or currency is frozen or locked, "
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: account or currency is frozen, "
|
||||
<< to_string(accountID) << " " << to_string(asset);
|
||||
|
||||
return ter;
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -280,20 +281,18 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
// AMM account or currency frozen
|
||||
if (auto const ter = checkFrozen(ctx.view, ammAccountID, amount->asset());
|
||||
!isTesSuccess(ter))
|
||||
if (isFrozen(ctx.view, ammAccountID, amount->asset()))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: AMM account or currency is frozen or locked, "
|
||||
<< to_string(accountID);
|
||||
return ter;
|
||||
JLOG(ctx.j.debug())
|
||||
<< "AMM Deposit: AMM account or currency is frozen, " << to_string(accountID);
|
||||
return tecFROZEN;
|
||||
}
|
||||
// Account frozen
|
||||
if (auto const ter = checkIndividualFrozen(ctx.view, accountID, amount->asset());
|
||||
!isTesSuccess(ter))
|
||||
if (isIndividualFrozen(ctx.view, accountID, amount->asset()))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen or locked, "
|
||||
<< to_string(accountID) << " " << to_string(amount->asset());
|
||||
return ter;
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen, " << to_string(accountID)
|
||||
<< " " << to_string(amount->asset());
|
||||
return tecFROZEN;
|
||||
}
|
||||
if (checkBalance)
|
||||
{
|
||||
@@ -346,10 +345,10 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const ter = canMPTTradeAndTransfer(ctx.view, ctx.tx[sfAsset], accountID, accountID);
|
||||
if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_DEPOSIT, ctx.tx[sfAsset], accountID);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
if (auto const ter = canMPTTradeAndTransfer(ctx.view, ctx.tx[sfAsset2], accountID, accountID);
|
||||
if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_DEPOSIT, ctx.tx[sfAsset2], accountID);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
@@ -211,24 +212,22 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx)
|
||||
return ter;
|
||||
}
|
||||
// AMM account or currency frozen
|
||||
if (auto const ter = checkFrozen(ctx.view, ammAccountID, amount->asset());
|
||||
!isTesSuccess(ter))
|
||||
if (isFrozen(ctx.view, ammAccountID, amount->asset()))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Withdraw: AMM account or currency is frozen or locked, "
|
||||
<< to_string(accountID);
|
||||
return ter;
|
||||
JLOG(ctx.j.debug())
|
||||
<< "AMM Withdraw: AMM account or currency is frozen, " << to_string(accountID);
|
||||
return tecFROZEN;
|
||||
}
|
||||
// Account frozen
|
||||
if (auto const ter = checkIndividualFrozen(ctx.view, accountID, amount->asset());
|
||||
!isTesSuccess(ter))
|
||||
if (isIndividualFrozen(ctx.view, accountID, amount->asset()))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen or locked, "
|
||||
<< to_string(accountID) << " " << to_string(amount->asset());
|
||||
return ter;
|
||||
JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, " << to_string(accountID)
|
||||
<< " " << to_string(amount->asset());
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
if (auto const ter =
|
||||
canMPTTradeAndTransfer(ctx.view, amount->asset(), accountID, accountID);
|
||||
checkMPTTxAllowed(ctx.view, ttAMM_WITHDRAW, amount->asset(), accountID);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
@@ -606,6 +605,10 @@ AMMWithdraw::withdraw(
|
||||
auto const balance_ = isIssue ? std::max(priorBalance, balance.xrp()) : priorBalance;
|
||||
if (balance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Update owner count.
|
||||
if (!isIssue)
|
||||
adjustOwnerCount(view, sleAccount, 1, journal);
|
||||
}
|
||||
return tesSUCCESS;
|
||||
};
|
||||
|
||||
@@ -146,15 +146,11 @@ OfferCreate::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
auto viewJ = ctx.registry.get().getJournal("View");
|
||||
|
||||
if (auto const ter = checkGlobalFrozen(ctx.view, saTakerPays.asset()); !isTesSuccess(ter))
|
||||
if (isGlobalFrozen(ctx.view, saTakerPays.asset()) ||
|
||||
isGlobalFrozen(ctx.view, saTakerGets.asset()))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Offer involves frozen asset";
|
||||
return ter;
|
||||
}
|
||||
if (auto const ter = checkGlobalFrozen(ctx.view, saTakerGets.asset()); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Offer involves frozen asset";
|
||||
return ter;
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
// Allow unfunded MPT for issuer (OutstandingAmount >= MaximumAmount)
|
||||
@@ -285,13 +281,7 @@ OfferCreate::checkAcceptAsset(
|
||||
[&](MPTIssue const& issue) -> TER {
|
||||
// WeakAuth - don't check if MPToken exists since it's created
|
||||
// if needed.
|
||||
if (auto const ter = requireAuth(view, issue, id, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter;
|
||||
}
|
||||
|
||||
return checkFrozen(view, id, issue);
|
||||
return requireAuth(view, issue, id, AuthType::WeakAuth);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -265,7 +265,6 @@ Payment::checkPermission(ReadView const& view, STTx const& tx)
|
||||
tx.isFieldPresent(sfPaths))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
// PaymentMint and PaymentBurn apply to both IOU and MPT direct payments.
|
||||
if (granularPermissions.contains(PaymentMint) && !isXRP(amountAsset) &&
|
||||
amountAsset.getIssuer() == tx[sfAccount])
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -105,6 +105,20 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
|
||||
return temDISABLED;
|
||||
}
|
||||
if (ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
|
||||
!ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
|
||||
!ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temMALFORMED;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
|
||||
ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
|
||||
ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temDISABLED;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
case ttAMENDMENT:
|
||||
case ttUNL_MODIFY:
|
||||
@@ -268,6 +282,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);
|
||||
|
||||
|
||||
46
src/libxrpl/tx/wasm/HostFuncImpl.cpp
Normal file
46
src/libxrpl/tx/wasm/HostFuncImpl.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include <xrpl/protocol/STBitString.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/tx/wasm/HostFuncImpl.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// =========================================================
|
||||
// SECTION: WRITE FUNCTION
|
||||
// =========================================================
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::updateData(Slice const& data)
|
||||
{
|
||||
if (data.size() > maxWasmDataLength)
|
||||
{
|
||||
return Unexpected(HostFunctionError::DATA_FIELD_TOO_LARGE);
|
||||
}
|
||||
data_ = Bytes(data.begin(), data.end());
|
||||
return data_->size();
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// SECTION: UTILS
|
||||
// =========================================================
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::checkSignature(
|
||||
Slice const& message,
|
||||
Slice const& signature,
|
||||
Slice const& pubkey) const
|
||||
{
|
||||
if (!publicKeyType(pubkey))
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
|
||||
PublicKey const pk(pubkey);
|
||||
return verify(pk, message, signature);
|
||||
}
|
||||
|
||||
Expected<Hash, HostFunctionError>
|
||||
WasmHostFunctionsImpl::computeSha512HalfHash(Slice const& data) const
|
||||
{
|
||||
auto const hash = sha512Half(data);
|
||||
return hash;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
673
src/libxrpl/tx/wasm/HostFuncImplFloat.cpp
Normal file
673
src/libxrpl/tx/wasm/HostFuncImplFloat.cpp
Normal file
@@ -0,0 +1,673 @@
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STBitString.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/tx/wasm/HostFuncImpl.h>
|
||||
|
||||
#ifdef _DEBUG
|
||||
// #define DEBUG_OUTPUT 1
|
||||
#endif
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace wasm_float {
|
||||
|
||||
namespace detail {
|
||||
|
||||
class WasmNumber : public Number
|
||||
{
|
||||
protected:
|
||||
static unsigned constexpr encodedFloatSize = 12;
|
||||
bool good_ = false;
|
||||
|
||||
public:
|
||||
WasmNumber(Slice const& data)
|
||||
{
|
||||
if (data.size() != encodedFloatSize)
|
||||
return;
|
||||
try
|
||||
{
|
||||
SerialIter it(data);
|
||||
Number const x = STNumber(it, sfNumber).value();
|
||||
*static_cast<Number*>(this) = x;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
good_ = true;
|
||||
}
|
||||
|
||||
WasmNumber(Number const& n) : WasmNumber(n.mantissa(), n.exponent()) // ensure Number canonized
|
||||
{
|
||||
}
|
||||
|
||||
template <class T>
|
||||
WasmNumber(T mantissa = 0, int32_t exponent = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
Number n;
|
||||
if constexpr (std::is_signed_v<T>)
|
||||
{
|
||||
n = Number(static_cast<int64_t>(mantissa), exponent);
|
||||
}
|
||||
else
|
||||
{
|
||||
n = Number(static_cast<uint64_t>(mantissa), exponent, Number::normalized());
|
||||
}
|
||||
*static_cast<Number*>(this) = n;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return;
|
||||
}
|
||||
good_ = true;
|
||||
}
|
||||
|
||||
WasmNumber&
|
||||
operator=(WasmNumber const&) = default;
|
||||
|
||||
explicit
|
||||
operator bool() const
|
||||
{
|
||||
return good_;
|
||||
}
|
||||
|
||||
explicit
|
||||
operator int64_t() const
|
||||
{
|
||||
return Number::operator int64_t();
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
toBytes() const
|
||||
{
|
||||
Serializer msg;
|
||||
STNumber(sfNumber, *this).add(msg);
|
||||
auto data = msg.getData();
|
||||
|
||||
#ifdef DEBUG_OUTPUT
|
||||
std::cout << "m: " << std::setw(20) << mantissa() << ", e: " << std::setw(12) << exponent()
|
||||
<< ", hex: ";
|
||||
std::cout << std::hex << std::uppercase << std::setfill('0');
|
||||
for (auto const& c : data)
|
||||
std::cout << std::setw(2) << (unsigned)c << " ";
|
||||
std::cout << std::dec << std::setfill(' ') << std::endl;
|
||||
#endif
|
||||
return Expected<Bytes, HostFunctionError>(std::move(data));
|
||||
}
|
||||
};
|
||||
|
||||
struct FloatState
|
||||
{
|
||||
Number::rounding_mode oldMode_;
|
||||
bool good_ = false;
|
||||
|
||||
FloatState(int32_t mode) : oldMode_(Number::getround())
|
||||
{
|
||||
if (mode < Number::rounding_mode::to_nearest || mode > Number::rounding_mode::upward)
|
||||
return;
|
||||
Number::setround(static_cast<Number::rounding_mode>(mode));
|
||||
good_ = true;
|
||||
}
|
||||
|
||||
~FloatState()
|
||||
{
|
||||
Number::setround(oldMode_);
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return good_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
std::string
|
||||
floatToString(Slice const& data)
|
||||
{
|
||||
// set default mode as we don't expect it will be used here
|
||||
detail::FloatState const rm(Number::rounding_mode::to_nearest);
|
||||
detail::WasmNumber const num(data);
|
||||
if (!num)
|
||||
{
|
||||
std::string hex;
|
||||
hex.reserve(data.size() * 2);
|
||||
boost::algorithm::hex(data.begin(), data.end(), std::back_inserter(hex));
|
||||
return "Invalid data: " + hex;
|
||||
}
|
||||
auto const s = to_string(num);
|
||||
return s;
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromIntImpl(int64_t x, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const num(x);
|
||||
if (!num)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); // LCOV_EXCL_LINE
|
||||
auto const r = num.toBytes();
|
||||
return r;
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromUintImpl(uint64_t x, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const num(x);
|
||||
if (!num)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); // LCOV_EXCL_LINE
|
||||
auto const r = num.toBytes();
|
||||
return r;
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTAmountImpl(STAmount const& x, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const num(static_cast<Number>(x));
|
||||
if (!num)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); // LCOV_EXCL_LINE
|
||||
auto const r = num.toBytes();
|
||||
return r;
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTNumberImpl(STNumber const& x, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const num(x.value());
|
||||
if (!num)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); // LCOV_EXCL_LINE
|
||||
auto const r = num.toBytes();
|
||||
return r;
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<int64_t, HostFunctionError>
|
||||
floatToIntImpl(Slice const& x, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const num(x);
|
||||
if (!num)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); // LCOV_EXCL_LINE
|
||||
int64_t const r(num);
|
||||
return r;
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<FloatPair, HostFunctionError>
|
||||
floatToMantissaAndExponentImpl(Slice const& x)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(Number::rounding_mode::to_nearest);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const num(x);
|
||||
if (!num)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); // LCOV_EXCL_LINE
|
||||
|
||||
return FloatPair(num.mantissa(), num.exponent());
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatNegateImpl(Slice const& x)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(Number::rounding_mode::to_nearest);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const num(x);
|
||||
if (!num)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const res = -num;
|
||||
return res.toBytes();
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatAbsImpl(Slice const& x)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(Number::rounding_mode::to_nearest);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const num(x);
|
||||
if (!num)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const res = abs(num);
|
||||
return res.toBytes();
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatSetImpl(int64_t mantissa, int32_t exponent, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const num(mantissa, exponent);
|
||||
if (!num)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
return num.toBytes();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
floatCompareImpl(Slice const& x, Slice const& y)
|
||||
{
|
||||
try
|
||||
{
|
||||
// set default mode as we don't expect it will be used here
|
||||
detail::FloatState const rm(Number::rounding_mode::to_nearest);
|
||||
|
||||
detail::WasmNumber const xx(x);
|
||||
if (!xx)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const yy(y);
|
||||
if (!yy)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
if (xx < yy)
|
||||
return 2;
|
||||
if (xx == yy)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatAddImpl(Slice const& x, Slice const& y, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const xx(x);
|
||||
if (!xx)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const yy(y);
|
||||
if (!yy)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const res = xx + yy;
|
||||
|
||||
return res.toBytes();
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatSubtractImpl(Slice const& x, Slice const& y, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const xx(x);
|
||||
if (!xx)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const yy(y);
|
||||
if (!yy)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const res = xx - yy;
|
||||
|
||||
return res.toBytes();
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatMultiplyImpl(Slice const& x, Slice const& y, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const xx(x);
|
||||
if (!xx)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const yy(y);
|
||||
if (!yy)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const res = xx * yy;
|
||||
|
||||
return res.toBytes();
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatDivideImpl(Slice const& x, Slice const& y, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const xx(x);
|
||||
if (!xx)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const yy(y);
|
||||
if (!yy)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
detail::WasmNumber const res = xx / yy;
|
||||
|
||||
return res.toBytes();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatRootImpl(Slice const& x, int32_t n, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (n < 1)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const xx(x);
|
||||
if (!xx)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const res(root(xx, n));
|
||||
|
||||
return res.toBytes();
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatPowerImpl(Slice const& x, int32_t n, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((n < 0) || (n > Number::maxExponent))
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const xx(x);
|
||||
if (!xx)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
if (xx == Number() && (n == 0))
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
|
||||
detail::WasmNumber const res(power(xx, n, 1));
|
||||
|
||||
return res.toBytes();
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatLogImpl(Slice const& x, int32_t mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
detail::FloatState const rm(mode);
|
||||
if (!rm)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const xx(x);
|
||||
if (!xx)
|
||||
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
|
||||
|
||||
detail::WasmNumber const res(log10(xx));
|
||||
|
||||
return res.toBytes();
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
} // namespace wasm_float
|
||||
|
||||
// =========================================================
|
||||
// ACTUAL HOST FUNCTIONS
|
||||
// =========================================================
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatFromInt(int64_t x, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatFromIntImpl(x, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatFromUint(uint64_t x, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatFromUintImpl(x, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatFromSTAmount(STAmount const& x, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatFromSTAmountImpl(x, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatFromSTNumber(STNumber const& x, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatFromSTNumberImpl(x, mode);
|
||||
}
|
||||
|
||||
Expected<int64_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatToInt(Slice const& x, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatToIntImpl(x, mode);
|
||||
}
|
||||
|
||||
Expected<FloatPair, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatToMantissaAndExponent(Slice const& x) const
|
||||
{
|
||||
return wasm_float::floatToMantissaAndExponentImpl(x);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatNegate(Slice const& x) const
|
||||
{
|
||||
return wasm_float::floatNegateImpl(x);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatAbs(Slice const& x) const
|
||||
{
|
||||
return wasm_float::floatAbsImpl(x);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatSet(int64_t mantissa, int32_t exponent, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatSetImpl(mantissa, exponent, mode);
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatCompare(Slice const& x, Slice const& y) const
|
||||
{
|
||||
return wasm_float::floatCompareImpl(x, y);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatAdd(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatAddImpl(x, y, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatSubtract(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatSubtractImpl(x, y, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatMultiply(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatMultiplyImpl(x, y, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatDivide(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatDivideImpl(x, y, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatRoot(Slice const& x, int32_t n, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatRootImpl(x, n, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatPower(Slice const& x, int32_t n, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatPowerImpl(x, n, mode);
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::floatLog(Slice const& x, int32_t mode) const
|
||||
{
|
||||
return wasm_float::floatLogImpl(x, mode);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
408
src/libxrpl/tx/wasm/HostFuncImplGetter.cpp
Normal file
408
src/libxrpl/tx/wasm/HostFuncImplGetter.cpp
Normal file
@@ -0,0 +1,408 @@
|
||||
#include <xrpl/protocol/STBitString.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/tx/wasm/HostFuncImpl.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
typedef std::variant<STBase const*, uint256 const*> FieldValue;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
Bytes
|
||||
getIntBytes(STBase const* obj)
|
||||
{
|
||||
static_assert(std::is_integral<T>::value, "Only integral types");
|
||||
|
||||
auto const& num(static_cast<STInteger<T> const*>(obj)); // NOLINT
|
||||
T const data = adjustWasmEndianess(num->value());
|
||||
auto const* b = reinterpret_cast<uint8_t const*>(&data);
|
||||
auto const* e = reinterpret_cast<uint8_t const*>(&data + 1);
|
||||
return Bytes{b, e};
|
||||
}
|
||||
|
||||
static Expected<Bytes, HostFunctionError>
|
||||
getAnyFieldData(STBase const* obj)
|
||||
{
|
||||
if (obj == nullptr)
|
||||
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
|
||||
|
||||
auto const stype = obj->getSType();
|
||||
switch (stype)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
case STI_UNKNOWN:
|
||||
case STI_NOTPRESENT:
|
||||
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case STI_OBJECT:
|
||||
case STI_ARRAY:
|
||||
case STI_VECTOR256:
|
||||
return Unexpected(HostFunctionError::NOT_LEAF_FIELD);
|
||||
|
||||
case STI_ACCOUNT: {
|
||||
auto const* account(static_cast<STAccount const*>(obj)); // NOLINT
|
||||
auto const& data = account->value();
|
||||
return Bytes{data.begin(), data.end()};
|
||||
}
|
||||
|
||||
case STI_ISSUE: {
|
||||
auto const* issue(static_cast<STIssue const*>(obj)); // NOLINT
|
||||
Asset const& asset(issue->value());
|
||||
// XRP and IOU will be processed by serializer
|
||||
if (asset.holds<MPTIssue>())
|
||||
{
|
||||
auto const& mptIssue = asset.get<MPTIssue>();
|
||||
auto const& mptID = mptIssue.getMptID();
|
||||
return Bytes{mptID.cbegin(), mptID.cend()};
|
||||
}
|
||||
break; // Use serializer
|
||||
}
|
||||
|
||||
case STI_VL: {
|
||||
auto const* vl(static_cast<STBlob const*>(obj)); // NOLINT
|
||||
auto const& data = vl->value();
|
||||
return Bytes{data.begin(), data.end()};
|
||||
}
|
||||
|
||||
case STI_UINT16:
|
||||
return getIntBytes<std::uint16_t>(obj);
|
||||
|
||||
case STI_UINT32:
|
||||
return getIntBytes<std::uint32_t>(obj);
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case STI_UINT64:
|
||||
return getIntBytes<std::uint64_t>(obj);
|
||||
|
||||
case STI_INT32:
|
||||
return getIntBytes<std::int32_t>(obj);
|
||||
|
||||
case STI_INT64:
|
||||
return getIntBytes<std::int64_t>(obj);
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case STI_UINT256: {
|
||||
auto const* uint256Obj(static_cast<STUInt256 const*>(obj)); // NOLINT
|
||||
auto const& data = uint256Obj->value();
|
||||
return Bytes{data.begin(), data.end()};
|
||||
}
|
||||
|
||||
case STI_AMOUNT:
|
||||
case STI_NUMBER:
|
||||
default:
|
||||
break; // Use serializer
|
||||
}
|
||||
|
||||
Serializer msg;
|
||||
obj->add(msg);
|
||||
return msg.getData();
|
||||
}
|
||||
|
||||
static Expected<Bytes, HostFunctionError>
|
||||
getAnyFieldData(FieldValue const& variantObj)
|
||||
{
|
||||
if (STBase const* const* obj = std::get_if<STBase const*>(&variantObj))
|
||||
{
|
||||
return getAnyFieldData(*obj);
|
||||
}
|
||||
|
||||
if (uint256 const* const* u = std::get_if<uint256 const*>(&variantObj))
|
||||
{
|
||||
return Bytes((*u)->begin(), (*u)->end());
|
||||
}
|
||||
|
||||
return Unexpected(HostFunctionError::INTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
static inline bool
|
||||
noField(STBase const* field)
|
||||
{
|
||||
return (field == nullptr) || (STI_NOTPRESENT == field->getSType()) ||
|
||||
(STI_UNKNOWN == field->getSType());
|
||||
}
|
||||
|
||||
static Expected<FieldValue, HostFunctionError>
|
||||
locateField(STObject const& obj, Slice const& locator)
|
||||
{
|
||||
if (locator.empty() || ((locator.size() & 3) != 0u)) // must be multiple of 4
|
||||
return Unexpected(HostFunctionError::LOCATOR_MALFORMED);
|
||||
|
||||
static_assert(maxWasmParamLength % sizeof(int32_t) == 0);
|
||||
int32_t locBuf[maxWasmParamLength / sizeof(int32_t)];
|
||||
int32_t const* locPtr = &locBuf[0];
|
||||
int32_t const locSize = locator.size() / sizeof(int32_t);
|
||||
|
||||
{
|
||||
uintptr_t const p = reinterpret_cast<uintptr_t>(locator.data());
|
||||
if ((p & (alignof(int32_t) - 1)) != 0u)
|
||||
{ // unaligned
|
||||
memcpy(&locBuf[0], locator.data(), locator.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
locPtr = reinterpret_cast<int32_t const*>(locator.data());
|
||||
}
|
||||
}
|
||||
|
||||
STBase const* field = nullptr;
|
||||
auto const& knownSFields = SField::getKnownCodeToField();
|
||||
|
||||
{
|
||||
int32_t const sfieldCode = adjustWasmEndianess(locPtr[0]);
|
||||
auto const it = knownSFields.find(sfieldCode);
|
||||
if (it == knownSFields.end())
|
||||
return Unexpected(HostFunctionError::INVALID_FIELD);
|
||||
|
||||
auto const& fname(*it->second);
|
||||
field = obj.peekAtPField(fname);
|
||||
if (noField(field))
|
||||
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
|
||||
}
|
||||
|
||||
for (int i = 1; i < locSize; ++i)
|
||||
{
|
||||
int32_t const sfieldCode = adjustWasmEndianess(locPtr[i]);
|
||||
|
||||
if (STI_ARRAY == field->getSType())
|
||||
{
|
||||
auto const* arr = static_cast<STArray const*>(field); // NOLINT
|
||||
if (sfieldCode < 0 || std::cmp_greater_equal(sfieldCode, arr->size()))
|
||||
return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS);
|
||||
field = &(arr->operator[](sfieldCode));
|
||||
}
|
||||
else if (STI_OBJECT == field->getSType())
|
||||
{
|
||||
auto const* o = static_cast<STObject const*>(field); // NOLINT
|
||||
|
||||
auto const it = knownSFields.find(sfieldCode);
|
||||
if (it == knownSFields.end())
|
||||
return Unexpected(HostFunctionError::INVALID_FIELD);
|
||||
|
||||
auto const& fname(*it->second);
|
||||
field = o->peekAtPField(fname);
|
||||
}
|
||||
else if (STI_VECTOR256 == field->getSType())
|
||||
{
|
||||
auto const* v = static_cast<STVector256 const*>(field); // NOLINT
|
||||
if (sfieldCode < 0 || std::cmp_greater_equal(sfieldCode, v->size()))
|
||||
return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS);
|
||||
return FieldValue(&(v->operator[](sfieldCode)));
|
||||
}
|
||||
else // simple field must be the last one
|
||||
{
|
||||
return Unexpected(HostFunctionError::LOCATOR_MALFORMED);
|
||||
}
|
||||
|
||||
if (noField(field))
|
||||
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
|
||||
}
|
||||
|
||||
return FieldValue(field);
|
||||
}
|
||||
|
||||
static inline Expected<int32_t, HostFunctionError>
|
||||
getArrayLen(FieldValue const& variantField)
|
||||
{
|
||||
if (STBase const* const* field = std::get_if<STBase const*>(&variantField))
|
||||
{
|
||||
if ((*field)->getSType() == STI_VECTOR256)
|
||||
return static_cast<STVector256 const*>(*field)->size(); // NOLINT
|
||||
if ((*field)->getSType() == STI_ARRAY)
|
||||
return static_cast<STArray const*>(*field)->size(); // NOLINT
|
||||
}
|
||||
// uint256 is not an array so that variant should still return NO_ARRAY
|
||||
|
||||
return Unexpected(HostFunctionError::NO_ARRAY); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::cacheLedgerObj(uint256 const& objId, int32_t cacheIdx)
|
||||
{
|
||||
auto const& keylet = keylet::unchecked(objId);
|
||||
if (cacheIdx < 0 || cacheIdx > MAX_CACHE)
|
||||
return Unexpected(HostFunctionError::SLOT_OUT_RANGE);
|
||||
|
||||
if (cacheIdx == 0)
|
||||
{
|
||||
for (cacheIdx = 0; cacheIdx < MAX_CACHE; ++cacheIdx)
|
||||
{
|
||||
if (!cache_[cacheIdx])
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cacheIdx--; // convert to 0-based index
|
||||
}
|
||||
|
||||
if (cacheIdx >= MAX_CACHE)
|
||||
return Unexpected(HostFunctionError::SLOTS_FULL);
|
||||
|
||||
cache_[cacheIdx] = ctx_.view().read(keylet);
|
||||
if (!cache_[cacheIdx])
|
||||
return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND);
|
||||
return cacheIdx + 1; // return 1-based index
|
||||
}
|
||||
|
||||
// Subsection: top level getters
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getTxField(SField const& fname) const
|
||||
{
|
||||
return detail::getAnyFieldData(ctx_.tx.peekAtPField(fname));
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getCurrentLedgerObjField(SField const& fname) const
|
||||
{
|
||||
auto const sle = getCurrentLedgerObj();
|
||||
if (!sle.has_value())
|
||||
return Unexpected(sle.error());
|
||||
return detail::getAnyFieldData(sle.value()->peekAtPField(fname));
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getLedgerObjField(int32_t cacheIdx, SField const& fname) const
|
||||
{
|
||||
auto const normalizedIdx = normalizeCacheIndex(cacheIdx);
|
||||
if (!normalizedIdx.has_value())
|
||||
return Unexpected(normalizedIdx.error());
|
||||
return detail::getAnyFieldData(cache_[normalizedIdx.value()]->peekAtPField(fname));
|
||||
}
|
||||
|
||||
// Subsection: nested getters
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getTxNestedField(Slice const& locator) const
|
||||
{
|
||||
auto const r = detail::locateField(ctx_.tx, locator);
|
||||
if (!r)
|
||||
return Unexpected(r.error());
|
||||
|
||||
return detail::getAnyFieldData(r.value());
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getCurrentLedgerObjNestedField(Slice const& locator) const
|
||||
{
|
||||
auto const sle = getCurrentLedgerObj();
|
||||
if (!sle.has_value())
|
||||
return Unexpected(sle.error());
|
||||
|
||||
auto const r = detail::locateField(*sle.value(), locator);
|
||||
if (!r)
|
||||
return Unexpected(r.error());
|
||||
|
||||
return detail::getAnyFieldData(r.value());
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) const
|
||||
{
|
||||
auto const normalizedIdx = normalizeCacheIndex(cacheIdx);
|
||||
if (!normalizedIdx.has_value())
|
||||
return Unexpected(normalizedIdx.error());
|
||||
|
||||
auto const r = detail::locateField(*cache_[normalizedIdx.value()], locator);
|
||||
if (!r)
|
||||
return Unexpected(r.error());
|
||||
|
||||
return detail::getAnyFieldData(r.value());
|
||||
}
|
||||
|
||||
// Subsection: array length getters
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getTxArrayLen(SField const& fname) const
|
||||
{
|
||||
if (fname.fieldType != STI_ARRAY && fname.fieldType != STI_VECTOR256)
|
||||
return Unexpected(HostFunctionError::NO_ARRAY);
|
||||
|
||||
auto const* field = ctx_.tx.peekAtPField(fname);
|
||||
if (detail::noField(field))
|
||||
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
|
||||
|
||||
return detail::getArrayLen(field);
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getCurrentLedgerObjArrayLen(SField const& fname) const
|
||||
{
|
||||
if (fname.fieldType != STI_ARRAY && fname.fieldType != STI_VECTOR256)
|
||||
return Unexpected(HostFunctionError::NO_ARRAY);
|
||||
|
||||
auto const sle = getCurrentLedgerObj();
|
||||
if (!sle.has_value())
|
||||
return Unexpected(sle.error());
|
||||
|
||||
auto const* field = sle.value()->peekAtPField(fname);
|
||||
if (detail::noField(field))
|
||||
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
|
||||
|
||||
return detail::getArrayLen(field);
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const
|
||||
{
|
||||
if (fname.fieldType != STI_ARRAY && fname.fieldType != STI_VECTOR256)
|
||||
return Unexpected(HostFunctionError::NO_ARRAY);
|
||||
|
||||
auto const normalizedIdx = normalizeCacheIndex(cacheIdx);
|
||||
if (!normalizedIdx.has_value())
|
||||
return Unexpected(normalizedIdx.error());
|
||||
|
||||
auto const* field = cache_[normalizedIdx.value()]->peekAtPField(fname);
|
||||
if (detail::noField(field))
|
||||
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
|
||||
|
||||
return detail::getArrayLen(field);
|
||||
}
|
||||
|
||||
// Subsection: nested array length getters
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getTxNestedArrayLen(Slice const& locator) const
|
||||
{
|
||||
auto const r = detail::locateField(ctx_.tx, locator);
|
||||
if (!r)
|
||||
return Unexpected(r.error());
|
||||
|
||||
auto const& field = r.value();
|
||||
return detail::getArrayLen(field);
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getCurrentLedgerObjNestedArrayLen(Slice const& locator) const
|
||||
{
|
||||
auto const sle = getCurrentLedgerObj();
|
||||
if (!sle.has_value())
|
||||
return Unexpected(sle.error());
|
||||
auto const r = detail::locateField(*sle.value(), locator);
|
||||
if (!r)
|
||||
return Unexpected(r.error());
|
||||
|
||||
auto const& field = r.value();
|
||||
return detail::getArrayLen(field);
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) const
|
||||
{
|
||||
auto const normalizedIdx = normalizeCacheIndex(cacheIdx);
|
||||
if (!normalizedIdx.has_value())
|
||||
return Unexpected(normalizedIdx.error());
|
||||
|
||||
auto const r = detail::locateField(*cache_[normalizedIdx.value()], locator);
|
||||
if (!r)
|
||||
return Unexpected(r.error());
|
||||
|
||||
auto const& field = r.value();
|
||||
return detail::getArrayLen(field);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
213
src/libxrpl/tx/wasm/HostFuncImplKeylet.cpp
Normal file
213
src/libxrpl/tx/wasm/HostFuncImplKeylet.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
#include <xrpl/protocol/STBitString.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/tx/wasm/HostFuncImpl.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::accountKeylet(AccountID const& account) const
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::account(account);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::ammKeylet(Asset const& issue1, Asset const& issue2) const
|
||||
{
|
||||
if (issue1 == issue2)
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
|
||||
// note: this should be removed with the MPT DEX amendment
|
||||
if (issue1.holds<MPTIssue>() || issue2.holds<MPTIssue>())
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
|
||||
auto const keylet = keylet::amm(issue1, issue2);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::checkKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::check(account, seq);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::credentialKeylet(
|
||||
AccountID const& subject,
|
||||
AccountID const& issuer,
|
||||
Slice const& credentialType) const
|
||||
{
|
||||
if (!subject || !issuer)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
|
||||
if (credentialType.empty() || credentialType.size() > maxCredentialTypeLength)
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
|
||||
auto const keylet = keylet::credential(subject, issuer, credentialType);
|
||||
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::didKeylet(AccountID const& account) const
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::did(account);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::delegateKeylet(AccountID const& account, AccountID const& authorize) const
|
||||
{
|
||||
if (!account || !authorize)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
if (account == authorize)
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
auto const keylet = keylet::delegate(account, authorize);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::depositPreauthKeylet(AccountID const& account, AccountID const& authorize)
|
||||
const
|
||||
{
|
||||
if (!account || !authorize)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
if (account == authorize)
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
auto const keylet = keylet::depositPreauth(account, authorize);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::escrowKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::escrow(account, seq);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::lineKeylet(
|
||||
AccountID const& account1,
|
||||
AccountID const& account2,
|
||||
Currency const& currency) const
|
||||
{
|
||||
if (!account1 || !account2)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
if (account1 == account2)
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
if (currency.isZero())
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
|
||||
auto const keylet = keylet::line(account1, account2, currency);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const
|
||||
{
|
||||
if (!issuer)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
|
||||
auto const keylet = keylet::mptIssuance(seq, issuer);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::mptokenKeylet(MPTID const& mptid, AccountID const& holder) const
|
||||
{
|
||||
if (!mptid)
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
if (!holder)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
|
||||
auto const keylet = keylet::mptoken(mptid, holder);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::nftOfferKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::nftoffer(account, seq);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::offerKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::offer(account, seq);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::oracleKeylet(AccountID const& account, std::uint32_t documentId) const
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::oracle(account, documentId);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::paychanKeylet(
|
||||
AccountID const& account,
|
||||
AccountID const& destination,
|
||||
std::uint32_t seq) const
|
||||
{
|
||||
if (!account || !destination)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
if (account == destination)
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
auto const keylet = keylet::payChan(account, destination, seq);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::permissionedDomain(account, seq);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::signersKeylet(AccountID const& account) const
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::signers(account);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::ticketKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::ticket(account, seq);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::vaultKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::vault(account, seq);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
49
src/libxrpl/tx/wasm/HostFuncImplLedgerHeader.cpp
Normal file
49
src/libxrpl/tx/wasm/HostFuncImplLedgerHeader.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <xrpl/ledger/AmendmentTable.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/tx/wasm/HostFuncImpl.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// =========================================================
|
||||
// SECTION: LEDGER HEADER FUNCTIONS
|
||||
// =========================================================
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getLedgerSqn() const
|
||||
{
|
||||
return ctx_.view().seq();
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getParentLedgerTime() const
|
||||
{
|
||||
return ctx_.view().parentCloseTime().time_since_epoch().count();
|
||||
}
|
||||
|
||||
Expected<Hash, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getParentLedgerHash() const
|
||||
{
|
||||
return ctx_.view().header().parentHash;
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getBaseFee() const
|
||||
{
|
||||
return ctx_.view().fees().base.drops();
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::isAmendmentEnabled(uint256 const& amendmentId) const
|
||||
{
|
||||
return ctx_.view().rules().enabled(amendmentId);
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::isAmendmentEnabled(std::string_view const& amendmentName) const
|
||||
{
|
||||
auto const& table = ctx_.registry.get().getAmendmentTable();
|
||||
auto const amendment = table.find(std::string(amendmentName));
|
||||
return ctx_.view().rules().enabled(amendment);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
67
src/libxrpl/tx/wasm/HostFuncImplNFT.cpp
Normal file
67
src/libxrpl/tx/wasm/HostFuncImplNFT.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
#include <xrpl/protocol/STBitString.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/tx/wasm/HostFuncImpl.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// =========================================================
|
||||
// SECTION: NFT UTILS
|
||||
// =========================================================
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getNFT(AccountID const& account, uint256 const& nftId) const
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HostFunctionError::INVALID_ACCOUNT);
|
||||
|
||||
if (!nftId)
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
|
||||
auto obj = nft::findToken(ctx_.view(), account, nftId);
|
||||
if (!obj)
|
||||
return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND);
|
||||
|
||||
auto objUri = obj->at(~sfURI);
|
||||
if (!objUri)
|
||||
return Unexpected(HostFunctionError::FIELD_NOT_FOUND);
|
||||
|
||||
Slice const s = objUri->value();
|
||||
return Bytes(s.begin(), s.end());
|
||||
}
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getNFTIssuer(uint256 const& nftId) const
|
||||
{
|
||||
auto const issuer = nft::getIssuer(nftId);
|
||||
if (!issuer)
|
||||
return Unexpected(HostFunctionError::INVALID_PARAMS);
|
||||
|
||||
return Bytes{issuer.begin(), issuer.end()};
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getNFTTaxon(uint256 const& nftId) const
|
||||
{
|
||||
return nft::toUInt32(nft::getTaxon(nftId));
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getNFTFlags(uint256 const& nftId) const
|
||||
{
|
||||
return nft::getFlags(nftId);
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getNFTTransferFee(uint256 const& nftId) const
|
||||
{
|
||||
return nft::getTransferFee(nftId);
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
WasmHostFunctionsImpl::getNFTSerial(uint256 const& nftId) const
|
||||
{
|
||||
return nft::getSerial(nftId);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user