Compare commits

...
28 changed files with 1199 additions and 294 deletions

No files matched your search

+2
View File
@@ -108,6 +108,8 @@
#define sfMPTAmount ((3U << 16U) + 26U)
#define sfIssuerNode ((3U << 16U) + 27U)
#define sfSubjectNode ((3U << 16U) + 28U)
#define sfHookCost ((3U << 16U) + 95U)
#define sfHookCallbackCost ((3U << 16U) + 96U)
#define sfTouchCount ((3U << 16U) + 97U)
#define sfAccountIndex ((3U << 16U) + 98U)
#define sfAccountCount ((3U << 16U) + 99U)
+1
View File
@@ -62,6 +62,7 @@
#define ttPERMISSIONED_DOMAIN_SET 71
#define ttPERMISSIONED_DOMAIN_DELETE 72
#define ttENTROPY 89
#define ttHOOK_DEFINITION_UPDATE 90
#define ttMANIFEST_SET 91
#define ttCRON 92
#define ttCRON_SET 93
+4
View File
@@ -115,3 +115,7 @@ enum AMMClawbackFlags : uint32_t {
enum BridgeModifyFlags : uint32_t {
tfClearAccountCreateAmount = 0x00010000,
};
enum HookDefinitionUpdateFlags : uint32_t {
tfValidateGuards = 0x00000001,
};
+7
View File
@@ -406,6 +406,13 @@ const uint8_t max_emit = 255;
const uint8_t max_params = 16;
const double fee_base_multiplier = 1.1f;
// HookFeeV2 execution cost units: every wasm instruction costs one unit and
// every hook api call costs api_call_cost units on top of its call
// instruction. cost_units_per_drop units make one drop, so an instruction
// costs 0.1 drop and an api call 10 drops.
const uint32_t api_call_cost = 100;
const uint32_t cost_units_per_drop = 10;
using APIWhitelist = std::map<std::string, std::vector<uint8_t>>;
namespace detail {
+57 -28
View File
@@ -145,6 +145,7 @@ struct WasmBlkInf
uint32_t sanity_check;
uint32_t iteration_bound;
uint32_t instruction_count;
uint32_t execution_cost;
WasmBlkInf* parent;
std::vector<WasmBlkInf*> children;
uint32_t start_byte;
@@ -159,6 +160,7 @@ struct WasmBlkInf
: sanity_check(0x1234ABCDU)
, iteration_bound(iteration_bound_)
, instruction_count(instruction_count_)
, execution_cost(0)
, parent(parent_)
, children({})
, start_byte(start_byte_)
@@ -194,7 +196,7 @@ struct WasmBlkInf
level, \
" " \
" ", \
worst_case_execution, \
instruction_count, \
blk->start_byte, \
blk->instruction_count, \
blk->iteration_bound, \
@@ -203,7 +205,7 @@ struct WasmBlkInf
&(blk->parent)); \
}
// compute worst case execution time
inline uint64_t
inline std::pair<uint64_t, uint64_t>
compute_wce(
const WasmBlkInf* blk,
int level,
@@ -213,14 +215,14 @@ compute_wce(
if (level > max_level)
{
*recursion_limit_reached = true;
return 0;
return {0, 0};
}
if (blk->sanity_check != 0x1234ABCDU)
{
printf("!!! sanity check failed\n");
*recursion_limit_reached = true;
return (uint64_t)-1;
return {(uint64_t)-1, (uint64_t)-1};
}
WasmBlkInf const* parent = blk->parent;
@@ -229,23 +231,28 @@ compute_wce(
{
printf("!!! parent sanity check failed\n");
*recursion_limit_reached = true;
return (uint64_t)-1;
return {(uint64_t)-1, (uint64_t)-1};
}
uint64_t worst_case_execution = blk->instruction_count;
uint64_t instruction_count = blk->instruction_count;
uint64_t execution_cost = blk->execution_cost;
double multiplier = 1.0;
if (blk->children.size() > 0)
for (auto const& child : blk->children)
worst_case_execution += compute_wce(
{
auto [child_instruction_count, child_execution_cost] = compute_wce(
child, level + 1, max_level, recursion_limit_reached);
instruction_count += child_instruction_count;
execution_cost += child_execution_cost;
}
if (parent == 0 ||
parent->iteration_bound ==
0) // this condtion should never occur [defensively programmed]
{
PRINT_WCE(1);
return worst_case_execution;
return {instruction_count, execution_cost};
}
// if the block has a parent then the quotient of its guard and its parent's
@@ -254,12 +261,18 @@ compute_wce(
multiplier =
((double)(blk->iteration_bound)) / ((double)(parent->iteration_bound));
worst_case_execution *= multiplier;
if (worst_case_execution < 1.0)
worst_case_execution = 1.0;
instruction_count *= multiplier;
if (instruction_count < 1.0)
instruction_count = 1.0;
// the loop multiplier must apply to the execution cost as well, otherwise
// an api call inside a loop is only ever charged for a single iteration
execution_cost *= multiplier;
if (execution_cost < 1.0)
execution_cost = 1.0;
PRINT_WCE(3);
return worst_case_execution;
return {instruction_count, execution_cost};
};
// checks the WASM binary for the appropriate required _g guard calls and
@@ -267,7 +280,7 @@ compute_wce(
// expr under analysis begins and end_offset is where it ends returns {worst
// case instruction count} if valid or {} if invalid may throw overflow_error,
// length_error
inline std::optional<uint64_t>
inline std::optional<uint64_t> // count or cost depending on returnCost
check_guard(
std::vector<uint8_t> const& wasm,
int codesec,
@@ -277,6 +290,7 @@ check_guard(
int last_import_idx,
GuardLog guardLog,
std::string guardLogAccStr,
bool returnCost,
/* RH NOTE:
* rules version is a bit field, so rule update 1 is 0x01, update 2 is 0x02
* and update 3 is 0x04 ideally at rule version 3 all bits so far are set
@@ -337,6 +351,7 @@ check_guard(
ADVANCE(1);
current->instruction_count++;
current->execution_cost++;
// unreachable and nop instructions
if (instr == 0x00U || // unreachable
@@ -516,6 +531,9 @@ check_guard(
GUARD_ERROR("Too many guard calls! Limit is 1024");
}
// every import (including _g) is a hook api call
current->execution_cost += hook_api::api_call_cost;
continue;
}
@@ -795,7 +813,7 @@ check_guard(
int max_level = 16;
if (rulesVersion & hook_api::GuardRuleDepth32)
max_level = 32;
uint64_t wce =
auto [instruction_count, execution_cost] =
compute_wce(&(*root), 0, max_level, &recursion_limit_reached);
if (recursion_limit_reached)
{
@@ -807,11 +825,7 @@ check_guard(
return {};
}
GUARDLOG(hook::log::INSTRUCTION_COUNT)
<< "GuardCheck "
<< "Total worse-case execution count: " << wce << "\n";
if (wce >= 0xFFFFU)
if (instruction_count >= 0xFFFFU)
{
GUARDLOG(hook::log::INSTRUCTION_EXCESS)
<< "GuardCheck "
@@ -821,7 +835,10 @@ check_guard(
<< "\n";
return {};
}
return wce;
if (returnCost)
return execution_cost;
else
return instruction_count;
}
// RH TODO: reprogram this function to use REQUIRE/ADVANCE
@@ -835,6 +852,7 @@ validateGuards(
std::vector<uint8_t> const& wasm,
GuardLog guardLog,
std::string guardLogAccStr,
bool returnCost,
hook_api::APIWhitelist const import_whitelist,
/* RH NOTE:
* rules version is a bit field, so rule update 1 is 0x01, update 2 is 0x02
@@ -883,6 +901,7 @@ validateGuards(
// this maps function ids to type ids, used for looking up the type of cbak
// and hook as established inside the wasm binary.
std::map<int, int> func_type_map;
// used to check that every import sharing a type has the same signature
std::map<
int /* type idx */,
std::map<int /* import index */, std::string /* api name */>>
@@ -1035,9 +1054,8 @@ validateGuards(
bool found_in_whitelist = (it != it_end);
if (import_name == "_g")
{
guard_import_number = func_upto;
}
if (!found_in_whitelist)
{
GUARDLOG(hook::log::IMPORT_ILLEGAL)
@@ -1051,12 +1069,8 @@ validateGuards(
}
// add to import map
if (import_type_map.find(type_idx) == import_type_map.end())
import_type_map[type_idx] = {
{func_upto, std::move(import_name)}};
else
import_type_map[type_idx].emplace(
func_upto, std::move(import_name));
import_type_map[type_idx].emplace(
func_upto, std::move(import_name));
func_upto++;
}
@@ -1509,15 +1523,30 @@ validateGuards(
last_import_number,
guardLog,
guardLogAccStr,
returnCost,
rulesVersion);
if (!valid)
return {};
if (hook_func_idx && *hook_func_idx == j)
{
GUARDLOG(hook::log::INSTRUCTION_COUNT)
<< "GuardCheck "
<< "Total hook worse-case execution "
<< (returnCost ? "cost: " : "count: ") << *valid
<< "\n";
maxInstrCountHook = *valid;
}
else if (cbak_func_idx && *cbak_func_idx == j)
{
GUARDLOG(hook::log::INSTRUCTION_COUNT)
<< "GuardCheck "
<< "Total cbak worse-case execution "
<< (returnCost ? "cost: " : "count: ") << *valid
<< "\n";
maxInstrCountCbak = *valid;
}
else
{
if (DEBUG_GUARD)
+1
View File
@@ -88,6 +88,7 @@ main(int argc, char** argv)
hook,
std::cout,
"",
true,
hook_api::getImportWhitelist(rules),
hook_api::getGuardRulesVersion(rules));
+6
View File
@@ -274,6 +274,12 @@ enum BridgeModifyFlags : uint32_t {
tfClearAccountCreateAmount = 0x00010000,
};
constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount);
// HookDefinitionUpdate flags:
enum HookDefinitionUpdateFlags : uint32_t {
tfValidateGuards = 0x00000001,
};
constexpr std::uint32_t tfHookDefinitionUpdateMask = ~(tfUniversal | tfValidateGuards);
// clang-format on
} // namespace ripple
@@ -34,6 +34,7 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
XRPL_FEATURE(HookFeeV2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(RNG, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PWALoader, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (20261005, Supported::yes, VoteBehavior::DefaultYes)
@@ -103,8 +103,10 @@ LEDGER_ENTRY(ltHOOK_DEFINITION, 'D', HookDefinition, hook_definition, ({
{sfCreateCode, soeREQUIRED},
{sfHookSetTxnID, soeREQUIRED},
{sfReferenceCount, soeREQUIRED},
{sfFee, soeREQUIRED},
{sfFee, soeOPTIONAL},
{sfHookCallbackFee, soeOPTIONAL},
{sfHookCost, soeOPTIONAL},
{sfHookCallbackCost, soeOPTIONAL},
{sfPreviousTxnID, soeOPTIONAL},
{sfPreviousTxnLgrSeq, soeOPTIONAL},
}))
@@ -156,6 +156,8 @@ TYPED_SFIELD(sfOutstandingAmount, UINT64, 25, SField::sMD_BaseTen|SFie
TYPED_SFIELD(sfMPTAmount, UINT64, 26, SField::sMD_BaseTen|SField::sMD_Default)
TYPED_SFIELD(sfIssuerNode, UINT64, 27)
TYPED_SFIELD(sfSubjectNode, UINT64, 28)
TYPED_SFIELD(sfHookCost, UINT64, 95)
TYPED_SFIELD(sfHookCallbackCost, UINT64, 96)
TYPED_SFIELD(sfTouchCount, UINT64, 97)
TYPED_SFIELD(sfAccountIndex, UINT64, 98)
TYPED_SFIELD(sfAccountCount, UINT64, 99)
@@ -517,6 +517,11 @@ TRANSACTION(ttENTROPY, 89, Entropy, ({
{sfRandomData, soeOPTIONAL},
}))
/** This transaction type updates a hook definition */
TRANSACTION(ttHOOK_DEFINITION_UPDATE, 90, HookDefinitionUpdate, ({
{sfHookHash, soeREQUIRED},
}))
/* sfAccount is supplied by TxFormats::commonFields; listing it here as well
makes the SOTemplate reject the format at construction. */
TRANSACTION(ttMANIFEST_SET, 91, SetManifest, ({
+1
View File
@@ -682,6 +682,7 @@ public:
auto const sa = supported_amendments();
testWithFeats(sa);
testWithFeats(sa - fixHookAPI20251128);
testWithFeats(sa - featureHookFeeV2);
}
};
+166
View File
@@ -0,0 +1,166 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 XRPL-Labs
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS S
OFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/genesis.h>
#include <test/jtx/hook.h>
#include <xrpl/protocol/jss.h>
namespace ripple {
namespace test {
class HookDefinitionUpdate_test : public beast::unit_test::suite
{
static Json::Value
hookDefUpdateTx(
jtx::Account const& account,
std::string const& hookHash,
uint32_t flags = tfValidateGuards)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::HookDefinitionUpdate;
jv[jss::Account] = account.human();
jv[jss::HookHash] = hookHash;
jv[jss::Flags] = flags;
return jv;
}
void
testInvalidPreflight(FeatureBitset features)
{
testcase("hook definition update invalid preflight");
using namespace test::jtx;
auto const alice = Account("alice");
// temDISABLED
{
Env env{*this, features - featureHookFeeV2};
env.fund(XRP(1000), alice);
env.close();
env(hookDefUpdateTx(alice, ""), ter(temDISABLED));
}
// temINVALID_FLAG
{
Env env{*this, features};
env.fund(XRP(1000), alice);
env.close();
env(hookDefUpdateTx(alice, "", 0x00000002), ter(temINVALID_FLAG));
}
}
void
testInvalidPreclaim(FeatureBitset features)
{
testcase("hook definition update invalid preclaim");
using namespace test::jtx;
auto const alice = Account("alice");
// tecNO_ENTRY
{
Env env{*this};
env.fund(XRP(1000), alice);
env.close();
env(hookDefUpdateTx(alice, ""), ter(tecNO_ENTRY));
}
}
void
testDoApply(FeatureBitset features)
{
testcase("hook definition update do apply");
using namespace test::jtx;
auto const alice = Account("alice");
Env env{*this, features - featureHookFeeV2};
env.fund(XRP(1000), alice);
env.close();
auto const hookCode = jtx::genesis::AcceptHook;
uint256 const hash = ripple::sha512Half_s(
ripple::Slice(hookCode.data(), hookCode.size()));
// prepare: Create Hook Definition without HookFeeV2
auto hookObj = hso(hookCode);
env(hook(alice, {{hookObj}}, 0), fee(XRP(1)));
env.close();
// prepare: enable HookFeeV2
env.enableFeature(featureHookFeeV2);
env.close();
// 1. Update Hook Definition (Fee -> Cost)
{
auto leb = env.le(keylet::hookDefinition(hash));
BEAST_EXPECT(leb->isFieldPresent(sfFee));
BEAST_EXPECT(!leb->isFieldPresent(sfHookCost));
env(hookDefUpdateTx(alice, to_string(hash)), fee(XRP(1)));
env.close();
auto lea = env.le(keylet::hookDefinition(hash));
BEAST_EXPECT(lea->isFieldPresent(sfHookCost));
BEAST_EXPECT(!lea->isFieldPresent(sfFee));
}
// 2. Update Hook Definition (Cost -> Cost)
{
env(hookDefUpdateTx(alice, to_string(hash)), fee(XRP(1)));
env.close();
auto const meta = env.meta();
BEAST_EXPECT(meta->isFieldPresent(sfAffectedNodes));
auto& nodes = meta->getFieldArray(sfAffectedNodes);
BEAST_EXPECT(nodes.size() == 1);
// no update for ltHookDefinition
BEAST_EXPECT(
nodes[0].getFieldU16(sfLedgerEntryType) == ltACCOUNT_ROOT);
}
// TODO: test callback cost
}
public:
void
run() override
{
using namespace test::jtx;
FeatureBitset const all{supported_amendments()};
testWithFeats(all);
}
void
testWithFeats(FeatureBitset features)
{
testInvalidPreflight(features);
testInvalidPreclaim(features);
testDoApply(features);
}
};
BEAST_DEFINE_TESTSUITE(HookDefinitionUpdate, app, ripple);
} // namespace test
} // namespace ripple
+21 -6
View File
@@ -4913,9 +4913,11 @@ private:
env.close(std::chrono::seconds(300));
Oracle oracle(
env,
{.owner = account,
.series = {{"XRP", "USD", 740, 1}},
.fee = 10000});
{
.owner = account,
.series = {{"XRP", "USD", 740, 1}},
.fee = 1'000'000,
});
// verify tsh hook triggered
testTSHStrongWeak(env, tshSTRONG, __LINE__);
@@ -4952,6 +4954,7 @@ private:
{
.owner = account,
.series = {{"XRP", "USD", 740, 1}},
.fee = 1'000'000,
});
// set tsh collect
@@ -4964,7 +4967,7 @@ private:
// delete oracle
oracle.remove(oracle::RemoveArg{
.documentID = oracle.documentID(),
.fee = 10000,
.fee = 1'000'000,
});
// verify tsh hook triggered
@@ -8257,6 +8260,15 @@ private:
testTSHStrongWeak(env, tx->getTransactionID(), expected, __LINE__);
}
}
void
testHookDefinitionUpdateTSH(FeatureBitset features)
{
testcase("hook definition update tsh");
// TODO
BEAST_EXPECT(true);
}
void
testEmissionOrdering(FeatureBitset features)
{
@@ -8541,9 +8553,10 @@ public:
using namespace test::jtx;
static FeatureBitset const all{supported_amendments()};
static std::array<FeatureBitset, 3> const feats{
static std::array<FeatureBitset, 4> const feats{
all,
all - featureEscrowDestinationCancel,
all - featureHookFeeV2,
all - featureIOUIssuerWeakTSH,
};
@@ -8574,11 +8587,13 @@ public:
};
SETHOOKTSH_TEST(1, false)
SETHOOKTSH_TEST(2, true)
SETHOOKTSH_TEST(2, false)
SETHOOKTSH_TEST(3, true)
BEAST_DEFINE_TESTSUITE_PRIO(SetHookTSH0, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHookTSH1, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHookTSH2, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHookTSH3, app, ripple, 2);
} // namespace test
} // namespace ripple
+278 -29
View File
@@ -82,14 +82,52 @@ using JSSMap =
std::string const x##_hash_str = to_string(x##_hash); \
Keylet const x##_keylet = keylet::hookDefinition(x##_hash);
#define EXPECT_HOOK_FEE(x, fee) \
do \
{ \
auto const hookSLE = env.le(x##_keylet); \
BEAST_EXPECTS( \
hookSLE->getFieldAmount(sfFee) == XRPAmount{fee}, \
"Hook fee mismatch: expected " #fee " got " + \
to_string(hookSLE->getFieldAmount(sfFee))); \
#define EXPECT_HOOK_FEE(x, fee) \
do \
{ \
auto const hookSLE = env.le(x##_keylet); \
if (env.current()->rules().enabled(featureHookFeeV2)) \
{ \
BEAST_EXPECTS( \
!hookSLE->isFieldPresent(sfFee), \
"Hook fee should not be present after featureHookFeeV2 is " \
"enabled"); \
} \
else \
{ \
BEAST_EXPECTS( \
hookSLE->getFieldAmount(sfFee) == XRPAmount{fee}, \
"Hook fee mismatch: expected " #fee " got " + \
to_string(hookSLE->getFieldAmount(sfFee))); \
} \
} while (false)
#define EXPECT_HOOK_COST(x, cost, fee) \
do \
{ \
auto const hookSLE = env.le(x##_keylet); \
if (env.current()->rules().enabled(featureHookFeeV2)) \
{ \
auto const actualCost = hookSLE->getFieldU64(sfHookCost); \
auto const calculatedFee = hook::hookCostToFee(actualCost); \
BEAST_EXPECTS( \
actualCost > 0, "Hook cost should be greater than 0"); \
BEAST_EXPECTS( \
calculatedFee == XRPAmount{fee}, \
"Hook fee(v2) mismatch: expected " #fee " got " + \
to_string(calculatedFee)); \
BEAST_EXPECTS( \
actualCost == cost, \
"Hook cost mismatch: expected " #cost " got " + \
to_string(actualCost)); \
} \
else \
{ \
BEAST_EXPECTS( \
!hookSLE->isFieldPresent(sfHookCost), \
"Hook cost should not be present before featureHookFeeV2 is " \
"enabled"); \
} \
} while (false)
class SetHook0_test : public beast::unit_test::suite
@@ -1379,7 +1417,7 @@ public:
{
testcase("Test hook on v2");
using namespace jtx;
Env env{*this, features};
Env env{*this, features - featureHookFeeV2};
bool const hookOnV2 = env.current()->rules().enabled(featureHookOnV2) ||
env.current()->rules().enabled(featureHookOnV2_1);
@@ -2265,7 +2303,12 @@ public:
auto jv = invoke::invoke(alice);
auto expectedFee =
calculateBaseFee(*env.current(), *env.jt(jv).stx);
BEAST_EXPECT(expectedFee == drops(19));
if (env.current()->rules().enabled(featureHookFeeV2))
// TODO: add test for hook fee v2
BEAST_EXPECT(expectedFee > env.current()->fees().base);
else
BEAST_EXPECT(expectedFee == drops(19));
env(jv,
M("Call named hook without specifying the hook name"),
HSFEE);
@@ -2284,7 +2327,11 @@ public:
jv[jss::HookName] = "41424345";
auto expectedFee =
calculateBaseFee(*env.current(), *env.jt(jv).stx);
BEAST_EXPECT(expectedFee == drops(19));
if (env.current()->rules().enabled(featureHookFeeV2))
// TODO: add test for hook fee v2
BEAST_EXPECT(expectedFee > env.current()->fees().base);
else
BEAST_EXPECT(expectedFee == drops(19));
env(jv, M("Call named hook with the wrong hook name"), HSFEE);
env.close();
// execute only non-named hook
@@ -2301,7 +2348,11 @@ public:
jv[jss::HookName] = "41424344";
auto expectedFee =
calculateBaseFee(*env.current(), *env.jt(jv).stx);
BEAST_EXPECT(expectedFee == drops(28));
if (env.current()->rules().enabled(featureHookFeeV2))
// TODO: add test for hook fee v2
BEAST_EXPECT(expectedFee > env.current()->fees().base);
else
BEAST_EXPECT(expectedFee == drops(28));
env(jv, M("Call named hook with the correct hook name"), HSFEE);
env.close();
// execute both named and non-named hooks
@@ -3580,6 +3631,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(accept, 9);
EXPECT_HOOK_COST(accept, 209, 21);
env(pay(bob, alice, XRP(1)), M("Test Accept Hook"), fee(XRP(1)));
env.close();
@@ -3602,6 +3654,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(rollback, 9);
EXPECT_HOOK_COST(rollback, 209, 21);
env(pay(bob, alice, XRP(1)),
M("Test Rollback Hook"),
@@ -3778,6 +3831,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 14);
EXPECT_HOOK_COST(hook, 114, 12);
}
// simple looping, c
@@ -3806,6 +3860,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 100);
EXPECT_HOOK_COST(hook, 2300, 230);
env(pay(bob, alice, XRP(1)), M("Test Loop 2"), fee(XRP(1)));
env.close();
@@ -3853,6 +3908,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 1944);
EXPECT_HOOK_COST(hook, 48244, 4825);
env(pay(bob, alice, XRP(1)), M("Test Loop 3"), fee(XRP(1)));
env.close();
@@ -3903,6 +3959,88 @@ public:
}
}
void
testGuardCost(FeatureBitset features)
{
testcase("Test guard cost");
using namespace jtx;
auto const alice = Account{"alice"};
auto const bob = Account{"bob"};
for (auto withCost : {true, false})
{
Env env{
*this,
withCost ? features | featureHookFeeV2
: features - featureHookFeeV2};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook_wasm = wasm[R"[test.hook](
#include <stdint.h>
extern int32_t _g (uint32_t id, uint32_t maxiter);
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t hook_account (uint32_t, uint32_t);
int64_t hook(uint32_t reserved)
{
uint8_t acc[20];
for (int i = 0; i < 10; ++i)
{
_g(1, 11);
for (int j = 0; j < 2; ++j)
{
_g(2, 30);
for (int k = 0; k < 5; ++k)
{
_g(3, 120);
hook_account(acc, 20);
}
for (int k = 0; k < 5; ++k)
{
_g(4, 120);
hook_account(acc, 20);
}
}
}
return accept(0,0,2);
}
)[test.hook]"];
HASH_WASM(hook);
env(ripple::test::jtx::hook(
alice, {{hso(hook_wasm, overrideFlag)}}, 0),
HSFEE);
env.close();
auto const hookDef = env.le(hook_keylet);
BEAST_EXPECT(hookDef);
if (withCost)
{
auto const hookCost = hookDef->getFieldU64(sfHookCost);
auto const expectedCost = 48244;
BEAST_EXPECTS(
hookCost == expectedCost,
"Hook cost mismatch: expected " + to_string(expectedCost) +
" got " + to_string(hookCost));
BEAST_EXPECT(!hookDef->isFieldPresent(sfFee));
}
else
{
auto const hookFee = hookDef->getFieldAmount(sfFee);
auto const expectedFee = XRPAmount{1944};
BEAST_EXPECTS(
hookFee == expectedFee,
"Hook fee mismatch: expected " + to_string(expectedFee) +
" got " + to_string(hookFee));
BEAST_EXPECT(!hookDef->isFieldPresent(sfHookCost));
}
}
}
void
test_emit(FeatureBitset features)
{
@@ -4243,6 +4381,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 342);
EXPECT_HOOK_COST(hook, 2542, 255);
Json::Value invoke;
invoke[jss::TransactionType] = "Invoke";
@@ -5044,13 +5183,21 @@ public:
etxn_reserve(1);
ASSERT(etxn_details((uint32_t)det, 116) == 116);
uint8_t expected1[49] = {
uint8_t expected1[16] = {
0xEDU, 0x20U, 0x2EU, 0x00U, 0x00U, 0x00U, 0x01U, 0x3DU, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU, 0xBCU, 0x09U, 0x95U,
0xFDU, 0x03U, 0x0FU, 0xF1U, 0x85U, 0xF4U, 0xECU, 0xACU, 0x94U, 0xDBU,
0x02U, 0x19U, 0x26U, 0xFEU, 0x58U, 0xD4U, 0x8DU, 0x48U, 0x21U, 0xCCU,
0x82U, 0x2EU, 0x4AU, 0x6AU, 0xC9U, 0xC8U, 0xC6U, 0x7CU, 0x88U
};
// 0x5B
// EmitParentTxnID 32bytes
uint8_t expected_emit_parent_txn_id[32] = {
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU
};
// 0x5CU
// EmitNonce 32bytes
uint8_t expected_emit_nonce[32] = {
@@ -5072,10 +5219,14 @@ public:
// current hook hash
ASSERT(hook_hash((uint32_t)expected_hook_hash, 32, -1) == 32);
for (int i = 0; GUARD(49), i < sizeof(expected1); ++i)
for (int i = 0; GUARD(16), i < sizeof(expected1); ++i)
ASSERT(det[i] == expected1[i]);
ASSERT(det[16] == 0x5BU);
// TODO: need to test EmitParentTxnID
// for (int i = 0; GUARD(32), i < sizeof(expected_emit_parent_txn_id); ++i)
// ASSERT(det[17 + i] == expected_emit_parent_txn_id[i]);
ASSERT(det[49] == 0x5CU);
// TODO: need to test this
// TODO: need to test EmitNonce
// for (int i = 0; GUARD(32), i < sizeof(expected_emit_nonce); ++i)
// ASSERT(det[50 + i] == expected_emit_nonce[i]);
ASSERT(det[82] == 0x5DU);
@@ -5093,7 +5244,8 @@ public:
M("set etxn_details"),
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 2436);
EXPECT_HOOK_FEE(hook, 1524);
EXPECT_HOOK_COST(hook, 8624, 863);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test etxn_details"), fee(XRP(1)));
@@ -5181,6 +5333,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 77);
EXPECT_HOOK_COST(hook, 1277, 128);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test etxn_fee_base"), fee(XRP(1)));
@@ -5264,6 +5417,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 11657);
EXPECT_HOOK_COST(hook, 90057, 9006);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test etxn_nonce"), fee(XRP(1)));
@@ -5316,6 +5470,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 69);
EXPECT_HOOK_COST(hook, 1269, 127);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test etxn_reserve"), fee(XRP(1)));
@@ -5359,6 +5514,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 20);
EXPECT_HOOK_COST(hook, 420, 42);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test fee_base"), fee(XRP(1)));
@@ -5494,8 +5650,9 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 583);
EXPECT_HOOK_COST(hook, 9683, 969);
env(pay(bob, alice, XRP(1)), M("test float_compare"), fee(XRP(1)));
env(pay(bob, alice, XRP(1)), M("test float_compare"), HSFEE);
env.close();
}
}
@@ -5696,6 +5853,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 1799);
EXPECT_HOOK_COST(hook, 15499, 1550);
env(pay(bob, alice, XRP(1)), M("test float_divide"), fee(XRP(1)));
env.close();
@@ -5828,8 +5986,9 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 1178);
EXPECT_HOOK_COST(hook, 17978, 1798);
env(pay(bob, alice, XRP(1)), M("test float_int"), fee(XRP(1)));
env(pay(bob, alice, XRP(1)), M("test float_int"), HSFEE);
env.close();
}
}
@@ -5919,6 +6078,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 329);
EXPECT_HOOK_COST(hook, 2929, 293);
env(pay(bob, alice, XRP(1)), M("test float_invert"), fee(XRP(1)));
env.close();
@@ -6005,6 +6165,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 388);
EXPECT_HOOK_COST(hook, 2988, 299);
env(pay(bob, alice, XRP(1)), M("test float_log"), fee(XRP(1)));
env.close();
@@ -6137,6 +6298,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 309);
EXPECT_HOOK_COST(hook, 5809, 581);
env(pay(bob, alice, XRP(1)), M("test float_mantissa"), fee(XRP(1)));
env.close();
@@ -6295,6 +6457,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 1683);
EXPECT_HOOK_COST(hook, 12583, 1259);
env(pay(bob, alice, XRP(1)), M("test float_mulratio"), fee(XRP(1)));
env.close();
@@ -6597,6 +6760,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 3180);
EXPECT_HOOK_COST(hook, 24680, 2468);
env(pay(bob, alice, XRP(1)), M("test float_multiply"), fee(XRP(1)));
env.close();
}
@@ -6670,6 +6834,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 105);
EXPECT_HOOK_COST(hook, 2505, 251);
env(pay(bob, alice, XRP(1)), M("test float_negate"), fee(XRP(1)));
env.close();
@@ -6714,6 +6879,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 20);
EXPECT_HOOK_COST(hook, 420, 42);
env(pay(bob, alice, XRP(1)), M("test float_one"), fee(XRP(1)));
env.close();
@@ -6795,6 +6961,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 257);
EXPECT_HOOK_COST(hook, 2257, 226);
env(pay(bob, alice, XRP(1)), M("test float_root"), fee(XRP(1)));
env.close();
@@ -6874,6 +7041,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 343);
EXPECT_HOOK_COST(hook, 5743, 575);
env(pay(bob, alice, XRP(1)), M("test float_set"), fee(XRP(1)));
env.close();
@@ -6990,6 +7158,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 296);
EXPECT_HOOK_COST(hook, 5796, 580);
env(pay(bob, alice, XRP(1)), M("test float_sign"), fee(XRP(1)));
env.close();
@@ -7198,6 +7367,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 920);
EXPECT_HOOK_COST(hook, 10320, 1032);
env(pay(bob, alice, XRP(1)), M("test float_sto"), fee(XRP(1)));
env.close();
@@ -7343,6 +7513,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 187);
EXPECT_HOOK_COST(hook, 3187, 319);
env(pay(bob, alice, XRP(1)), M("test float_sto_set"), fee(XRP(1)));
env.close();
@@ -7526,6 +7697,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 1735);
EXPECT_HOOK_COST(hook, 12035, 1204);
env(pay(bob, alice, XRP(1)), M("test float_sum"), fee(XRP(1)));
env.close();
@@ -7581,6 +7753,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 72);
EXPECT_HOOK_COST(hook, 1072, 108);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test hook_account"), fee(XRP(1)));
@@ -7708,6 +7881,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 54);
EXPECT_HOOK_COST(hook, 854, 86);
env(pay(bob, alice, XRP(1)), M("test hook_again"), fee(XRP(1)));
env.close();
@@ -7781,6 +7955,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 62);
EXPECT_HOOK_COST(hook, 862, 87);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test hook_hash"), fee(XRP(1)));
@@ -7843,6 +8018,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook2, 62);
EXPECT_HOOK_COST(hook, 862, 87);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test hook_hash 2"), fee(XRP(1)));
@@ -8020,6 +8196,7 @@ public:
env(jv, M("set hook_param"), HSFEE, ter(tesSUCCESS));
env.close();
EXPECT_HOOK_FEE(hook, 2412);
EXPECT_HOOK_COST(hook, 12612, 1262);
// invoke
env(pay(bob, alice, XRP(1)), M("test hook_param"), fee(XRP(1)));
@@ -8232,6 +8409,8 @@ public:
env.close();
EXPECT_HOOK_FEE(checker, 475);
EXPECT_HOOK_FEE(setter, 759);
EXPECT_HOOK_COST(checker, 2775, 278);
EXPECT_HOOK_COST(setter, 6859, 686);
// invoke
env(pay(bob, alice, XRP(1)), M("test hook_param_set"), fee(XRP(1)));
@@ -8277,6 +8456,7 @@ public:
ter(tesSUCCESS));
env.close();
EXPECT_HOOK_FEE(hook, 11);
EXPECT_HOOK_COST(hook, 311, 32);
// invoke the hooks
env(pay(bob, alice, XRP(1)), M("test hook_pos"), fee(XRP(1)));
@@ -8400,6 +8580,8 @@ public:
env.close();
EXPECT_HOOK_FEE(skip, 263);
EXPECT_HOOK_FEE(pos, 11);
EXPECT_HOOK_COST(skip, 4063, 407);
EXPECT_HOOK_COST(pos, 211, 22);
// invoke the hooks
{
@@ -8522,6 +8704,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 415);
EXPECT_HOOK_COST(hook, 4015, 402);
env(pay(bob, alice, XRP(1)), M("test ledger_keylet"), fee(XRP(1)));
env.close();
@@ -8573,6 +8756,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 59);
EXPECT_HOOK_COST(hook, 859, 86);
for (uint32_t i = 0; i < 3; ++i)
{
@@ -8636,6 +8820,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 11);
EXPECT_HOOK_COST(hook, 311, 32);
// invoke the hook a few times
for (uint32_t i = 0; i < 3; ++i)
@@ -8721,6 +8906,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 74);
EXPECT_HOOK_COST(hook, 1074, 108);
// invoke the hook
auto const seq =
@@ -8806,6 +8992,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 11);
EXPECT_HOOK_COST(hook, 311, 32);
// invoke the hook a few times
for (uint32_t i = 0; i < 3; ++i)
@@ -8905,6 +9092,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 139);
EXPECT_HOOK_COST(hook, 2039, 204);
env(pay(bob, alice, XRP(1)), M("test meta_slot"), fee(XRP(1)));
env.close();
@@ -9030,6 +9218,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 3245);
EXPECT_HOOK_COST(hook, 31445, 3145);
auto checkResult =
[this](auto const& meta, uint64_t expectedCode) -> void {
@@ -9124,6 +9313,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 732);
EXPECT_HOOK_COST(hook, 4732, 474);
// invoke the hook
env(pay(alice, bob, XRP(1)), M("test otxn_field"), fee(XRP(1)));
@@ -9209,6 +9399,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 1077);
EXPECT_HOOK_COST(hook, 6277, 628);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test otxn_id"), fee(XRP(1)));
@@ -9293,6 +9484,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 6442);
EXPECT_HOOK_COST(hook, 62742, 6275);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test otxn_slot"), fee(XRP(1)));
@@ -9355,6 +9547,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 51);
EXPECT_HOOK_COST(hook, 951, 96);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test otxn_type"), fee(XRP(1)));
@@ -9473,6 +9666,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 2412);
EXPECT_HOOK_COST(hook, 12612, 1262);
// invoke
Json::Value invoke;
@@ -9606,6 +9800,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 232);
EXPECT_HOOK_COST(hook, 3232, 324);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot"), fee(XRP(1)));
@@ -9666,6 +9861,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 83);
EXPECT_HOOK_COST(hook, 1483, 149);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_clear"), fee(XRP(1)));
@@ -9733,6 +9929,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 97);
EXPECT_HOOK_COST(hook, 1697, 170);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_count"), fee(XRP(1)));
@@ -9810,6 +10007,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 112);
EXPECT_HOOK_COST(hook, 1912, 192);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_float"), fee(XRP(1)));
@@ -9925,6 +10123,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 11645);
EXPECT_HOOK_COST(hook, 118745, 11875);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_set"), fee(XRP(1)));
@@ -9999,7 +10198,7 @@ public:
ASSERT(s > 0);
// pull the object out into a buffer, check the number of bytes written is correct
uint8_t buf[4096];
uint8_t buf[8210]; // ltSkip(3)+FLS(6)+LLS(6)+ Hashes(3+8,192(32*256))
ASSERT(slot(SBUF(buf), 1) == s);
// check the object is valid
@@ -10017,6 +10216,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 114);
EXPECT_HOOK_COST(hook, 1814, 182);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_size"), fee(XRP(1)));
@@ -10140,6 +10340,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 6212);
EXPECT_HOOK_COST(hook, 61112, 6112);
// generate an array of memos to attach
Json::Value jv;
@@ -10262,6 +10463,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 6109);
EXPECT_HOOK_COST(hook, 60909, 6091);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_subfield"), fee(XRP(1)));
@@ -10403,6 +10605,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 284);
EXPECT_HOOK_COST(hook, 4384, 439);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_type"), fee(XRP(1)));
@@ -10490,6 +10693,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 2254);
EXPECT_HOOK_COST(hook, 11254, 1126);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test state"), fee(XRP(1)));
@@ -10549,6 +10753,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 2134);
EXPECT_HOOK_COST(hook, 9534, 954);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test state 2"), fee(XRP(1)));
@@ -10618,6 +10823,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 72);
EXPECT_HOOK_COST(hook, 872, 88);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test state_foreign"), fee(XRP(1)));
@@ -10711,6 +10917,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 2408);
EXPECT_HOOK_COST(hook, 11808, 1181);
// invoke the hook
@@ -10802,6 +11009,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(ns_maxHook, 103);
EXPECT_HOOK_COST(ns_maxHook, 1003, 101);
// invoke the hook
for (uint32_t i = 0; i < 255; ++i)
@@ -10836,7 +11044,10 @@ public:
testcase("Test state_foreign_set");
using namespace jtx;
Env env{*this, features};
Env env{
*this,
network::makeNetworkConfig(21137, "10", "200000000", "50000000"),
features};
auto const david = Account("david"); // grantee generic
auto const cho = Account{"cho"}; // invoker
@@ -10976,6 +11187,7 @@ public:
env(json, M("set state_foreign_set"), HSFEE);
env.close();
EXPECT_HOOK_FEE(grantor, 103);
EXPECT_HOOK_COST(grantor, 903, 91);
}
// install the grantee hook on bob
@@ -10986,6 +11198,7 @@ public:
env(json, M("set state_foreign_set 2"), HSFEE);
env.close();
EXPECT_HOOK_FEE(grantee, 234);
EXPECT_HOOK_COST(grantee, 2634, 264);
}
auto const aliceid = Account("alice").id();
@@ -11248,6 +11461,7 @@ public:
env(json, M("set state_foreign_set 12"), HSFEE);
env.close();
EXPECT_HOOK_FEE(exhaustion, 10582);
EXPECT_HOOK_COST(exhaustion, 88282, 8829);
}
// now invoke repeatedly until exhaustion is reached
@@ -11885,7 +12099,10 @@ public:
testcase("Test state_set");
using namespace jtx;
Env env{*this, features};
Env env{
*this,
network::makeNetworkConfig(21137, "10", "200000000", "50000000"),
features};
auto const bob = Account{"bob"};
auto const alice = Account{"alice"};
@@ -11981,6 +12198,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 143);
EXPECT_HOOK_COST(hook, 1943, 195);
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
@@ -12108,6 +12326,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 85);
EXPECT_HOOK_COST(hook, 685, 69);
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
@@ -12309,6 +12528,8 @@ public:
env.close();
EXPECT_HOOK_FEE(hook, 82);
EXPECT_HOOK_FEE(hook2, 525);
EXPECT_HOOK_COST(hook, 682, 69);
EXPECT_HOOK_COST(hook2, 2725, 273);
// two hooks + two state objects = 4
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 4);
@@ -12386,6 +12607,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook3, 560);
EXPECT_HOOK_COST(hook3, 3060, 306);
// invoke the hook with cho (rollback after alice's hooks have
// executed)
@@ -12493,6 +12715,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 52);
EXPECT_HOOK_COST(hook, 552, 56);
// invoke from alice to cho, this will cause a rollback, however the
// hook state should still be updated because the hook specified
@@ -12606,6 +12829,7 @@ public:
env(json, M("set state_set 6"), HSFEE);
env.close();
EXPECT_HOOK_FEE(exhaustion, 54114);
EXPECT_HOOK_COST(exhaustion, 355114, 35512);
}
// now invoke repeatedly until exhaustion is reached
@@ -12759,6 +12983,7 @@ public:
env(jv, M("Create scaled state hook"), HSFEE, ter(tesSUCCESS));
env.close();
EXPECT_HOOK_FEE(scaled_state, 227);
EXPECT_HOOK_COST(scaled_state, 2927, 293);
BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 1);
BEAST_EXPECT(!env.le(gary)->isFieldPresent(sfHookStateCount));
@@ -12865,6 +13090,7 @@ public:
env(ripple::test::jtx::hook(hank, {{jv}}, 0), HSFEE);
env.close();
EXPECT_HOOK_FEE(extended_state_reserve_hook, 95);
EXPECT_HOOK_COST(extended_state_reserve_hook, 1495, 150);
Json::Value jv1 = noop(hank);
jv1[sfHookStateScale.fieldName] = 8;
@@ -13086,6 +13312,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 15024);
EXPECT_HOOK_COST(hook, 73424, 7343);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_emplace"), fee(XRP(1)));
@@ -13147,6 +13374,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 36);
EXPECT_HOOK_COST(hook, 536, 54);
// invoke the hook
env(pay(bob, alice, XRP(1)),
@@ -13305,6 +13533,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 10021);
EXPECT_HOOK_COST(hook, 46721, 4673);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_erase"), fee(XRP(1)));
@@ -13383,6 +13612,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 95);
EXPECT_HOOK_COST(hook, 1495, 150);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_subarray"), fee(XRP(1)));
@@ -13449,6 +13679,8 @@ public:
M("test sto_subarray"),
fee(XRP(1)));
env.close();
EXPECT_HOOK_FEE(hook, 19);
EXPECT_HOOK_COST(hook, 419, 42);
auto const meta = env.meta();
BEAST_REQUIRE(meta);
@@ -13578,6 +13810,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 123);
EXPECT_HOOK_COST(hook, 1923, 193);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_subfield"), fee(XRP(1)));
@@ -13665,6 +13898,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 130);
EXPECT_HOOK_COST(hook, 1730, 173);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_validate"), fee(XRP(1)));
@@ -13936,6 +14170,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 103);
EXPECT_HOOK_COST(hook, 1503, 151);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test trace"), fee(XRP(1)));
@@ -13982,6 +14217,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 37);
EXPECT_HOOK_COST(hook, 637, 64);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test trace_float"), fee(XRP(1)));
@@ -14028,6 +14264,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 37);
EXPECT_HOOK_COST(hook, 637, 64);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test trace_num"), fee(XRP(1)));
@@ -14298,6 +14535,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 3101);
EXPECT_HOOK_COST(hook, 9901, 991);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test util_accid"), fee(XRP(1)));
@@ -14981,6 +15219,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 1786);
EXPECT_HOOK_COST(hook, 14286, 1429);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test util_keylet"), fee(XRP(1)));
@@ -15436,6 +15675,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 4279);
EXPECT_HOOK_COST(hook, 11079, 1108);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test util_raddr"), fee(XRP(1)));
@@ -15811,6 +16051,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 2875);
EXPECT_HOOK_COST(hook, 9675, 968);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test util_sha512h"), fee(XRP(1)));
@@ -15927,6 +16168,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 230);
EXPECT_HOOK_COST(hook, 3030, 303);
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test util_verify"), fee(XRP(1)));
@@ -16322,6 +16564,7 @@ public:
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 755);
EXPECT_HOOK_COST(hook, 4655, 466);
}
else if (i == 2)
{
@@ -16456,7 +16699,7 @@ public:
// invoke the hook
env(pay(caller, acc, XRP(1)),
M("test hookcanemit 3"),
fee(XRP(1)));
fee(XRP(10)));
env.close();
auto meta = env.meta();
@@ -16514,6 +16757,7 @@ public:
test_exit_out_of_bounds(features);
testGuards(features);
testGuardCost(features);
test_emit(features); //
test_emit_failure_callback(features);
@@ -16611,13 +16855,16 @@ public:
using namespace test::jtx;
static FeatureBitset const all{supported_amendments()};
static std::array<FeatureBitset, 6> const feats{
static std::array<FeatureBitset, 7> const feats{
all,
all - fixNSDelete,
all - fixNSDelete - fixPageCap,
all - fixNSDelete - fixPageCap - featureHookCanEmit,
all - fixNSDelete - fixPageCap - featureExtendedHookState,
all - featureNamedHooks,
all - featureHookFeeV2,
all - fixNSDelete - featureHookFeeV2,
all - fixNSDelete - fixPageCap - featureHookFeeV2,
all - fixNSDelete - fixPageCap - featureHookCanEmit -
featureHookFeeV2,
all - fixNSDelete - fixPageCap - featureExtendedHookState -
featureHookFeeV2,
all - featureNamedHooks - featureHookFeeV2,
};
if (BEAST_EXPECT(instance < feats.size()))
@@ -16880,7 +17127,8 @@ SETHOOK_TEST(1, false)
SETHOOK_TEST(2, false)
SETHOOK_TEST(3, false)
SETHOOK_TEST(4, false)
SETHOOK_TEST(5, true)
SETHOOK_TEST(5, false)
SETHOOK_TEST(6, true)
BEAST_DEFINE_TESTSUITE_PRIO(SetHook0, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook1, app, ripple, 2);
@@ -16888,6 +17136,7 @@ BEAST_DEFINE_TESTSUITE_PRIO(SetHook2, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook3, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook4, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook5, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook6, app, ripple, 2);
} // namespace test
} // namespace ripple
#undef M
+259 -141
View File
@@ -594,6 +594,109 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x05U, 0x0BU,
}},
{R"[test.hook](
#include <stdint.h>
extern int32_t _g (uint32_t id, uint32_t maxiter);
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t hook_account (uint32_t, uint32_t);
int64_t hook(uint32_t reserved)
{
uint8_t acc[20];
for (int i = 0; i < 10; ++i)
{
_g(1, 11);
for (int j = 0; j < 2; ++j)
{
_g(2, 30);
for (int k = 0; k < 5; ++k)
{
_g(3, 120);
hook_account(acc, 20);
}
for (int k = 0; k < 5; ++k)
{
_g(4, 120);
hook_account(acc, 20);
}
}
}
return accept(0,0,2);
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x19U,
0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x02U, 0x7FU,
0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU,
0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, 0x2AU, 0x03U, 0x03U, 0x65U,
0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x0CU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U, 0x63U, 0x63U,
0x6FU, 0x75U, 0x6EU, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U,
0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U,
0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x21U,
0x05U, 0x7FU, 0x01U, 0x41U, 0x80U, 0x88U, 0x04U, 0x0BU, 0x7FU, 0x00U,
0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU,
0x7FU, 0x00U, 0x41U, 0x80U, 0x88U, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U,
0x80U, 0x08U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU,
0x6BU, 0x00U, 0x03U, 0x0AU, 0xB3U, 0x84U, 0x00U, 0x01U, 0xAFU, 0x84U,
0x00U, 0x02U, 0x02U, 0x7FU, 0x01U, 0x7EU, 0x23U, 0x80U, 0x80U, 0x80U,
0x80U, 0x00U, 0x41U, 0x20U, 0x6BU, 0x22U, 0x01U, 0x24U, 0x80U, 0x80U,
0x80U, 0x80U, 0x00U, 0x41U, 0x0AU, 0x21U, 0x02U, 0x03U, 0x40U, 0x41U,
0x01U, 0x41U, 0x0BU, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x41U, 0x02U, 0x41U, 0x1EU, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x41U, 0x03U, 0x41U, 0xF8U, 0x00U, 0x10U, 0x80U, 0x80U, 0x80U,
0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x14U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x03U, 0x41U, 0xF8U, 0x00U, 0x10U,
0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x14U,
0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x03U, 0x41U,
0xF8U, 0x00U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U,
0x01U, 0x41U, 0x14U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x41U, 0x03U, 0x41U, 0xF8U, 0x00U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x14U, 0x10U, 0x81U, 0x80U, 0x80U,
0x80U, 0x00U, 0x1AU, 0x41U, 0x03U, 0x41U, 0xF8U, 0x00U, 0x10U, 0x80U,
0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x14U, 0x10U,
0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x04U, 0x41U, 0xF8U,
0x00U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U,
0x41U, 0x14U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U,
0x04U, 0x41U, 0xF8U, 0x00U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x20U, 0x01U, 0x41U, 0x14U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x41U, 0x04U, 0x41U, 0xF8U, 0x00U, 0x10U, 0x80U, 0x80U,
0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x14U, 0x10U, 0x81U,
0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x04U, 0x41U, 0xF8U, 0x00U,
0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U,
0x14U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x04U,
0x41U, 0xF8U, 0x00U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x20U, 0x01U, 0x41U, 0x14U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x41U, 0x02U, 0x41U, 0x1EU, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x41U, 0x03U, 0x41U, 0xF8U, 0x00U, 0x10U, 0x80U, 0x80U,
0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x14U, 0x10U, 0x81U,
0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x03U, 0x41U, 0xF8U, 0x00U,
0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U,
0x14U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x03U,
0x41U, 0xF8U, 0x00U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x20U, 0x01U, 0x41U, 0x14U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x41U, 0x03U, 0x41U, 0xF8U, 0x00U, 0x10U, 0x80U, 0x80U, 0x80U,
0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x14U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x03U, 0x41U, 0xF8U, 0x00U, 0x10U,
0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x14U,
0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x04U, 0x41U,
0xF8U, 0x00U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U,
0x01U, 0x41U, 0x14U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x41U, 0x04U, 0x41U, 0xF8U, 0x00U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x14U, 0x10U, 0x81U, 0x80U, 0x80U,
0x80U, 0x00U, 0x1AU, 0x41U, 0x04U, 0x41U, 0xF8U, 0x00U, 0x10U, 0x80U,
0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x14U, 0x10U,
0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x04U, 0x41U, 0xF8U,
0x00U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U,
0x41U, 0x14U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U,
0x04U, 0x41U, 0xF8U, 0x00U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x20U, 0x01U, 0x41U, 0x14U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x20U, 0x02U, 0x41U, 0x7FU, 0x6AU, 0x22U, 0x02U, 0x0DU,
0x00U, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x02U, 0x10U, 0x82U,
0x80U, 0x80U, 0x80U, 0x00U, 0x21U, 0x03U, 0x20U, 0x01U, 0x41U, 0x20U,
0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x03U, 0x0BU,
}},
{R"[test.hook](
#include <stdint.h>
extern int32_t _g(uint32_t, uint32_t);
@@ -1669,13 +1772,21 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
etxn_reserve(1);
ASSERT(etxn_details((uint32_t)det, 116) == 116);
uint8_t expected1[49] = {
uint8_t expected1[16] = {
0xEDU, 0x20U, 0x2EU, 0x00U, 0x00U, 0x00U, 0x01U, 0x3DU, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU, 0xBCU, 0x09U, 0x95U,
0xFDU, 0x03U, 0x0FU, 0xF1U, 0x85U, 0xF4U, 0xECU, 0xACU, 0x94U, 0xDBU,
0x02U, 0x19U, 0x26U, 0xFEU, 0x58U, 0xD4U, 0x8DU, 0x48U, 0x21U, 0xCCU,
0x82U, 0x2EU, 0x4AU, 0x6AU, 0xC9U, 0xC8U, 0xC6U, 0x7CU, 0x88U
};
// 0x5B
// EmitParentTxnID 32bytes
uint8_t expected_emit_parent_txn_id[32] = {
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU
};
// 0x5CU
// EmitNonce 32bytes
uint8_t expected_emit_nonce[32] = {
@@ -1697,10 +1808,14 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
// current hook hash
ASSERT(hook_hash((uint32_t)expected_hook_hash, 32, -1) == 32);
for (int i = 0; GUARD(49), i < sizeof(expected1); ++i)
for (int i = 0; GUARD(16), i < sizeof(expected1); ++i)
ASSERT(det[i] == expected1[i]);
ASSERT(det[16] == 0x5BU);
// TODO: need to test EmitParentTxnID
// for (int i = 0; GUARD(32), i < sizeof(expected_emit_parent_txn_id); ++i)
// ASSERT(det[17 + i] == expected_emit_parent_txn_id[i]);
ASSERT(det[49] == 0x5CU);
// TODO: need to test this
// TODO: need to test EmitNonce
// for (int i = 0; GUARD(32), i < sizeof(expected_emit_nonce); ++i)
// ASSERT(det[50 + i] == expected_emit_nonce[i]);
ASSERT(det[82] == 0x5DU);
@@ -1727,11 +1842,11 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x68U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U,
0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x03U,
0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x21U, 0x05U, 0x7FU, 0x01U,
0x41U, 0xD0U, 0x8BU, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xC6U, 0x0BU,
0x41U, 0xC0U, 0x8BU, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xB6U, 0x0BU,
0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U,
0xD0U, 0x8BU, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU,
0xC0U, 0x8BU, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU,
0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x06U,
0x0AU, 0xF4U, 0x84U, 0x00U, 0x01U, 0xF0U, 0x84U, 0x00U, 0x02U, 0x03U,
0x0AU, 0x97U, 0x85U, 0x00U, 0x01U, 0x93U, 0x85U, 0x00U, 0x02U, 0x03U,
0x7FU, 0x01U, 0x7EU, 0x23U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U,
0xA0U, 0x01U, 0x6BU, 0x22U, 0x01U, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U,
0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U,
@@ -1760,87 +1875,89 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x7FU, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x42U, 0x7FU, 0x37U, 0x03U,
0x08U, 0x20U, 0x01U, 0x42U, 0x7FU, 0x37U, 0x03U, 0x00U, 0x02U, 0x40U,
0x20U, 0x01U, 0x41U, 0x20U, 0x41U, 0x7FU, 0x10U, 0x84U, 0x80U, 0x80U,
0x80U, 0x00U, 0x42U, 0x20U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xA1U, 0x8AU,
0x80U, 0x80U, 0x00U, 0x41U, 0x36U, 0x42U, 0x3AU, 0x10U, 0x82U, 0x80U,
0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0xBCU, 0x80U, 0x80U, 0x80U,
0x78U, 0x41U, 0x32U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x41U, 0x00U, 0x21U, 0x02U, 0x03U, 0x40U, 0x41U, 0xBCU, 0x80U, 0x80U,
0x80U, 0x78U, 0x41U, 0x32U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU, 0x20U, 0x02U,
0x6AU, 0x2DU, 0x00U, 0x00U, 0x20U, 0x02U, 0x41U, 0xF0U, 0x89U, 0x80U,
0x80U, 0x00U, 0x6AU, 0x2DU, 0x00U, 0x00U, 0x46U, 0x0DU, 0x00U, 0x41U,
0xD7U, 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x17U, 0x42U, 0x3DU, 0x10U,
0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x20U, 0x02U, 0x41U,
0x01U, 0x6AU, 0x22U, 0x02U, 0x41U, 0x31U, 0x47U, 0x0DU, 0x00U, 0x0BU,
0x02U, 0x40U, 0x20U, 0x01U, 0x2DU, 0x00U, 0x51U, 0x41U, 0xDCU, 0x00U,
0x46U, 0x0DU, 0x00U, 0x41U, 0xEEU, 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U,
0x11U, 0x42U, 0x3EU, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x2DU, 0x00U, 0x72U, 0x41U, 0xDDU,
0x00U, 0x46U, 0x0DU, 0x00U, 0x41U, 0xFFU, 0x8AU, 0x80U, 0x80U, 0x00U,
0x41U, 0x11U, 0x42U, 0xC2U, 0x00U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x0BU, 0x41U, 0xC3U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U,
0x21U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U,
0x41U, 0xF3U, 0x00U, 0x6AU, 0x21U, 0x03U, 0x41U, 0x00U, 0x21U, 0x02U,
0x03U, 0x40U, 0x41U, 0xC3U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U,
0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x02U, 0x40U, 0x20U,
0x03U, 0x20U, 0x02U, 0x6AU, 0x2DU, 0x00U, 0x00U, 0x20U, 0x01U, 0x20U,
0x02U, 0x6AU, 0x2DU, 0x00U, 0x00U, 0x46U, 0x0DU, 0x00U, 0x41U, 0x90U,
0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x25U, 0x42U, 0xC4U, 0x00U, 0x10U,
0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x20U, 0x02U, 0x41U,
0x01U, 0x6AU, 0x22U, 0x02U, 0x41U, 0x20U, 0x47U, 0x0DU, 0x00U, 0x0BU,
0x02U, 0x40U, 0x20U, 0x01U, 0x2DU, 0x00U, 0x93U, 0x01U, 0x41U, 0xE1U,
0x01U, 0x46U, 0x0DU, 0x00U, 0x41U, 0xB5U, 0x8BU, 0x80U, 0x80U, 0x00U,
0x41U, 0x11U, 0x42U, 0xC5U, 0x00U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U,
0x85U, 0x80U, 0x80U, 0x80U, 0x00U, 0x21U, 0x04U, 0x20U, 0x01U, 0x41U,
0xA0U, 0x01U, 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U,
0x04U, 0x0BU, 0x0BU, 0xCEU, 0x03U, 0x01U, 0x00U, 0x41U, 0x80U, 0x08U,
0x0BU, 0xC6U, 0x03U, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U,
0x74U, 0x61U, 0x69U, 0x6CU, 0x73U, 0x28U, 0x31U, 0x30U, 0x30U, 0x30U,
0x30U, 0x30U, 0x30U, 0x2CU, 0x20U, 0x31U, 0x31U, 0x36U, 0x29U, 0x20U,
0x3DU, 0x3DU, 0x20U, 0x4FU, 0x55U, 0x54U, 0x5FU, 0x4FU, 0x46U, 0x5FU,
0x42U, 0x4FU, 0x55U, 0x4EU, 0x44U, 0x53U, 0x00U, 0x65U, 0x74U, 0x78U,
0x80U, 0x00U, 0x42U, 0x20U, 0x51U, 0x0DU, 0x00U, 0x41U, 0x80U, 0x8AU,
0x80U, 0x80U, 0x00U, 0x41U, 0x36U, 0x42U, 0xC2U, 0x00U, 0x10U, 0x82U,
0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0xC4U, 0x80U, 0x80U,
0x80U, 0x78U, 0x41U, 0x11U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x41U, 0x00U, 0x21U, 0x02U, 0x03U, 0x40U, 0x41U, 0xC4U, 0x80U,
0x80U, 0x80U, 0x78U, 0x41U, 0x11U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU, 0x20U,
0x02U, 0x6AU, 0x2DU, 0x00U, 0x00U, 0x20U, 0x02U, 0x41U, 0xF0U, 0x89U,
0x80U, 0x80U, 0x00U, 0x6AU, 0x2DU, 0x00U, 0x00U, 0x46U, 0x0DU, 0x00U,
0x41U, 0xB6U, 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x17U, 0x42U, 0xC5U,
0x00U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x20U,
0x02U, 0x41U, 0x01U, 0x6AU, 0x22U, 0x02U, 0x41U, 0x10U, 0x47U, 0x0DU,
0x00U, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x2DU, 0x00U, 0x30U, 0x41U,
0xDBU, 0x00U, 0x46U, 0x0DU, 0x00U, 0x41U, 0xCDU, 0x8AU, 0x80U, 0x80U,
0x00U, 0x41U, 0x11U, 0x42U, 0xC6U, 0x00U, 0x10U, 0x82U, 0x80U, 0x80U,
0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x2DU, 0x00U,
0x51U, 0x41U, 0xDCU, 0x00U, 0x46U, 0x0DU, 0x00U, 0x41U, 0xDEU, 0x8AU,
0x80U, 0x80U, 0x00U, 0x41U, 0x11U, 0x42U, 0xCAU, 0x00U, 0x10U, 0x82U,
0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U,
0x2DU, 0x00U, 0x72U, 0x41U, 0xDDU, 0x00U, 0x46U, 0x0DU, 0x00U, 0x41U,
0xEFU, 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x11U, 0x42U, 0xCEU, 0x00U,
0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0xCFU,
0x80U, 0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U, 0x80U, 0x80U, 0x80U,
0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0xF3U, 0x00U, 0x6AU, 0x21U,
0x03U, 0x41U, 0x00U, 0x21U, 0x02U, 0x03U, 0x40U, 0x41U, 0xCFU, 0x80U,
0x80U, 0x80U, 0x78U, 0x41U, 0x21U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x02U, 0x40U, 0x20U, 0x03U, 0x20U, 0x02U, 0x6AU, 0x2DU,
0x00U, 0x00U, 0x20U, 0x01U, 0x20U, 0x02U, 0x6AU, 0x2DU, 0x00U, 0x00U,
0x46U, 0x0DU, 0x00U, 0x41U, 0x80U, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U,
0x25U, 0x42U, 0xD0U, 0x00U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x0BU, 0x20U, 0x02U, 0x41U, 0x01U, 0x6AU, 0x22U, 0x02U, 0x41U,
0x20U, 0x47U, 0x0DU, 0x00U, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x2DU,
0x00U, 0x93U, 0x01U, 0x41U, 0xE1U, 0x01U, 0x46U, 0x0DU, 0x00U, 0x41U,
0xA5U, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x11U, 0x42U, 0xD1U, 0x00U,
0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U,
0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x85U, 0x80U, 0x80U, 0x80U, 0x00U,
0x21U, 0x04U, 0x20U, 0x01U, 0x41U, 0xA0U, 0x01U, 0x6AU, 0x24U, 0x80U,
0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x04U, 0x0BU, 0x0BU, 0xBEU, 0x03U,
0x01U, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0xB6U, 0x03U, 0x65U, 0x74U,
0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, 0x69U, 0x6CU, 0x73U,
0x28U, 0x31U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x2CU, 0x20U,
0x31U, 0x31U, 0x36U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x4FU, 0x55U,
0x54U, 0x5FU, 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U, 0x4EU, 0x44U,
0x53U, 0x00U, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U,
0x61U, 0x69U, 0x6CU, 0x73U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x31U, 0x30U,
0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U,
0x4FU, 0x55U, 0x54U, 0x5FU, 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U,
0x4EU, 0x44U, 0x53U, 0x00U, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U,
0x65U, 0x74U, 0x61U, 0x69U, 0x6CU, 0x73U, 0x28U, 0x28U, 0x75U, 0x69U,
0x6EU, 0x74U, 0x33U, 0x32U, 0x5FU, 0x74U, 0x29U, 0x64U, 0x65U, 0x74U,
0x2CU, 0x20U, 0x31U, 0x31U, 0x35U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U,
0x54U, 0x4FU, 0x4FU, 0x5FU, 0x53U, 0x4DU, 0x41U, 0x4CU, 0x4CU, 0x00U,
0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, 0x69U,
0x6CU, 0x73U, 0x28U, 0x28U, 0x75U, 0x69U, 0x6EU, 0x74U, 0x33U, 0x32U,
0x5FU, 0x74U, 0x29U, 0x64U, 0x65U, 0x74U, 0x2CU, 0x20U, 0x31U, 0x31U,
0x36U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x50U, 0x52U, 0x45U, 0x52U,
0x45U, 0x51U, 0x55U, 0x49U, 0x53U, 0x49U, 0x54U, 0x45U, 0x5FU, 0x4EU,
0x4FU, 0x54U, 0x5FU, 0x4DU, 0x45U, 0x54U, 0x00U, 0x65U, 0x74U, 0x78U,
0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, 0x69U, 0x6CU, 0x73U, 0x28U,
0x30U, 0x2CU, 0x20U, 0x31U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U,
0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x4FU, 0x55U, 0x54U, 0x5FU, 0x4FU,
0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U, 0x4EU, 0x44U, 0x53U, 0x00U, 0x65U,
0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, 0x69U, 0x6CU,
0x73U, 0x28U, 0x28U, 0x75U, 0x69U, 0x6EU, 0x74U, 0x33U, 0x32U, 0x5FU,
0x74U, 0x29U, 0x64U, 0x65U, 0x74U, 0x2CU, 0x20U, 0x31U, 0x31U, 0x35U,
0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x53U,
0x4DU, 0x41U, 0x4CU, 0x4CU, 0x00U, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU,
0x64U, 0x65U, 0x74U, 0x61U, 0x69U, 0x6CU, 0x73U, 0x28U, 0x28U, 0x75U,
0x69U, 0x6EU, 0x74U, 0x33U, 0x32U, 0x5FU, 0x74U, 0x29U, 0x64U, 0x65U,
0x74U, 0x2CU, 0x20U, 0x31U, 0x31U, 0x36U, 0x29U, 0x20U, 0x3DU, 0x3DU,
0x20U, 0x50U, 0x52U, 0x45U, 0x52U, 0x45U, 0x51U, 0x55U, 0x49U, 0x53U,
0x49U, 0x54U, 0x45U, 0x5FU, 0x4EU, 0x4FU, 0x54U, 0x5FU, 0x4DU, 0x45U,
0x54U, 0x00U, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U,
0x61U, 0x69U, 0x6CU, 0x73U, 0x28U, 0x28U, 0x75U, 0x69U, 0x6EU, 0x74U,
0x33U, 0x32U, 0x5FU, 0x74U, 0x29U, 0x64U, 0x65U, 0x74U, 0x2CU, 0x20U,
0x31U, 0x31U, 0x36U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x31U, 0x31U,
0x36U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0xEDU, 0x20U, 0x2EU, 0x00U, 0x00U, 0x00U, 0x01U,
0x3DU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU,
0xBCU, 0x09U, 0x95U, 0xFDU, 0x03U, 0x0FU, 0xF1U, 0x85U, 0xF4U, 0xECU,
0xACU, 0x94U, 0xDBU, 0x02U, 0x19U, 0x26U, 0xFEU, 0x58U, 0xD4U, 0x8DU,
0x48U, 0x21U, 0xCCU, 0x82U, 0x2EU, 0x4AU, 0x6AU, 0xC9U, 0xC8U, 0xC6U,
0x7CU, 0x88U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U, 0x61U, 0x73U,
0x68U, 0x28U, 0x28U, 0x75U, 0x69U, 0x6EU, 0x74U, 0x33U, 0x32U, 0x5FU,
0x74U, 0x29U, 0x65U, 0x78U, 0x70U, 0x65U, 0x63U, 0x74U, 0x65U, 0x64U,
0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U, 0x61U, 0x73U, 0x68U,
0x2CU, 0x20U, 0x33U, 0x32U, 0x2CU, 0x20U, 0x2DU, 0x31U, 0x29U, 0x20U,
0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, 0x64U, 0x65U, 0x74U, 0x5BU,
0x69U, 0x5DU, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x65U, 0x78U, 0x70U, 0x65U,
0x63U, 0x74U, 0x65U, 0x64U, 0x31U, 0x5BU, 0x69U, 0x5DU, 0x00U, 0x64U,
0x65U, 0x74U, 0x5BU, 0x34U, 0x39U, 0x5DU, 0x20U, 0x3DU, 0x3DU, 0x20U,
0x30U, 0x78U, 0x35U, 0x43U, 0x55U, 0x00U, 0x64U, 0x65U, 0x74U, 0x5BU,
0x38U, 0x32U, 0x5DU, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x78U, 0x35U,
0x44U, 0x55U, 0x00U, 0x64U, 0x65U, 0x74U, 0x5BU, 0x38U, 0x33U, 0x20U,
0x2BU, 0x20U, 0x69U, 0x5DU, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x65U, 0x78U,
0x70U, 0x65U, 0x63U, 0x74U, 0x65U, 0x64U, 0x5FU, 0x68U, 0x6FU, 0x6FU,
0x6BU, 0x5FU, 0x68U, 0x61U, 0x73U, 0x68U, 0x5BU, 0x69U, 0x5DU, 0x00U,
0x64U, 0x65U, 0x74U, 0x5BU, 0x31U, 0x31U, 0x35U, 0x5DU, 0x20U, 0x3DU,
0x3DU, 0x20U, 0x30U, 0x78U, 0x45U, 0x31U, 0x00U,
0x28U, 0x75U, 0x69U, 0x6EU, 0x74U, 0x33U, 0x32U, 0x5FU, 0x74U, 0x29U,
0x64U, 0x65U, 0x74U, 0x2CU, 0x20U, 0x31U, 0x31U, 0x36U, 0x29U, 0x20U,
0x3DU, 0x3DU, 0x20U, 0x31U, 0x31U, 0x36U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0xEDU, 0x20U,
0x2EU, 0x00U, 0x00U, 0x00U, 0x01U, 0x3DU, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x01U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U,
0x61U, 0x73U, 0x68U, 0x28U, 0x28U, 0x75U, 0x69U, 0x6EU, 0x74U, 0x33U,
0x32U, 0x5FU, 0x74U, 0x29U, 0x65U, 0x78U, 0x70U, 0x65U, 0x63U, 0x74U,
0x65U, 0x64U, 0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U, 0x61U,
0x73U, 0x68U, 0x2CU, 0x20U, 0x33U, 0x32U, 0x2CU, 0x20U, 0x2DU, 0x31U,
0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, 0x64U, 0x65U,
0x74U, 0x5BU, 0x69U, 0x5DU, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x65U, 0x78U,
0x70U, 0x65U, 0x63U, 0x74U, 0x65U, 0x64U, 0x31U, 0x5BU, 0x69U, 0x5DU,
0x00U, 0x64U, 0x65U, 0x74U, 0x5BU, 0x31U, 0x36U, 0x5DU, 0x20U, 0x3DU,
0x3DU, 0x20U, 0x30U, 0x78U, 0x35U, 0x42U, 0x55U, 0x00U, 0x64U, 0x65U,
0x74U, 0x5BU, 0x34U, 0x39U, 0x5DU, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U,
0x78U, 0x35U, 0x43U, 0x55U, 0x00U, 0x64U, 0x65U, 0x74U, 0x5BU, 0x38U,
0x32U, 0x5DU, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x78U, 0x35U, 0x44U,
0x55U, 0x00U, 0x64U, 0x65U, 0x74U, 0x5BU, 0x38U, 0x33U, 0x20U, 0x2BU,
0x20U, 0x69U, 0x5DU, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x65U, 0x78U, 0x70U,
0x65U, 0x63U, 0x74U, 0x65U, 0x64U, 0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU,
0x5FU, 0x68U, 0x61U, 0x73U, 0x68U, 0x5BU, 0x69U, 0x5DU, 0x00U, 0x64U,
0x65U, 0x74U, 0x5BU, 0x31U, 0x31U, 0x35U, 0x5DU, 0x20U, 0x3DU, 0x3DU,
0x20U, 0x30U, 0x78U, 0x45U, 0x31U, 0x00U,
}},
{R"[test.hook](
@@ -12784,7 +12901,7 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
ASSERT(s > 0);
// pull the object out into a buffer, check the number of bytes written is correct
uint8_t buf[4096];
uint8_t buf[8210]; // ltSkip(3)+FLS(6)+LLS(6)+ Hashes(3+8,192(32*256))
ASSERT(slot(SBUF(buf), 1) == s);
// check the object is valid
@@ -12815,63 +12932,64 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xE0U, 0x89U, 0x04U,
0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U,
0x80U, 0x08U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU,
0x6BU, 0x00U, 0x07U, 0x0AU, 0xCCU, 0x82U, 0x00U, 0x01U, 0xC8U, 0x82U,
0x6BU, 0x00U, 0x07U, 0x0AU, 0xCFU, 0x82U, 0x00U, 0x01U, 0xCBU, 0x82U,
0x00U, 0x02U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x23U, 0x80U, 0x80U, 0x80U,
0x80U, 0x00U, 0x41U, 0x80U, 0x20U, 0x6BU, 0x22U, 0x01U, 0x24U, 0x80U,
0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x80U,
0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x02U, 0x40U, 0x41U, 0x01U, 0x10U,
0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7BU, 0x51U, 0x0DU, 0x00U,
0x41U, 0xA2U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x1DU, 0x42U, 0x21U,
0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U,
0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x22U, 0x41U, 0x01U,
0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x01U, 0x51U, 0x0DU,
0x00U, 0x41U, 0xBFU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x1EU, 0x42U,
0x24U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U,
0x80U, 0x00U, 0x41U, 0xA0U, 0xC0U, 0x00U, 0x6BU, 0x22U, 0x01U, 0x24U,
0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U,
0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x02U, 0x40U, 0x41U, 0x01U,
0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7BU, 0x51U, 0x0DU,
0x00U, 0x41U, 0xA2U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x1DU, 0x42U,
0x21U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U,
0x40U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x22U, 0x41U,
0xFFU, 0x01U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0xFFU,
0x01U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xDDU, 0x88U, 0x80U, 0x80U, 0x00U,
0x41U, 0x22U, 0x42U, 0x25U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U,
0x80U, 0x00U, 0x41U, 0xFFU, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U,
0x00U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xFFU, 0x88U, 0x80U, 0x80U, 0x00U,
0x41U, 0x1FU, 0x42U, 0x28U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U,
0x80U, 0x00U, 0x22U, 0x02U, 0x42U, 0x00U, 0x55U, 0x0DU, 0x00U, 0x41U,
0x9EU, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x06U, 0x42U, 0x2CU, 0x10U,
0x01U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x01U, 0x51U,
0x0DU, 0x00U, 0x41U, 0xBFU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x1EU,
0x42U, 0x24U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU,
0x02U, 0x40U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x22U,
0x41U, 0xFFU, 0x01U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U,
0xFFU, 0x01U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xDDU, 0x88U, 0x80U, 0x80U,
0x00U, 0x41U, 0x22U, 0x42U, 0x25U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x01U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x41U, 0xFFU, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U,
0x80U, 0x00U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xFFU, 0x88U, 0x80U, 0x80U,
0x00U, 0x41U, 0x1FU, 0x42U, 0x28U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x01U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x22U, 0x02U, 0x42U, 0x00U, 0x55U, 0x0DU, 0x00U,
0x41U, 0x9EU, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x06U, 0x42U, 0x2CU,
0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U,
0x20U, 0x01U, 0x41U, 0x92U, 0xC0U, 0x00U, 0x41U, 0x01U, 0x10U, 0x84U,
0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x02U, 0x51U, 0x0DU, 0x00U, 0x41U,
0xA4U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x18U, 0x42U, 0x30U, 0x10U,
0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U,
0x01U, 0x41U, 0x80U, 0x20U, 0x41U, 0x01U, 0x10U, 0x84U, 0x80U, 0x80U,
0x80U, 0x00U, 0x20U, 0x02U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xA4U, 0x89U,
0x80U, 0x80U, 0x00U, 0x41U, 0x18U, 0x42U, 0x30U, 0x10U, 0x82U, 0x80U,
0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x20U,
0x02U, 0xA7U, 0x10U, 0x85U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x01U,
0x51U, 0x0DU, 0x00U, 0x41U, 0xBCU, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U,
0x1AU, 0x42U, 0x33U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x86U, 0x80U,
0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x80U, 0x20U, 0x6AU,
0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x02U, 0x0BU, 0x0BU,
0xE4U, 0x01U, 0x02U, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x22U, 0x00U,
0x68U, 0xB4U, 0x97U, 0x9AU, 0x36U, 0xCDU, 0xC7U, 0xF3U, 0xD3U, 0xD5U,
0xC3U, 0x1AU, 0x4EU, 0xAEU, 0x2AU, 0xC7U, 0xD7U, 0x20U, 0x9DU, 0xDAU,
0x87U, 0x75U, 0x88U, 0xB9U, 0xAFU, 0xC6U, 0x67U, 0x99U, 0x69U, 0x2AU,
0xB0U, 0xD6U, 0x6BU, 0x00U, 0x41U, 0xA2U, 0x08U, 0x0BU, 0xB4U, 0x01U,
0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x69U, 0x7AU, 0x65U, 0x28U,
0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x44U, 0x4FU, 0x45U, 0x53U,
0x4EU, 0x54U, 0x5FU, 0x45U, 0x58U, 0x49U, 0x53U, 0x54U, 0x00U, 0x73U,
0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x53U, 0x42U,
0x55U, 0x46U, 0x28U, 0x6BU, 0x6CU, 0x5FU, 0x73U, 0x6BU, 0x29U, 0x2CU,
0x20U, 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x31U, 0x00U, 0x73U,
0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x53U, 0x42U,
0x55U, 0x46U, 0x28U, 0x6BU, 0x6CU, 0x5FU, 0x73U, 0x6BU, 0x29U, 0x2CU,
0x20U, 0x32U, 0x35U, 0x35U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U,
0x35U, 0x35U, 0x00U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x69U,
0x7AU, 0x65U, 0x28U, 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x73U,
0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x69U, 0x7AU, 0x65U, 0x28U, 0x32U,
0x35U, 0x35U, 0x29U, 0x00U, 0x73U, 0x20U, 0x3EU, 0x20U, 0x30U, 0x00U,
0x73U, 0x6CU, 0x6FU, 0x74U, 0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U,
0x62U, 0x75U, 0x66U, 0x29U, 0x2CU, 0x20U, 0x31U, 0x29U, 0x20U, 0x3DU,
0x3DU, 0x20U, 0x73U, 0x00U, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x76U, 0x61U,
0x6CU, 0x69U, 0x64U, 0x61U, 0x74U, 0x65U, 0x28U, 0x62U, 0x75U, 0x66U,
0x2CU, 0x20U, 0x73U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x31U, 0x00U,
0x01U, 0x20U, 0x02U, 0xA7U, 0x10U, 0x85U, 0x80U, 0x80U, 0x80U, 0x00U,
0x42U, 0x01U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xBCU, 0x89U, 0x80U, 0x80U,
0x00U, 0x41U, 0x1AU, 0x42U, 0x33U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U,
0x86U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0xA0U,
0xC0U, 0x00U, 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U,
0x02U, 0x0BU, 0x0BU, 0xE4U, 0x01U, 0x02U, 0x00U, 0x41U, 0x80U, 0x08U,
0x0BU, 0x22U, 0x00U, 0x68U, 0xB4U, 0x97U, 0x9AU, 0x36U, 0xCDU, 0xC7U,
0xF3U, 0xD3U, 0xD5U, 0xC3U, 0x1AU, 0x4EU, 0xAEU, 0x2AU, 0xC7U, 0xD7U,
0x20U, 0x9DU, 0xDAU, 0x87U, 0x75U, 0x88U, 0xB9U, 0xAFU, 0xC6U, 0x67U,
0x99U, 0x69U, 0x2AU, 0xB0U, 0xD6U, 0x6BU, 0x00U, 0x41U, 0xA2U, 0x08U,
0x0BU, 0xB4U, 0x01U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x69U,
0x7AU, 0x65U, 0x28U, 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x44U,
0x4FU, 0x45U, 0x53U, 0x4EU, 0x54U, 0x5FU, 0x45U, 0x58U, 0x49U, 0x53U,
0x54U, 0x00U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x65U, 0x74U,
0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x6BU, 0x6CU, 0x5FU, 0x73U,
0x6BU, 0x29U, 0x2CU, 0x20U, 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U,
0x31U, 0x00U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x65U, 0x74U,
0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x6BU, 0x6CU, 0x5FU, 0x73U,
0x6BU, 0x29U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x35U, 0x29U, 0x20U, 0x3DU,
0x3DU, 0x20U, 0x32U, 0x35U, 0x35U, 0x00U, 0x73U, 0x6CU, 0x6FU, 0x74U,
0x5FU, 0x73U, 0x69U, 0x7AU, 0x65U, 0x28U, 0x31U, 0x29U, 0x20U, 0x3DU,
0x3DU, 0x20U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x69U, 0x7AU,
0x65U, 0x28U, 0x32U, 0x35U, 0x35U, 0x29U, 0x00U, 0x73U, 0x20U, 0x3EU,
0x20U, 0x30U, 0x00U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x28U, 0x53U, 0x42U,
0x55U, 0x46U, 0x28U, 0x62U, 0x75U, 0x66U, 0x29U, 0x2CU, 0x20U, 0x31U,
0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x73U, 0x00U, 0x73U, 0x74U, 0x6FU,
0x5FU, 0x76U, 0x61U, 0x6CU, 0x69U, 0x64U, 0x61U, 0x74U, 0x65U, 0x28U,
0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x73U, 0x29U, 0x20U, 0x3DU, 0x3DU,
0x20U, 0x31U, 0x00U,
}},
{R"[test.hook](
+1
View File
@@ -1410,6 +1410,7 @@ public:
testAllTxns(sa);
testAllTxns(sa - fixHookAPI20251128);
testAllTxns(sa - featureTouch - fixHookAPI20251128);
testAllTxns(sa - featureHookFeeV2);
}
};
+12
View File
@@ -142,6 +142,18 @@ computeExecutionFee(uint64_t instructionCount);
int64_t
computeCreationFee(uint64_t byteCount);
// HookFeeV2: converts a hook's cost units (see hook_api::api_call_cost) into
// drops, rounding up
XRPAmount
hookCostToFee(uint64_t hookCost);
std::optional<std::pair<uint64_t, uint64_t>>
doValidateGuards(
STTx const& tx,
Blob const& wasm,
Rules const& rules,
beast::Journal const& j);
struct HookResult
{
ripple::uint256 const hookSetTxnID;
+81
View File
@@ -9,6 +9,7 @@
#include <xrpld/app/tx/detail/NFTokenUtils.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/hook/Guard.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/PublicKey.h>
@@ -553,6 +554,9 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
case ttREMARKS_SET: {
break;
}
case ttHOOK_DEFINITION_UPDATE: {
break;
}
// pseudo transactions
case ttAMENDMENT:
case ttFEE:
@@ -692,6 +696,83 @@ hook::computeCreationFee(uint64_t byteCount)
return fee;
}
XRPAmount
hook::hookCostToFee(uint64_t hookCost)
{
// round up, and avoid overflow: the quotient of a uint64 by 10 always
// fits in an int64
uint64_t const drops = hookCost / hook_api::cost_units_per_drop +
(hookCost % hook_api::cost_units_per_drop != 0);
return XRPAmount{static_cast<XRPAmount::value_type>(drops)};
}
std::optional<std::pair<uint64_t, uint64_t>>
hook::doValidateGuards(
STTx const& tx,
Blob const& wasm,
Rules const& rules,
beast::Journal const& j)
{
// RH NOTE: validateGuards has a generic non-rippled specific
// interface so it can be used in other projects (i.e. tooling).
// As such the calling here is a bit convoluted.
std::optional<std::reference_wrapper<std::basic_ostream<char>>> logger;
std::ostringstream loggerStream;
std::string hsacc{""};
if (j.trace())
{
logger = loggerStream;
std::stringstream ss;
#define HS_ACC() tx.getAccountID(sfAccount) << "-" << tx.getTransactionID()
ss << HS_ACC();
#undef HS_ACC
hsacc = ss.str();
}
auto result = validateGuards(
wasm, // wasm to verify
logger,
hsacc,
rules.enabled(featureHookFeeV2),
hook_api::getImportWhitelist(rules),
hook_api::getGuardRulesVersion(rules));
if (j.trace())
{
// clunky but to get the stream to accept the output
// correctly we will split on new line and feed each line
// one by one into the trace stream beast::Journal should be
// updated to inherit from basic_ostream<char> then this
// wouldn't be necessary.
// is this a needless copy or does the compiler do copy
// elision here?
std::string s = loggerStream.str();
char* data = s.data();
size_t len = s.size();
char* last = data;
size_t i = 0;
for (; i < len; ++i)
{
if (data[i] == '\n')
{
data[i] = '\0';
j.trace() << last;
last = data + i;
}
}
if (last < data + i)
j.trace() << last;
}
return result;
}
// many datatypes can be encoded into an int64_t
inline std::variant<uint64_t, hook_api::hook_return_code>
data_as_int64(void const* ptr_raw, uint32_t len)
+1
View File
@@ -775,6 +775,7 @@ Change::activateXahauGenesis()
wasmBytes, // wasm to verify
loggerStream,
"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
false,
hook_api::getImportWhitelist(ctx_.view().rules()),
hook_api::getGuardRulesVersion(ctx_.view().rules()));
@@ -0,0 +1,127 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 XRPL-Labs
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/app/tx/detail/HookDefinitionUpdate.h>
#include <xrpl/protocol/TxFlags.h>
namespace ripple {
NotTEC
HookDefinitionUpdate::preflight(PreflightContext const& ctx)
{
if (!ctx.rules.enabled(featureHookFeeV2))
return temDISABLED;
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
auto const& tx = ctx.tx;
if (tx.getFlags() & tfHookDefinitionUpdateMask)
return temINVALID_FLAG;
return preflight2(ctx);
}
TER
HookDefinitionUpdate::preclaim(PreclaimContext const& ctx)
{
auto const& view = ctx.view;
auto const& tx = ctx.tx;
if (!view.exists(keylet::hookDefinition(tx.getFieldH256(sfHookHash))))
return tecNO_ENTRY;
return tesSUCCESS;
}
TER
HookDefinitionUpdate::doApply()
{
auto& view = ctx_.view();
auto const& tx = ctx_.tx;
auto hookDefSle =
view.peek(keylet::hookDefinition(tx.getFieldH256(sfHookHash)));
if (!hookDefSle)
return tefINTERNAL; // LCOV_EXCL_LINE
Blob hook = hookDefSle->getFieldVL(sfCreateCode);
if (!tx.isFlag(tfValidateGuards))
return tecINTERNAL; // LCOV_EXCL_LINE
auto const result =
hook::doValidateGuards(tx, hook, view.rules(), ctx_.journal);
if (!result)
// TODO: TEQU better error code
// For compatibility, if an already deployed HookDefinition fails with a
// new guard-checker, don't update/delete it
return tecINTERNAL;
auto const [hookCost, callbackCost] = *result;
{
// if result is same as old, don't update sle
if (hookCost == hookDefSle->getFieldU64(sfHookCost) &&
callbackCost == hookDefSle->getFieldU64(sfHookCallbackCost))
return tesSUCCESS;
}
{
// set new cost fields
hookDefSle->setFieldU64(sfHookCost, hookCost);
if (callbackCost > 0)
hookDefSle->setFieldU64(sfHookCallbackCost, callbackCost);
else if (hookDefSle->isFieldPresent(sfHookCallbackCost))
hookDefSle->makeFieldAbsent(sfHookCallbackCost);
}
{
// remove old fee fields
if (hookDefSle->isFieldPresent(sfFee))
hookDefSle->makeFieldAbsent(sfFee);
if (hookDefSle->isFieldPresent(sfHookCallbackFee))
hookDefSle->makeFieldAbsent(sfHookCallbackFee);
}
view.update(hookDefSle);
return tesSUCCESS;
}
XRPAmount
HookDefinitionUpdate::calculateBaseFee(ReadView const& view, STTx const& tx)
{
auto const baseFee = Transactor::calculateBaseFee(view, tx);
auto const& hookDefSle =
view.read(keylet::hookDefinition(tx.getFieldH256(sfHookHash)));
if (!hookDefSle || !hookDefSle->isFieldPresent(sfCreateCode))
// if hook definition is not present, doValidateGuards will not call, so
// no additional fee is needed
return baseFee;
auto const hookCodeSize = hookDefSle->getFieldVL(sfCreateCode).size();
return baseFee + XRPAmount{hook::computeCreationFee(hookCodeSize)};
}
} // namespace ripple
@@ -0,0 +1,53 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 XRPL-Labs
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TX_HOOKDEFINITIONUPDATE_H_INCLUDED
#define RIPPLE_TX_HOOKDEFINITIONUPDATE_H_INCLUDED
#include <xrpld/app/tx/detail/Transactor.h>
#include <xrpl/basics/Log.h>
#include <xrpl/protocol/Indexes.h>
namespace ripple {
class HookDefinitionUpdate : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit HookDefinitionUpdate(ApplyContext& ctx) : Transactor(ctx)
{
}
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const&);
TER
doApply() override;
};
} // namespace ripple
#endif
+24 -62
View File
@@ -556,59 +556,8 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
Blob hook = hookSetObj.getFieldVL(sfCreateCode);
// RH NOTE: validateGuards has a generic non-rippled specific
// interface so it can be used in other projects (i.e. tooling).
// As such the calling here is a bit convoluted.
std::optional<std::reference_wrapper<std::basic_ostream<char>>>
logger;
std::ostringstream loggerStream;
std::string hsacc{""};
if (ctx.j.trace())
{
logger = loggerStream;
std::stringstream ss;
ss << HS_ACC();
hsacc = ss.str();
}
auto result = validateGuards(
hook, // wasm to verify
logger,
hsacc,
hook_api::getImportWhitelist(ctx.rules),
hook_api::getGuardRulesVersion(ctx.rules));
if (ctx.j.trace())
{
// clunky but to get the stream to accept the output
// correctly we will split on new line and feed each line
// one by one into the trace stream beast::Journal should be
// updated to inherit from basic_ostream<char> then this
// wouldn't be necessary.
// is this a needless copy or does the compiler do copy
// elision here?
std::string s = loggerStream.str();
char* data = s.data();
size_t len = s.size();
char* last = data;
size_t i = 0;
for (; i < len; ++i)
{
if (data[i] == '\n')
{
data[i] = '\0';
ctx.j.trace() << last;
last = data + i;
}
}
if (last < data + i)
ctx.j.trace() << last;
}
auto const result =
hook::doValidateGuards(ctx.tx, hook, ctx.rules, ctx.j);
if (!result)
return false;
@@ -1879,9 +1828,11 @@ SetHook::setHook()
"SetHook::hsoCREATE : should never happen");
}
// otherwise assign instruction counts
std::tie(maxInstrCountHook, maxInstrCountCbak) =
auto const instrCounts =
std::get<std::pair<uint64_t, uint64_t>>(valid);
std::tie(maxInstrCountHook, maxInstrCountCbak) =
instrCounts;
}
catch (std::exception& e)
{
@@ -1935,15 +1886,26 @@ SetHook::setHook()
newHookDef->setFieldH256(
sfHookSetTxnID, ctx.tx.getTransactionID());
newHookDef->setFieldU64(sfReferenceCount, 1);
newHookDef->setFieldAmount(
sfFee,
XRPAmount{
hook::computeExecutionFee(maxInstrCountHook)});
if (maxInstrCountCbak > 0)
if (view().rules().enabled(featureHookFeeV2))
{
newHookDef->setFieldU64(sfHookCost, maxInstrCountHook);
if (maxInstrCountCbak > 0)
newHookDef->setFieldU64(
sfHookCallbackCost, maxInstrCountCbak);
}
else
{
newHookDef->setFieldAmount(
sfHookCallbackFee,
sfFee,
XRPAmount{
hook::computeExecutionFee(maxInstrCountCbak)});
hook::computeExecutionFee(maxInstrCountHook)});
if (maxInstrCountCbak > 0)
newHookDef->setFieldAmount(
sfHookCallbackFee,
XRPAmount{hook::computeExecutionFee(
maxInstrCountCbak)});
}
if (flags)
newHookDef->setFieldU32(sfFlags, newFlags);
+74 -26
View File
@@ -251,7 +251,7 @@ Transactor::Transactor(ApplyContext& ctx)
// RH NOTE: this only computes one chain at a time, so if there is a receiving
// side to a txn then it must seperately be computed by a second call here
XRPAmount
std::pair<XRPAmount, uint64_t>
Transactor::calculateHookChainFee(
ReadView const& view,
STTx const& tx,
@@ -261,9 +261,10 @@ Transactor::calculateHookChainFee(
{
std::shared_ptr<SLE const> hookSLE = view.read(hookKeylet);
if (!hookSLE)
return XRPAmount{0};
return std::make_pair(XRPAmount{0}, 0);
XRPAmount fee{0};
uint64_t hookCost{0};
auto const& hooks = hookSLE->getFieldArray(sfHooks);
@@ -304,19 +305,35 @@ Transactor::calculateHookChainFee(
if (hook::canHook(tx, hookOn, hookName) &&
(!collectCallsOnly || (flags & hook::hsfCOLLECT)))
{
XRPAmount const toAdd{hookDef->getFieldAmount(sfFee).xrp().drops()};
if (hookDef->isFieldPresent(sfFee))
{
XRPAmount const toAdd{
hookDef->getFieldAmount(sfFee).xrp().drops()};
// this overflow should never happen, if somehow it does
// fee is set to the largest possible valid xrp value to force
// fail the transaction
if (fee + toAdd < fee)
fee = XRPAmount{INITIAL_XRP.drops()};
// this overflow should never happen, if somehow it does
// fee is set to the largest possible valid xrp value to force
// fail the transaction
if (fee + toAdd < fee)
fee = XRPAmount{INITIAL_XRP.drops()};
else
fee += toAdd;
}
else
fee += toAdd;
{
uint64_t const toAdd{hookDef->getFieldU64(sfHookCost)};
// this overflow should never happen, if somehow it does
// fee is set to the largest possible valid xrp value to force
// fail the transaction
if (hookCost + toAdd < hookCost)
hookCost = std::numeric_limits<uint64_t>::max();
else
hookCost += toAdd;
}
}
}
return fee;
return std::make_pair(fee, hookCost);
}
XRPAmount
@@ -342,6 +359,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0;
XRPAmount hookExecutionFee{0};
uint64_t hookCost{0};
uint64_t burden{1};
if (view.rules().enabled(featureHooks))
{
@@ -364,18 +382,32 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
std::shared_ptr<SLE const> hookDef =
view.read(keylet::hookDefinition(callbackHookHash));
if (hookDef && hookDef->isFieldPresent(sfHookCallbackFee))
if (hookDef)
{
XRPAmount const toAdd{
hookDef->getFieldAmount(sfHookCallbackFee).xrp().drops()};
if (hookDef->isFieldPresent(sfHookCallbackFee))
{
XRPAmount const toAdd{
hookDef->getFieldAmount(sfHookCallbackFee)
.xrp()
.drops()};
// this overflow should never happen, if somehow it does
// fee is set to the largest possible valid xrp value to force
// fail the transaction
if (hookExecutionFee + toAdd < hookExecutionFee)
hookExecutionFee = XRPAmount{INITIAL_XRP.drops()};
else
hookExecutionFee += toAdd;
// this overflow should never happen, if somehow it does
// fee is set to the largest possible valid xrp value to
// force fail the transaction
if (hookExecutionFee + toAdd < hookExecutionFee)
hookExecutionFee = XRPAmount{INITIAL_XRP.drops()};
else
hookExecutionFee += toAdd;
}
if (hookDef->isFieldPresent(sfHookCallbackCost))
{
uint64_t const toAdd{
hookDef->getFieldU64(sfHookCallbackCost)};
if (hookCost + toAdd < hookCost)
hookCost = std::numeric_limits<uint64_t>::max();
else
hookCost += toAdd;
}
}
XRPL_ASSERT(
@@ -385,8 +417,12 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
burden = emitDetails.getFieldU64(sfEmitBurden);
}
else
hookExecutionFee += calculateHookChainFee(
{
auto const hookChainFee = calculateHookChainFee(
view, tx, keylet::hook(tx.getAccountID(sfAccount)), true);
hookExecutionFee += hookChainFee.first;
hookCost += hookChainFee.second;
}
// find any additional stakeholders whose hooks will be executed and
// charged to this transaction
@@ -395,8 +431,12 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
for (auto& [tshAcc, canRollback] : tsh)
if (canRollback)
hookExecutionFee += calculateHookChainFee(
{
auto const hookChainFee = calculateHookChainFee(
view, tx, keylet::hook(tshAcc), false);
hookExecutionFee += hookChainFee.first;
hookCost += hookChainFee.second;
}
}
XRPAmount accumulator = baseFee;
@@ -450,6 +490,8 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
// transaction.
do
{
hookExecutionFee += hook::hookCostToFee(hookCost);
if (accumulator * burden < accumulator)
break;
@@ -1452,7 +1494,8 @@ Transactor::executeHookChain(
return tecINTERNAL;
}
bool hasCallback = hookDef->isFieldPresent(sfHookCallbackFee);
bool hasCallback = hookDef->isFieldPresent(sfHookCallbackFee) ||
hookDef->isFieldPresent(sfHookCallbackCost);
try
{
@@ -1572,7 +1615,8 @@ Transactor::doHookCallback(
return;
}
if (!hookDef->isFieldPresent(sfHookCallbackFee))
if (!hookDef->isFieldPresent(sfHookCallbackFee) &&
!hookDef->isFieldPresent(sfHookCallbackCost))
{
JLOG(j_.trace()) << "HookInfo[" << callbackAccountID
<< "]: Callback specified by emitted txn "
@@ -1799,8 +1843,11 @@ Transactor::doTSH(
continue;
// compute and deduct fees for the TSH if applicable
XRPAmount tshFeeDrops = calculateHookChainFee(
auto const tshFee = calculateHookChainFee(
view, ctx_.tx, klTshHook, false, !canRollback);
XRPAmount tshFeeDrops{tshFee.first};
tshFeeDrops += hook::hookCostToFee(tshFee.second);
// no hooks to execute, skip tsh
if (tshFeeDrops == 0)
@@ -1955,7 +2002,8 @@ Transactor::doAgainAsWeak(
stateMap,
ctx_,
hookAccountID,
hookDef->isFieldPresent(sfHookCallbackFee),
hookDef->isFieldPresent(sfHookCallbackFee) ||
hookDef->isFieldPresent(sfHookCallbackCost),
false,
false,
2UL, // param 2 = aaw
+1 -1
View File
@@ -174,7 +174,7 @@ public:
// Hooks
static XRPAmount
static std::pair<XRPAmount, uint64_t>
calculateHookChainFee(
ReadView const& view,
STTx const& tx,
+1
View File
@@ -44,6 +44,7 @@
#include <xrpld/app/tx/detail/DepositPreauth.h>
#include <xrpld/app/tx/detail/Escrow.h>
#include <xrpld/app/tx/detail/GenesisMint.h>
#include <xrpld/app/tx/detail/HookDefinitionUpdate.h>
#include <xrpld/app/tx/detail/Import.h>
#include <xrpld/app/tx/detail/Invoke.h>
#include <xrpld/app/tx/detail/LedgerStateFix.h>
+2
View File
@@ -76,10 +76,12 @@ doHookValidate(RPC::JsonContext& context)
std::optional<std::pair<uint64_t, uint64_t>> result;
try
{
auto const returnCost = rules.enabled(featureHookFeeV2);
result = validateGuards(
*blob,
logger,
"",
returnCost,
hook_api::getImportWhitelist(rules),
hook_api::getGuardRulesVersion(rules));
}
@@ -59,6 +59,13 @@ MAGIC_ENUM_FLAG(ripple::PaymentChannelClaimFlags);
MAGIC_ENUM_FLAG(ripple::NFTokenMintFlags);
MAGIC_ENUM_FLAG(ripple::NFTokenCreateOfferFlags);
MAGIC_ENUM_FLAG(ripple::ClaimRewardFlags);
MAGIC_ENUM_FLAG(ripple::HookDefinitionUpdateFlags);
MAGIC_ENUM_FLAG(ripple::CronSetFlags);
MAGIC_ENUM_FLAG(ripple::BridgeModifyFlags);
MAGIC_ENUM_FLAG(ripple::MPTokenIssuanceCreateFlags);
MAGIC_ENUM_FLAG(ripple::MPTokenAuthorizeFlags);
MAGIC_ENUM_FLAG(ripple::MPTokenIssuanceSetFlags);
MAGIC_ENUM_FLAG(ripple::AMMClawbackFlags);
MAGIC_ENUM_16(ripple::AccountFlags);
// hook_return_code spans [-10024, 0] because of INVALID_FLOAT
@@ -379,6 +386,7 @@ private:
addFlagsToJson<NFTokenMintFlags>(ret, "NFTokenMint");
addFlagsToJson<NFTokenCreateOfferFlags>(ret, "NFTokenCreateOffer");
addFlagsToJson<ClaimRewardFlags>(ret, "ClaimReward");
addFlagsToJson<HookDefinitionUpdateFlags>(ret, "HookDefinitionUpdate");
addFlagsToJson<CronSetFlags>(ret, "CronSet");
addFlagsToJson<BridgeModifyFlags>(ret, "XChainModifyBridge");
addFlagsToJson<MPTokenIssuanceCreateFlags>(