mirror of
https://github.com/Xahau/xahaud.git
synced 2026-04-29 15:37:46 +00:00
2196 lines
81 KiB
C++
2196 lines
81 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2024 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 <ripple/app/hook/Enum.h>
|
|
#include <ripple/app/ledger/LedgerMaster.h>
|
|
#include <ripple/app/tx/impl/SetHook.h>
|
|
#include <ripple/protocol/TxFlags.h>
|
|
#include <ripple/protocol/jss.h>
|
|
#include <test/app/SetJSHook_wasm.h>
|
|
#include <test/jtx.h>
|
|
#include <test/jtx/hook.h>
|
|
#include <unordered_map>
|
|
|
|
// DA TODO: Move duplicated functions to jtx
|
|
// JSSMap
|
|
// JSSHasher
|
|
// JSSEq
|
|
// overrideFlag
|
|
|
|
namespace ripple {
|
|
|
|
namespace test {
|
|
|
|
#define DEBUG_TESTS 1
|
|
|
|
using TestHook = std::vector<uint8_t> const&;
|
|
|
|
using JSSMap =
|
|
std::unordered_map<Json::StaticString, Json::Value, JSSHasher, JSSEq>;
|
|
|
|
// Identical to BEAST_EXPECT except it returns from the function
|
|
// if the condition isn't met (and would otherwise therefore cause a crash)
|
|
#define BEAST_REQUIRE(x) \
|
|
{ \
|
|
BEAST_EXPECT(!!(x)); \
|
|
if (!(x)) \
|
|
return; \
|
|
}
|
|
|
|
#define HASH_WASM(x) \
|
|
uint256 const x##_hash = \
|
|
ripple::sha512Half_s(ripple::Slice(x##_wasm.data(), x##_wasm.size())); \
|
|
std::string const x##_hash_str = to_string(x##_hash); \
|
|
Keylet const x##_keylet = keylet::hookDefinition(x##_hash);
|
|
|
|
class SetJSHook_test : public beast::unit_test::suite
|
|
{
|
|
private:
|
|
// helper
|
|
void static overrideFlag(Json::Value& jv)
|
|
{
|
|
jv[jss::Flags] = hsfOVERRIDE;
|
|
}
|
|
|
|
public:
|
|
// This is a large fee, large enough that we can set most small test hooks
|
|
// without running into fee issues we only want to test fee code specifically in
|
|
// fee unit tests, the rest of the time we want to ignore it.
|
|
#define HSFEE fee(100'000'000)
|
|
#define M(m) memo(m, "", "")
|
|
void
|
|
testHooksOwnerDir(FeatureBitset features)
|
|
{
|
|
testcase("Test owner directory");
|
|
|
|
using namespace jtx;
|
|
|
|
Env env{*this, features};
|
|
|
|
auto const alice = Account{"alice"};
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(10000), alice, gw);
|
|
env.close();
|
|
env.trust(USD(100000), alice);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10000)));
|
|
|
|
for (int i = 1; i < 34; ++i)
|
|
{
|
|
std::string const uri(i, '?');
|
|
env(uritoken::mint(alice, uri));
|
|
}
|
|
env.close();
|
|
|
|
env(ripple::test::jtx::hook(
|
|
alice, {{hsov1(accept_wasm, 1, overrideFlag)}}, 0),
|
|
HSFEE,
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
env(ripple::test::jtx::hook(
|
|
alice, {{hsov1(accept_wasm, 1, overrideFlag)}}, 0),
|
|
HSFEE,
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// delete hook
|
|
Json::Value jv;
|
|
jv[jss::Account] = alice.human();
|
|
jv[jss::TransactionType] = jss::SetHook;
|
|
jv[jss::Flags] = 0;
|
|
jv[jss::Hooks] = Json::Value{Json::arrayValue};
|
|
Json::Value iv;
|
|
iv[jss::Flags] = 1;
|
|
iv[jss::CreateCode] = "";
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
|
|
env(jv, HSFEE, ter(tesSUCCESS));
|
|
env.close();
|
|
}
|
|
|
|
void
|
|
testHooksDisabled(FeatureBitset features)
|
|
{
|
|
testcase("Check for disabled amendment");
|
|
using namespace jtx;
|
|
Env env{*this, features - featureHooks};
|
|
auto const alice = Account{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
|
|
env(ripple::test::jtx::hook(
|
|
alice, {{hsov1(accept_wasm, 1, overrideFlag)}}, 0),
|
|
M("Hooks Disabled"),
|
|
HSFEE,
|
|
ter(temDISABLED));
|
|
}
|
|
|
|
void
|
|
testTxStructure(FeatureBitset features)
|
|
{
|
|
testcase("Checks malformed transactions");
|
|
using namespace jtx;
|
|
Env env{*this, features};
|
|
|
|
auto const alice = Account{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
|
|
// Test outer structure
|
|
|
|
env(ripple::test::jtx::hook(alice, {}, 0),
|
|
M("Must have a hooks field"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
|
|
env(ripple::test::jtx::hook(alice, {{}}, 0),
|
|
M("Must have a non-empty hooks field"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
|
|
env(ripple::test::jtx::hook(
|
|
alice,
|
|
{{hsov1(accept_wasm, 1),
|
|
hsov1(accept_wasm, 1),
|
|
hsov1(accept_wasm, 1),
|
|
hsov1(accept_wasm, 1),
|
|
hsov1(accept_wasm, 1),
|
|
hsov1(accept_wasm, 1),
|
|
hsov1(accept_wasm, 1),
|
|
hsov1(accept_wasm, 1),
|
|
hsov1(accept_wasm, 1),
|
|
hsov1(accept_wasm, 1),
|
|
hsov1(accept_wasm, 1)}},
|
|
0),
|
|
M("Must have fewer than 11 entries"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
|
|
{
|
|
Json::Value jv;
|
|
jv[jss::Account] = alice.human();
|
|
jv[jss::TransactionType] = jss::SetHook;
|
|
jv[jss::Flags] = 0;
|
|
jv[jss::Hooks] = Json::Value{Json::arrayValue};
|
|
|
|
Json::Value iv;
|
|
iv[jss::MemoData] = "DEADBEEF";
|
|
iv[jss::MemoFormat] = "";
|
|
iv[jss::MemoType] = "";
|
|
jv[jss::Hooks][0U][jss::Memo] = iv;
|
|
env(jv,
|
|
M("Hooks Array must contain Hook objects"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testInstall(FeatureBitset features)
|
|
{
|
|
testcase("Checks malformed install operation");
|
|
using namespace jtx;
|
|
Env env{*this, features};
|
|
|
|
auto const alice = Account{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
|
|
auto const bob = Account{"bob"};
|
|
env.fund(XRP(10000), bob);
|
|
|
|
// create a hook that we can then install
|
|
{
|
|
env(ripple::test::jtx::hook(
|
|
bob, {{hsov1(accept_wasm, 1), hsov1(rollback_wasm, 1)}}, 0),
|
|
M("First set = tesSUCCESS"),
|
|
HSFEE,
|
|
ter(tesSUCCESS));
|
|
}
|
|
|
|
Json::Value jv;
|
|
jv[jss::Account] = alice.human();
|
|
jv[jss::TransactionType] = jss::SetHook;
|
|
jv[jss::Flags] = 0;
|
|
jv[jss::Hooks] = Json::Value{Json::arrayValue};
|
|
|
|
// can't set api version
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::HookHash] = accept_hash_str;
|
|
iv[jss::HookApiVersion] = 0U;
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv,
|
|
M("Hook Install operation cannot set apiversion"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
// can't set non-existent hook
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::HookHash] =
|
|
"DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBE"
|
|
"EF";
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv,
|
|
M("Hook Install operation cannot set non existent hook hash"),
|
|
HSFEE,
|
|
ter(terNO_HOOK));
|
|
env.close();
|
|
}
|
|
|
|
// can set extant hook
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::HookHash] = accept_hash_str;
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv,
|
|
M("Hook Install operation can set extant hook hash"),
|
|
HSFEE,
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
}
|
|
|
|
// can't set extant hook over other hook without override flag
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::HookHash] = rollback_hash_str;
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv,
|
|
M("Hook Install operation can set extant hook hash"),
|
|
HSFEE,
|
|
ter(tecREQUIRES_FLAG));
|
|
env.close();
|
|
}
|
|
|
|
// can set extant hook over other hook with override flag
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::HookHash] = rollback_hash_str;
|
|
iv[jss::Flags] = hsfOVERRIDE;
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv,
|
|
M("Hook Install operation can set extant hook hash"),
|
|
HSFEE,
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testDelete(FeatureBitset features)
|
|
{
|
|
testcase("Checks malformed delete operation");
|
|
using namespace jtx;
|
|
Env env{*this, features};
|
|
|
|
auto const alice = Account{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
|
|
Json::Value jv;
|
|
jv[jss::Account] = alice.human();
|
|
jv[jss::TransactionType] = jss::SetHook;
|
|
jv[jss::Flags] = 0;
|
|
jv[jss::Hooks] = Json::Value{Json::arrayValue};
|
|
|
|
// flag required
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::CreateCode] = "";
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv,
|
|
M("Hook DELETE operation must include hsfOVERRIDE flag"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
// invalid flags
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::CreateCode] = "";
|
|
iv[jss::Flags] = "2147483648";
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv,
|
|
M("Hook DELETE operation must include hsfOVERRIDE flag"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
// grants, parameters, hookon, hookapiversion, hooknamespace keys must
|
|
// be absent
|
|
for (auto const& [key, value] : JSSMap{
|
|
{jss::HookGrants, Json::arrayValue},
|
|
{jss::HookParameters, Json::arrayValue},
|
|
{jss::HookOn,
|
|
"000000000000000000000000000000000000000000000000000000000000"
|
|
"0000"},
|
|
{jss::HookApiVersion, "1"},
|
|
{jss::HookNamespace, to_string(uint256{beast::zero})}})
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::CreateCode] = "";
|
|
iv[key] = value;
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv,
|
|
M("Hook DELETE operation cannot include: grants, params, "
|
|
"hookon, apiversion, namespace"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
// create and delete single hook
|
|
{
|
|
{
|
|
Json::Value jv = ripple::test::jtx::hook(
|
|
alice, {{hsov1(accept_wasm, 1)}}, 0);
|
|
env(jv, M("Normal accept create"), HSFEE, ter(tesSUCCESS));
|
|
env.close();
|
|
}
|
|
|
|
BEAST_REQUIRE(env.le(accept_keylet));
|
|
|
|
Json::Value iv;
|
|
iv[jss::CreateCode] = "";
|
|
iv[jss::Flags] = hsfOVERRIDE;
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
|
|
env(jv, M("Normal hook DELETE"), HSFEE);
|
|
env.close();
|
|
|
|
// check to ensure definition is deleted and hooks object too
|
|
auto const def = env.le(accept_keylet);
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
|
|
BEAST_EXPECT(!def);
|
|
BEAST_EXPECT(!hook);
|
|
}
|
|
|
|
// create four hooks then delete the second last one
|
|
{
|
|
// create
|
|
{
|
|
Json::Value jv = ripple::test::jtx::hook(
|
|
alice,
|
|
{{hsov1(accept_wasm, 1),
|
|
hsov1(makestate_wasm, 1),
|
|
hsov1(rollback_wasm, 1),
|
|
hsov1(accept2_wasm, 1)}},
|
|
0);
|
|
env(jv, M("Create four"), HSFEE, ter(tesSUCCESS));
|
|
env.close();
|
|
}
|
|
|
|
// delete third and check
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::CreateCode] = "";
|
|
iv[jss::Flags] = hsfOVERRIDE;
|
|
for (uint8_t i = 0; i < 4; ++i)
|
|
jv[jss::Hooks][i][jss::Hook] = Json::Value{};
|
|
jv[jss::Hooks][2U][jss::Hook] = iv;
|
|
|
|
env(jv, M("Normal hooki DELETE (third pos)"), HSFEE);
|
|
env.close();
|
|
|
|
// check the hook definitions are consistent with reference
|
|
// count dropping to zero on the third
|
|
auto const accept_def = env.le(accept_keylet);
|
|
auto const rollback_def = env.le(rollback_keylet);
|
|
auto const makestate_def = env.le(makestate_keylet);
|
|
auto const accept2_def = env.le(accept2_keylet);
|
|
|
|
BEAST_REQUIRE(accept_def);
|
|
BEAST_EXPECT(!rollback_def);
|
|
BEAST_REQUIRE(makestate_def);
|
|
BEAST_REQUIRE(accept2_def);
|
|
|
|
// check the hooks array is correct
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_REQUIRE(hook);
|
|
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_REQUIRE(hooks.size() == 4);
|
|
|
|
// make sure only the third is deleted
|
|
BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_REQUIRE(hooks[1].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(!hooks[2].isFieldPresent(sfHookHash));
|
|
BEAST_REQUIRE(hooks[3].isFieldPresent(sfHookHash));
|
|
|
|
// check hashes on the three remaining
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash);
|
|
BEAST_EXPECT(
|
|
hooks[1].getFieldH256(sfHookHash) == makestate_hash);
|
|
BEAST_EXPECT(hooks[3].getFieldH256(sfHookHash) == accept2_hash);
|
|
}
|
|
|
|
// delete rest and check
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::CreateCode] = "";
|
|
iv[jss::Flags] = hsfOVERRIDE;
|
|
for (uint8_t i = 0; i < 4; ++i)
|
|
{
|
|
if (i != 2U)
|
|
jv[jss::Hooks][i][jss::Hook] = iv;
|
|
else
|
|
jv[jss::Hooks][i][jss::Hook] = Json::Value{};
|
|
}
|
|
|
|
env(jv,
|
|
M("Normal hook DELETE (first, second, fourth pos)"),
|
|
HSFEE);
|
|
env.close();
|
|
|
|
// check the hook definitions are consistent with reference
|
|
// count dropping to zero on the third
|
|
auto const accept_def = env.le(accept_keylet);
|
|
auto const rollback_def = env.le(rollback_keylet);
|
|
auto const makestate_def = env.le(makestate_keylet);
|
|
auto const accept2_def = env.le(accept2_keylet);
|
|
|
|
BEAST_EXPECT(!accept_def);
|
|
BEAST_EXPECT(!rollback_def);
|
|
BEAST_EXPECT(!makestate_def);
|
|
BEAST_EXPECT(!accept2_def);
|
|
|
|
// check the hooks object is gone
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_EXPECT(!hook);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testNSDelete(FeatureBitset features)
|
|
{
|
|
testcase("Checks malformed nsdelete operation");
|
|
using namespace jtx;
|
|
Env env{*this, features};
|
|
|
|
auto const alice = Account{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
|
|
auto const bob = Account{"bob"};
|
|
env.fund(XRP(10000), bob);
|
|
|
|
Json::Value jv;
|
|
jv[jss::Account] = alice.human();
|
|
jv[jss::TransactionType] = jss::SetHook;
|
|
jv[jss::Flags] = 0;
|
|
jv[jss::Hooks] = Json::Value{Json::arrayValue};
|
|
|
|
for (auto const& [key, value] : JSSMap{
|
|
{jss::HookGrants, Json::arrayValue},
|
|
{jss::HookParameters, Json::arrayValue},
|
|
{jss::HookOn,
|
|
"000000000000000000000000000000000000000000000000000000000000"
|
|
"0000"},
|
|
{jss::HookApiVersion, "1"},
|
|
})
|
|
{
|
|
Json::Value iv;
|
|
iv[key] = value;
|
|
iv[jss::Flags] = hsfNSDELETE;
|
|
iv[jss::HookNamespace] = to_string(uint256{beast::zero});
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv,
|
|
M("Hook NSDELETE operation cannot include: grants, params, "
|
|
"hookon, apiversion"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
auto const key = uint256::fromVoid(
|
|
(std::array<uint8_t, 32>{
|
|
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
|
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
|
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
|
0x00U, 0x00U, 0x00U, 0x00U, 'k', 'e', 'y', 0x00U})
|
|
.data());
|
|
|
|
auto const ns = uint256::fromVoid(
|
|
(std::array<uint8_t, 32>{
|
|
0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU,
|
|
0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU,
|
|
0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU,
|
|
0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU})
|
|
.data());
|
|
|
|
auto const stateKeylet =
|
|
keylet::hookState(Account("alice").id(), key, ns);
|
|
|
|
// create a namespace
|
|
std::string ns_str =
|
|
"CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE";
|
|
{
|
|
// create hook
|
|
Json::Value jv =
|
|
ripple::test::jtx::hook(alice, {{hsov1(makestate_wasm, 1)}}, 0);
|
|
|
|
jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str;
|
|
env(jv, M("Create makestate hook"), HSFEE, ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// run hook
|
|
env(pay(bob, alice, XRP(1)),
|
|
M("Run create state hook"),
|
|
fee(XRP(1)));
|
|
env.close();
|
|
|
|
// check if the hookstate object was created
|
|
auto const hookstate = env.le(stateKeylet);
|
|
BEAST_EXPECT(!!hookstate);
|
|
|
|
// check if the value was set correctly
|
|
auto const& data = hookstate->getFieldVL(sfHookStateData);
|
|
|
|
BEAST_REQUIRE(data.size() == 6);
|
|
BEAST_EXPECT(
|
|
data[0] == 'v' && data[1] == 'a' && data[2] == 'l' &&
|
|
data[3] == 'u' && data[4] == 'e' && data[5] == '\0');
|
|
}
|
|
|
|
// delete the namespace
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::Flags] = hsfNSDELETE;
|
|
iv[jss::HookNamespace] = ns_str;
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv, M("Normal NSDELETE operation"), HSFEE, ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// ensure the hook is still installed
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_REQUIRE(hook);
|
|
|
|
BEAST_REQUIRE(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() > 0);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == makestate_hash);
|
|
|
|
// ensure the directory is gone
|
|
auto const dirKeylet =
|
|
keylet::hookStateDir(Account("alice").id(), ns);
|
|
BEAST_EXPECT(!env.le(dirKeylet));
|
|
|
|
// ensure the state object is gone
|
|
BEAST_EXPECT(!env.le(stateKeylet));
|
|
}
|
|
}
|
|
|
|
void
|
|
testCreate(FeatureBitset features)
|
|
{
|
|
testcase("Checks malformed create operation");
|
|
using namespace jtx;
|
|
Env env{*this, features};
|
|
|
|
auto const bob = Account{"bob"};
|
|
env.fund(XRP(10000), bob);
|
|
|
|
auto const alice = Account{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
|
|
// test normal create and missing override flag
|
|
{
|
|
env(ripple::test::jtx::hook(bob, {{hsov1(accept_wasm, 1)}}, 0),
|
|
M("First set = tesSUCCESS"),
|
|
HSFEE,
|
|
ter(tesSUCCESS));
|
|
|
|
env(ripple::test::jtx::hook(bob, {{hsov1(accept_wasm, 1)}}, 0),
|
|
M("Second set = tecREQUIRES_FLAG"),
|
|
HSFEE,
|
|
ter(tecREQUIRES_FLAG));
|
|
env.close();
|
|
}
|
|
|
|
Json::Value jv;
|
|
jv[jss::Account] = alice.human();
|
|
jv[jss::TransactionType] = jss::SetHook;
|
|
jv[jss::Flags] = 0;
|
|
jv[jss::Hooks] = Json::Value{Json::arrayValue};
|
|
|
|
// DA TODO: FAILING
|
|
// // payload too large
|
|
// {
|
|
// env(ripple::test::jtx::hook(alice, {{hsov1(long_wasm, 1)}}, 0),
|
|
// M("If CreateCode is present, then it must be less than 64kib"),
|
|
// HSFEE,
|
|
// ter(temMALFORMED));
|
|
// env.close();
|
|
// }
|
|
|
|
// namespace missing
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::CreateCode] = strHex(accept_wasm);
|
|
iv[jss::HookApiVersion] = 1U;
|
|
iv[jss::HookOn] =
|
|
"00000000000000000000000000000000000000000000000000000000000000"
|
|
"00";
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
|
|
env(jv,
|
|
M("HSO Create operation must contain namespace"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
// api version missing
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::CreateCode] = strHex(accept_wasm);
|
|
iv[jss::HookNamespace] = to_string(uint256{beast::zero});
|
|
iv[jss::HookOn] =
|
|
"00000000000000000000000000000000000000000000000000000000000000"
|
|
"00";
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
|
|
env(jv,
|
|
M("HSO Create operation must contain api version"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
// api version wrong
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::CreateCode] = strHex(accept_wasm);
|
|
iv[jss::HookNamespace] = to_string(uint256{beast::zero});
|
|
iv[jss::HookApiVersion] = 2U;
|
|
iv[jss::HookOn] =
|
|
"00000000000000000000000000000000000000000000000000000000000000"
|
|
"00";
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
|
|
env(jv,
|
|
M("HSO Create operation must contain valid api version"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
// hookon missing
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::CreateCode] = strHex(accept_wasm);
|
|
iv[jss::HookNamespace] = to_string(uint256{beast::zero});
|
|
iv[jss::HookApiVersion] = 1U;
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
|
|
env(jv,
|
|
M("HSO Create operation must contain hookon"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
// hook hash present
|
|
{
|
|
Json::Value jv =
|
|
ripple::test::jtx::hook(alice, {{hsov1(accept_wasm, 1)}}, 0);
|
|
Json::Value iv = jv[jss::Hooks][0U];
|
|
iv[jss::Hook][jss::HookHash] = to_string(uint256{beast::zero});
|
|
jv[jss::Hooks][0U] = iv;
|
|
env(jv,
|
|
M("Cannot have both CreateCode and HookHash"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
// correctly formed
|
|
{
|
|
Json::Value jv =
|
|
ripple::test::jtx::hook(alice, {{hsov1(accept_wasm, 1)}}, 0);
|
|
env(jv, M("Normal accept"), HSFEE, ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
auto const def = env.le(accept_keylet);
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
|
|
// check if the hook definition exists
|
|
BEAST_EXPECT(!!def);
|
|
|
|
// check if the user account has a hooks object
|
|
BEAST_EXPECT(!!hook);
|
|
|
|
// check if the hook is correctly set at position 1
|
|
BEAST_EXPECT(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() > 0);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash);
|
|
|
|
// check if the wasm binary was correctly set
|
|
BEAST_EXPECT(def->isFieldPresent(sfCreateCode));
|
|
auto const& wasm = def->getFieldVL(sfCreateCode);
|
|
auto const wasm_hash =
|
|
sha512Half_s(ripple::Slice(wasm.data(), wasm.size()));
|
|
BEAST_EXPECT(wasm_hash == accept_hash);
|
|
}
|
|
|
|
// add a second hook
|
|
{
|
|
Json::Value jv =
|
|
ripple::test::jtx::hook(alice, {{hsov1(accept_wasm, 1)}}, 0);
|
|
Json::Value iv = jv[jss::Hooks][0U];
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = Json::Value{};
|
|
jv[jss::Hooks][1U] = iv;
|
|
env(jv,
|
|
M("Normal accept, second position"),
|
|
HSFEE,
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
auto const def = env.le(accept_keylet);
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
|
|
// check if the hook definition exists
|
|
BEAST_EXPECT(!!def);
|
|
|
|
// check if the user account has a hooks object
|
|
BEAST_EXPECT(!!hook);
|
|
|
|
// check if the hook is correctly set at position 2
|
|
BEAST_EXPECT(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() > 1);
|
|
BEAST_EXPECT(hooks[1].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[1].getFieldH256(sfHookHash) == accept_hash);
|
|
|
|
// check if the reference count was correctly incremented
|
|
BEAST_EXPECT(def->isFieldPresent(sfReferenceCount));
|
|
// two references from alice, one from bob (first test above)
|
|
BEAST_EXPECT(def->getFieldU64(sfReferenceCount) == 3ULL);
|
|
}
|
|
|
|
auto const rollback_hash = ripple::sha512Half_s(
|
|
ripple::Slice(rollback_wasm.data(), rollback_wasm.size()));
|
|
|
|
// test override
|
|
{
|
|
Json::Value jv =
|
|
ripple::test::jtx::hook(alice, {{hsov1(rollback_wasm, 1)}}, 0);
|
|
jv[jss::Hooks][0U][jss::Hook][jss::Flags] = hsfOVERRIDE;
|
|
env(jv, M("Rollback override"), HSFEE, ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
auto const rollback_def = env.le(rollback_keylet);
|
|
auto const accept_def = env.le(accept_keylet);
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
|
|
// check if the hook definition exists
|
|
BEAST_EXPECT(rollback_def);
|
|
BEAST_EXPECT(accept_def);
|
|
|
|
// check if the user account has a hooks object
|
|
BEAST_EXPECT(hook);
|
|
|
|
// check if the hook is correctly set at position 1
|
|
BEAST_EXPECT(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() > 1);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == rollback_hash);
|
|
BEAST_EXPECT(hooks[1].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[1].getFieldH256(sfHookHash) == accept_hash);
|
|
|
|
// check if the wasm binary was correctly set
|
|
BEAST_EXPECT(rollback_def->isFieldPresent(sfCreateCode));
|
|
auto const& wasm = rollback_def->getFieldVL(sfCreateCode);
|
|
auto const wasm_hash =
|
|
sha512Half_s(ripple::Slice(wasm.data(), wasm.size()));
|
|
BEAST_EXPECT(wasm_hash == rollback_hash);
|
|
|
|
// check if the reference count was correctly incremented
|
|
BEAST_EXPECT(rollback_def->isFieldPresent(sfReferenceCount));
|
|
BEAST_EXPECT(rollback_def->getFieldU64(sfReferenceCount) == 1ULL);
|
|
|
|
// check if the reference count was correctly decremented
|
|
BEAST_EXPECT(accept_def->isFieldPresent(sfReferenceCount));
|
|
BEAST_EXPECT(accept_def->getFieldU64(sfReferenceCount) == 2ULL);
|
|
}
|
|
}
|
|
|
|
void
|
|
testUpdate(FeatureBitset features)
|
|
{
|
|
testcase("Checks malformed update operation");
|
|
using namespace jtx;
|
|
Env env{*this, features};
|
|
|
|
auto const alice = Account{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
|
|
auto const bob = Account{"bob"};
|
|
env.fund(XRP(10000), bob);
|
|
|
|
Json::Value jv;
|
|
jv[jss::Account] = alice.human();
|
|
jv[jss::TransactionType] = jss::SetHook;
|
|
jv[jss::Flags] = 0;
|
|
jv[jss::Hooks] = Json::Value{Json::arrayValue};
|
|
|
|
// first create the hook
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::CreateCode] = strHex(accept_wasm);
|
|
iv[jss::HookNamespace] = to_string(uint256{beast::zero});
|
|
iv[jss::HookApiVersion] = 1U;
|
|
iv[jss::HookOn] =
|
|
"00000000000000000000000000000000000000000000000000000000000000"
|
|
"00";
|
|
iv[jss::HookParameters] = Json::Value{Json::arrayValue};
|
|
iv[jss::HookParameters][0U] = Json::Value{};
|
|
iv[jss::HookParameters][0U][jss::HookParameter] = Json::Value{};
|
|
iv[jss::HookParameters][0U][jss::HookParameter]
|
|
[jss::HookParameterName] = "AAAAAAAAAAAA";
|
|
iv[jss::HookParameters][0U][jss::HookParameter]
|
|
[jss::HookParameterValue] = "BBBBBB";
|
|
|
|
iv[jss::HookParameters][1U] = Json::Value{};
|
|
iv[jss::HookParameters][1U][jss::HookParameter] = Json::Value{};
|
|
iv[jss::HookParameters][1U][jss::HookParameter]
|
|
[jss::HookParameterName] = "CAFE";
|
|
iv[jss::HookParameters][1U][jss::HookParameter]
|
|
[jss::HookParameterValue] = "FACADE";
|
|
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv, M("Create accept"), HSFEE, ter(tesSUCCESS));
|
|
env.close();
|
|
}
|
|
|
|
// all alice operations below are then updates
|
|
|
|
// must not specify override flag
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::Flags] = hsfOVERRIDE;
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
|
|
env(jv,
|
|
M("Override flag not allowed on update"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
// must not specify NSDELETE unless also Namespace
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::Flags] = hsfNSDELETE;
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
|
|
env(jv,
|
|
M("NSDELETE flag not allowed on update unless HookNamespace "
|
|
"also present"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
// api version not allowed in update
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::HookApiVersion] = 1U;
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
|
|
env(jv,
|
|
M("ApiVersion not allowed in update"),
|
|
HSFEE,
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
// try individually updating the various allowed fields
|
|
{
|
|
Json::Value params{Json::arrayValue};
|
|
params[0U][jss::HookParameter] = Json::Value{};
|
|
params[0U][jss::HookParameter][jss::HookParameterName] = "CAFE";
|
|
params[0U][jss::HookParameter][jss::HookParameterValue] = "BABE";
|
|
|
|
Json::Value grants{Json::arrayValue};
|
|
grants[0U][jss::HookGrant] = Json::Value{};
|
|
grants[0U][jss::HookGrant][jss::HookHash] = accept_hash_str;
|
|
|
|
for (auto const& [key, value] : JSSMap{
|
|
{jss::HookOn,
|
|
"00000000000000000000000000000000000000000000000000000000"
|
|
"00000001"},
|
|
{jss::HookNamespace,
|
|
"CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE"
|
|
"CAFECAFE"},
|
|
{jss::HookParameters, params},
|
|
{jss::HookGrants, grants}})
|
|
{
|
|
Json::Value iv;
|
|
iv[key] = value;
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
|
|
env(jv, M("Normal update"), HSFEE, ter(tesSUCCESS));
|
|
env.close();
|
|
}
|
|
|
|
// ensure hook still exists
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_REQUIRE(hook);
|
|
BEAST_REQUIRE(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() == 1);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash);
|
|
|
|
// check all fields were updated to correct values
|
|
BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookOn));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookOn) == UINT256_BIT[0]);
|
|
|
|
auto const ns = uint256::fromVoid(
|
|
(std::array<uint8_t, 32>{
|
|
0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU,
|
|
0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU,
|
|
0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU,
|
|
0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU})
|
|
.data());
|
|
BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookNamespace));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookNamespace) == ns);
|
|
|
|
BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookParameters));
|
|
const auto& p = hooks[0].getFieldArray(sfHookParameters);
|
|
BEAST_REQUIRE(p.size() == 1);
|
|
BEAST_REQUIRE(p[0].isFieldPresent(sfHookParameterName));
|
|
BEAST_REQUIRE(p[0].isFieldPresent(sfHookParameterValue));
|
|
|
|
const auto pn = p[0].getFieldVL(sfHookParameterName);
|
|
BEAST_REQUIRE(pn.size() == 2);
|
|
BEAST_REQUIRE(pn[0] == 0xCAU && pn[1] == 0xFEU);
|
|
|
|
const auto pv = p[0].getFieldVL(sfHookParameterValue);
|
|
BEAST_REQUIRE(pv.size() == 2);
|
|
BEAST_REQUIRE(pv[0] == 0xBAU && pv[1] == 0xBEU);
|
|
|
|
BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookGrants));
|
|
const auto& g = hooks[0].getFieldArray(sfHookGrants);
|
|
BEAST_REQUIRE(g.size() == 1);
|
|
BEAST_REQUIRE(g[0].isFieldPresent(sfHookHash));
|
|
BEAST_REQUIRE(g[0].getFieldH256(sfHookHash) == accept_hash);
|
|
}
|
|
|
|
// reset hookon and namespace to defaults
|
|
{
|
|
for (auto const& [key, value] : JSSMap{
|
|
{jss::HookOn,
|
|
"00000000000000000000000000000000000000000000000000000000"
|
|
"00000000"},
|
|
{jss::HookNamespace, to_string(uint256{beast::zero})}})
|
|
{
|
|
Json::Value iv;
|
|
iv[key] = value;
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
|
|
env(jv, M("Reset to default"), HSFEE, ter(tesSUCCESS));
|
|
env.close();
|
|
}
|
|
|
|
// ensure hook still exists
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_REQUIRE(hook);
|
|
BEAST_REQUIRE(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() == 1);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash);
|
|
|
|
// ensure the two fields are now absent (because they were reset to
|
|
// the defaults on the hook def)
|
|
BEAST_EXPECT(!hooks[0].isFieldPresent(sfHookOn));
|
|
BEAST_EXPECT(!hooks[0].isFieldPresent(sfHookNamespace));
|
|
}
|
|
|
|
// add three additional parameters
|
|
std::map<ripple::Blob, ripple::Blob> params{
|
|
{{0xFEU, 0xEDU, 0xFAU, 0xCEU}, {0xF0U, 0x0DU}},
|
|
{{0xA0U}, {0xB0U}},
|
|
{{0xCAU, 0xFEU}, {0xBAU, 0xBEU}},
|
|
{{0xAAU}, {0xBBU, 0xCCU}}};
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::HookParameters] = Json::Value{Json::arrayValue};
|
|
iv[jss::HookParameters][0U] = Json::Value{};
|
|
iv[jss::HookParameters][0U][jss::HookParameter] = Json::Value{};
|
|
iv[jss::HookParameters][0U][jss::HookParameter]
|
|
[jss::HookParameterName] = "FEEDFACE";
|
|
iv[jss::HookParameters][0U][jss::HookParameter]
|
|
[jss::HookParameterValue] = "F00D";
|
|
|
|
iv[jss::HookParameters][1U] = Json::Value{};
|
|
iv[jss::HookParameters][1U][jss::HookParameter] = Json::Value{};
|
|
iv[jss::HookParameters][1U][jss::HookParameter]
|
|
[jss::HookParameterName] = "A0";
|
|
iv[jss::HookParameters][1U][jss::HookParameter]
|
|
[jss::HookParameterValue] = "B0";
|
|
|
|
iv[jss::HookParameters][2U] = Json::Value{};
|
|
iv[jss::HookParameters][2U][jss::HookParameter] = Json::Value{};
|
|
iv[jss::HookParameters][2U][jss::HookParameter]
|
|
[jss::HookParameterName] = "AA";
|
|
iv[jss::HookParameters][2U][jss::HookParameter]
|
|
[jss::HookParameterValue] = "BBCC";
|
|
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv, M("Add three parameters"), HSFEE, ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// ensure hook still exists
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_REQUIRE(hook);
|
|
BEAST_REQUIRE(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() == 1);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash);
|
|
|
|
// check all the previous parameters plus the new ones
|
|
BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookParameters));
|
|
const auto& p = hooks[0].getFieldArray(sfHookParameters);
|
|
BEAST_REQUIRE(p.size() == params.size());
|
|
|
|
std::set<ripple::Blob> already;
|
|
|
|
for (uint8_t i = 0; i < params.size(); ++i)
|
|
{
|
|
const auto pn = p[i].getFieldVL(sfHookParameterName);
|
|
const auto pv = p[i].getFieldVL(sfHookParameterValue);
|
|
|
|
// make sure it's not a duplicate entry
|
|
BEAST_EXPECT(already.find(pn) == already.end());
|
|
|
|
// make sure it exists
|
|
BEAST_EXPECT(params.find(pn) != params.end());
|
|
|
|
// make sure the value matches
|
|
BEAST_EXPECT(params[pn] == pv);
|
|
already.emplace(pn);
|
|
}
|
|
}
|
|
|
|
// try to reset CAFE parameter to default
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::HookParameters] = Json::Value{Json::arrayValue};
|
|
iv[jss::HookParameters][0U] = Json::Value{};
|
|
iv[jss::HookParameters][0U][jss::HookParameter] = Json::Value{};
|
|
iv[jss::HookParameters][0U][jss::HookParameter]
|
|
[jss::HookParameterName] = "CAFE";
|
|
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv,
|
|
M("Reset cafe param to default using Absent Value"),
|
|
HSFEE,
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// ensure hook still exists
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_REQUIRE(hook);
|
|
BEAST_REQUIRE(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() == 1);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash);
|
|
|
|
params.erase({0xCAU, 0xFEU});
|
|
|
|
// check there right number of parameters exist
|
|
BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookParameters));
|
|
const auto& p = hooks[0].getFieldArray(sfHookParameters);
|
|
BEAST_REQUIRE(p.size() == params.size());
|
|
|
|
// and that they still have the expected values and that there are
|
|
// no duplicates
|
|
std::set<ripple::Blob> already;
|
|
for (uint8_t i = 0; i < params.size(); ++i)
|
|
{
|
|
const auto pn = p[i].getFieldVL(sfHookParameterName);
|
|
const auto pv = p[i].getFieldVL(sfHookParameterValue);
|
|
|
|
// make sure it's not a duplicate entry
|
|
BEAST_EXPECT(already.find(pn) == already.end());
|
|
|
|
// make sure it exists
|
|
BEAST_EXPECT(params.find(pn) != params.end());
|
|
|
|
// make sure the value matches
|
|
BEAST_EXPECT(params[pn] == pv);
|
|
already.emplace(pn);
|
|
}
|
|
}
|
|
|
|
// now re-add CAFE parameter but this time as an explicit blank (Empty
|
|
// value)
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::HookParameters] = Json::Value{Json::arrayValue};
|
|
iv[jss::HookParameters][0U] = Json::Value{};
|
|
iv[jss::HookParameters][0U][jss::HookParameter] = Json::Value{};
|
|
iv[jss::HookParameters][0U][jss::HookParameter]
|
|
[jss::HookParameterName] = "CAFE";
|
|
iv[jss::HookParameters][0U][jss::HookParameter]
|
|
[jss::HookParameterValue] = "";
|
|
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv,
|
|
M("Set cafe param to blank using Empty Value"),
|
|
HSFEE,
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// ensure hook still exists
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_REQUIRE(hook);
|
|
BEAST_REQUIRE(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() == 1);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash);
|
|
|
|
params[Blob{0xCAU, 0xFEU}] = Blob{};
|
|
|
|
// check there right number of parameters exist
|
|
BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookParameters));
|
|
const auto& p = hooks[0].getFieldArray(sfHookParameters);
|
|
BEAST_REQUIRE(p.size() == params.size());
|
|
|
|
// and that they still have the expected values and that there are
|
|
// no duplicates
|
|
std::set<ripple::Blob> already;
|
|
for (uint8_t i = 0; i < params.size(); ++i)
|
|
{
|
|
const auto pn = p[i].getFieldVL(sfHookParameterName);
|
|
const auto pv = p[i].getFieldVL(sfHookParameterValue);
|
|
|
|
// make sure it's not a duplicate entry
|
|
BEAST_EXPECT(already.find(pn) == already.end());
|
|
|
|
// make sure it exists
|
|
BEAST_EXPECT(params.find(pn) != params.end());
|
|
|
|
// make sure the value matches
|
|
BEAST_EXPECT(params[pn] == pv);
|
|
already.emplace(pn);
|
|
}
|
|
}
|
|
|
|
// try to delete all parameters (reset to defaults) using EMA (Empty
|
|
// Parameters Array)
|
|
{
|
|
Json::Value iv;
|
|
iv[jss::HookParameters] = Json::Value{Json::arrayValue};
|
|
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = iv;
|
|
env(jv, M("Unset all params on hook"), HSFEE, ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// ensure hook still exists
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_REQUIRE(hook);
|
|
BEAST_REQUIRE(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() == 1);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash);
|
|
|
|
// check there right number of parameters exist
|
|
BEAST_REQUIRE(!hooks[0].isFieldPresent(sfHookParameters));
|
|
}
|
|
|
|
// try to set each type of field on a non existent hook
|
|
{
|
|
Json::Value params{Json::arrayValue};
|
|
params[0U][jss::HookParameter] = Json::Value{};
|
|
params[0U][jss::HookParameter][jss::HookParameterName] = "CAFE";
|
|
params[0U][jss::HookParameter][jss::HookParameterValue] = "BABE";
|
|
|
|
Json::Value grants{Json::arrayValue};
|
|
grants[0U][jss::HookGrant] = Json::Value{};
|
|
grants[0U][jss::HookGrant][jss::HookHash] = accept_hash_str;
|
|
|
|
for (auto const& [key, value] : JSSMap{
|
|
{jss::HookOn,
|
|
"00000000000000000000000000000000000000000000000000000000"
|
|
"00000001"},
|
|
{jss::HookNamespace,
|
|
"CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE"
|
|
"CAFECAFE"},
|
|
{jss::HookParameters, params},
|
|
{jss::HookGrants, grants}})
|
|
{
|
|
Json::Value iv;
|
|
iv[key] = value;
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = Json::Value{};
|
|
jv[jss::Hooks][1U] = Json::Value{};
|
|
jv[jss::Hooks][1U][jss::Hook] = iv;
|
|
|
|
env(jv,
|
|
M("Invalid update on non existent hook"),
|
|
HSFEE,
|
|
ter(tecNO_ENTRY));
|
|
env.close();
|
|
}
|
|
|
|
// ensure hook still exists and that there was no created new entry
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_REQUIRE(hook);
|
|
BEAST_REQUIRE(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() == 1);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash);
|
|
}
|
|
|
|
// test adding multiple grants
|
|
{
|
|
{
|
|
// add a second hook
|
|
env(ripple::test::jtx::hook(
|
|
alice, {{{}, hsov1(accept_wasm, 1)}}, 0),
|
|
M("Add second hook"),
|
|
HSFEE,
|
|
ter(tesSUCCESS));
|
|
}
|
|
|
|
Json::Value grants{Json::arrayValue};
|
|
grants[0U][jss::HookGrant] = Json::Value{};
|
|
grants[0U][jss::HookGrant][jss::HookHash] = rollback_hash_str;
|
|
grants[0U][jss::HookGrant][jss::Authorize] = bob.human();
|
|
|
|
grants[1U][jss::HookGrant] = Json::Value{};
|
|
grants[1U][jss::HookGrant][jss::HookHash] = accept_hash_str;
|
|
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = Json::objectValue;
|
|
jv[jss::Hooks][1U] = Json::Value{};
|
|
jv[jss::Hooks][1U][jss::Hook] = Json::Value{};
|
|
jv[jss::Hooks][1U][jss::Hook][jss::HookGrants] = grants;
|
|
|
|
env(jv, M("Add grants"), HSFEE);
|
|
env.close();
|
|
|
|
// ensure hook still exists
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_REQUIRE(hook);
|
|
|
|
BEAST_REQUIRE(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() == 2);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash);
|
|
|
|
// check there right number of grants exist
|
|
// hook 0 should have 1 grant
|
|
BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookGrants));
|
|
BEAST_REQUIRE(hooks[0].getFieldArray(sfHookGrants).size() == 1);
|
|
// hook 1 should have 2 grants
|
|
{
|
|
BEAST_REQUIRE(hooks[1].isFieldPresent(sfHookGrants));
|
|
auto const& grants = hooks[1].getFieldArray(sfHookGrants);
|
|
BEAST_REQUIRE(grants.size() == 2);
|
|
|
|
BEAST_REQUIRE(grants[0].isFieldPresent(sfHookHash));
|
|
BEAST_REQUIRE(grants[0].isFieldPresent(sfAuthorize));
|
|
BEAST_REQUIRE(grants[1].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(!grants[1].isFieldPresent(sfAuthorize));
|
|
|
|
BEAST_EXPECT(
|
|
grants[0].getFieldH256(sfHookHash) == rollback_hash);
|
|
BEAST_EXPECT(grants[0].getAccountID(sfAuthorize) == bob.id());
|
|
|
|
BEAST_EXPECT(grants[1].getFieldH256(sfHookHash) == accept_hash);
|
|
}
|
|
}
|
|
|
|
// update grants
|
|
{
|
|
Json::Value grants{Json::arrayValue};
|
|
grants[0U][jss::HookGrant] = Json::Value{};
|
|
grants[0U][jss::HookGrant][jss::HookHash] = makestate_hash_str;
|
|
|
|
jv[jss::Hooks][0U] = Json::Value{};
|
|
jv[jss::Hooks][0U][jss::Hook] = Json::objectValue;
|
|
jv[jss::Hooks][1U] = Json::Value{};
|
|
jv[jss::Hooks][1U][jss::Hook] = Json::Value{};
|
|
jv[jss::Hooks][1U][jss::Hook][jss::HookGrants] = grants;
|
|
|
|
env(jv, M("update grants"), HSFEE);
|
|
env.close();
|
|
|
|
// ensure hook still exists
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_REQUIRE(hook);
|
|
|
|
BEAST_REQUIRE(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() == 2);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash);
|
|
|
|
// check there right number of grants exist
|
|
// hook 1 should have 1 grant
|
|
{
|
|
BEAST_REQUIRE(hooks[1].isFieldPresent(sfHookGrants));
|
|
auto const& grants = hooks[1].getFieldArray(sfHookGrants);
|
|
BEAST_REQUIRE(grants.size() == 1);
|
|
BEAST_REQUIRE(grants[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(
|
|
grants[0].getFieldH256(sfHookHash) == makestate_hash);
|
|
}
|
|
}
|
|
|
|
// use an empty grants array to reset the grants
|
|
{
|
|
jv[jss::Hooks][0U] = Json::objectValue;
|
|
jv[jss::Hooks][0U][jss::Hook] = Json::objectValue;
|
|
jv[jss::Hooks][1U] = Json::Value{};
|
|
jv[jss::Hooks][1U][jss::Hook] = Json::Value{};
|
|
jv[jss::Hooks][1U][jss::Hook][jss::HookGrants] = Json::arrayValue;
|
|
|
|
env(jv, M("clear grants"), HSFEE);
|
|
env.close();
|
|
|
|
// ensure hook still exists
|
|
auto const hook = env.le(keylet::hook(Account("alice").id()));
|
|
BEAST_REQUIRE(hook);
|
|
|
|
BEAST_REQUIRE(hook->isFieldPresent(sfHooks));
|
|
auto const& hooks = hook->getFieldArray(sfHooks);
|
|
BEAST_EXPECT(hooks.size() == 2);
|
|
BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash));
|
|
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash);
|
|
|
|
// check there right number of grants exist
|
|
// hook 1 should have 0 grants
|
|
BEAST_REQUIRE(!hooks[1].isFieldPresent(sfHookGrants));
|
|
}
|
|
}
|
|
|
|
void
|
|
testWithTickets(FeatureBitset features)
|
|
{
|
|
testcase("with tickets");
|
|
using namespace jtx;
|
|
|
|
Env env{*this, features};
|
|
|
|
auto const alice = Account{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
|
|
std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
|
|
env(ticket::create(alice, 10));
|
|
std::uint32_t const aliceSeq{env.seq(alice)};
|
|
env.require(owners(alice, 10));
|
|
|
|
env(ripple::test::jtx::hook(alice, {{hsov1(accept_wasm, 1)}}, 0),
|
|
HSFEE,
|
|
ticket::use(aliceTicketSeq++),
|
|
ter(tesSUCCESS));
|
|
|
|
env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
|
|
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
|
env.require(owners(alice, 9 + 1));
|
|
}
|
|
|
|
// void
|
|
// testWasm(FeatureBitset features)
|
|
// {
|
|
// testcase("Checks malformed hook binaries");
|
|
// using namespace jtx;
|
|
// Env env{*this, features};
|
|
|
|
// auto const alice = Account{"alice"};
|
|
// env.fund(XRP(10000), alice);
|
|
|
|
// env(ripple::test::jtx::hook(alice, {{hsov1(illegalfunc_wasm, 1)}}, 0),
|
|
// M("Must only contain hook and cbak"),
|
|
// HSFEE,
|
|
// ter(temMALFORMED));
|
|
// }
|
|
|
|
void
|
|
test_accept(FeatureBitset features)
|
|
{
|
|
testcase("Test accept() hookapi");
|
|
using namespace jtx;
|
|
Env env{*this, features};
|
|
|
|
auto const alice = Account{"alice"};
|
|
auto const bob = Account{"bob"};
|
|
env.fund(XRP(10000), alice);
|
|
env.fund(XRP(10000), bob);
|
|
|
|
env(ripple::test::jtx::hook(alice, {{hsov1(accept_wasm, 1)}}, 0),
|
|
M("Install Accept Hook"),
|
|
HSFEE);
|
|
env.close();
|
|
|
|
env(pay(bob, alice, XRP(1)), M("Test Accept Hook"), fee(XRP(1)));
|
|
env.close();
|
|
}
|
|
|
|
void
|
|
test_rollback(FeatureBitset features)
|
|
{
|
|
testcase("Test rollback() hookapi");
|
|
using namespace jtx;
|
|
Env env{*this, features};
|
|
|
|
auto const bob = Account{"bob"};
|
|
auto const alice = Account{"alice"};
|
|
env.fund(XRP(10000), alice);
|
|
env.fund(XRP(10000), bob);
|
|
|
|
env(ripple::test::jtx::hook(alice, {{hsov1(rollback_wasm, 1)}}, 0),
|
|
M("Install Rollback Hook"),
|
|
HSFEE);
|
|
env.close();
|
|
|
|
env(pay(bob, alice, XRP(1)),
|
|
M("Test Rollback Hook"),
|
|
fee(XRP(1)),
|
|
ter(tecHOOK_REJECTED));
|
|
env.close();
|
|
}
|
|
|
|
void
|
|
test_emit(FeatureBitset features)
|
|
{
|
|
testcase("Test float_emit");
|
|
using namespace jtx;
|
|
Env env{
|
|
*this, envconfig(), features, nullptr, beast::severities::kWarning
|
|
// beast::severities::kTrace
|
|
};
|
|
|
|
auto const alice = Account{"alice"};
|
|
auto const bob = Account{"bob"};
|
|
env.fund(XRP(10000), alice);
|
|
env.fund(XRP(10000), bob);
|
|
|
|
TestHook hook = wasm[R"[test.hook](
|
|
#include <stdint.h>
|
|
extern int32_t _g(uint32_t, uint32_t);
|
|
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
|
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
|
extern int64_t emit (uint32_t, uint32_t, uint32_t, uint32_t);
|
|
extern int64_t etxn_reserve(uint32_t);
|
|
extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t);
|
|
extern int64_t hook_account(uint32_t, uint32_t);
|
|
extern int64_t otxn_field (
|
|
uint32_t write_ptr,
|
|
uint32_t write_len,
|
|
uint32_t field_id
|
|
);
|
|
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
|
|
#define OUT_OF_BOUNDS (-1)
|
|
#define ttPAYMENT 0
|
|
#define tfCANONICAL 0x80000000UL
|
|
#define amAMOUNT 1U
|
|
#define amFEE 8U
|
|
#define atACCOUNT 1U
|
|
#define DOESNT_EXIST (-5)
|
|
#define atDESTINATION 3U
|
|
#define SBUF(x) (uint32_t)x,sizeof(x)
|
|
|
|
#define PREREQUISITE_NOT_MET -9
|
|
#define ENCODE_DROPS_SIZE 9
|
|
#define ENCODE_DROPS(buf_out, drops, amount_type ) \
|
|
{\
|
|
uint8_t uat = amount_type; \
|
|
uint64_t udrops = drops; \
|
|
buf_out[0] = 0x60U +(uat & 0x0FU ); \
|
|
buf_out[1] = 0b01000000 + (( udrops >> 56 ) & 0b00111111 ); \
|
|
buf_out[2] = (udrops >> 48) & 0xFFU; \
|
|
buf_out[3] = (udrops >> 40) & 0xFFU; \
|
|
buf_out[4] = (udrops >> 32) & 0xFFU; \
|
|
buf_out[5] = (udrops >> 24) & 0xFFU; \
|
|
buf_out[6] = (udrops >> 16) & 0xFFU; \
|
|
buf_out[7] = (udrops >> 8) & 0xFFU; \
|
|
buf_out[8] = (udrops >> 0) & 0xFFU; \
|
|
buf_out += ENCODE_DROPS_SIZE; \
|
|
}
|
|
|
|
#define _06_XX_ENCODE_DROPS(buf_out, drops, amount_type )\
|
|
ENCODE_DROPS(buf_out, drops, amount_type );
|
|
|
|
#define ENCODE_DROPS_AMOUNT(buf_out, drops )\
|
|
ENCODE_DROPS(buf_out, drops, amAMOUNT );
|
|
#define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\
|
|
ENCODE_DROPS_AMOUNT(buf_out, drops );
|
|
|
|
#define ENCODE_DROPS_FEE(buf_out, drops )\
|
|
ENCODE_DROPS(buf_out, drops, amFEE );
|
|
#define _06_08_ENCODE_DROPS_FEE(buf_out, drops )\
|
|
ENCODE_DROPS_FEE(buf_out, drops );
|
|
|
|
#define ENCODE_TT_SIZE 3
|
|
#define ENCODE_TT(buf_out, tt )\
|
|
{\
|
|
uint8_t utt = tt;\
|
|
buf_out[0] = 0x12U;\
|
|
buf_out[1] =(utt >> 8 ) & 0xFFU;\
|
|
buf_out[2] =(utt >> 0 ) & 0xFFU;\
|
|
buf_out += ENCODE_TT_SIZE; \
|
|
}
|
|
#define _01_02_ENCODE_TT(buf_out, tt)\
|
|
ENCODE_TT(buf_out, tt);
|
|
|
|
|
|
#define ENCODE_ACCOUNT_SIZE 22
|
|
#define ENCODE_ACCOUNT(buf_out, account_id, account_type)\
|
|
{\
|
|
uint8_t uat = account_type;\
|
|
buf_out[0] = 0x80U + uat;\
|
|
buf_out[1] = 0x14U;\
|
|
*(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\
|
|
*(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\
|
|
*(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\
|
|
buf_out += ENCODE_ACCOUNT_SIZE;\
|
|
}
|
|
#define _08_XX_ENCODE_ACCOUNT(buf_out, account_id, account_type)\
|
|
ENCODE_ACCOUNT(buf_out, account_id, account_type);
|
|
|
|
#define ENCODE_ACCOUNT_SRC_SIZE 22
|
|
#define ENCODE_ACCOUNT_SRC(buf_out, account_id)\
|
|
ENCODE_ACCOUNT(buf_out, account_id, atACCOUNT);
|
|
#define _08_01_ENCODE_ACCOUNT_SRC(buf_out, account_id)\
|
|
ENCODE_ACCOUNT_SRC(buf_out, account_id);
|
|
|
|
#define ENCODE_ACCOUNT_DST_SIZE 22
|
|
#define ENCODE_ACCOUNT_DST(buf_out, account_id)\
|
|
ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION);
|
|
#define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\
|
|
ENCODE_ACCOUNT_DST(buf_out, account_id);
|
|
|
|
#define ENCODE_ACCOUNT_OWNER_SIZE 22
|
|
#define ENCODE_ACCOUNT_OWNER(buf_out, account_id) \
|
|
ENCODE_ACCOUNT(buf_out, account_id, atOWNER);
|
|
#define _08_02_ENCODE_ACCOUNT_OWNER(buf_out, account_id) \
|
|
ENCODE_ACCOUNT_OWNER(buf_out, account_id);
|
|
|
|
#define ENCODE_UINT32_COMMON_SIZE 5U
|
|
#define ENCODE_UINT32_COMMON(buf_out, i, field)\
|
|
{\
|
|
uint32_t ui = i; \
|
|
uint8_t uf = field; \
|
|
buf_out[0] = 0x20U +(uf & 0x0FU); \
|
|
buf_out[1] =(ui >> 24 ) & 0xFFU; \
|
|
buf_out[2] =(ui >> 16 ) & 0xFFU; \
|
|
buf_out[3] =(ui >> 8 ) & 0xFFU; \
|
|
buf_out[4] =(ui >> 0 ) & 0xFFU; \
|
|
buf_out += ENCODE_UINT32_COMMON_SIZE; \
|
|
}
|
|
#define _02_XX_ENCODE_UINT32_COMMON(buf_out, i, field)\
|
|
ENCODE_UINT32_COMMON(buf_out, i, field)\
|
|
|
|
#define ENCODE_UINT32_UNCOMMON_SIZE 6U
|
|
#define ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
|
|
{\
|
|
uint32_t ui = i; \
|
|
uint8_t uf = field; \
|
|
buf_out[0] = 0x20U; \
|
|
buf_out[1] = uf; \
|
|
buf_out[2] =(ui >> 24 ) & 0xFFU; \
|
|
buf_out[3] =(ui >> 16 ) & 0xFFU; \
|
|
buf_out[4] =(ui >> 8 ) & 0xFFU; \
|
|
buf_out[5] =(ui >> 0 ) & 0xFFU; \
|
|
buf_out += ENCODE_UINT32_UNCOMMON_SIZE; \
|
|
}
|
|
#define _02_XX_ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
|
|
ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
|
|
|
|
#define ENCODE_LLS_SIZE 6U
|
|
#define ENCODE_LLS(buf_out, lls )\
|
|
ENCODE_UINT32_UNCOMMON(buf_out, lls, 0x1B );
|
|
#define _02_27_ENCODE_LLS(buf_out, lls )\
|
|
ENCODE_LLS(buf_out, lls );
|
|
|
|
#define ENCODE_FLS_SIZE 6U
|
|
#define ENCODE_FLS(buf_out, fls )\
|
|
ENCODE_UINT32_UNCOMMON(buf_out, fls, 0x1A );
|
|
#define _02_26_ENCODE_FLS(buf_out, fls )\
|
|
ENCODE_FLS(buf_out, fls );
|
|
|
|
#define ENCODE_TAG_SRC_SIZE 5
|
|
#define ENCODE_TAG_SRC(buf_out, tag )\
|
|
ENCODE_UINT32_COMMON(buf_out, tag, 0x3U );
|
|
#define _02_03_ENCODE_TAG_SRC(buf_out, tag )\
|
|
ENCODE_TAG_SRC(buf_out, tag );
|
|
|
|
#define ENCODE_TAG_DST_SIZE 5
|
|
#define ENCODE_TAG_DST(buf_out, tag )\
|
|
ENCODE_UINT32_COMMON(buf_out, tag, 0xEU );
|
|
#define _02_14_ENCODE_TAG_DST(buf_out, tag )\
|
|
ENCODE_TAG_DST(buf_out, tag );
|
|
|
|
#define ENCODE_SEQUENCE_SIZE 5
|
|
#define ENCODE_SEQUENCE(buf_out, sequence )\
|
|
ENCODE_UINT32_COMMON(buf_out, sequence, 0x4U );
|
|
#define _02_04_ENCODE_SEQUENCE(buf_out, sequence )\
|
|
ENCODE_SEQUENCE(buf_out, sequence );
|
|
|
|
#define ENCODE_FLAGS_SIZE 5
|
|
#define ENCODE_FLAGS(buf_out, tag )\
|
|
ENCODE_UINT32_COMMON(buf_out, tag, 0x2U );
|
|
#define _02_02_ENCODE_FLAGS(buf_out, tag )\
|
|
ENCODE_FLAGS(buf_out, tag );
|
|
|
|
#define ENCODE_SIGNING_PUBKEY_SIZE 35
|
|
#define ENCODE_SIGNING_PUBKEY(buf_out, pkey )\
|
|
{\
|
|
buf_out[0] = 0x73U;\
|
|
buf_out[1] = 0x21U;\
|
|
*(uint64_t*)(buf_out + 2) = *(uint64_t*)(pkey + 0);\
|
|
*(uint64_t*)(buf_out + 10) = *(uint64_t*)(pkey + 8);\
|
|
*(uint64_t*)(buf_out + 18) = *(uint64_t*)(pkey + 16);\
|
|
*(uint64_t*)(buf_out + 26) = *(uint64_t*)(pkey + 24);\
|
|
buf[34] = pkey[32];\
|
|
buf_out += ENCODE_SIGNING_PUBKEY_SIZE;\
|
|
}
|
|
|
|
#define _07_03_ENCODE_SIGNING_PUBKEY(buf_out, pkey )\
|
|
ENCODE_SIGNING_PUBKEY(buf_out, pkey );
|
|
|
|
#define ENCODE_SIGNING_PUBKEY_NULL_SIZE 35
|
|
#define ENCODE_SIGNING_PUBKEY_NULL(buf_out )\
|
|
{\
|
|
buf_out[0] = 0x73U;\
|
|
buf_out[1] = 0x21U;\
|
|
*(uint64_t*)(buf_out+2) = 0;\
|
|
*(uint64_t*)(buf_out+10) = 0;\
|
|
*(uint64_t*)(buf_out+18) = 0;\
|
|
*(uint64_t*)(buf_out+25) = 0;\
|
|
buf_out += ENCODE_SIGNING_PUBKEY_NULL_SIZE;\
|
|
}
|
|
|
|
#define _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out )\
|
|
ENCODE_SIGNING_PUBKEY_NULL(buf_out );
|
|
|
|
extern int64_t etxn_fee_base (
|
|
uint32_t read_ptr,
|
|
uint32_t read_len
|
|
);
|
|
extern int64_t etxn_details (
|
|
uint32_t write_ptr,
|
|
uint32_t write_len
|
|
);
|
|
extern int64_t ledger_seq (void);
|
|
|
|
#define PREPARE_PAYMENT_SIMPLE_SIZE 270U
|
|
#define PREPARE_PAYMENT_SIMPLE(buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\
|
|
{\
|
|
uint8_t* buf_out = buf_out_master;\
|
|
uint8_t acc[20];\
|
|
uint64_t drops_amount = (drops_amount_raw);\
|
|
uint32_t dest_tag = (dest_tag_raw);\
|
|
uint32_t src_tag = (src_tag_raw);\
|
|
uint32_t cls = (uint32_t)ledger_seq();\
|
|
hook_account(SBUF(acc));\
|
|
_01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \
|
|
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \
|
|
_02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \
|
|
_02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* uint32 | size 5 */ \
|
|
_02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \
|
|
_02_26_ENCODE_FLS (buf_out, cls + 1 ); /* uint32 | size 6 */ \
|
|
_02_27_ENCODE_LLS (buf_out, cls + 5 ); /* uint32 | size 6 */ \
|
|
_06_01_ENCODE_DROPS_AMOUNT (buf_out, drops_amount ); /* amount | size 9 */ \
|
|
uint8_t* fee_ptr = buf_out;\
|
|
_06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \
|
|
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \
|
|
_08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \
|
|
_08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \
|
|
int64_t edlen = etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_SIZE); /* emitdet | size 1?? */ \
|
|
int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \
|
|
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \
|
|
}
|
|
|
|
#define UINT16_FROM_BUF(buf)\
|
|
(((uint64_t)((buf)[0]) << 8U) +\
|
|
((uint64_t)((buf)[1]) << 0U))
|
|
|
|
#define BUFFER_EQUAL_32(buf1, buf2)\
|
|
(\
|
|
*(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\
|
|
*(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\
|
|
*(((uint64_t*)(buf1)) + 2) == *(((uint64_t*)(buf2)) + 2) &&\
|
|
*(((uint64_t*)(buf1)) + 3) == *(((uint64_t*)(buf2)) + 3) &&\
|
|
*(((uint64_t*)(buf1)) + 4) == *(((uint64_t*)(buf2)) + 4) &&\
|
|
*(((uint64_t*)(buf1)) + 5) == *(((uint64_t*)(buf2)) + 5) &&\
|
|
*(((uint64_t*)(buf1)) + 6) == *(((uint64_t*)(buf2)) + 6) &&\
|
|
*(((uint64_t*)(buf1)) + 7) == *(((uint64_t*)(buf2)) + 7))
|
|
|
|
#define ASSERT(x)\
|
|
if (!(x))\
|
|
rollback((uint32_t)#x,sizeof(#x),__LINE__)
|
|
|
|
#define sfDestination ((8U << 16U) + 3U)
|
|
|
|
extern int64_t etxn_generation(void);
|
|
extern int64_t otxn_generation(void);
|
|
extern int64_t otxn_burden(void);
|
|
extern int64_t etxn_burden(void);
|
|
|
|
int64_t cbak(uint32_t r)
|
|
{
|
|
// on callback we emit 2 more txns
|
|
uint8_t bob[20];
|
|
ASSERT(otxn_field(SBUF(bob), sfDestination) == 20);
|
|
|
|
ASSERT(otxn_generation() + 1 == etxn_generation());
|
|
|
|
ASSERT(etxn_burden() == PREREQUISITE_NOT_MET);
|
|
|
|
ASSERT(etxn_reserve(2) == 2);
|
|
|
|
ASSERT(otxn_burden() > 0);
|
|
ASSERT(etxn_burden() == otxn_burden() * 2);
|
|
|
|
uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE];
|
|
PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0);
|
|
|
|
uint8_t hash1[32];
|
|
ASSERT(emit(SBUF(hash1), SBUF(tx)) == 32);
|
|
|
|
ASSERT(etxn_details(tx + 132, 138) == 138);
|
|
uint8_t hash2[32];
|
|
ASSERT(emit(SBUF(hash2), SBUF(tx)) == 32);
|
|
|
|
ASSERT(!BUFFER_EQUAL_32(hash1, hash2));
|
|
|
|
return accept(0,0,0);
|
|
}
|
|
|
|
int64_t hook(uint32_t r)
|
|
{
|
|
_g(1,1);
|
|
|
|
etxn_reserve(1);
|
|
|
|
// bounds checks
|
|
ASSERT(emit(1000000, 32, 0, 32) == OUT_OF_BOUNDS);
|
|
ASSERT(emit(0,1000000, 0, 32) == OUT_OF_BOUNDS);
|
|
ASSERT(emit(0,32, 1000000, 32) == OUT_OF_BOUNDS);
|
|
ASSERT(emit(0,32, 0, 1000000) == OUT_OF_BOUNDS);
|
|
|
|
ASSERT(otxn_generation() == 0);
|
|
ASSERT(otxn_burden == 1);
|
|
|
|
uint8_t bob[20];
|
|
ASSERT(otxn_param(SBUF(bob), "bob", 3) == 20);
|
|
|
|
uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE];
|
|
PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0);
|
|
|
|
uint8_t hash[32];
|
|
ASSERT(emit(SBUF(hash), SBUF(tx)) == 32);
|
|
|
|
return accept(0,0,0);
|
|
}
|
|
)[test.hook]"];
|
|
|
|
env(ripple::test::jtx::hook(alice, {{hsov1(hook, 1, overrideFlag)}}, 0),
|
|
M("set emit"),
|
|
HSFEE);
|
|
env.close();
|
|
|
|
Json::Value invoke;
|
|
invoke[jss::TransactionType] = "Invoke";
|
|
invoke[jss::Account] = alice.human();
|
|
|
|
Json::Value params{Json::arrayValue};
|
|
params[0U][jss::HookParameter][jss::HookParameterName] =
|
|
strHex(std::string("bob"));
|
|
params[0U][jss::HookParameter][jss::HookParameterValue] =
|
|
strHex(bob.id());
|
|
|
|
invoke[jss::HookParameters] = params;
|
|
|
|
env(invoke, M("test emit"), fee(XRP(1)));
|
|
|
|
bool const fixV2 = env.current()->rules().enabled(fixXahauV2);
|
|
|
|
std::optional<uint256> emithash;
|
|
{
|
|
auto meta = env.meta(); // meta can close
|
|
|
|
// ensure hook execution occured
|
|
BEAST_REQUIRE(meta);
|
|
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
|
|
|
|
auto const hookEmissions = meta->getFieldArray(sfHookEmissions);
|
|
BEAST_EXPECT(
|
|
hookEmissions[0u].isFieldPresent(sfEmitNonce) == fixV2 ? true
|
|
: false);
|
|
BEAST_EXPECT(
|
|
hookEmissions[0u].getAccountID(sfHookAccount) == alice.id());
|
|
|
|
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
|
|
BEAST_REQUIRE(hookExecutions.size() == 1);
|
|
|
|
// ensure there was one emitted txn
|
|
BEAST_EXPECT(hookExecutions[0].getFieldU16(sfHookEmitCount) == 1);
|
|
|
|
BEAST_REQUIRE(meta->isFieldPresent(sfAffectedNodes));
|
|
|
|
BEAST_REQUIRE(meta->getFieldArray(sfAffectedNodes).size() == 3);
|
|
|
|
for (auto const& node : meta->getFieldArray(sfAffectedNodes))
|
|
{
|
|
SField const& metaType = node.getFName();
|
|
uint16_t nodeType = node.getFieldU16(sfLedgerEntryType);
|
|
if (metaType == sfCreatedNode && nodeType == ltEMITTED_TXN)
|
|
{
|
|
BEAST_REQUIRE(node.isFieldPresent(sfNewFields));
|
|
|
|
auto const& nf = const_cast<ripple::STObject&>(node)
|
|
.getField(sfNewFields)
|
|
.downcast<STObject>();
|
|
|
|
auto const& et = const_cast<ripple::STObject&>(nf)
|
|
.getField(sfEmittedTxn)
|
|
.downcast<STObject>();
|
|
|
|
auto const& em = const_cast<ripple::STObject&>(et)
|
|
.getField(sfEmitDetails)
|
|
.downcast<STObject>();
|
|
|
|
BEAST_EXPECT(em.getFieldU32(sfEmitGeneration) == 1);
|
|
BEAST_EXPECT(em.getFieldU64(sfEmitBurden) == 1);
|
|
|
|
Blob txBlob = et.getSerializer().getData();
|
|
auto const tx = std::make_unique<STTx>(
|
|
Slice{txBlob.data(), txBlob.size()});
|
|
emithash = tx->getTransactionID();
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
BEAST_REQUIRE(emithash);
|
|
BEAST_EXPECT(
|
|
emithash == hookEmissions[0u].getFieldH256(sfEmittedTxnID));
|
|
}
|
|
|
|
{
|
|
auto balbefore = env.balance(bob).value().xrp().drops();
|
|
|
|
env.close();
|
|
|
|
auto const ledger = env.closed();
|
|
|
|
int txcount = 0;
|
|
for (auto& i : ledger->txs)
|
|
{
|
|
auto const& hash = i.first->getTransactionID();
|
|
txcount++;
|
|
BEAST_EXPECT(hash == *emithash);
|
|
}
|
|
|
|
BEAST_EXPECT(txcount == 1);
|
|
|
|
auto balafter = env.balance(bob).value().xrp().drops();
|
|
|
|
BEAST_EXPECT(balafter - balbefore == 1000);
|
|
|
|
env.close();
|
|
}
|
|
|
|
uint64_t burden_expected = 2;
|
|
for (int j = 0; j < 7; ++j)
|
|
{
|
|
auto const ledger = env.closed();
|
|
for (auto& i : ledger->txs)
|
|
{
|
|
auto const& em = const_cast<ripple::STTx&>(*(i.first))
|
|
.getField(sfEmitDetails)
|
|
.downcast<STObject>();
|
|
BEAST_EXPECT(em.getFieldU64(sfEmitBurden) == burden_expected);
|
|
BEAST_EXPECT(em.getFieldU32(sfEmitGeneration) == j + 2);
|
|
BEAST_REQUIRE(i.second->isFieldPresent(sfHookExecutions));
|
|
auto const hookExecutions =
|
|
i.second->getFieldArray(sfHookExecutions);
|
|
BEAST_EXPECT(hookExecutions.size() == 1);
|
|
BEAST_EXPECT(
|
|
hookExecutions[0].getFieldU64(sfHookReturnCode) == 0);
|
|
BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3);
|
|
BEAST_EXPECT(
|
|
hookExecutions[0].getFieldU16(sfHookEmitCount) == 2);
|
|
if (fixV2)
|
|
BEAST_EXPECT(hookExecutions[0].getFieldU32(sfFlags) == 2);
|
|
}
|
|
env.close();
|
|
burden_expected *= 2U;
|
|
}
|
|
|
|
{
|
|
auto const ledger = env.closed();
|
|
int txcount = 0;
|
|
for (auto& i : ledger->txs)
|
|
{
|
|
txcount++;
|
|
auto const& em = const_cast<ripple::STTx&>(*(i.first))
|
|
.getField(sfEmitDetails)
|
|
.downcast<STObject>();
|
|
BEAST_EXPECT(em.getFieldU64(sfEmitBurden) == 256);
|
|
BEAST_EXPECT(em.getFieldU32(sfEmitGeneration) == 9);
|
|
BEAST_REQUIRE(i.second->isFieldPresent(sfHookExecutions));
|
|
auto const hookExecutions =
|
|
i.second->getFieldArray(sfHookExecutions);
|
|
BEAST_EXPECT(hookExecutions.size() == 1);
|
|
BEAST_EXPECT(
|
|
hookExecutions[0].getFieldU64(sfHookReturnCode) ==
|
|
283); // emission failure on first emit
|
|
if (fixV2)
|
|
BEAST_EXPECT(hookExecutions[0].getFieldU32(sfFlags) == 2);
|
|
}
|
|
BEAST_EXPECT(txcount == 256);
|
|
}
|
|
|
|
// next close will lead to zero transactions
|
|
env.close();
|
|
{
|
|
auto const ledger = env.closed();
|
|
int txcount = 0;
|
|
for ([[maybe_unused]] auto& i : ledger->txs)
|
|
txcount++;
|
|
BEAST_EXPECT(txcount == 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
testWithFeatures(FeatureBitset features)
|
|
{
|
|
testHooksOwnerDir(features);
|
|
testHooksDisabled(features);
|
|
testTxStructure(features);
|
|
// testInferHookSetOperation(); // Not Version Specific
|
|
// testParams(features); // Not Version Specific
|
|
// testGrants(features); // Not Version Specific
|
|
|
|
testInstall(features);
|
|
testDelete(features);
|
|
testNSDelete(features);
|
|
testCreate(features);
|
|
testUpdate(features);
|
|
testWithTickets(features);
|
|
|
|
// DA TODO: illegalfunc_wasm
|
|
// testWasm(features);
|
|
test_accept(features);
|
|
test_rollback(features);
|
|
|
|
// testGuards(features); // Not Used in JSHooks
|
|
|
|
// test_emit(features); //
|
|
// // test_etxn_burden(features); // tested above
|
|
// // test_etxn_generation(features); // tested above
|
|
// // test_otxn_burden(features); // tested above
|
|
// // test_otxn_generation(features); // tested above
|
|
// test_etxn_details(features); //
|
|
// test_etxn_fee_base(features); //
|
|
// test_etxn_nonce(features); //
|
|
// test_etxn_reserve(features); //
|
|
// test_fee_base(features); //
|
|
|
|
// test_otxn_field(features); //
|
|
|
|
// test_ledger_keylet(features); //
|
|
|
|
// test_float_compare(features); //
|
|
// test_float_divide(features); //
|
|
// test_float_int(features); //
|
|
// test_float_invert(features); //
|
|
// test_float_log(features); //
|
|
// test_float_mantissa(features); //
|
|
// test_float_mulratio(features); //
|
|
// test_float_multiply(features); //
|
|
// test_float_negate(features); //
|
|
// test_float_one(features); //
|
|
// test_float_root(features); //
|
|
// test_float_set(features); //
|
|
// test_float_sign(features); //
|
|
// test_float_sto(features); //
|
|
// test_float_sto_set(features); //
|
|
// test_float_sum(features); //
|
|
|
|
// test_hook_account(features); //
|
|
// test_hook_again(features); //
|
|
// test_hook_hash(features); //
|
|
// test_hook_param(features); //
|
|
// test_hook_param_set(features); //
|
|
// test_hook_pos(features); //
|
|
// test_hook_skip(features); //
|
|
|
|
// test_ledger_last_hash(features); //
|
|
// test_ledger_last_time(features); //
|
|
// test_ledger_nonce(features); //
|
|
// test_ledger_seq(features); //
|
|
|
|
// test_meta_slot(features); //
|
|
|
|
// test_otxn_id(features); //
|
|
// test_otxn_slot(features); //
|
|
// test_otxn_type(features); //
|
|
// test_otxn_param(features); //
|
|
|
|
// test_slot(features); //
|
|
// test_slot_clear(features); //
|
|
// test_slot_count(features); //
|
|
// test_slot_float(features); //
|
|
// test_slot_set(features); //
|
|
// test_slot_size(features); //
|
|
// test_slot_subarray(features); //
|
|
// test_slot_subfield(features); //
|
|
// test_slot_type(features); //
|
|
|
|
// test_state(features); //
|
|
// test_state_foreign(features); //
|
|
// test_state_foreign_set(features); //
|
|
// test_state_foreign_set_max(features); //
|
|
// test_state_set(features); //
|
|
|
|
// test_sto_emplace(features); //
|
|
// test_sto_erase(features); //
|
|
// test_sto_subarray(features); //
|
|
// test_sto_subfield(features); //
|
|
// test_sto_validate(features); //
|
|
|
|
// test_trace(features); //
|
|
// test_trace_float(features); //
|
|
// test_trace_num(features); //
|
|
|
|
// test_util_accid(features); //
|
|
// test_util_keylet(features); //
|
|
// test_util_raddr(features); //
|
|
// test_util_sha512h(features); //
|
|
// test_util_verify(features); //
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
using namespace test::jtx;
|
|
auto const sa = supported_amendments();
|
|
testWithFeatures(sa);
|
|
}
|
|
|
|
private:
|
|
TestHook accept_wasm = // WASM: 0
|
|
jswasm[
|
|
R"[test.hook](
|
|
const Hook = (arg) => {
|
|
return accept("0", 0);
|
|
}
|
|
)[test.hook]"];
|
|
|
|
HASH_WASM(accept);
|
|
|
|
TestHook rollback_wasm = // WASM: 1
|
|
jswasm[
|
|
R"[test.hook](
|
|
const Hook = (arg) => {
|
|
return rollback("0", 0);
|
|
}
|
|
)[test.hook]"];
|
|
|
|
HASH_WASM(rollback);
|
|
|
|
TestHook illegalfunc_wasm = // WASM: 3
|
|
jswasm[
|
|
R"[test.hook](
|
|
console.log("HERE");
|
|
return accept(ret, 0);
|
|
}
|
|
)[test.hook]"];
|
|
|
|
TestHook long_wasm = // WASM: 4
|
|
jswasm[
|
|
R"[test.hook](
|
|
const M_REPEAT_10 = (X) => X.repeat(10);
|
|
const M_REPEAT_100 = (X) => M_REPEAT_10(X).repeat(10);
|
|
const M_REPEAT_1000 = (X) => M_REPEAT_100(X).repeat(10);
|
|
const Hook = (arg) => {
|
|
const ret = M_REPEAT_1000("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz01234567890123");
|
|
return accept(ret, 0);
|
|
}
|
|
)[test.hook]"];
|
|
|
|
TestHook makestate_wasm = // WASM: 5
|
|
jswasm[
|
|
R"[test.hook](
|
|
const Hook = (arg) => {
|
|
const test_key = "0000000000000000000000000000000000000000000000006b657900";
|
|
const test_value = "76616C756500";
|
|
return accept("0", state_set(test_value, test_key));
|
|
}
|
|
)[test.hook]"];
|
|
|
|
HASH_WASM(makestate);
|
|
|
|
// this is just used as a second small hook with a unique hash
|
|
TestHook accept2_wasm = // WASM: 6
|
|
jswasm[
|
|
R"[test.hook](
|
|
const Hook = (arg) => {
|
|
return accept("0", 2);
|
|
}
|
|
)[test.hook]"];
|
|
|
|
HASH_WASM(accept2);
|
|
};
|
|
BEAST_DEFINE_TESTSUITE(SetJSHook, app, ripple);
|
|
} // namespace test
|
|
} // namespace ripple
|
|
#undef M
|