Files
xahaud/src/test/app/SetJSHook_test.cpp

11305 lines
410 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 <boost/regex.hpp>
#include "ripple/json/Writer.h"
#include "ripple/json/json_writer.h"
#include <test/app/Import_json.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
// overrideFlag
namespace ripple {
namespace test {
#define DEBUG_TESTS 1
using TestHook = std::vector<uint8_t> const&;
// 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) \
[[maybe_unused]] uint256 const x##_hash = \
ripple::sha512Half_s(ripple::Slice(x##_wasm.data(), x##_wasm.size())); \
[[maybe_unused]] std::string const x##_hash_str = to_string(x##_hash); \
[[maybe_unused]] Keylet const x##_keylet = keylet::hookDefinition(x##_hash);
#define TEST_TARGET_PATTERN(a, target_regex_pattern, test_func_name, ...) \
do \
{ \
const char* current_test_name = #test_func_name; \
const std::string& pattern_str = (target_regex_pattern); \
Json::Value& names = a; \
names.append(std::string(current_test_name)); \
/* Check 1: Does the name match the regex pattern (or is the pattern \
* empty?) */ \
bool name_matches = pattern_str.empty(); \
if (!name_matches && pattern_str != ":log-test-names:") \
{ \
try \
{ \
boost::regex pattern_regex(pattern_str); \
name_matches = \
boost::regex_search(current_test_name, pattern_regex); \
} \
catch (const boost::regex_error& e) \
{ \
std::cerr << "Warning: Invalid regex pattern '" << pattern_str \
<< "' for TEST_TARGET_PATTERN: " << e.what() \
<< std::endl; \
name_matches = false; \
} \
} \
\
if (name_matches) \
{ \
/* Pattern matched (or was empty): Execute the test */ \
test_func_name(__VA_ARGS__); \
} \
} while (0)
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 HSDROPS drops(99'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, HSDROPS, overrideFlag)}}, 0),
HSFEE,
ter(tesSUCCESS));
env.close();
env(ripple::test::jtx::hook(
alice, {{hsov1(accept_wasm, 1, HSDROPS, 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, HSDROPS, 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, HSDROPS),
hsov1(accept_wasm, 1, HSDROPS),
hsov1(accept_wasm, 1, HSDROPS),
hsov1(accept_wasm, 1, HSDROPS),
hsov1(accept_wasm, 1, HSDROPS),
hsov1(accept_wasm, 1, HSDROPS),
hsov1(accept_wasm, 1, HSDROPS),
hsov1(accept_wasm, 1, HSDROPS),
hsov1(accept_wasm, 1, HSDROPS),
hsov1(accept_wasm, 1, HSDROPS),
hsov1(accept_wasm, 1, HSDROPS)}},
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, HSDROPS),
hsov1(rollback_wasm, 1, HSDROPS)}},
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 with invalid fee
{
for (STAmount _fee : {drops(0), drops(1000001), bob["USD"](0)})
{
Json::Value iv;
iv[jss::HookHash] = accept_hash_str;
iv[jss::Fee] = _fee.getJson(JsonOptions::none);
jv[jss::Hooks][0U][jss::Hook] = iv;
env(jv,
M("Hook Install operation must contain valid fee"),
HSFEE,
ter(temMALFORMED));
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, HSDROPS)}}, 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, HSDROPS),
hsov1(makestate_wasm, 1, HSDROPS),
hsov1(rollback_wasm, 1, HSDROPS),
hsov1(accept2_wasm, 1, HSDROPS)}},
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, HSDROPS)}}, 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, HSDROPS)}}, 0),
M("First set = tesSUCCESS"),
HSFEE,
ter(tesSUCCESS));
env(ripple::test::jtx::hook(
bob, {{hsov1(accept_wasm, 1, HSDROPS)}}, 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();
}
// fee missing
{
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";
jv[jss::Hooks][0U] = Json::Value{};
jv[jss::Hooks][0U][jss::Hook] = iv;
env(jv,
M("HSO Create operation must contain fee"),
HSFEE,
ter(temMALFORMED));
env.close();
}
// invalid fee
{
for (STAmount _fee : {drops(0), drops(1000001), bob["USD"](0)})
{
Json::Value iv;
iv[jss::CreateCode] = strHex(accept_wasm);
iv[jss::HookNamespace] = to_string(uint256{beast::zero});
iv[jss::HookApiVersion] = 1U;
iv[jss::HookOn] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
iv[jss::Fee] = _fee.getJson(JsonOptions::none);
jv[jss::Hooks][0U] = Json::Value{};
jv[jss::Hooks][0U][jss::Hook] = iv;
env(jv,
M("HSO Create operation must contain valid fee"),
HSFEE,
ter(temMALFORMED));
env.close();
}
}
// hook hash present
{
Json::Value jv = ripple::test::jtx::hook(
alice, {{hsov1(accept_wasm, 1, HSDROPS)}}, 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, HSDROPS)}}, 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, HSDROPS)}}, 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, HSDROPS)}}, 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::Fee] = "100";
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();
}
// fee should be set with valid fee
{
for (STAmount _fee : {drops(0), drops(1000001), bob["USD"](0)})
{
Json::Value iv;
iv[jss::Fee] = _fee.getJson(JsonOptions::none);
jv[jss::Hooks][0U] = Json::Value{};
jv[jss::Hooks][0U][jss::Hook] = iv;
env(jv,
M("Hook Update operation must contain valid fee"),
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},
{jss::Fee, "1000000"}})
{
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, HSDROPS)}}, 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, HSDROPS)}}, 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, HSDROPS)}}, 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, HSDROPS)}}, 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_float_compare(FeatureBitset features)
{
testcase("Test float_compare");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var INVALID_ARGUMENT = -7
var INVALID_FLOAT = -10024
var EQ = 1
var LT = 2
var GT = 4
var LTE = 3
var GTE = 5
var NEQ = 6
var Hook = (arg) => {
{
ASSERT(float_compare(-1, -2, EQ) == INVALID_FLOAT)
ASSERT(float_compare(0, -2, EQ) == INVALID_FLOAT)
ASSERT(float_compare(-1, 0, EQ) == INVALID_FLOAT)
}
{
ASSERT(float_compare(0, 0, 8) == INVALID_ARGUMENT)
ASSERT(float_compare(0, 0, 16) == INVALID_ARGUMENT)
ASSERT(float_compare(0, 0, ~7) == INVALID_ARGUMENT)
ASSERT(float_compare(0, 0, 7) == INVALID_ARGUMENT)
ASSERT(float_compare(0, 0, 0) == INVALID_ARGUMENT)
}
{
ASSERT(float_compare(0, 0, EQ))
ASSERT(float_compare(0, float_one(), LT))
ASSERT(float_compare(0, float_one(), GT) == 0)
ASSERT(float_compare(0, float_one(), GTE) == 0)
ASSERT(float_compare(0, float_one(), LTE))
ASSERT(float_compare(0, float_one(), NEQ))
const large_negative = 1622844335003378700
const small_negative = 1352229899321149e3
const small_positive = 5713898440837103e3
const large_positive = 7749425685711506e3
ASSERT(float_compare(large_negative, small_negative, LT))
ASSERT(float_compare(large_negative, small_negative, LTE))
ASSERT(float_compare(large_negative, small_negative, NEQ))
ASSERT(float_compare(large_negative, small_negative, GT) == 0)
ASSERT(float_compare(large_negative, small_negative, GTE) == 0)
ASSERT(float_compare(large_negative, small_negative, EQ) == 0)
ASSERT(float_compare(large_negative, large_positive, LT))
ASSERT(float_compare(large_negative, large_positive, LTE))
ASSERT(float_compare(large_negative, large_positive, NEQ))
ASSERT(float_compare(large_negative, large_positive, GT) == 0)
ASSERT(float_compare(large_negative, large_positive, GTE) == 0)
ASSERT(float_compare(large_negative, large_positive, EQ) == 0)
ASSERT(float_compare(small_negative, small_positive, LT))
ASSERT(float_compare(small_negative, small_positive, LTE))
ASSERT(float_compare(small_negative, small_positive, NEQ))
ASSERT(float_compare(small_negative, small_positive, GT) == 0)
ASSERT(float_compare(small_negative, small_positive, GTE) == 0)
ASSERT(float_compare(small_negative, small_positive, EQ) == 0)
ASSERT(float_compare(small_positive, large_positive, LT))
ASSERT(float_compare(small_positive, large_positive, LTE))
ASSERT(float_compare(small_positive, large_positive, NEQ))
ASSERT(float_compare(small_positive, large_positive, GT) == 0)
ASSERT(float_compare(small_positive, large_positive, GTE) == 0)
ASSERT(float_compare(small_positive, large_positive, EQ) == 0)
ASSERT(float_compare(small_negative, 0, LT))
ASSERT(float_compare(large_negative, 0, LT))
ASSERT(float_compare(small_positive, 0, GT))
ASSERT(float_compare(large_positive, 0, GT))
}
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_compare"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_compare"), fee(XRP(1)));
env.close();
}
}
void
test_float_divide(FeatureBitset features)
{
testcase("Test float_divide");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
function frexp(value) {
if (value === BigInt(0)) return [0, 0]
const data = new DataView(new ArrayBuffer(8))
data.setFloat64(0, Number(value))
let bits = (data.getUint32(0) >>> 20) & 2047
if (bits === 0) {
data.setFloat64(0, Number(value) * Math.pow(2, 64))
bits = ((data.getUint32(0) >>> 20) & 2047) - 64
}
const exponent = bits - 1022
const mantissa = Number(value) / Math.pow(2, exponent)
return [mantissa, exponent]
}
function floatMantissa(x) {
if (x === BigInt(0)) return 0
const [mantissa, exponent] = frexp(x)
return mantissa * Math.pow(2, 52)
}
function floatExponent(x) {
if (x === BigInt(0)) return 0
const [mantissa, exponent] = frexp(x)
return exponent
}
function ASSERT_EQUAL(x, y) {
const px = x
const py = y
let mx = floatMantissa(px)
let my = floatMantissa(py)
const diffexp = floatExponent(px) - floatExponent(py)
if (diffexp === 1) mx *= 10
if (diffexp === -1) my *= 10
let diffman = mx - my
if (diffman < 0) diffman *= -1
if (Math.abs(diffexp) > 1 || diffman > 5e6 || mx < 0 || my < 0) {
rollback('x', 0)
}
}
var INVALID_ARGUMENT = -7
var DIVISION_BY_ZERO = -25
var XFL_OVERFLOW = -30
var INVALID_FLOAT = -10024
var Hook = (arg) => {
ASSERT(float_divide(void 0, float_one()) === INVALID_ARGUMENT)
ASSERT(float_divide(float_one(), void 0) === INVALID_ARGUMENT)
ASSERT(float_divide(-1, float_one()) === INVALID_FLOAT)
ASSERT(float_divide(float_one(), 0) === DIVISION_BY_ZERO)
ASSERT(float_divide(0, float_one()) === 0n)
ASSERT(float_divide(float_one(), float_one()) === float_one())
ASSERT(
float_divide(float_one(), float_negate(float_one())) ===
float_negate(float_one())
)
ASSERT(
float_divide(float_negate(float_one()), float_one()) ===
float_negate(float_one())
)
ASSERT(
float_divide(float_negate(float_one()), float_negate(float_one())) ===
float_one()
)
ASSERT_EQUAL(
float_divide(float_one(), 6107881094714392576n),
6071852297695428608n
)
ASSERT_EQUAL(
float_divide(6234216452170766464n, 6144532891733356544n),
6168530993200328528n
)
ASSERT_EQUAL(
float_divide(1478426356228633688n, 6846826132016365020n),
711756787386903390n
)
ASSERT(
float_divide(4638834963451748340n, float_one()) === 4638834963451748340n
)
ASSERT(float_divide(4638834963451748340n, 7441363081262569392n) === 0n)
ASSERT(
float_divide(6846826132016365020n, 4638834963451748340n) === XFL_OVERFLOW
)
ASSERT_EQUAL(
float_divide(
3121244226425810900n,
2135203055881892282n
/* -9.50403176301817e+36 */
),
7066645550312560102n
/* 5.001334595622374e+54 */
)
ASSERT_EQUAL(
float_divide(
2473507938381460320n,
6365869885731270068n
/* 6787211884129716 */
),
2187897766692155363n
/* -8.155547044835299e+39 */
)
ASSERT_EQUAL(
float_divide(
1716271542690607496n,
3137794549622534856n
/* -3.28920897266964e+92 */
),
4667220053951274769n
/* 1.490839995440913e-79 */
)
ASSERT_EQUAL(
float_divide(
1588045991926420391n,
5933338827267685794n
/* 6.601717648113058e-9 */
),
1733591650950017206n
/* -420939403974674.2 */
)
ASSERT_EQUAL(
float_divide(
5880783758174228306n,
1396720886139976383n
/* -0.00009612200909863615 */
),
1341481714205255877n
/* -8.416224503589061e-8 */
)
ASSERT_EQUAL(
float_divide(
5567703563029955929n,
2184969513100691140n
/* -5.227293453371076e+39 */
),
236586937995245543n
/* -2.399757371979751e-69 */
)
ASSERT_EQUAL(
float_divide(
7333313065548121054n,
1755926008837497886n
/* -8529353417745438 */
),
2433647177826281173n
/* -1.703379046213333e+53 */
)
ASSERT_EQUAL(
float_divide(
1172441975040622050n,
6692015311011173216n
/* 8.673463993357152e+33 */
),
560182767210134346n
/* -1.736413416192842e-51 */
)
ASSERT_EQUAL(
float_divide(
577964843368607493n,
6422931182144699580n
/* 9805312769113276000 */
),
235721135837751035n
/* -1.533955214485243e-69 */
)
ASSERT_EQUAL(
float_divide(
6039815413139899240n,
2117655488444284242n
/* -9.970862834892113e+35 */
),
779625635892827768n
/* -5.006499985102456e-39 */
)
ASSERT_EQUAL(
float_divide(
1353563835098586141n,
6450909070545770298n
/* 175440415122002600000 */
),
992207753070525611n
/* -1.415835049016491e-27 */
)
ASSERT_EQUAL(
float_divide(
6382158843584616121n,
5373794957212741595n
/* 5.504201387110363e-40 */
),
7088854809772330055n
/* 9.196195545910343e+55 */
)
ASSERT_EQUAL(
float_divide(
2056891719200540975n,
1754532627802542730n
/* -7135972382790282 */
),
6381651867337939070n
/* 45547949813167340 */
)
ASSERT_EQUAL(
float_divide(
5730152450208688630n,
1663581695074866883n
/* -62570322025.24355 */
),
921249452789827075n
/* -2.515128806245891e-31 */
)
ASSERT_EQUAL(
float_divide(
6234301156018475310n,
2868710604383082256n
/* -4.4212413754468e+77 */
),
219156721749007916n
/* -2.983939635224108e-70 */
)
ASSERT_EQUAL(
float_divide(
2691125731495874243n,
7394070851520237320n
/* 8.16746263262388e+72 */
),
1377640825464715759n
/* -0.000008546538744084975 */
)
ASSERT_EQUAL(
float_divide(
5141867696142208039n,
5369434678231981897n
/* 1.143922406350665e-40 */
),
5861466794943198400n
/* 6.7872793615536e-13 */
)
ASSERT_EQUAL(
float_divide(
638296190872832492n,
5161669734904371378n
/* 9.551761192523954e-52 */
),
1557396184145861422n
/* -81579.12330410798 */
)
ASSERT_EQUAL(
float_divide(
2000727145906286285n,
2096625200460673392n
/* -6.954973360763248e+34 */
),
5982403476503576795n
/* 0.000001623171355558107 */
)
ASSERT_EQUAL(
float_divide(
640472838055334326n,
5189754252349396763n
/* 1.607481618585371e-50 */
),
1537425431139169736n
/* -6201.557833201096 */
)
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_divide"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_divide"), fee(XRP(1)));
env.close();
}
}
void
test_float_int(FeatureBitset features)
{
testcase("Test float_int");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var TOO_BIG = -3
var INVALID_ARGUMENT = -7
var CANT_RETURN_NEGATIVE = -33
var INVALID_FLOAT = -10024
var Hook = (arg) => {
ASSERT(float_int(void 0, 1, 1) === INVALID_ARGUMENT)
ASSERT(float_int(1, void 0, 1) === INVALID_ARGUMENT)
ASSERT(float_int(1, 1, void 0) === INVALID_ARGUMENT)
ASSERT(float_int(-1, 0, 0) === INVALID_FLOAT)
ASSERT(float_int(float_one(), 0, 0) === 1)
ASSERT(float_int(5729808726015270912n, 0, 0) === 0)
ASSERT(float_int(5729808726015270912n, 15, 0) === 0)
ASSERT(float_int(5729808726015270912n, 16, 0) === INVALID_ARGUMENT)
ASSERT(float_int(float_one(), 15, 0) === 1e15)
ASSERT(float_int(float_one(), 14, 0) === 1e14)
ASSERT(float_int(float_one(), 13, 0) === 1e13)
ASSERT(float_int(float_one(), 12, 0) === 1e12)
ASSERT(float_int(float_one(), 11, 0) === 1e11)
ASSERT(float_int(float_one(), 10, 0) === 1e10)
ASSERT(float_int(float_one(), 9, 0) === 1e9)
ASSERT(float_int(float_one(), 8, 0) === 1e8)
ASSERT(float_int(float_one(), 7, 0) === 1e7)
ASSERT(float_int(float_one(), 6, 0) === 1e6)
ASSERT(float_int(float_one(), 5, 0) === 1e5)
ASSERT(float_int(float_one(), 4, 0) === 1e4)
ASSERT(float_int(float_one(), 3, 0) === 1e3)
ASSERT(float_int(float_one(), 2, 0) === 100)
ASSERT(float_int(float_one(), 1, 0) === 10)
ASSERT(float_int(float_one(), 0, 0) === 1)
ASSERT(float_int(6360317241828374919n, 0, 0) === 1234567981234567)
ASSERT(float_int(6360317241828374919n, 1, 0) === TOO_BIG)
ASSERT(float_int(6360317241828374919n, 15, 0) === TOO_BIG)
ASSERT(float_int(6090101264186145159n, 0, 0) === 1)
ASSERT(float_int(6090101264186145159n, 1, 0) === 12)
ASSERT(float_int(6090101264186145159n, 2, 0) === 123)
ASSERT(float_int(6090101264186145159n, 3, 0) === 1234)
ASSERT(float_int(6090101264186145159n, 4, 0) === 12345)
ASSERT(float_int(6090101264186145159n, 5, 0) === 123456)
ASSERT(float_int(6090101264186145159n, 6, 0) === 1234567)
ASSERT(float_int(6090101264186145159n, 7, 0) === 12345679)
ASSERT(float_int(6090101264186145159n, 8, 0) === 123456798)
ASSERT(float_int(6090101264186145159n, 9, 0) === 1234567981)
ASSERT(float_int(6090101264186145159n, 10, 0) === 12345679812)
ASSERT(float_int(6090101264186145159n, 11, 0) === 123456798123)
ASSERT(float_int(6090101264186145159n, 12, 0) === 1234567981234)
ASSERT(float_int(6090101264186145159n, 13, 0) === 12345679812345)
ASSERT(float_int(6090101264186145159n, 14, 0) === 123456798123456)
ASSERT(float_int(6090101264186145159n, 15, 0) === 1234567981234567)
ASSERT(float_int(1478415245758757255n, 0, 1) === 1)
ASSERT(float_int(1478415245758757255n, 1, 1) === 12)
ASSERT(float_int(1478415245758757255n, 2, 1) === 123)
ASSERT(float_int(1478415245758757255n, 3, 1) === 1234)
ASSERT(float_int(1478415245758757255n, 4, 1) === 12345)
ASSERT(float_int(1478415245758757255n, 5, 1) === 123456)
ASSERT(float_int(1478415245758757255n, 6, 1) === 1234567)
ASSERT(float_int(1478415245758757255n, 7, 1) === 12345679)
ASSERT(float_int(1478415245758757255n, 8, 1) === 123456798)
ASSERT(float_int(1478415245758757255n, 9, 1) === 1234567981)
ASSERT(float_int(1478415245758757255n, 10, 1) === 12345679812)
ASSERT(float_int(1478415245758757255n, 11, 1) === 123456798123)
ASSERT(float_int(1478415245758757255n, 12, 1) === 1234567981234)
ASSERT(float_int(1478415245758757255n, 13, 1) === 12345679812345)
ASSERT(float_int(1478415245758757255n, 14, 1) === 123456798123456)
ASSERT(float_int(1478415245758757255n, 15, 1) === 1234567981234567)
ASSERT(float_int(1478415245758757255n, 15, 0) === CANT_RETURN_NEGATIVE)
ASSERT(float_int(5819885286543915399n, 15, 0) === 1)
for (let i = 1; i < 15; ++i)
ASSERT(float_int(5819885286543915399n, i, 0) === 0)
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_int"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_int"), fee(XRP(1)));
env.close();
}
}
void
test_float_invert(FeatureBitset features)
{
testcase("Test float_invert");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var float_exponent = (f) => ((((f) >> 54n) & 0xFFn) - 97n)
var ASSERT_EQUAL = (x, y) => {
const px = (x);
const py = (y);
let mx = Number(float_mantissa(px));
let my = Number(float_mantissa(py));
let diffexp = Number(float_exponent(px)) - Number(float_exponent(py));
if (diffexp == 1)
mx *= 10;
if (diffexp == -1)
my *= 10;
let diffman = mx - my;
if (diffman < 0) diffman *= -1;
if (diffexp < 0) diffexp *= -1;
if (diffexp > 1 || diffman > 5000000 || mx < 0 || my < 0)
rollback('', 1);
}
var INVALID_ARGUMENT = -7
var DIVISION_BY_ZERO = -25
var INVALID_FLOAT = -10024
var Hook = (arg) => {
ASSERT(float_invert(void 0) === INVALID_ARGUMENT)
ASSERT(float_invert(0) === DIVISION_BY_ZERO)
ASSERT(float_invert(-1) === INVALID_FLOAT)
ASSERT(float_invert(float_one()) === float_one())
ASSERT_EQUAL(float_invert(6107881094714392576n), 6071852297695428608n)
ASSERT_EQUAL(float_invert(6126125493223874560n), 6042953581977277640n)
ASSERT_EQUAL(float_invert(6360317241747140351n), 5808736320061298978n)
ASSERT_EQUAL(float_invert(4630700416936869888n), 7549032975472951296n)
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_invert"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_invert"), fee(XRP(1)));
env.close();
}
}
void
test_float_log(FeatureBitset features)
{
testcase("Test float_log");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var INVALID_ARGUMENT = -7
var COMPLEX_NOT_SUPPORTED = -39
var Hook = (arg) => {
ASSERT(float_log(void 0) === INVALID_ARGUMENT)
if (float_log(0) !== INVALID_ARGUMENT) rollback('false', 0)
ASSERT(float_log(6349533412187342878n) === 6108373858112734914n)
if (float_log(1532223873305968640n) !== COMPLEX_NOT_SUPPORTED)
rollback('false', 0)
ASSERT(float_log(6143909891733356544n) === 6091866696204910592n)
ASSERT(float_log(6071976107695428608n) === 1468659350345448364n)
ASSERT(float_log(5783744921543716864n) === 1496890038311378526n)
ASSERT(float_log(7206759403792793600n) === 6113081094714392576n)
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_log"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_log"), fee(XRP(1)));
env.close();
}
}
void
test_float_mantissa(FeatureBitset features)
{
testcase("Test float_mantissa");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var INVALID_ARGUMENT = -7
var INVALID_FLOAT = -10024
var Hook = (arg) => {
ASSERT(float_mantissa(void 0) === INVALID_ARGUMENT)
{
ASSERT(float_mantissa(-1) === INVALID_FLOAT)
ASSERT(float_mantissa(-11010191919n) === INVALID_FLOAT)
}
ASSERT(float_mantissa(0) === 0n)
{
ASSERT(float_mantissa(float_one()) === 1000000000000000n)
ASSERT(float_mantissa(float_negate(float_one())) === 1000000000000000n)
}
{
ASSERT(
float_mantissa(
4763370308433150973n
/* 7.569101929907197e-74 */
) === 7569101929907197n
)
ASSERT(
float_mantissa(
668909658849475214n
/* -2.376913998641806e-45 */
) === 2376913998641806n
)
ASSERT(
float_mantissa(
962271544155031248n
/* -7.508423152486096e-29 */
) === 7508423152486096n
)
ASSERT(
float_mantissa(
7335644976228470276n
/* 3.784782869302788e+69 */
) === 3784782869302788n
)
ASSERT(
float_mantissa(
2837780149340315954n
/* -9.519583351644467e+75 */
) === 9519583351644466n
)
ASSERT(
float_mantissa(
2614004940018599738n
/* -1.917156143712058e+63 */
) === 1917156143712058n
)
ASSERT(
float_mantissa(
4812250541755005603n
/* 2.406139723315875e-71 */
) === 2406139723315875n
)
ASSERT(
float_mantissa(
5140304866732560580n
/* 6.20129153019514e-53 */
) === 6201291530195140n
)
ASSERT(
float_mantissa(
1124677839589482624n
/* -7.785132001599617e-20 */
) === 7785132001599616n
)
ASSERT(
float_mantissa(
5269336076015865585n
/* 9.131711247126257e-46 */
) === 9131711247126257n
)
ASSERT(
float_mantissa(
2296179634826760368n
/* -8.3510241225484e+45 */
) === 8351024122548400n
)
ASSERT(
float_mantissa(
1104028240398536470n
/* -5.149931320135446e-21 */
) === 5149931320135446n
)
ASSERT(
float_mantissa(
2691222059222981864n
/* -7.076681310166248e+67 */
) === 7076681310166248n
)
ASSERT(
float_mantissa(
6113256168823855946n
/* 63.7507410946337 */
) === 6375074109463370n
)
ASSERT(
float_mantissa(
311682216630003626n
/* -5.437441968809898e-65 */
) === 5437441968809898n
)
ASSERT(
float_mantissa(
794955605753965262n
/* -2.322071336757966e-38 */
) === 2322071336757966n
)
ASSERT(
float_mantissa(
204540636400815950n
/* -6.382252796514126e-71 */
) === 6382252796514126n
)
ASSERT(
float_mantissa(
5497195278343034975n
/* 2.803732951029855e-33 */
) === 2803732951029855n
)
ASSERT(
float_mantissa(
1450265914369875626n
/* -0.09114033611316906 */
) === 9114033611316906n
)
ASSERT(
float_mantissa(
7481064015089962668n
/* 5.088633654939308e+77 */
) === 5088633654939308n
)
}
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_mantissa"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_mantissa"), fee(XRP(1)));
env.close();
}
}
void
test_float_mulratio(FeatureBitset features)
{
testcase("Test float_mulratio");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var INVALID_ARGUMENT = -7
var XFL_OVERFLOW = -30
var INVALID_FLOAT = -10024
var Hook = (arg) => {
ASSERT(float_mulratio(void 0, 1, 1, 1) === INVALID_ARGUMENT)
ASSERT(float_mulratio(float_one(), void 0, 1, 1) === INVALID_ARGUMENT)
ASSERT(float_mulratio(float_one(), 1, void 0, 1) === INVALID_ARGUMENT)
ASSERT(float_mulratio(float_one(), 1, 1, void 0) === INVALID_ARGUMENT)
ASSERT(float_mulratio(-1, 0, 1, 1) === INVALID_FLOAT)
ASSERT(float_mulratio(float_one(), 0, 0, 1) === 0n)
ASSERT(float_mulratio(0, 0, 1, 1) === 0n)
ASSERT(float_mulratio(float_one(), 0, 1, 1) === float_one())
ASSERT(
float_mulratio(float_negate(float_one()), 0, 1, 1) ===
float_negate(float_one())
)
ASSERT(
float_mulratio(7801234554605699072n, 0, 4294967295, 1) === XFL_OVERFLOW
)
ASSERT(float_mulratio(7801234554605699072n, 0, 10, 1) === XFL_OVERFLOW)
ASSERT(float_mulratio(3189548536178311168n, 0, 10, 1) === XFL_OVERFLOW)
ASSERT(float_mulratio(3189548536178311168n, 0, 1, 1) === 3189548536178311168n)
ASSERT(
float_mulratio(2296131684119423544n, 0, 2210828011, 2814367554) ===
2294351094683836182n
)
ASSERT(
float_mulratio(565488225163275031n, 0, 2373474507, 4203973264) ===
562422045628095449n
)
ASSERT(
float_mulratio(2292703263479286183n, 0, 3170020147, 773892643) ===
2307839765178024100n
)
ASSERT(
float_mulratio(758435948837102675n, 0, 3802740780, 1954123588) ===
760168290112163547n
)
ASSERT(
float_mulratio(3063742137774439410n, 0, 2888815591, 4122448592) ===
3053503824756415637n
)
ASSERT(
float_mulratio(974014561126802184n, 0, 689168634, 3222648522) ===
957408554638995792n
)
ASSERT(
float_mulratio(2978333847445611553n, 0, 1718558513, 2767410870) ===
2976075722223325259n
)
ASSERT(
float_mulratio(6577058837932757648n, 0, 1423256719, 1338068927) ===
6577173649752398013n
)
ASSERT(
float_mulratio(2668681541248816636n, 0, 345215754, 4259223936) ===
2650183845127530219n
)
ASSERT(
float_mulratio(651803640367065917n, 0, 327563234, 1191613855) ===
639534906402789367n
)
ASSERT(
float_mulratio(3154958130393015979n, 0, 1304112625, 3024066701) ===
3153571282364880741n
)
ASSERT(
float_mulratio(1713286099776800976n, 0, 1902151138, 2927030061) ===
1712614441093927707n
)
ASSERT(
float_mulratio(2333142120591277120n, 0, 914099656, 108514965) ===
2349692988167140473n
)
ASSERT(
float_mulratio(995968561418010814n, 0, 1334462574, 846156977) ===
998955931389416093n
)
ASSERT(
float_mulratio(6276035843030312442n, 0, 2660687613, 236740983) ===
6294920527635363073n
)
ASSERT(
float_mulratio(7333118474702086419n, 0, 46947714, 2479204760) ===
7298214153648998534n
)
ASSERT(
float_mulratio(2873297486994296492n, 0, 880591893, 436034100) ===
2884122995598532758n
)
ASSERT(
float_mulratio(1935815261812737573n, 0, 3123665800, 3786746543) ===
1934366328810191207n
)
ASSERT(
float_mulratio(7249556282125616118n, 0, 2378803159, 2248850590) ===
7250005170160875416n
)
ASSERT(
float_mulratio(311005347529659996n, 0, 992915590, 2433548552) ===
308187142737041831n
)
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_mulratio"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_mulratio"), fee(XRP(1)));
env.close();
}
}
void
test_float_multiply(FeatureBitset features)
{
testcase("Test float_multiply");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var INVALID_ARGUMENT = -7
var XFL_OVERFLOW = -30
var INVALID_FLOAT = -10024
var Hook = (arg) => {
ASSERT(float_multiply(void 0, float_one()) === INVALID_ARGUMENT)
ASSERT(float_multiply(float_one(), void 0) === INVALID_ARGUMENT)
ASSERT(float_multiply(-1, float_one()) === INVALID_FLOAT)
ASSERT(float_multiply(float_one(), 0) === 0n)
ASSERT(float_multiply(0, float_one()) === 0n)
ASSERT(float_multiply(float_one(), float_one()) === float_one())
ASSERT(
float_multiply(float_one(), float_negate(float_one())) ===
float_negate(float_one())
)
ASSERT(
float_multiply(float_negate(float_one()), float_one()) ===
float_negate(float_one())
)
ASSERT(
float_multiply(float_negate(float_one()), float_negate(float_one())) ===
float_one()
)
ASSERT(
float_multiply(7801234554605699072n, 7801234554605699072n) === XFL_OVERFLOW
)
ASSERT(
float_multiply(7801234554605699072n, 6107881094714392576n) === XFL_OVERFLOW
)
ASSERT(
float_multiply(6107881094714392576n, 7801234554605699072n) === XFL_OVERFLOW
)
ASSERT(
float_multiply(3189548536178311168n, 6107881094714392576n) === XFL_OVERFLOW
)
ASSERT(
float_multiply(3189548536178311168n, float_one()),
3189548536178311168n
)
ASSERT(
float_multiply(float_one(), 3189548536178311168n),
3189548536178311168n
)
ASSERT(
float_multiply(
7791757438262485039n,
4759088999670263908n
/* 3.287793167020132e-74 */
) === 6470304726017852129n
/* 3.135661113819873e+21 */
)
ASSERT(
float_multiply(
7534790022873909775n,
1017891960669847079n
/* -9.085644138855975e-26 */
) === 2472307761756037978n
/* -4.335165957006171e+55 */
)
ASSERT(
float_multiply(
2813999069907898454n,
4962524721184225460n
/* 8.56513107667986e-63 */
) === 1696567870013294731n
/* -3214410121988.235 */
)
ASSERT(
float_multiply(
2151742066453140308n,
437647738130579252n
/* -5.302173903011636e-58 */
) === 5732835652591705549n
/* 4.256926576434637e-20 */
)
ASSERT(
float_multiply(
5445302332922546340n,
7770966530708354172n
/* 6.760773121619068e+93 */
) === 7137051085305881332n
/* 3.349275551015668e+58 */
)
ASSERT(
float_multiply(
2542989542826132533n,
6308418769944702613n
/* 3379291626008.213 */
) === 2775217422137696933n
/* -1.000051677471398e+72 */
)
ASSERT(
float_multiply(
5017652318929433511n,
6601401767766764916n
/* 8.131913296358772e+28 */
) === 5538267259220228819n
/* 7.846916809259732e-31 */
)
ASSERT(
float_multiply(
892430323307269235n,
1444078017997143500n
/* -0.0292613723858478 */
) === 5479222755754111850n
/* 2.845608871588714e-34 */
)
ASSERT(
float_multiply(
7030632722283214253n,
297400838197636668n
/* -9.170462045924924e-66 */
) === 1247594596364389994n
/* -4.601099210133098e-13 */
)
ASSERT(
float_multiply(
1321751204165279730n,
2451801790748530375n
/* -1.843593458980551e+54 */
) === 6918764256086244704n
/* 1.235228445162848e+46 */
)
ASSERT(
float_multiply(
2055496484261758590n,
2079877890137711361n
/* -8.222061547283201e+33 */
) === 7279342234795540004n
/* 1.525236964818469e+66 */
)
ASSERT(
float_multiply(
2439875962311968674n,
4707485682591872793n
/* 5.727671617074969e-77 */
) === 1067392794851803610n
/* -4.543282792366554e-23 */
)
ASSERT(
float_multiply(
6348574818322812800n,
6474046245013515838n
/* 6.877180109483582e+21 */
) === 6742547427357110773n
/* 5.162384810848757e+36 */
)
ASSERT(
float_multiply(
1156137305783593424n,
351790564990861307n
/* -9.516993310703611e-63 */
) === 4650775291275116746n
/* 3.060475828764875e-80 */
)
ASSERT(
float_multiply(
5786888485280994123n,
6252137323085080394n
/* 1141040294.831946 */
) === 5949619829273756853n
/* 4.868321144702132e-8 */
)
ASSERT(
float_multiply(
2078182880999439640n,
1662438186251269392n
/* -51135233789.26864 */
) === 6884837854131013999n
/* 3.33762350889611e+44 */
)
ASSERT(
float_multiply(
1823781083140711248n,
1120252241608199010n
/* -3.359534020316002e-20 */
) === 6090320310700749729n
/* 1.453614495839137 */
)
ASSERT(
float_multiply(
6617782604883935174n,
6185835042802056262n
/* 689635.404973575 */
) === 6723852137583788318n
/* 4.481493547008287e+35 */
)
ASSERT(
float_multiply(
333952667495151166n,
1556040883317758614n
/* -68026.1150230799 */
) === 5032611291744396930n
/* 6.594107598923394e-59 */
)
ASSERT(
float_multiply(
2326968399632616779n,
707513695207834635n
/* -4.952153338037259e-43 */
) === 6180479299649214949n
/* 154061.0896894437 */
)
ASSERT(
float_multiply(
1271003508324696477n,
5321949753651889765n
/* 7.702193354704484e-43 */
) === 512101972406838313n
/* -7.698814141342762e-54 */
)
ASSERT(
float_multiply(
1928646740923345323n,
4639329980209973352n
/* 9.629563273103463e-81 */
) === 487453886143282122n
/* -1.065126387268554e-55 */
)
ASSERT(
float_multiply(
6023906813956669432n,
944348444470060009n
/* -7.599721976996842e-30 */
) === 888099590592064433n
/* -5.394063627447218e-33 */
)
ASSERT(
float_multiply(
6580290597764062787n,
6164319297265300034n
/* 33950.07022461506 */
) === 6667036882686408592n
/* 1.709434178074513e+32 */
)
ASSERT(
float_multiply(
2523439530503240484n,
5864448766677980801n
/* 9.769251096336e-13 */
) === 2307233895764065602n
/* -1.39088655037165e+46 */
)
ASSERT(
float_multiply(
6760707453987140465n,
5951641080643457645n
/* 6.889572514402925e-8 */
) === 6632955645489194550n
/* 3.656993999824438e+30 */
)
ASSERT(
float_multiply(
6494270716308443375n,
564752637895553836n
/* -6.306284101612332e-51 */
) === 978508199357889360n
/* -5.730679845862224e-28 */
)
ASSERT(
float_multiply(
6759145618427534062n,
4721897842483633304n
/* 2.125432999353496e-76 */
) === 5394267403342547164n
/* 7.962249007433949e-39 */
)
ASSERT(
float_multiply(
1232673571201806425n,
6884256144221925318n
/* 2.75591359980743e+44 */
) === 2037747561727791011n
/* -2.12053015632682e+31 */
)
ASSERT(
float_multiply(
1427694775835421031n,
4883952867277976402n
/* 2.050871208358738e-67 */
) === 225519204318055258n
/* -9.34642220427145e-70 */
)
ASSERT(
float_multiply(
5843509949864662087n,
5264483986612843822n
/* 4.279621844104494e-46 */
) === 5028946513739275800n
/* 2.929329593802264e-59 */
)
ASSERT(
float_multiply(
6038444022009738988n,
7447499078040748850n
/* 7.552493624689458e+75 */
) === 7406652183825856092n
/* 2.734396428760669e+73 */
)
ASSERT(
float_multiply(
939565473697468970n,
1100284903077087966n
/* -1.406593998686942e-21 */
) === 5174094397561240824n
/* 3.962025339911417e-51 */
)
ASSERT(
float_multiply(
5694071830210473617n,
5536709154363579683n
/* 6.288811952610595e-31 */
) === 5143674525748709390n
/* 9.570950546343951e-53 */
)
ASSERT(
float_multiply(
600729862341871819n,
6330630279715378440n
/* 75764028872020.56 */
) === 851415551394320909n
/* -4.738821448667662e-35 */
)
ASSERT(
float_multiply(
1876763139233864902n,
4849561230315278754n
/* 3.688031264625058e-69 */
) === 649722744589988028n
/* -1.204398248636604e-46 */
)
ASSERT(
float_multiply(
3011947542126279863n,
1557732559110376235n
/* -84942.87294925611 */
) === 7713172080438368541n
/* 3.009518380079389e+90 */
)
ASSERT(
float_multiply(
5391579936313268788n,
1018647290024655822n
/* -9.840973493664718e-26 */
) === 329450072133864645n
/* -5.190898963188932e-64 */
)
ASSERT(
float_multiply(
2815029221608845312n,
4943518985822088837n
/* 7.57379422402522e-64 */
) === 1678961648155863225n
/* -362258677403.8713 */
)
ASSERT(
float_multiply(
1377509900308195934n,
7702104197062186199n
/* 9.95603351337903e+89 */
) === 2998768765665354001n
/* -8.378613091344656e+84 */
)
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_multiply"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_multiply"), fee(XRP(1)));
env.close();
}
}
void
test_emit(FeatureBitset features)
{
testcase("Test emit");
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);
TestHook hook = jswasm[R"[test.hook](
const INVALID_ARGUMENT = -7
const PREREQUISITE_NOT_MET = -9
const EMISSION_FAILURE = -11
const sfDestination = ((8 << 16) + 3)
const ASSERT = (x, code) => {
if (!x) rollback(x.toString(), code || 0)
}
const Callback = (reserves) => {
// on callback we emit 2 more txns
const bob = otxn_field(sfDestination)
ASSERT(bob.length === 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)
let tx = {
TransactionType: "Payment",
Destination: util_raddr(bob),
Amount: "1000"
}
const hash1 = emit(prepare(tx))
ASSERT(hash1.length === 32, 123)
const hash2 = emit(prepare(tx))
ASSERT(hash2.length === 32)
ASSERT(JSON.stringify(hash1) !== JSON.stringify(hash2));
return accept("",0);
}
const Hook = (reserves) => {
etxn_reserve(1);
ASSERT(emit(undefined) === INVALID_ARGUMENT);
ASSERT(emit([]) === INVALID_ARGUMENT);
ASSERT(emit({}) === INVALID_ARGUMENT);
ASSERT(emit({ TransactionType: 'InvalidTransactionType' }) === INVALID_ARGUMENT);
ASSERT(emit({ TransactionType: 'AccountSet', InvalidField: 1 }) === INVALID_ARGUMENT);
ASSERT(emit({ TransactionType: 'AccountSet', Account: 1234 }) === INVALID_ARGUMENT);
ASSERT(emit({ TransactionType: 'AccountSet' }) === EMISSION_FAILURE);
ASSERT(prepare(undefined) === INVALID_ARGUMENT);
ASSERT(prepare([]) === INVALID_ARGUMENT);
ASSERT(prepare({}) === INVALID_ARGUMENT);
ASSERT(prepare({ TransactionType: 'InvalidTransactionType' }) === INVALID_ARGUMENT);
ASSERT(prepare({ TransactionType: 'AccountSet', InvalidField: 1 }) === INVALID_ARGUMENT);
ASSERT(otxn_generation() === 0);
ASSERT(otxn_burden() === 1);
const acc_id = hook_account()
const radd = util_raddr(acc_id)
const seq = ledger_seq()
const bobRadd = util_raddr(otxn_param(['b','o','b'].map(x => x.charCodeAt(0))))
// Tested with Tx built manually and with prepare
// tx_json_1: prepare
let tx_json_1 = {
TransactionType: "Payment",
Destination: bobRadd,
Amount: "1000"
}
tx_json_1 = prepare(tx_json_1)
// tx_json_2: manually built
const tx_json_2 = {
TransactionType: "Payment",
Account: radd,
Destination: bobRadd,
Amount: "1000",
Sequence: 0,
LastLedgerSequence: seq + 5,
FirstLedgerSequence: seq + 1,
EmitDetails: {
EmitCallback: radd,
EmitBurden: etxn_burden().toString(16),
EmitGeneration: etxn_generation(),
EmitHookHash: hook_hash(hook_pos()).map(x => x.toString(16).padStart(2, '0')).join('').toUpperCase(),
EmitParentTxnID: otxn_id(0).map(x => x.toString(16).padStart(2, '0')).join('').toUpperCase(),
EmitNonce: etxn_nonce().map(x => x.toString(16).padStart(2, '0')).join('').toUpperCase(),
},
SigningPubKey: "",
Fee: "0"
}
const fee = etxn_fee_base(sto_from_json(tx_json_2))
tx_json_2.Fee = fee.toString()
// can convert both to sto and same length
const prepared_tx1_sto = sto_from_json(tx_json_1)
const prepared_tx2_sto = sto_from_json(tx_json_2)
ASSERT(typeof prepared_tx1_sto !== 'number' && prepared_tx1_sto.length === prepared_tx2_sto.length)
// same fields excepts for EmitDetails.EmitNonce
const fields = ['Account', 'Destination', 'Amount', 'Sequence', 'LastLedgerSequence', 'FirstLedgerSequence', 'SigningPubKey', 'Fee']
for (const field of fields) {
ASSERT(tx_json_1[field] === tx_json_2[field])
}
const detailFields = ['EmitCallback', 'EmitBurden', 'EmitGeneration', 'EmitHookHash', 'EmitParentTxnID']
for (const field of detailFields) {
ASSERT(tx_json_1['EmitDetails'][field] === tx_json_2['EmitDetails'][field])
}
// EmitNonce is different
ASSERT(tx_json_1['EmitDetails']['EmitNonce'] !== tx_json_2['EmitDetails']['EmitNonce'])
ASSERT(emit(tx_json_1).length === 32)
return accept("", 0);
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, 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();
uint64_t 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) ==
123); // 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
test_etxn_details(FeatureBitset features)
{
// mainly tested in test_emit
testcase("Test etxn_details");
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);
TestHook hook = jswasm[R"[test.hook](
const TOO_SMALL = -4
const OUT_OF_BOUNDS = -1
const PREREQUISITE_NOT_MET = -9
const ASSERT = (x) => {
if (!(x))
rollback(x.toString(), 0);
}
const Hook = (reserved) => {
ASSERT(etxn_details() == PREREQUISITE_NOT_MET);
etxn_reserve(1);
// jshooks always has Callback
ASSERT(etxn_details().length === 138);
return accept("", 0);
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set etxn_details"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test etxn_details"), fee(XRP(1)));
}
void
test_etxn_fee_base(FeatureBitset features)
{
// mainly tested in test_emit
testcase("Test etxn_fee_base");
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);
TestHook hook = jswasm[R"[test.hook](
const TOO_SMALL = -4
const OUT_OF_BOUNDS = -1
const PREREQUISITE_NOT_MET = -9
const INVALID_TXN = -37
const ASSERT = (x) => {
if (!(x))
rollback(x.toString(), 0);
}
const Hook = (reserved) => {
const tx = new Array(116).fill(0)
ASSERT(etxn_fee_base(tx) === PREREQUISITE_NOT_MET);
etxn_reserve(1);
ASSERT(etxn_fee_base(tx) === INVALID_TXN);
return accept("", 0);
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set etxn_fee_base"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test etxn_fee_base"), fee(XRP(1)));
}
void
test_etxn_nonce(FeatureBitset features)
{
// mainly tested in test_emit
testcase("Test etxn_nonce");
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);
TestHook hook = jswasm[R"[test.hook](
const TOO_SMALL = -4
const OUT_OF_BOUNDS = -1
const TOO_MANY_NONCES = -12
const ASSERT = (x) => {
if (!(x))
rollback(x.toString(), 0);
}
const Hook = (reserved) => {
let nonces = [[], []];
for (let i = 0; i < 256; ++i)
{
nonces[i % 2] = etxn_nonce();
ASSERT(nonces[i % 2].length === 32);
ASSERT(JSON.stringify(nonces[i % 2]) !== JSON.stringify(nonces[(i + 1) % 2]));
}
ASSERT(etxn_nonce() === TOO_MANY_NONCES);
return accept("",0);
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set etxn_nonce"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test etxn_nonce"), fee(XRP(1)));
}
void
test_etxn_reserve(FeatureBitset features)
{
// mainly tested in test_emit
testcase("Test etxn_reserve");
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);
TestHook hook = jswasm[R"[test.hook](
const TOO_BIG = -3
const TOO_SMALL = -4
const ALREADY_SET = -8
const ASSERT = (x) => {
if (!(x))
rollback(x.toString(), 0);
}
const Hook = (reserved) => {
ASSERT(etxn_reserve(0) === TOO_SMALL);
ASSERT(etxn_reserve(256) === TOO_BIG);
ASSERT(etxn_reserve(255) === 255);
ASSERT(etxn_reserve(255) === ALREADY_SET);
ASSERT(etxn_reserve(1) === ALREADY_SET);
return accept("",0);
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set etxn_reserve"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test etxn_reserve"), fee(XRP(1)));
}
void
test_fee_base(FeatureBitset features)
{
testcase("Test fee_base");
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);
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x) => {
if (!x) rollback(x.toString(), 0)
}
const Hook = (arg) => {
ASSERT(fee_base() === 10)
return accept("", 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set fee_base"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test fee_base"), fee(XRP(1)));
}
void
test_otxn_field(FeatureBitset features)
{
testcase("Test otxn_field");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
const INVALID_FIELD = -17
const sfAccount = 0x80001
const ASSERT = (x, code) => {
if (!x) {
rollback(x.toString(), code);
}
}
const Hook = (arg) => {
const acc = otxn_field(sfAccount);
ASSERT(typeof acc != 'number');
ASSERT(otxn_field(1) == INVALID_FIELD);
ASSERT(acc.length == 20);
let acc2 = hook_account();
ASSERT(acc2.length == 20);
for (var i = 0; i < 20; ++i)
ASSERT(acc[i] == acc2[i]);
return accept("0", 0);
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set otxn_field"),
HSFEE);
env.close();
// invoke the hook
env(pay(alice, bob, XRP(1)), M("test otxn_field"), fee(XRP(1)));
}
void
test_ledger_keylet(FeatureBitset features)
{
testcase("Test ledger_keylet");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x) => {
if (!x) rollback(x.toString(), 0)
}
const INVALID_ARGUMENT = -7
const DOESNT_EXIST = -5
const DOES_NOT_MATCH = -40
const Hook = (reserved) => {
const arr33 = Array(33).fill(0)
const arr34 = Array(34).fill(0)
const arr35 = Array(35).fill(0)
ASSERT(ledger_keylet(undefined, arr34) === INVALID_ARGUMENT)
ASSERT(ledger_keylet(arr34, undefined) === INVALID_ARGUMENT)
ASSERT(ledger_keylet(arr33, arr34) === INVALID_ARGUMENT)
ASSERT(ledger_keylet(arr34, arr33) === INVALID_ARGUMENT)
ASSERT(ledger_keylet(arr35, arr34) === INVALID_ARGUMENT)
ASSERT(ledger_keylet(arr34, arr35) === INVALID_ARGUMENT)
const trash = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4,
]
const trash2 = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 5
]
ASSERT(ledger_keylet(trash2, trash) === DOESNT_EXIST)
ASSERT(ledger_keylet(trash, trash2) === DOESNT_EXIST)
const first = Array(34).fill(0)
const last = [
0x00, 0x01,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
]
ASSERT(ledger_keylet(first, last) === DOES_NOT_MATCH)
last[1] = 0x00
const out = ledger_keylet(first, last)
ASSERT(out.length === 34)
ASSERT(slot_set(out, 1) === 1)
accept("", 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set ledger_keylet"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test ledger_keylet"), fee(XRP(1)));
env.close();
}
void
test_float_negate(FeatureBitset features)
{
testcase("Test float_negate");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var INVALID_ARGUMENT = -7
var INVALID_FLOAT = -10024
var Hook = (arg) => {
ASSERT(float_negate(void 0) === INVALID_ARGUMENT)
{
ASSERT(float_negate(-1) === INVALID_FLOAT)
ASSERT(float_negate(-11010191919n) === INVALID_FLOAT)
}
ASSERT(float_negate(0) === 0n)
{
ASSERT(float_negate(float_one()) !== float_one())
ASSERT(float_negate(float_negate(float_one())) === float_one())
}
{
ASSERT(float_negate(6488646939756037240n) === 1876960921328649336n)
ASSERT(float_negate(float_one()) === 1478180677777522688n)
ASSERT(float_negate(1838620299498162368n) === 6450306317925550272n)
}
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_negate"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_negate"), fee(XRP(1)));
env.close();
}
}
void
test_float_one(FeatureBitset features)
{
testcase("Test float_one");
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);
{
TestHook hook = jswasm[R"[test.hook](
var Hook = (arg) => {
const f = float_one()
f === 6089866696204910592n ? accept('', 2) : rollback('', 1)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_one"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_one"), fee(XRP(1)));
env.close();
}
}
void
test_float_root(FeatureBitset features)
{
testcase("Test float_root");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback('ASSERT.error', 0)
}
}
var INVALID_ARGUMENT = -7
var COMPLEX_NOT_SUPPORTED = -39
var Hook = (arg) => {
ASSERT(float_root(void 0, 0) === INVALID_ARGUMENT)
ASSERT(float_root(0, void 0) === INVALID_ARGUMENT)
ASSERT(float_root(float_one(), 2), float_one())
ASSERT(float_root(6097866696204910592n, 2) === 6091866696204910592n)
ASSERT(float_root(6143909891733356544n, 3) === 6098866696204910590n)
ASSERT(float_root(1478180677777522688n, 2) === COMPLEX_NOT_SUPPORTED)
ASSERT(float_root(0, 10) === 0n)
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_root"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_root"), fee(XRP(1)));
env.close();
}
}
void
test_float_set(FeatureBitset features)
{
testcase("Test float_set");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback('ASSERT.error', 0)
}
}
var INVALID_ARGUMENT = -7
var INVALID_FLOAT = -10024
var Hook = (arg) => {
ASSERT(float_set(void 0, 0) === INVALID_ARGUMENT)
ASSERT(float_set(0, void 0) === INVALID_ARGUMENT)
ASSERT(float_set(2147483648, 0) === INVALID_ARGUMENT)
ASSERT(float_set(-5, 0) === 0n)
ASSERT(float_set(50, 0) === 0n)
ASSERT(float_set(-50, 0) === 0n)
ASSERT(float_set(0, 0) === 0n)
ASSERT(float_set(-97, 1) === INVALID_FLOAT)
ASSERT(float_set(97, 1) === INVALID_FLOAT)
ASSERT(float_set(-5, 6541432897943971n) === 6275552114197674403n)
ASSERT(float_set(-83, 7906202688397446n) === 4871793800248533126n)
ASSERT(float_set(76, 4760131426754533n) === 7732937091994525669n)
ASSERT(float_set(37, -8019384286534438n) === 2421948784557120294n)
ASSERT(float_set(50, 5145342538007840n) === 7264947941859247392n)
ASSERT(float_set(-70, 4387341302202416n) === 5102462119485603888n)
ASSERT(float_set(-26, -1754544005819476n) === 1280776838179040340n)
ASSERT(float_set(36, 8261761545780560n) === 7015862781734272336n)
ASSERT(float_set(35, 7975622850695472n) === 6997562244529705264n)
ASSERT(float_set(17, -4478222822793996n) === 2058119652903740172n)
ASSERT(float_set(-53, 5506604247857835n) === 5409826157092453035n)
ASSERT(float_set(-60, 5120164869507050n) === 5283338928147728362n)
ASSERT(float_set(41, 5176113875683063n) === 7102849126611584759n)
ASSERT(float_set(-54, -3477931844992923n) === 778097067752718235n)
ASSERT(float_set(21, 6345031894305479n) === 6743730074440567495n)
ASSERT(float_set(-23, 5091583691147091n) === 5949843091820201811n)
ASSERT(float_set(-33, 7509684078851678n) === 5772117207113086558n)
ASSERT(float_set(-72, -1847771838890268n) === 452207734575939868n)
ASSERT(float_set(71, -9138413713437220n) === 3035557363306410532n)
ASSERT(float_set(28, 4933894067102586n) === 6868419726179738490n)
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_set"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_set"), fee(XRP(1)));
env.close();
}
}
void
test_float_sign(FeatureBitset features)
{
testcase("Test float_sign");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback('ASSERT.error', 0)
}
}
var INVALID_ARGUMENT = -7
var INVALID_FLOAT = -10024
var Hook = (arg) => {
ASSERT(float_sign(void 0) === INVALID_ARGUMENT)
{
ASSERT(float_sign(-1) === INVALID_FLOAT)
ASSERT(float_sign(-11010191919n) === INVALID_FLOAT)
}
ASSERT(float_sign(0) === 0)
ASSERT(float_sign(float_one()) === 0)
ASSERT(float_sign(float_negate(float_one())) === 1)
ASSERT(
float_sign(
7248434512952957686n
/* 6.646312141200119e+64 */
) === 0
)
ASSERT(
float_sign(
889927818394811978n
/* -7.222291430194763e-33 */
) === 1
)
ASSERT(
float_sign(
5945816149233111421n
/* 1.064641104056701e-8 */
) === 0
)
ASSERT(
float_sign(
6239200145838704863n
/* 621826155.7938399 */
) === 0
)
ASSERT(
float_sign(
6992780785042190360n
/* 3.194163363180568e+50 */
) === 0
)
ASSERT(
float_sign(
6883099933108789087n
/* 1.599702486671199e+44 */
) === 0
)
ASSERT(
float_sign(
890203738162163464n
/* -7.498211197546248e-33 */
) === 1
)
ASSERT(
float_sign(
4884803073052080964n
/* 2.9010769824633e-67 */
) === 0
)
ASSERT(
float_sign(
2688292350356944394n
/* -4.146972444128778e+67 */
) === 1
)
ASSERT(
float_sign(
4830109852288093280n
/* 2.251051746921568e-70 */
) === 0
)
ASSERT(
float_sign(
294175951907940320n
/* -5.945575756228576e-66 */
) === 1
)
ASSERT(
float_sign(
7612037404955382316n
/* 9.961233953985069e+84 */
) === 0
)
ASSERT(
float_sign(
7520840929603658997n
/* 8.83675114967167e+79 */
) === 0
)
ASSERT(
float_sign(
4798982086157926282n
/* 7.152082635718538e-72 */
) === 0
)
ASSERT(
float_sign(
689790136568817905n
/* -5.242993208502513e-44 */
) === 1
)
ASSERT(
float_sign(
5521738045011558042n
/* 9.332101110070938e-32 */
) === 0
)
ASSERT(
float_sign(
728760820583452906n
/* -8.184880204173546e-42 */
) === 1
)
ASSERT(
float_sign(
2272937984362856794n
/* -3.12377216812681e+44 */
) === 1
)
ASSERT(
float_sign(
1445723661896317830n
/* -0.0457178113775911 */
) === 1
)
ASSERT(
float_sign(
5035721527359772724n
/* 9.704343214299189e-59 */
) === 0
)
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_sign"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_sign"), fee(XRP(1)));
env.close();
}
}
void
test_float_sto(FeatureBitset features)
{
testcase("Test float_sto");
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);
{
TestHook hook = jswasm[R"[test.hook](
const INVALID_FLOAT = -10024
const INVALID_ARGUMENT = -7
const ASSERT = (x) => {
if (!x) rollback(x.toString(), 1)
}
const sfAmount = (6 << 16) + 1
const sfDeliveredAmount = (6 << 16) + 18
const cur1 = ['U'.charCodeAt(0), 'S'.charCodeAt(0), 'D'.charCodeAt(0)]
const cur1full = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...cur1, 0, 0, 0, 0, 0]
const BUFFER_EQUAL_20 = (buf1, buf2) => {
if (buf1.length !== 20 || buf2.length !== 20) return false
for (let i = 0; i < 20; i++) {
if (buf1[i] !== buf2[i]) return false
}
return true
}
const Hook = (reserved) => {
let y
const cur2 = new Array(20).fill(0)
const iss = hook_account()
ASSERT(typeof iss !== 'number' && iss.length === 20)
// zero issuer/currency pointers must be accompanied by 0 length
// TODO: https://github.com/Xahau/xahaud/issues/461
// ASSERT(float_sto([0], undefined, 0n, 0) === INVALID_ARGUMENT)
// ASSERT(float_sto(undefined, [0], 0n, 0) === INVALID_ARGUMENT)
// zero issuer/currency lengths must tbe accompanied by 0 pointers
ASSERT(float_sto([], undefined, 0n, 0) === INVALID_ARGUMENT)
ASSERT(float_sto(undefined, [], 0n, 0) === INVALID_ARGUMENT)
// issuer without currency is invalid
ASSERT(float_sto(undefined, iss, 0n, sfAmount) === INVALID_ARGUMENT)
// currency without issuer is invalid
ASSERT(float_sto(cur1, undefined, 0n, sfAmount) === INVALID_ARGUMENT)
// currency and issuer with field code 0 = XRP is invalid
ASSERT(float_sto(cur1, iss, 0n, 0) === INVALID_ARGUMENT)
// invalid XFL
ASSERT(float_sto(cur2, iss, -1n, sfAmount) === INVALID_FLOAT)
// invalid currency length (not 3 or 20 bytes)
ASSERT(float_sto([1, 2], iss, 0n, sfAmount) === INVALID_ARGUMENT)
ASSERT(float_sto([1, 2, 3, 4], iss, 0n, sfAmount) === INVALID_ARGUMENT)
ASSERT(float_sto(new Array(19).fill(0), iss, 0n, sfAmount) === INVALID_ARGUMENT)
ASSERT(float_sto(new Array(21).fill(0), iss, 0n, sfAmount) === INVALID_ARGUMENT)
// invalid issuer length (not 20 bytes)
ASSERT(float_sto(cur1, [1, 2, 3], 0n, sfAmount) === INVALID_ARGUMENT)
ASSERT(float_sto(cur1, new Array(19).fill(0), 0n, sfAmount) === INVALID_ARGUMENT)
ASSERT(float_sto(cur1, new Array(21).fill(0), 0n, sfAmount) === INVALID_ARGUMENT)
// currency and issuer with field code not XRP is valid (XFL = 1234567.0)
// try with a three letter currency
y = float_sto(cur1, iss, 6198187654261802496n, sfAmount)
ASSERT(typeof y !== 'number' && y.length === 49)
// check the output contains the correct currency code
ASSERT(BUFFER_EQUAL_20(y.slice(9, 29), cur1full))
// again with a 20 byte currency
y = float_sto(cur2, iss, 6198187654261802496n, sfAmount)
ASSERT(typeof y !== 'number' && y.length === 49)
// check the output contains the correct currency code
ASSERT(BUFFER_EQUAL_20(y.slice(9, 29), cur2))
// check the output contains the correct issuer
ASSERT(BUFFER_EQUAL_20(y.slice(29, 49), iss))
// check the field code is correct
ASSERT(y[0] === 0x61) // sfAmount
// reverse the operation and check the XFL amount is correct
ASSERT(float_sto_set(y) === 6198187654261802496n)
// test 0
y = float_sto(cur2, iss, 0n, sfAmount)
ASSERT(typeof y !== 'number' && y.length === 49)
ASSERT(float_sto_set(y) === 0n)
y = float_sto(cur2, iss, 6198187654261802496n, sfDeliveredAmount)
ASSERT(typeof y !== 'number' && y.length === 50)
// check the first 2 bytes
ASSERT(y[0] === 0x60 && y[1] === 0x12)
// same checks as above moved along one
// check the output contains the correct currency code
ASSERT(BUFFER_EQUAL_20(y.slice(10, 30), cur2))
// check the output contains the correct issuer
ASSERT(BUFFER_EQUAL_20(y.slice(30, 50), iss))
// reverse the operation and check the XFL amount is correct
ASSERT(float_sto_set(y) === 6198187654261802496n)
// and the same again except use -1 as field code to supress field type bytes
{
// zero the serialized amount bytes
for (let i = 0; i < 8; i++) y[2 + i] = 0
let z
// request fieldcode -1 = only serialize the number
z = float_sto(undefined, undefined, 6198187654261802496n, 0xffffffff)
ASSERT(typeof z !== 'number' && z.length === 8)
for (let i = 0; i < 8; i++) y[2 + i] = z[i]
// reverse the operation and check the XFL amount is correct
ASSERT(float_sto_set(y) === 6198187654261802496n)
// try again with some different xfls
z = float_sto(undefined, undefined, 1244912689067196128n, 0xffffffff)
ASSERT(typeof z !== 'number' && z.length === 8)
for (let i = 0; i < 8; i++) y[2 + i] = z[i]
ASSERT(float_sto_set(y) === 1244912689067196128n)
// test 0
z = float_sto(undefined, undefined, 0n, 0xffffffff)
ASSERT(typeof z !== 'number' && z.length === 8)
for (let i = 0; i < 8; i++) y[2 + i] = z[i]
ASSERT(float_sto_set(y) === 0n)
}
// finally test xrp
{
// zero the serialized amount bytes
for (let i = 0; i < 8; i++) y[2 + i] = 0
// request fieldcode 0 = xrp amount serialized
let z
z = float_sto(undefined, undefined, 6198187654261802496n, 0)
ASSERT(typeof z !== 'number' && z.length === 8)
for (let i = 0; i < 8; i++) y[1 + i] = z[i]
y[0] = 0x61
ASSERT(float_sto_set(y.slice(0, 9)) === 6198187654261802496n)
// test 0
z = float_sto(undefined, undefined, 0n, 0)
for (let i = 0; i < 8; i++) y[1 + i] = z[i]
ASSERT(float_sto_set(y.slice(0, 9)) === 0n)
//6198187654373024496
}
return accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_sto"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_sto"), fee(XRP(1)));
env.close();
}
}
void
test_float_sto_set(FeatureBitset features)
{
testcase("Test float_sto_set");
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);
{
TestHook hook = jswasm[R"[test.hook](
const NOT_AN_OBJECT = -23
const ASSERT = (x) => {
if (!x) rollback(x.toString(), 1)
}
// 1234567000000000 * 10**-9, currency USD, issuer 7C4C8D5B2FDA1D16E9A4F5BB579AC2926C146235 (alice)
const iou =
//6198187654261802496
[
0x61, 0xd6, 0x04, 0x62, 0xd5, 0x07, 0x7c, 0x86, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x4c, 0x8d, 0x5b, 0x2f, 0xda, 0x1d,
0x16, 0xe9, 0xa4, 0xf5, 0xbb, 0x57, 0x9a, 0xc2, 0x92, 0x6c, 0x14, 0x62,
0x35,
]
// as above but value is negative
const iou_neg =
//1586501635834414592
[
0x61, 0x96, 0x04, 0x62, 0xd5, 0x07, 0x7c, 0x86, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x4c, 0x8d, 0x5b, 0x2f, 0xda, 0x1d,
0x16, 0xe9, 0xa4, 0xf5, 0xbb, 0x57, 0x9a, 0xc2, 0x92, 0x6c, 0x14, 0x62,
0x35,
]
// as above but value is 0
const iou_zero =
// 0
[
0x61, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x4c, 0x8d, 0x5b, 0x2f, 0xda, 0x1d,
0x16, 0xe9, 0xa4, 0xf5, 0xbb, 0x57, 0x9a, 0xc2, 0x92, 0x6c, 0x14, 0x62,
0x35,
]
// XRP short code 1234567 drops
const xrp_short =
//6198187654261802496
[0x61, 0x40, 0x00, 0x00, 0x00, 0x00, 0x12, 0xd6, 0x87]
// XRP long code 755898701447 drops
const xrp_long =
//6294584066823682416
[0x60, 0x11, 0x40, 0x00, 0x00, 0xaf, 0xff, 0x12, 0xd6, 0x87]
// XRP negative 1234567 drops
const xrp_neg =
//1586501635834414592
[0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0xd6, 0x87]
// XRP negative zero
const xrp_neg_zero =
// 0
[0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
// XRP positive zero
const xrp_pos_zero =
// 0
[0x61, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
const Hook = (reserved) => {
// too small check
ASSERT(float_sto_set(Array(7).fill(0)) === NOT_AN_OBJECT)
// garbage check
ASSERT(float_sto_set(Array(9).fill(0)) === NOT_AN_OBJECT)
ASSERT(float_sto_set(Array(8).fill(0)) === 0n)
ASSERT(float_sto_set(iou) === 6198187654261802496n)
ASSERT(float_sto_set(xrp_short) === 6198187654261802496n)
ASSERT(float_sto_set(iou_neg) === 1586501635834414592n)
ASSERT(float_sto_set(xrp_neg) === 1586501635834414592n)
ASSERT(float_sto_set(xrp_pos_zero) === 0n)
ASSERT(float_sto_set(xrp_neg_zero) === 0n)
ASSERT(float_sto_set(iou_zero) === 0n)
ASSERT(float_sto_set(xrp_long) === 6294584066823682416n)
return accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_sto_set"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_sto_set"), fee(XRP(1)));
env.close();
}
}
void
test_float_sum(FeatureBitset features)
{
testcase("Test float_sum");
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);
{
TestHook hook = jswasm[R"[test.hook](
const float_exponent = (f) => Number(((f >> 54n) & 0xffn) - 97n)
const ASSERT_EQUAL = (x, y) => {
const px = x
const py = y
let mx = float_mantissa(px)
let my = float_mantissa(py)
let diffexp = float_exponent(px) - float_exponent(py)
if (diffexp === 1) mx *= 10n
if (diffexp === -1) my *= 10n
let diffman = mx - my
if (diffman < 0n) diffman *= -1n
if (diffexp < 0) diffexp *= -1
if (diffexp > 1 || diffman > 5000000n || mx < 0n || my < 0n) rollback('', 0)
}
const Hook = (reserved) => {
// 1 + 1 = 2
ASSERT_EQUAL(6090866696204910592n, float_sum(float_one(), float_one()))
// 1 - 1 = 0
ASSERT_EQUAL(0n, float_sum(float_one(), float_negate(float_one())))
// 45678 + 0.345678 = 45678.345678
ASSERT_EQUAL(
6165492124810638528n,
float_sum(6165492090242838528n, 6074309077695428608n)
)
// -151864512641 + 100000000000000000 = 99999848135487359
ASSERT_EQUAL(
6387097057170171072n,
float_sum(1676857706508234512n, 6396111470866104320n)
)
// auto generated random sums
ASSERT_EQUAL(
float_sum(
95785354843184473n /* -5.713362295774553e-77 */,
7607324992379065667n /* 5.248821377668419e+84 */
),
7607324992379065667n /* 5.248821377668419e+84 */
)
ASSERT_EQUAL(
float_sum(
1011203427860697296n /* -2.397111329706192e-26 */,
7715811566197737722n /* 5.64900413944857e+90 */
),
7715811566197737722n /* 5.64900413944857e+90 */
)
ASSERT_EQUAL(
float_sum(
6507979072644559603n /* 4.781210721563379e+23 */,
422214339164556094n /* -7.883173446470462e-59 */
),
6507979072644559603n /* 4.781210721563379e+23 */
)
ASSERT_EQUAL(
float_sum(
129493221419941559n /* -3.392431853567671e-75 */,
6742079437952459317n /* 4.694395406197301e+36 */
),
6742079437952459317n /* 4.694395406197301e+36 */
)
ASSERT_EQUAL(
float_sum(
5172806703808250354n /* 2.674331586920946e-51 */,
3070396690523275533n /* -7.948943911338253e+88 */
),
3070396690523275533n /* -7.948943911338253e+88 */
)
ASSERT_EQUAL(
float_sum(
2440992231195047997n /* -9.048432414980156e+53 */,
4937813945440933271n /* 1.868753842869655e-64 */
),
2440992231195047996n /* -9.048432414980156e+53 */
)
ASSERT_EQUAL(
float_sum(
7351918685453062372n /* 2.0440935844129e+70 */,
6489541496844182832n /* 4.358033430668592e+22 */
),
7351918685453062372n /* 2.0440935844129e+70 */
)
ASSERT_EQUAL(
float_sum(
4960621423606196948n /* 6.661833498651348e-63 */,
6036716382996689576n /* 0.001892882320224936 */
),
6036716382996689576n /* 0.001892882320224936 */
)
ASSERT_EQUAL(
float_sum(
1342689232407435206n /* -9.62374270576839e-8 */,
5629833007898276923n /* 9.340672939897915e-26 */
),
1342689232407435206n /* -9.62374270576839e-8 */
)
ASSERT_EQUAL(
float_sum(
7557687707019793516n /* 9.65473154684222e+81 */,
528084028396448719n /* -5.666471621471183e-53 */
),
7557687707019793516n /* 9.65473154684222e+81 */
)
ASSERT_EQUAL(
float_sum(
130151633377050812n /* -4.050843810676924e-75 */,
2525286695563827336n /* -3.270904236349576e+58 */
),
2525286695563827336n /* -3.270904236349576e+58 */
)
ASSERT_EQUAL(
float_sum(
5051914485221832639n /* 7.88290256687712e-58 */,
7518727241611221951n /* 6.723063157234623e+79 */
),
7518727241611221951n /* 6.723063157234623e+79 */
)
ASSERT_EQUAL(
float_sum(
3014788764095798870n /* -6.384213012307542e+85 */,
7425019819707800346n /* 3.087633801222938e+74 */
),
3014788764095767995n /* -6.384213012276667e+85 */
)
ASSERT_EQUAL(
float_sum(
4918950856932792129n /* 1.020063844210497e-65 */,
7173510242188034581n /* 3.779635414204949e+60 */
),
7173510242188034581n /* 3.779635414204949e+60 */
)
ASSERT_EQUAL(
float_sum(
20028000442705357n /* -2.013601933223373e-81 */,
95248745393457140n /* -5.17675284604722e-77 */
),
95248946753650462n /* -5.176954206240542e-77 */
)
ASSERT_EQUAL(
float_sum(
5516870225060928024n /* 4.46428115944092e-32 */,
7357202055584617194n /* 7.327463715967722e+70 */
),
7357202055584617194n /* 7.327463715967722e+70 */
)
ASSERT_EQUAL(
float_sum(
2326103538819088036n /* -2.2461310959121e+47 */,
1749360946246242122n /* -1964290826489674 */
),
2326103538819088036n /* -2.2461310959121e+47 */
)
ASSERT_EQUAL(
float_sum(
1738010758208819410n /* -862850129854894.6 */,
2224610859005732191n /* -8.83984233944816e+41 */
),
2224610859005732192n /* -8.83984233944816e+41 */
)
ASSERT_EQUAL(
float_sum(
4869534730307487904n /* 5.647132747352224e-68 */,
2166841923565712115n /* -5.114102427874035e+38 */
),
2166841923565712115n /* -5.114102427874035e+38 */
)
ASSERT_EQUAL(
float_sum(
1054339559322014937n /* -9.504445772059864e-24 */,
1389511416678371338n /* -0.0000240273144825857 */
),
1389511416678371338n /* -0.0000240273144825857 */
)
accept('', 0)
}
)[test.hook]"];
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set float_sum"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test float_sum"), fee(XRP(1)));
env.close();
}
}
void
test_hook_account(FeatureBitset features)
{
testcase("Test hook_account");
using namespace jtx;
auto const test = [&](Account alice) -> void {
Env env{*this, features};
auto const bob = Account{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[
R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
rollback(x.toString(), code)
}
}
const toHex = (arr) => {
return arr
.map((num) => num.toString(16).padStart(2, '0'))
.join('')
.toUpperCase()
}
const Hook = (arg) => {
let acc2 = hook_account()
trace('acc2', acc2, false)
ASSERT(acc2.length == 20)
return accept(toHex(acc2), 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set hook_account"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test hook_account"), fee(XRP(1)));
{
auto meta = env.meta();
// ensure hook execution occured
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
// ensure there was only one hook execution
auto const hookExecutions =
meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 1);
// get the data in the return string of the extention
auto const tmpRet =
hookExecutions[0].getFieldVL(sfHookReturnString);
// DA: TODO Fix `accept` and `rollback` and remove these lines
std::string tmpStr(tmpRet.begin(), tmpRet.end());
auto const tmpBlob = strUnHex(tmpStr);
Blob const retStr = Blob(tmpBlob->begin(), tmpBlob->end());
// check that it matches the account id
BEAST_EXPECT(retStr.size() == 20);
auto const a = alice.id();
BEAST_EXPECT(memcmp(retStr.data(), a.data(), 20) == 0);
}
// install the same hook bob
env(ripple::test::jtx::hook(
bob, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set hook_account 2"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test hook_account 2"), fee(XRP(1)));
// there should be two hook executions, the first should be bob's
// address the second should be alice's
{
auto meta = env.meta();
// ensure hook execution occured
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
// ensure there were two hook executions
auto const hookExecutions =
meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 2);
{
// get the data in the return string of the extention
auto const tmpRet =
hookExecutions[0].getFieldVL(sfHookReturnString);
// DA: TODO Fix `accept` and `rollback` and remove these
// lines
std::string tmpStr(tmpRet.begin(), tmpRet.end());
auto const tmpBlob = strUnHex(tmpStr);
Blob const retStr = Blob(tmpBlob->begin(), tmpBlob->end());
// check that it matches the account id
BEAST_EXPECT(retStr.size() == 20);
auto const b = bob.id();
BEAST_EXPECT(memcmp(retStr.data(), b.data(), 20) == 0);
}
{
// get the data in the return string of the extention
auto const tmpRet =
hookExecutions[1].getFieldVL(sfHookReturnString);
// DA: TODO Fix `accept` and `rollback` and remove these
// lines
std::string tmpStr(tmpRet.begin(), tmpRet.end());
auto const tmpBlob = strUnHex(tmpStr);
Blob const retStr = Blob(tmpBlob->begin(), tmpBlob->end());
// check that it matches the account id
BEAST_EXPECT(retStr.size() == 20);
auto const a = alice.id();
BEAST_EXPECT(memcmp(retStr.data(), a.data(), 20) == 0);
}
}
};
test(Account{"alice"});
test(Account{"cho"});
}
void
test_hook_again(FeatureBitset features)
{
testcase("Test hook_again");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
const PREREQUISITE_NOT_MET = -9
const ALREADY_SET = -8
const Hook = (r) => {
if (r > 0) {
if (hook_again() != PREREQUISITE_NOT_MET) return rollback('', 253)
return accept('', 1)
}
if (hook_again() != 1) return rollback('', 254)
if (hook_again() != ALREADY_SET) return rollback('', 255)
return accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set hook_again"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test hook_again"), fee(XRP(1)));
env.close();
auto meta = env.meta();
// ensure hook execution occured
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
// ensure there were two executions
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 2);
// get the data in the return code of the execution
bool const fixV2 = env.current()->rules().enabled(fixXahauV2);
if (fixV2)
{
BEAST_EXPECT(hookExecutions[0].getFieldU32(sfFlags) == 5);
BEAST_EXPECT(hookExecutions[1].getFieldU32(sfFlags) == 0);
}
BEAST_EXPECT(hookExecutions[0].getFieldU64(sfHookReturnCode) == 0);
BEAST_EXPECT(hookExecutions[1].getFieldU64(sfHookReturnCode) == 1);
// RH TODO: test hook_again on a weak execution not following a strong
// execution to make sure it fails
}
void
test_hook_hash(FeatureBitset features)
{
testcase("Test hook_hash");
using namespace jtx;
auto const test = [&](Account alice) -> void {
Env env{*this, features};
// Env env{*this, envconfig(), features, nullptr,
// beast::severities::kTrace
// };
auto const bob = Account{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
rollback(x.toString(), code)
}
}
const toHex = (arr) => {
return arr
.map((num) => num.toString(16).padStart(2, '0'))
.join('')
.toUpperCase()
}
const Hook = (arg) => {
const hash = hook_hash(-1)
ASSERT(hash.length == 32, 0)
trace('hash', hash, false)
// return the hash as the return string
return accept(toHex(hash), 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set hook_hash"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test hook_hash"), fee(XRP(1)));
{
auto meta = env.meta();
// ensure hook execution occured
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
// ensure there was only one hook execution
auto const hookExecutions =
meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 1);
// get the data in the return string of the extention
auto const tmpRet =
hookExecutions[0].getFieldVL(sfHookReturnString);
// DA: TODO Fix `accept` and `rollback` and remove these lines
std::string tmpStr(tmpRet.begin(), tmpRet.end());
auto const tmpBlob = strUnHex(tmpStr);
Blob const retStr = Blob(tmpBlob->begin(), tmpBlob->end());
// check that it matches the hook hash
BEAST_EXPECT(retStr.size() == 32);
auto const hash = hookExecutions[0].getFieldH256(sfHookHash);
BEAST_EXPECT(memcmp(hash.data(), retStr.data(), 32) == 0);
}
TestHook hook2 = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
rollback(x.toString(), code)
}
}
const toHex = (arr) => {
return arr
.map((num) => num.toString(16).padStart(2, '0'))
.join('')
.toUpperCase()
}
const Hook = (arg) => {
const hash = hook_hash(-1)
ASSERT(hash.length == 32, 0)
trace('hash', hash, false)
// return the hash as the return string
return accept(toHex(hash), 2)
}
)[test.hook]"];
// install a slightly different hook on bob
env(ripple::test::jtx::hook(
bob, {{hsov1(hook2, 1, HSDROPS, overrideFlag)}}, 0),
M("set hook_hash 2"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test hook_hash 2"), fee(XRP(1)));
// there should be two hook executions, the first should have bob's
// hook hash the second should have alice's hook hash
{
auto meta = env.meta();
// ensure hook execution occured
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
// ensure there was only one hook execution
auto const hookExecutions =
meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 2);
// get the data in the return string of the extention
auto const tmpRet1 =
hookExecutions[0].getFieldVL(sfHookReturnString);
// DA: TODO Fix `accept` and `rollback` and remove these lines
std::string tmpStr1(tmpRet1.begin(), tmpRet1.end());
auto const tmpBlob1 = strUnHex(tmpStr1);
Blob const retStr1 = Blob(tmpBlob1->begin(), tmpBlob1->end());
// check that it matches the hook hash
BEAST_EXPECT(retStr1.size() == 32);
auto const hash1 = hookExecutions[0].getFieldH256(sfHookHash);
BEAST_EXPECT(memcmp(hash1.data(), retStr1.data(), 32) == 0);
// get the data in the return string of the extention
auto const tmpRet2 =
hookExecutions[1].getFieldVL(sfHookReturnString);
// DA: TODO Fix `accept` and `rollback` and remove these lines
std::string tmpStr2(tmpRet2.begin(), tmpRet2.end());
auto const tmpBlob2 = strUnHex(tmpStr2);
Blob const retStr2 = Blob(tmpBlob2->begin(), tmpBlob2->end());
// check that it matches the hook hash
BEAST_EXPECT(retStr2.size() == 32);
auto const hash2 = hookExecutions[1].getFieldH256(sfHookHash);
BEAST_EXPECT(memcmp(hash2.data(), retStr2.data(), 32) == 0);
// make sure they're not the same
BEAST_EXPECT(memcmp(hash1.data(), hash2.data(), 32) != 0);
// compute the hashes
auto computedHash2 = ripple::sha512Half_s(
ripple::Slice(hook.data(), hook.size()));
auto computedHash1 = ripple::sha512Half_s(
ripple::Slice(hook2.data(), hook2.size()));
// ensure the computed hashes match
BEAST_EXPECT(computedHash1 == hash1);
BEAST_EXPECT(computedHash2 == hash2);
}
};
test(Account{"alice"});
}
void
test_hook_param(FeatureBitset features)
{
testcase("Test hook_param");
using namespace jtx;
Env env{*this, features};
// Env env{*this, envconfig(), features, nullptr,
// beast::severities::kTrace
// };
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var DOESNT_EXIST = -5
var INVALID_ARGUMENT = -7
var names = [
'706172616d30',
'706172616d31',
'706172616d32',
'706172616d33',
'706172616d34',
'706172616d35',
'706172616d36',
'706172616d37',
'706172616d38',
'706172616d39',
'706172616d3130',
'706172616d3131',
'706172616d3132',
'706172616d3133',
'706172616d3134',
'706172616d3135',
]
var values = [
[118, 97, 108, 117, 101, 48],
[118, 97, 108, 117, 101, 49],
[118, 97, 108, 117, 101, 50],
[118, 97, 108, 117, 101, 51],
[118, 97, 108, 117, 101, 52],
[118, 97, 108, 117, 101, 53],
[118, 97, 108, 117, 101, 54],
[118, 97, 108, 117, 101, 55],
[118, 97, 108, 117, 101, 56],
[118, 97, 108, 117, 101, 57],
[118, 97, 108, 117, 101, 49, 48],
[118, 97, 108, 117, 101, 49, 49],
[118, 97, 108, 117, 101, 49, 50],
[118, 97, 108, 117, 101, 49, 51],
[118, 97, 108, 117, 101, 49, 52],
[118, 97, 108, 117, 101, 49, 53],
]
var Hook = (arg) => {
ASSERT(hook_param(0) === INVALID_ARGUMENT)
ASSERT(hook_param('') === INVALID_ARGUMENT)
ASSERT(hook_param([]) === INVALID_ARGUMENT)
ASSERT(hook_param(void 0) === INVALID_ARGUMENT)
ASSERT(hook_param(null) === INVALID_ARGUMENT)
ASSERT(hook_param([0]) === DOESNT_EXIST)
for (let i = 0; i < 16; ++i) {
const s = 6 + (i < 10 ? 0 : 1)
const buf = hook_param(names[i])
ASSERT(buf.length === s)
ASSERT(
buf[0] === 118 &&
buf[1] === 97 &&
buf[2] === 108 &&
buf[3] === 117 &&
buf[4] === 101
)
ASSERT(buf[buf.length - 1] === values[i][buf.length - 1])
ASSERT(buf[buf.length - 2] === values[i][buf.length - 2])
}
return accept('', 0)
}
)[test.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::CreateCode] = strHex(hook);
iv[jss::HookOn] =
"0000000000000000000000000000000000000000000000000000000000000000";
iv[jss::Fee] = "99000";
iv[jss::HookApiVersion] = 1U;
iv[jss::HookNamespace] = to_string(uint256{beast::zero});
Json::Value params{Json::arrayValue};
for (uint32_t i = 0; i < 16; ++i)
{
Json::Value pv;
Json::Value piv;
piv[jss::HookParameterName] = strHex("param" + std::to_string(i));
piv[jss::HookParameterValue] = strHex("value" + std::to_string(i));
pv[jss::HookParameter] = piv;
params[i] = pv;
}
iv[jss::HookParameters] = params;
jv[jss::Hooks][0U][jss::Hook] = iv;
env(jv, M("set hook_param"), HSFEE, ter(tesSUCCESS));
env.close();
// invoke
env(pay(bob, alice, XRP(1)), M("test hook_param"), fee(XRP(1)));
env.close();
}
void
test_hook_param_set(FeatureBitset features)
{
testcase("Test hook_param_set");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook checker_wasm = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
rollback(x.toString(), code)
}
}
const DOESNT_EXIST = -5
const names = ['param0', 'param1', 'param2', 'param3']
function toHex(str) {
let hexString = ''
for (let i = 0; i < str.length; i++) {
hexString += str.charCodeAt(i).toString(16).padStart(2, '0')
}
return hexString.toUpperCase()
}
const Hook = (arg) => {
// TEQU: https://github.com/Xahau/xahaud/issues/444
// ASSERT(hook_param(toHex('checker')) == DOESNT_EXIST)
// this entry should havebeen added by the setter
let buf = hook_param(toHex('hello'))
ASSERT(buf.length === 5)
ASSERT(
buf[0] == 'w'.charCodeAt(0) &&
buf[1] == 'o'.charCodeAt(0) &&
buf[2] == 'r'.charCodeAt(0) &&
buf[3] == 'l'.charCodeAt(0) &&
buf[4] == 'd'.charCodeAt(0)
)
// these pre-existing entries should be modified by the setter
for (let i = 0; i < 4; ++i) {
buf = hook_param(toHex(names[i]))
ASSERT(buf.length == 6)
ASSERT(
buf[0] == 'v'.charCodeAt(0) &&
buf[1] == 'a'.charCodeAt(0) &&
buf[2] == 'l'.charCodeAt(0) &&
buf[3] == 'u'.charCodeAt(0) &&
buf[4] == 'e'.charCodeAt(0) &&
buf[5] == '0'.charCodeAt(0) + i
)
}
return accept('', 0)
}
)[test.hook]"];
TestHook setter_wasm = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
rollback(x.toString(), code)
}
}
function toHex(str) {
let hexString = ''
for (let i = 0; i < str.length; i++) {
hexString += str.charCodeAt(i).toString(16).padStart(2, '0')
}
return hexString.toUpperCase()
}
const names = ['param0', 'param1', 'param2', 'param3']
const values = ['value0', 'value1', 'value2', 'value3']
const Hook = (arg) => {
const checker_hash = hook_param(toHex('checker'))
ASSERT(checker_hash.length === 32)
for (let i = 0; i < 4; ++i) {
ASSERT(
hook_param_set(toHex(values[i]), toHex(names[i]), checker_hash) ==
6
)
}
// "delete" the checker entry" for when the checker runs
ASSERT(hook_param_set('', toHex('checker'), checker_hash) == 0)
ASSERT(hook_param_set(undefined, toHex('checker'), checker_hash) == 0)
// add a parameter that did not previously exist
ASSERT(hook_param_set(toHex('world'), toHex('hello'), checker_hash) == 5)
// ensure this hook's parameters did not change
for (let i = 0; i < 4; ++i) {
const buf = hook_param(toHex(names[i]))
ASSERT(buf.length == 6)
ASSERT(
buf[0] == 'v'.charCodeAt(0) &&
buf[1] == 'a'.charCodeAt(0) &&
buf[2] == 'l'.charCodeAt(0) &&
buf[3] == 'u'.charCodeAt(0) &&
buf[4] == 'e'.charCodeAt(0) &&
buf[5] == '0'.charCodeAt(0)
)
}
return accept('', 0)
}
)[test.hook]"];
HASH_WASM(checker);
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::CreateCode] = strHex(setter_wasm);
iv[jss::HookOn] =
"0000000000000000000000000000000000000000000000000000000000000000";
iv[jss::Fee] = "99000";
iv[jss::HookApiVersion] = 1U;
iv[jss::HookNamespace] = to_string(uint256{beast::zero});
Json::Value checkerpv;
{
Json::Value piv;
piv[jss::HookParameterName] = strHex(std::string("checker"));
piv[jss::HookParameterValue] = checker_hash_str;
checkerpv[jss::HookParameter] = piv;
}
Json::Value params{Json::arrayValue};
for (uint32_t i = 0; i < 4; ++i)
{
Json::Value pv;
Json::Value piv;
piv[jss::HookParameterName] = strHex("param" + std::to_string(i));
piv[jss::HookParameterValue] = strHex(std::string("value0"));
pv[jss::HookParameter] = piv;
params[i] = pv;
}
params[4U] = checkerpv;
iv[jss::HookParameters] = params;
jv[jss::Hooks][0U][jss::Hook] = iv;
{
iv[jss::CreateCode] = strHex(checker_wasm);
Json::Value params{Json::arrayValue};
params[0U] = checkerpv;
iv[jss::HookParameters] = params;
jv[jss::Hooks][3U][jss::Hook] = iv;
jv[jss::Hooks][1U][jss::Hook] = Json::objectValue;
jv[jss::Hooks][2U][jss::Hook] = Json::objectValue;
}
env(jv, M("set hook_param_set"), HSFEE, ter(tesSUCCESS));
env.close();
// invoke
env(pay(bob, alice, XRP(1)), M("test hook_param_set"), fee(XRP(1)));
env.close();
}
void
test_hook_pos(FeatureBitset features)
{
testcase("Test hook_pos");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
const Hook = (arg) => {
return accept('', hook_pos())
}
)[test.hook]"];
// install the hook on alice in all four spots
env(ripple::test::jtx::hook(
alice,
{{hsov1(hook, 1, HSDROPS),
hsov1(hook, 1, HSDROPS),
hsov1(hook, 1, HSDROPS),
hsov1(hook, 1, HSDROPS)}},
0),
M("set hook_pos"),
HSFEE,
ter(tesSUCCESS));
env.close();
// invoke the hooks
env(pay(bob, alice, XRP(1)), M("test hook_pos"), fee(XRP(1)));
env.close();
auto meta = env.meta();
// ensure hook execution occured
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
// ensure there was four hook executions
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 4);
// get the data in the return code of the execution
BEAST_EXPECT(hookExecutions[0].getFieldU64(sfHookReturnCode) == 0);
BEAST_EXPECT(hookExecutions[1].getFieldU64(sfHookReturnCode) == 1);
BEAST_EXPECT(hookExecutions[2].getFieldU64(sfHookReturnCode) == 2);
BEAST_EXPECT(hookExecutions[3].getFieldU64(sfHookReturnCode) == 3);
}
void
test_hook_skip(FeatureBitset features)
{
testcase("Test hook_skip");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook skip_wasm = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
rollback(x.toString(), code)
}
}
const DOESNT_EXIST = -5
const INVALID_ARGUMENT = -7
const sfInvoiceID = 0x50011
const Hook = (arg) => {
// bounds checks
ASSERT(hook_skip('00'.repeat(31), 1) === INVALID_ARGUMENT)
ASSERT(hook_skip('00'.repeat(31), 2) === INVALID_ARGUMENT)
ASSERT(hook_skip('00'.repeat(33), 1) === INVALID_ARGUMENT)
ASSERT(hook_skip('00'.repeat(33), 2) === INVALID_ARGUMENT)
// garbage check
ASSERT(hook_skip('00'.repeat(32), 0) === DOESNT_EXIST)
ASSERT(hook_skip('00'.repeat(32), 1) === DOESNT_EXIST)
ASSERT(hook_skip('00'.repeat(32), 2) === INVALID_ARGUMENT)
// the hook to skip is passed in by invoice id
const skip = otxn_field(sfInvoiceID)
ASSERT(skip.length === 32)
// get this hook's hash
const hash = hook_hash(hook_pos())
ASSERT(hash.length === 32)
// to test if the "remove" function works in the api we will add this hook hash itself and then
// remove it again. Therefore if the hook is placed at positions 0 and 3, the one at 3 should still
// run
ASSERT(hook_skip(hash, 1) === DOESNT_EXIST)
ASSERT(hook_skip(hash, 0) === 1)
ASSERT(hook_skip(hash, 1) === 1)
// finally skip the hook hash indicated by invoice id
ASSERT(hook_skip(skip, 0))
return accept('', hook_pos())
}
)[test.hook]"];
TestHook pos_wasm = jswasm[R"[test.hook](
const Hook = (arg) => {
return accept('', 255)
}
)[test.hook]"];
HASH_WASM(pos);
// install the hook on alice in one places
env(ripple::test::jtx::hook(
alice,
{{hsov1(skip_wasm, 1, HSDROPS),
hsov1(pos_wasm, 1, HSDROPS),
hsov1(pos_wasm, 1, HSDROPS),
hsov1(skip_wasm, 1, HSDROPS)}},
0),
M("set hook_skip"),
HSFEE,
ter(tesSUCCESS));
env.close();
// invoke the hooks
{
Json::Value json = pay(bob, alice, XRP(1));
json[jss::InvoiceID] = pos_hash_str;
env(json, fee(XRP(1)), M("test hook_skip"), ter(tesSUCCESS));
env.close();
}
auto meta = env.meta();
// ensure hook execution occured
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
// ensure there was four hook executions
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 2);
// get the data in the return code of the execution
BEAST_EXPECT(hookExecutions[0].getFieldU64(sfHookReturnCode) == 0);
BEAST_EXPECT(hookExecutions[1].getFieldU64(sfHookReturnCode) == 3);
}
void
test_ledger_last_hash(FeatureBitset features)
{
testcase("Test ledger_last_hash");
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);
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
rollback(x.toString(), code)
}
}
const toHex = (arr) => {
return arr
.map((num) => num.toString(16).padStart(2, '0'))
.join('')
.toUpperCase()
}
const Hook = (arg) => {
const hash = ledger_last_hash()
ASSERT(hash.length === 32)
// return the hash
return accept(toHex(hash), 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set ledger_last_hash"),
HSFEE);
env.close();
for (uint32_t i = 0; i < 3; ++i)
{
auto const llh =
env.app().getLedgerMaster().getClosedLedger()->info().hash;
env(pay(bob, alice, XRP(1)),
M("test ledger_last_hash"),
fee(XRP(1)));
auto meta = env.meta();
// ensure hook execution occured
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
// ensure there was only one hook execution
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 1);
// get the data in the return string of the extention
auto const tmpRet =
hookExecutions[0].getFieldVL(sfHookReturnString);
// DA: TODO Fix `accept` and `rollback` and remove these lines
std::string tmpStr(tmpRet.begin(), tmpRet.end());
auto const tmpBlob = strUnHex(tmpStr);
Blob const retStr = Blob(tmpBlob->begin(), tmpBlob->end());
// check that it matches the expected size (32 bytes)
BEAST_EXPECT(retStr.size() == 32);
BEAST_EXPECT(llh == uint256::fromVoid(retStr.data()));
}
}
void
test_ledger_last_time(FeatureBitset features)
{
testcase("Test ledger_last_time");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
const Hook = (arg) => {
return accept('', ledger_last_time())
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set ledger_last_time"),
HSFEE);
env.close();
// invoke the hook a few times
for (uint32_t i = 0; i < 3; ++i)
{
int64_t llc = std::chrono::duration_cast<std::chrono::seconds>(
env.app()
.getLedgerMaster()
.getCurrentLedger()
->info()
.parentCloseTime.time_since_epoch())
.count();
env(pay(bob, alice, XRP(1)),
M("test ledger_last_time"),
fee(XRP(1)));
env.close();
{
auto meta = env.meta();
// ensure hook execution occured
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
// ensure there was only one hook execution
auto const hookExecutions =
meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 1);
// get the data in the return code of the execution
auto const rc = hookExecutions[0].getFieldU64(sfHookReturnCode);
// check that it matches the last ledger seq number
BEAST_EXPECT(llc == rc);
}
}
}
void
test_ledger_nonce(FeatureBitset features)
{
testcase("Test ledger_nonce");
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);
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
rollback(x.toString(), code)
}
}
const toHex = (arr) => {
return arr
.map((num) => num.toString(16).padStart(2, '0'))
.join('')
.toUpperCase()
}
const Hook = (arg) => {
// Test out of bounds check
let nonce = ledger_nonce()
ASSERT(nonce.length == 32)
nonce = nonce.concat(ledger_nonce())
ASSERT(nonce.length == 64)
// return the two nonces as the return string
accept(toHex(nonce), 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set ledger_nonce"),
HSFEE);
env.close();
// invoke the hook
auto const seq =
env.app().getLedgerMaster().getCurrentLedger()->info().seq;
auto const llc = env.app()
.getLedgerMaster()
.getCurrentLedger()
->info()
.parentCloseTime.time_since_epoch()
.count();
auto const llh =
env.app().getLedgerMaster().getCurrentLedger()->info().hash;
env(pay(bob, alice, XRP(1)), M("test ledger_nonce"), fee(XRP(1)));
auto const txid = env.txid();
auto meta = env.meta();
// ensure hook execution occured
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
// ensure there was only one hook execution
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 1);
// get the data in the return string of the extention
auto const tmpRet = hookExecutions[0].getFieldVL(sfHookReturnString);
// DA: TODO Fix `accept` and `rollback` and remove these lines
std::string tmpStr(tmpRet.begin(), tmpRet.end());
auto const tmpBlob = strUnHex(tmpStr);
Blob const retStr = Blob(tmpBlob->begin(), tmpBlob->end());
// check that it matches the expected size (two nonces = 64 bytes)
BEAST_EXPECT(retStr.size() == 64);
auto const computed_hash_1 = ripple::sha512Half(
ripple::HashPrefix::hookNonce,
seq,
llc,
llh,
txid,
(uint16_t)0UL,
alice.id());
auto const computed_hash_2 = ripple::sha512Half(
ripple::HashPrefix::hookNonce,
seq,
llc,
llh,
txid,
(uint16_t)1UL, // second nonce
alice.id());
BEAST_EXPECT(computed_hash_1 == uint256::fromVoid(retStr.data()));
BEAST_EXPECT(computed_hash_2 == uint256::fromVoid(retStr.data() + 32));
}
void
test_ledger_seq(FeatureBitset features)
{
testcase("Test ledger_seq");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
const Hook = (arg) => {
return accept('', ledger_seq())
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set ledger_seq"),
HSFEE);
env.close();
// invoke the hook a few times
for (uint32_t i = 0; i < 3; ++i)
{
env(pay(bob, alice, XRP(1)), M("test ledger_seq"), fee(XRP(1)));
env.close();
{
auto meta = env.meta();
// ensure hook execution occured
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
// ensure there was only one hook execution
auto const hookExecutions =
meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 1);
// get the data in the return code of the execution
auto const rc = hookExecutions[0].getFieldU64(sfHookReturnCode);
// check that it matches the last ledger seq number
BEAST_EXPECT(
env.app().getLedgerMaster().getClosedLedger()->info().seq ==
rc);
}
}
}
void
test_meta_slot(FeatureBitset features)
{
testcase("Test meta_slot");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
const PREREQUISITE_NOT_MET = -9
const ASSERT = (x) => {
if (!x) rollback(x.toString(), 0)
}
const sfHookExecutions = (15 << 16) + 18
const sfTransactionResult = (16 << 16) + 3
const sfAffectedNodes = (15 << 16) + 8
const sfTransactionIndex = (2 << 16) + 28
const Hook = (r) => {
if (r > 0) {
ASSERT(meta_slot(1) === 1)
const buf = slot(1)
ASSERT(buf.length > 200)
ASSERT(slot_subfield(1, sfTransactionIndex, 2) === 2)
ASSERT(slot_subfield(1, sfAffectedNodes, 3) === 3)
ASSERT(slot_subfield(1, sfHookExecutions, 4) === 4)
ASSERT(slot_subfield(1, sfTransactionResult, 5) === 5)
return accept('', 1)
}
if (hook_again() !== 1) return rollback('', 254)
ASSERT(meta_slot(1) === PREREQUISITE_NOT_MET)
return accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set meta_slot"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)), M("test meta_slot"), fee(XRP(1)));
env.close();
auto meta = env.meta();
// ensure hook execution occured
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
// ensure there were two executions
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 2);
// get the data in the return code of the execution
BEAST_EXPECT(hookExecutions[0].getFieldU64(sfHookReturnCode) == 0);
BEAST_EXPECT(hookExecutions[1].getFieldU64(sfHookReturnCode) == 1);
}
void
test_xpop_slot(FeatureBitset features)
{
testcase("Test xpop_slot");
using namespace jtx;
std::vector<std::string> const keys = {
"ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC"
"1"};
Env env{*this, network::makeNetworkVLConfig(21337, keys)};
auto const master = Account("masterpassphrase");
env(noop(master), fee(10'000'000'000), ter(tesSUCCESS));
env.close();
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
const ttIMPORT = 97
const DOESNT_EXIST = -5
const NO_FREE_SLOTS = -6
const INVALID_ARGUMENT = -7
const ALREADY_SET = -8
const PREREQUISITE_NOT_MET = -9
const INVALID_TXN = -37
const ASSERT = (x) => {
if (!x) rollback(x.toString(), 0);
}
const sfBlob = (7 << 16) + 26
const sfAccount = (8 << 16) + 1
const sfTransactionType = (1 << 16) + 2
const sfHookExecutions = (15 << 16) + 18
const sfTransactionResult = (16 << 16) + 3
const sfAffectedNodes = (15 << 16) + 8
const sfTransactionIndex = (2 << 16) + 28
const Hook = (r) => {
// invalid tt
if (otxn_type() !== ttIMPORT)
{
ASSERT(xpop_slot(1, 2) === PREREQUISITE_NOT_MET);
return accept("",1);
}
// invalid slotno
ASSERT(xpop_slot(256, 1) === INVALID_ARGUMENT);
ASSERT(xpop_slot(1, 256) === INVALID_ARGUMENT);
ASSERT(xpop_slot(1, 1) === INVALID_ARGUMENT);
ASSERT(xpop_slot(1, 11) === ((1 << 16) + 11));
ASSERT(slot_subfield(1, sfTransactionType, 2) === 2);
ASSERT(slot_subfield(1, sfAccount, 3) === 3);
ASSERT(slot_subfield(11, sfTransactionIndex, 12) === 12);
ASSERT(slot_subfield(11, sfAffectedNodes, 13) === 13);
ASSERT(slot_subfield(11, sfTransactionResult, 14) === 14);
for(let i = 1; i <= 255; ++i) {
otxn_slot(i);
}
ASSERT(xpop_slot(0, 0) === NO_FREE_SLOTS)
return accept("",2);
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set xpop_slot"),
HSFEE);
env.close();
auto checkResult =
[this](auto const& meta, uint64_t expectedCode) -> void {
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 1);
BEAST_EXPECT(
hookExecutions[0].getFieldU64(sfHookReturnCode) ==
expectedCode);
};
env(pay(bob, alice, XRP(1)), M("test xpop_slot"), fee(XRP(1)));
env.close();
auto meta = env.meta();
checkResult(meta, 1);
// sfBlob is required and validity check is done in the Import
// transaction.
auto const xpopJson = import::loadXpop(ImportTCAccountSet::w_seed);
env(import::import(alice, xpopJson), M("test xpop_slot"), fee(XRP(1)));
env.close();
meta = env.meta();
checkResult(meta, 2);
}
void
test_otxn_id(FeatureBitset features)
{
testcase("Test otxn_id");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var Hook = (arg) => {
const id = otxn_id(0)
ASSERT(id.length === 32, 1)
ASSERT(otxn_slot(1) === 1, 2)
const buf = slot(1)
const size = buf.length
ASSERT(size > 0, 3)
buf.unshift(0)
buf.unshift(78)
buf.unshift(88)
buf.unshift(84)
const hash = util_sha512h(buf)
ASSERT(hash.length === 32, 4)
for (let i = 0; i < 32; ++i) ASSERT(hash[i] === id[i], 5)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set otxn_id"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test otxn_id"), fee(XRP(1)));
}
void
test_otxn_slot(FeatureBitset features)
{
testcase("Test otxn_slot");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var INVALID_ARGUMENT = -7
var NO_FREE_SLOTS = -6
var Hook = (arg) => {
ASSERT(otxn_slot(256) === INVALID_ARGUMENT, 1)
ASSERT(otxn_slot(1) === 1, 2)
const id = otxn_id(0)
ASSERT(id.length === 32, 1)
ASSERT(otxn_slot(1) === 1, 3)
const buf = slot(1)
const size = buf.length
ASSERT(size > 0, 4)
buf.unshift(0)
buf.unshift(78)
buf.unshift(88)
buf.unshift(84)
const hash = util_sha512h(buf)
ASSERT(hash.length === 32, 5)
for (let i = 0; i < 32; ++i) ASSERT(hash[i] === id[i], 6)
for (let i = 0; i < 254; ++i) ASSERT(otxn_slot(0) > 0, 7)
ASSERT(otxn_slot(0) == NO_FREE_SLOTS, 8)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set otxn_slot"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test otxn_slot"), fee(XRP(1)));
}
void
test_otxn_type(FeatureBitset features)
{
testcase("Test otxn_type");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var sfTransactionType = 65538
var Hook = (arg) => {
// Set originating transaction into slot 1
ASSERT(otxn_slot(1) === 1, 1)
// Set field sfTransactionType of slot 1 into slot 2
ASSERT(slot_subfield(1, sfTransactionType, 2) === 2, 2)
// Get the value of slot 2 into variable
const tt = slot(2, true)
ASSERT(tt === otxn_type(), 0)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set otxn_type"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test otxn_type"), fee(XRP(1)));
// invoke it another way
Json::Value jv;
jv[jss::Account] = alice.human();
jv[jss::TransactionType] = jss::AccountSet;
jv[jss::Flags] = 0;
// invoke the hook
env(jv, M("test otxn_type 2"), fee(XRP(1)));
// RH TODO: test behaviour on emit failure
}
void
test_otxn_param(FeatureBitset features)
{
testcase("Test otxn_param");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var DOESNT_EXIST = -5
var INVALID_ARGUMENT = -7
var names = [
'706172616d30',
'706172616d31',
'706172616d32',
'706172616d33',
'706172616d34',
'706172616d35',
'706172616d36',
'706172616d37',
'706172616d38',
'706172616d39',
'706172616d3130',
'706172616d3131',
'706172616d3132',
'706172616d3133',
'706172616d3134',
'706172616d3135',
]
var values = [
[118, 97, 108, 117, 101, 48],
[118, 97, 108, 117, 101, 49],
[118, 97, 108, 117, 101, 50],
[118, 97, 108, 117, 101, 51],
[118, 97, 108, 117, 101, 52],
[118, 97, 108, 117, 101, 53],
[118, 97, 108, 117, 101, 54],
[118, 97, 108, 117, 101, 55],
[118, 97, 108, 117, 101, 56],
[118, 97, 108, 117, 101, 57],
[118, 97, 108, 117, 101, 49, 48],
[118, 97, 108, 117, 101, 49, 49],
[118, 97, 108, 117, 101, 49, 50],
[118, 97, 108, 117, 101, 49, 51],
[118, 97, 108, 117, 101, 49, 52],
[118, 97, 108, 117, 101, 49, 53],
]
var ba = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1,
]
var a = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1,
]
var Hook = (arg) => {
ASSERT(otxn_param([]) === INVALID_ARGUMENT, 1)
ASSERT(otxn_param('') === INVALID_ARGUMENT, 2)
ASSERT(otxn_param(ba) === INVALID_ARGUMENT, 3)
ASSERT(otxn_param(a) === DOESNT_EXIST, 4)
for (let i = 0; i < 16; ++i) {
const s = 6 + (i < 10 ? 0 : 1)
const buf = otxn_param(names[i])
ASSERT(buf.length === s, 5)
ASSERT(
buf[0] === 118 &&
buf[1] === 97 &&
buf[2] === 108 &&
buf[3] === 117 &&
buf[4] === 101,
6
)
ASSERT(buf[buf.length - 1] === values[i][buf.length - 1], 6)
ASSERT(buf[buf.length - 2] === values[i][buf.length - 2], 7)
}
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set otxn_param"),
HSFEE);
env.close();
// invoke
Json::Value invoke;
invoke[jss::TransactionType] = "Invoke";
invoke[jss::Account] = bob.human();
invoke[jss::Destination] = alice.human();
Json::Value params{Json::arrayValue};
for (uint32_t i = 0; i < 16; ++i)
{
Json::Value pv;
Json::Value piv;
piv[jss::HookParameterName] = strHex("param" + std::to_string(i));
piv[jss::HookParameterValue] = strHex("value" + std::to_string(i));
pv[jss::HookParameter] = piv;
params[i] = pv;
}
invoke[jss::HookParameters] = params;
env(invoke, M("test otxn_param"), fee(XRP(1)));
env.close();
}
void
test_otxn_json(FeatureBitset features)
{
testcase("Test otxn_json");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
function arrayEqual(arr1, arr2) {
if (arr1.length !== arr2.length) return false
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) return false
}
return true
}
var Hook = (arg) => {
const txn = otxn_json()
ASSERT(typeof txn === 'object', 1)
ASSERT(txn.TransactionType === 'Invoke', 2)
ASSERT(arrayEqual(util_accid(txn.Destination), hook_account()), 4)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set otxn_json"),
HSFEE);
env.close();
// invoke
Json::Value invoke;
invoke[jss::TransactionType] = "Invoke";
invoke[jss::Account] = bob.human();
invoke[jss::Destination] = alice.human();
env(invoke, M("test otxn_json"), fee(XRP(1)));
env.close();
}
void
test_slot(FeatureBitset features)
{
testcase("Test slot");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var INVALID_ARGUMENT = -7
var DOESNT_EXIST = -5
var TOO_BIG = -3
var KEYLET_ACCOUNT = 3
var sfBalance = 393218
var Hook = (arg) => {
ASSERT(slot(null) === INVALID_ARGUMENT, 1)
ASSERT(slot(void 0) === INVALID_ARGUMENT, 1)
ASSERT(slot('') === INVALID_ARGUMENT, 1)
ASSERT(slot(0, null) === INVALID_ARGUMENT, 2)
ASSERT(slot(0) === DOESNT_EXIST, 3)
const acc = hook_account()
ASSERT(20 === acc.length, 4)
const kl = util_keylet(KEYLET_ACCOUNT, acc)
ASSERT(34 === kl.length, 5)
const slot_no = slot_set(kl, 0)
ASSERT(slot_no > 0, 6)
const size = slot_size(slot_no)
ASSERT(size > 0, 7)
ASSERT(slot(slot_no, true) === TOO_BIG, 8)
const buf = slot(slot_no)
ASSERT(buf.length == size, 9)
ASSERT(sto_validate(buf) === 1, 10)
ASSERT(sto_subfield(buf, sfBalance) > 0, 11)
ASSERT(slot_subfield(slot_no, sfBalance, 200) === 200, 12)
ASSERT(slot(200, true) > 0, 13)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set slot"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot"), fee(XRP(1)));
}
void
test_slot_clear(FeatureBitset features)
{
testcase("Test slot_clear");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var DOESNT_EXIST = -5
var Hook = (arg) => {
ASSERT(otxn_slot(1) === 1, 1)
ASSERT(slot_size(1) > 0, 2)
ASSERT(slot_clear(1) === 1, 3)
ASSERT(slot_size(1) === DOESNT_EXIST, 4)
ASSERT(slot_clear(1) == DOESNT_EXIST, 5)
ASSERT(slot_clear(10) == DOESNT_EXIST, 6)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set slot_clear"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_clear"), fee(XRP(1)));
}
void
test_slot_count(FeatureBitset features)
{
testcase("Test slot_count");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var DOESNT_EXIST = -5
var NOT_AN_ARRAY = -22
var sfMemos = 983049
var Hook = (arg) => {
ASSERT(otxn_slot(1) === 1, 1)
ASSERT(slot_size(1) > 0, 2)
ASSERT(slot_count(1) === NOT_AN_ARRAY, 3)
ASSERT(slot_count(0) === DOESNT_EXIST, 4)
ASSERT(slot_subfield(1, sfMemos, 1) === 1, 5)
ASSERT(slot_size(1) > 0, 6)
ASSERT(slot_count(1) === 1, 7)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set slot_count"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_count"), fee(XRP(1)));
}
void
test_slot_float(FeatureBitset features)
{
testcase("Test slot_float");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var DOESNT_EXIST = -5
var NOT_AN_AMOUNT = -32
var sfFee = 393224
var Hook = (arg) => {
ASSERT(otxn_slot(1) === 1, 1)
ASSERT(slot_size(1) > 0, 2)
ASSERT(slot_subfield(1, sfFee, 2) === 2, 3)
ASSERT(slot_size(2) > 0, 4)
ASSERT(slot_float(0) === DOESNT_EXIST, 5)
ASSERT(slot_float(1) === NOT_AN_AMOUNT, 6)
const xfl = slot_float(2)
ASSERT(xfl > 0, 7)
ASSERT(float_int(xfl, 6, 0) === 1e6, 8)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set slot_float"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_float"), fee(XRP(1)));
}
void
test_slot_set(FeatureBitset features)
{
testcase("Test slot_set");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var DOESNT_EXIST = -5
var INVALID_ARGUMENT = -7
var kl_sk = [
0, 104, 180, 151, 154, 54, 205, 199, 243, 211, 213, 195, 26, 78, 174, 42, 199,
215, 32, 157, 218, 135, 117, 136, 185, 175, 198, 103, 153, 105, 42, 176, 214,
107,
]
var a31 = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
]
var a33 = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
]
var a35 = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
]
var a34 = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1,
]
var Hook = (arg) => {
const kl_zero = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1,
]
ASSERT(slot_set([], 0) === INVALID_ARGUMENT, 1)
ASSERT(slot_set(a34, void 0) === INVALID_ARGUMENT, 1)
ASSERT(slot_set(a34, -1) === INVALID_ARGUMENT, 1)
ASSERT(slot_set(a31, 0) === INVALID_ARGUMENT, 2)
ASSERT(slot_set(a33, 0) === INVALID_ARGUMENT, 3)
ASSERT(slot_set(a35, 0) === INVALID_ARGUMENT, 4)
ASSERT(slot_set(a34, 256) === INVALID_ARGUMENT, 5)
ASSERT(slot_set(kl_zero, 0) === DOESNT_EXIST, 6)
kl_zero[0] = 1
ASSERT(slot_set(kl_zero, 0) === DOESNT_EXIST, 7)
ASSERT(slot_size(1) === DOESNT_EXIST, 8)
ASSERT(slot_set(kl_sk, 0) > 0, 9)
ASSERT(slot_size(1) > 0, 10)
for (let i = 1; i < 255; ++i) ASSERT(slot_set(kl_sk, 0) > 0, 10)
ASSERT(slot_set(kl_sk, 0), 11)
ASSERT(slot_set(kl_sk, 10) === 10, 12)
const s = slot_size(1)
for (let i = 2; i < 256; ++i) ASSERT(s === slot_size(i), 13)
const txn = otxn_id(0)
ASSERT(txn.length === 32, 14)
ASSERT(slot_set(txn, 1) === 1, 15)
const s2 = slot_size(1)
ASSERT(s != s2 && s2 > 0, 16)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set slot_set"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_set"), fee(XRP(1)));
}
void
test_slot_size(FeatureBitset features)
{
testcase("Test slot_size");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice"};
Account const bob{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var DOESNT_EXIST = -5
var kl_sk = [
0, 104, 180, 151, 154, 54, 205, 199, 243, 211, 213, 195, 26, 78, 174, 42, 199,
215, 32, 157, 218, 135, 117, 136, 185, 175, 198, 103, 153, 105, 42, 176, 214,
107,
]
var Hook = (arg) => {
ASSERT(slot_size(1) === DOESNT_EXIST, 1)
ASSERT(slot_set(kl_sk, 1) === 1, 2)
ASSERT(slot_set(kl_sk, 255) === 255, 3)
ASSERT(slot_size(1) === slot_size(255), 4)
const s = slot_size(1)
ASSERT(s > 0, 5)
const buf = slot(1)
ASSERT(buf.length === s, 6)
ASSERT(sto_validate(buf) === 1, 7)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set slot_size"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_size"), fee(XRP(1)));
}
void
test_slot_subarray(FeatureBitset features)
{
testcase("Test slot_subarray");
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);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var DOESNT_EXIST = -5
var NO_FREE_SLOTS = -6
var NOT_AN_ARRAY = -22
var sfMemos = 983049
var sfMemoData = 458765
var kl_sk = [
0, 104, 180, 151, 154, 54, 205, 199, 243, 211, 213, 195, 26, 78, 174, 42, 199,
215, 32, 157, 218, 135, 117, 136, 185, 175, 198, 103, 153, 105, 42, 176, 214,
107,
]
var Hook = (arg) => {
ASSERT(slot_subarray(1, 1, 1) === DOESNT_EXIST, 1)
ASSERT(slot_set(kl_sk, 1) === 1, 2)
ASSERT(slot_size(1) > 0, 3)
ASSERT(slot_subarray(1, 1, 1) === NOT_AN_ARRAY, 4)
ASSERT(otxn_slot(2) === 2, 5)
ASSERT(slot_subfield(2, sfMemos, 3) === 3, 6)
ASSERT(slot_count(3) === 9, 7)
ASSERT(slot_subarray(3, 0, 0) > 0, 8)
ASSERT(slot_subarray(3, 5, 100) === 100, 9)
ASSERT(slot_subarray(3, 6, 100) === 100, 10)
ASSERT(slot_subfield(100, sfMemoData, 100) === 100, 11)
let buf = slot(100)
ASSERT(6 === buf.length, 11)
ASSERT(
buf[0] === 5 &&
buf[1] === 192 &&
buf[2] === 1 &&
buf[3] === 202 &&
buf[4] === 254 &&
buf[5] === 6,
12
)
ASSERT(slot_subarray(3, 0, 100) === 100, 13)
ASSERT(slot_subfield(100, sfMemoData, 100) === 100, 14)
buf = slot(100)
ASSERT(buf.length === 6, 15)
ASSERT(
buf[0] === 5 &&
buf[1] === 192 &&
buf[2] === 1 &&
buf[3] === 202 &&
buf[4] === 254 &&
buf[5] === 0,
16
)
for (let i = 0; i < 250; ++i) ASSERT(slot_subarray(3, 0, 0) > 0, 17)
ASSERT(slot_subarray(3, 0, 0) === NO_FREE_SLOTS, 18)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set slot_subarray"),
HSFEE);
env.close();
// generate an array of memos to attach
Json::Value jv;
jv[jss::Account] = bob.human();
jv[jss::TransactionType] = jss::Payment;
jv[jss::Flags] = 0;
jv[jss::Amount] = "1";
jv[jss::Memos] = Json::Value{Json::arrayValue};
jv[jss::Destination] = alice.human();
Json::Value iv;
for (uint32_t i = 0; i < 8; ++i)
{
std::string v = "C001CAFE00";
v.data()[9] = '0' + i;
iv[jss::MemoData] = v.c_str();
iv[jss::MemoFormat] = "";
iv[jss::MemoType] = "";
jv[jss::Memos][i][jss::Memo] = iv;
}
// invoke the hook
env(jv, M("test slot_subarray"), fee(XRP(1)));
}
void
test_slot_subfield(FeatureBitset features)
{
testcase("Test slot_subfield");
using namespace jtx;
Env env{*this, features};
// Env env{*this, envconfig(), features, nullptr,
// beast::severities::kTrace
// };
auto const bob = Account{"bob"};
auto const alice = Account{"alice"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var DOESNT_EXIST = -5
var NO_FREE_SLOTS = -6
var INVALID_ARGUMENT = -7
var INVALID_FIELD = -17
var NOT_AN_OBJECT = -23
var sfMemos = 983049
var sfMemoData = 458765
var sfLastLedgerSequence = 131099
var sfHashes = 1245186
var kl_sk = [
0, 104, 180, 151, 154, 54, 205, 199, 243, 211, 213, 195, 26, 78, 174, 42, 199,
215, 32, 157, 218, 135, 117, 136, 185, 175, 198, 103, 153, 105, 42, 176, 214,
107,
]
var Hook = (arg) => {
ASSERT(slot_subfield(void 0, 1, 1) === INVALID_ARGUMENT)
ASSERT(slot_subfield(1, void 0, 1) === INVALID_ARGUMENT)
ASSERT(slot_subfield(1, 1, void 0) === INVALID_ARGUMENT)
ASSERT(slot_subfield(1, 1, 1) === DOESNT_EXIST)
ASSERT(slot_subfield(1, 1, 1) === DOESNT_EXIST)
ASSERT(slot_set(kl_sk, 1) === 1)
ASSERT(slot_size(1) > 0)
ASSERT(slot_subfield(1, sfLastLedgerSequence, 0) === 2)
ASSERT(slot_size(2) > 0)
ASSERT(slot_size(1) > slot_size(2))
ASSERT(slot_subfield(1, sfHashes, 0) === 3)
ASSERT(slot_size(3) > 0)
ASSERT(slot_size(1) > slot_size(3))
ASSERT(slot_subfield(1, 4294967295, 0) === INVALID_FIELD)
ASSERT(slot_subfield(1, sfMemos, 0) === DOESNT_EXIST)
ASSERT(slot_subfield(3, sfMemoData, 0) === NOT_AN_OBJECT)
ASSERT(slot_subfield(1, sfLastLedgerSequence, 3) === 3)
ASSERT(slot_size(2) === slot_size(3))
for (let i = 0; i < 252; ++i)
ASSERT(slot_subfield(1, sfLastLedgerSequence, 0) > 0)
ASSERT(slot_subfield(1, sfLastLedgerSequence, 0) === NO_FREE_SLOTS)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set slot_subfield"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_subfield"), fee(XRP(1)));
}
void
test_slot_type(FeatureBitset features)
{
testcase("Test slot_type");
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);
// set up a trustline which we can retrieve later
env(trust(alice, bob["USD"](600)));
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var DOESNT_EXIST = -5
var NOT_AN_AMOUNT = -32
var KEYLET_LINE = 9
var sfLedgerEntry = 655491329
var sfTransaction = 655425793
var sfAccount = 524289
var sfAmount = 393217
var sfHighLimit = 393223
var sfLastLedgerSequence = 131099
var kl_sk = [
0, 104, 180, 151, 154, 54, 205, 199, 243, 211, 213, 195, 26, 78, 174, 42, 199,
215, 32, 157, 218, 135, 117, 136, 185, 175, 198, 103, 153, 105, 42, 176, 214,
107,
]
var Hook = (arg) => {
ASSERT(slot_type(1, 0) === DOESNT_EXIST)
ASSERT(slot_set(kl_sk, 1) === 1)
ASSERT(slot_size(1) > 0)
ASSERT(slot_type(1, 0) === sfLedgerEntry)
ASSERT(slot_subfield(1, sfLastLedgerSequence, 0) === 2)
ASSERT(slot_size(2) > 0)
ASSERT(slot_size(1) > slot_size(2))
ASSERT(slot_type(2, 0) === sfLastLedgerSequence)
ASSERT(otxn_slot(3) === 3)
ASSERT(slot_type(3, 0) === sfTransaction)
ASSERT(slot_subfield(3, sfAmount, 4) === 4)
ASSERT(slot_type(4, 1) === 1)
ASSERT(slot_type(3, 1) === NOT_AN_AMOUNT)
const addra = hook_account()
const addrb = otxn_field(sfAccount)
ASSERT(addra.length === 20)
ASSERT(addrb.length === 20)
const cur = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 83, 68, 0, 0, 0, 0, 0]
const kl_tr = util_keylet(KEYLET_LINE, addra, addrb, cur)
ASSERT(kl_tr.length === 34)
ASSERT(slot_set(kl_tr, 5) === 5)
ASSERT(slot_subfield(5, sfHighLimit, 6) === 6)
ASSERT(slot_type(6, 1) === 0)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set slot_type"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_type"), fee(XRP(1)));
}
void
test_slot_json(FeatureBitset features)
{
testcase("Test slot_json");
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);
// set up a trustline which we can retrieve later
env(trust(alice, bob["USD"](600)));
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var DOESNT_EXIST = -5
var INVALID_ARGUMENT = -7
var KEYLET_LINE = 9
var sfAccount = 524289
var kl_sk = [
0, 104, 180, 151, 154, 54, 205, 199, 243, 211, 213, 195, 26, 78, 174, 42, 199,
215, 32, 157, 218, 135, 117, 136, 185, 175, 198, 103, 153, 105, 42, 176, 214,
107,
]
var Hook = (arg) => {
ASSERT(slot_json(void 0) === INVALID_ARGUMENT)
ASSERT(slot_json(1) === DOESNT_EXIST)
ASSERT(slot_set(kl_sk, 1) === 1)
ASSERT(slot_size(1) > 0)
const ls = slot_json(1)
trace('ls', ls, false)
ASSERT(typeof ls === 'object')
ASSERT(ls.LedgerEntryType === 'LedgerHashes')
ASSERT(otxn_slot(2) === 2)
ASSERT(slot_size(2) > 0)
const txs = slot_json(2)
trace('txs', txs, false)
ASSERT(typeof txs === 'object')
ASSERT(txs.TransactionType === 'Payment')
ASSERT(txs.Amount === '1000000')
const addra = hook_account()
const addrb = otxn_field(sfAccount)
ASSERT(addra.length === 20)
ASSERT(addrb.length === 20)
const cur = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 83, 68, 0, 0, 0, 0, 0]
const kl_tr = util_keylet(KEYLET_LINE, addra, addrb, cur)
ASSERT(kl_tr.length === 34)
ASSERT(slot_set(kl_tr, 3) === 3)
const lines = slot_json(3)
trace('lines', lines, false)
ASSERT(typeof lines === 'object')
ASSERT(lines.HighLimit.issuer === 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK')
ASSERT(lines.HighLimit.currency === 'USD')
ASSERT(lines.HighLimit.value === '0')
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set slot_json"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test slot_json"), fee(XRP(1)));
}
void
test_state(FeatureBitset features)
{
testcase("Test state");
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);
{
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
trace('error', 0, false)
rollback(x.toString(), code)
}
}
const Hook = (arg) => {
ASSERT(state_set('636F6E74656E74', '6B6579') == 7, 0)
ASSERT(state_set('636F6E74656E7432', '6B657932') == 8, 1)
const bytes1 = state('6B6579')
trace('bytes1', bytes1, false)
ASSERT(bytes1.length == 7, 2)
const bytes2 = state('6B657932')
trace('bytes2', bytes2, false)
ASSERT(bytes2.length == 8, 3)
for (let i = 32; i < bytes1.length; ++i) {
ASSERT(bytes1[i] === [99, 111, 110, 116, 101, 110, 116][i], 4)
}
for (let i = 32; i < bytes2.length; ++i) {
ASSERT(bytes2[i] === [99, 111, 110, 116, 101, 110, 116, 50][i], 5)
}
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set state"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test state"), fee(XRP(1)));
env.close();
}
// override hook with a second version that just reads those state
// objects
{
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
trace('error', 0, false)
rollback(x.toString(), code)
}
}
const Hook = (arg) => {
const bytes1 = state('6B6579')
trace('bytes1', bytes1, false)
ASSERT(bytes1.length == 7, 2)
const bytes2 = state('6B657932')
trace('bytes2', bytes2, false)
ASSERT(bytes2.length == 8, 3)
for (let i = 32; i < bytes1.length; ++i) {
ASSERT(bytes1[i] === [99, 111, 110, 116, 101, 110, 116][i], 4)
}
for (let i = 32; i < bytes2.length; ++i) {
ASSERT(bytes2[i] === [99, 111, 110, 116, 101, 110, 116, 50][i], 5)
}
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set state 2"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test state 2"), fee(XRP(1)));
}
}
void
test_state_foreign(FeatureBitset features)
{
testcase("Test state_foreign");
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);
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, code) => {
if (!x) {
trace('error', 0, false)
rollback(x.toString(), code)
}
}
var Hook = (arg) => {
ASSERT(state_set('636F6E74656E74', '6B6579') == 7, 0)
const ns = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
const acc = hook_account()
ASSERT(acc.length === 20, 1)
ASSERT(state_foreign_set('636F6E74656E7432', '6B657932', ns, acc) === 8, 2)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set state_foreign"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test state_foreign"), fee(XRP(1)));
}
// set a second hook on bob that will read the state objects from alice
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, code) => {
if (!x) {
trace('error', 0, false)
rollback(x.toString(), code)
}
}
var sfAccount = 524289
var DOESNT_EXIST = -5
var Hook = (arg) => {
const acc = otxn_field(sfAccount)
ASSERT(acc.length === 20, 1)
const ns1 = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
]
const bytes1 = state_foreign('6B6579', ns1, acc)
trace('bytes1', bytes1, false)
ASSERT(bytes1.length == 7, 2)
let bytes2 = state_foreign('6B657932', ns1, acc)
trace('bytes2', bytes2, false)
ASSERT(bytes2 == DOESNT_EXIST, 3)
const ns2 = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 171, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
]
bytes2 = state_foreign('6B657932', ns2, acc)
trace('bytes2', bytes2, false)
ASSERT(bytes2.length == 8, 4)
for (let i = 32; i < bytes1.length; ++i) {
ASSERT(bytes1[i] === [99, 111, 110, 116, 101, 110, 116][i], 5)
}
for (let i = 32; i < bytes2.length; ++i) {
ASSERT(bytes2[i] === [99, 111, 110, 116, 101, 110, 116, 50][i], 6)
}
accept('', 0)
}
)[test.hook]"];
// install the hook on bob
env(ripple::test::jtx::hook(
bob, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set state_foreign 2"),
HSFEE);
env.close();
// invoke the hook
env(pay(alice, bob, XRP(1)),
M("test state_foreign 2"),
fee(XRP(1)));
}
}
void
test_state_foreign_set(FeatureBitset features)
{
testcase("Test state_foreign_set");
using namespace jtx;
Env env{*this, features};
// Env env{*this, envconfig(), features, nullptr,
// beast::severities::kTrace
// };
auto const david = Account("david"); // grantee generic
auto const cho = Account{"cho"}; // invoker
auto const bob = Account{"bob"}; // grantee specific
auto const alice = Account{"alice"}; // grantor
auto const eve = Account{"eve"}; // grantor with small balance
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
env.fund(XRP(10000), cho);
env.fund(XRP(10000), david);
env.fund(XRP(2600), eve);
TestHook grantee_wasm = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var sfInvoiceID = 327697
var TOO_BIG = -3
var TOO_SMALL = -4
var INVALID_ARGUMENT = -7
var a = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1,
]
var aa = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
var ba = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1,
]
var sa = [1]
var ha = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1,
]
var Hook = (arg) => {
ASSERT(state_foreign_set(a, [], a, aa) === TOO_SMALL, 1)
ASSERT(state_foreign_set(a, '', a, aa) === TOO_SMALL, 2)
ASSERT(state_foreign_set(a, a, ba, aa) === INVALID_ARGUMENT, 3)
ASSERT(state_foreign_set(a, a, sa, aa) === INVALID_ARGUMENT, 4)
ASSERT(state_foreign_set(a, a, a, ba) === INVALID_ARGUMENT, 5)
ASSERT(state_foreign_set(a, a, a, sa) === INVALID_ARGUMENT, 6)
ASSERT(state_foreign_set(a, a, null, aa) === INVALID_ARGUMENT, 7)
ASSERT(state_foreign_set(a, a, 0, aa) === INVALID_ARGUMENT, 8)
ASSERT(state_foreign_set(a, a, void 0, hook_account()) === 32, 9)
ASSERT(state_foreign_set(a, a, [], aa) === INVALID_ARGUMENT, 10)
ASSERT(state_foreign_set(a, a, '', aa) === INVALID_ARGUMENT, 11)
ASSERT(state_foreign_set(a, a, a, null) === INVALID_ARGUMENT, 12)
ASSERT(state_foreign_set(a, a, a, 0) === INVALID_ARGUMENT, 13)
ASSERT(state_foreign_set(a, a, a, void 0) === 32, 14)
ASSERT(state_foreign_set(a, a, a, []) === INVALID_ARGUMENT, 15)
ASSERT(state_foreign_set(a, a, a, '') === INVALID_ARGUMENT, 16)
ASSERT(state_foreign_set(null, a, a, hook_account()) === INVALID_ARGUMENT, 17)
ASSERT(state_foreign_set(0, a, a, hook_account()) === INVALID_ARGUMENT, 18)
ASSERT(state_foreign_set(void 0, a, a, hook_account()) === 0, 19)
ASSERT(state_foreign_set('', a, a, hook_account()) === 0, 20)
ASSERT(state_foreign_set([], a, a, hook_account()) === 0, 21)
ASSERT(state_foreign_set(ha, a, a, hook_account()) === TOO_BIG, 22)
const txn = otxn_id(0)
ASSERT(txn.length === 32, 23)
const grantor = otxn_field(sfInvoiceID)
ASSERT(grantor.length === 32, 24)
const one = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1,
]
const zero = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
]
trace('grantor', grantor.slice(12), false)
trace('#25', state_foreign_set(txn, one, zero, grantor.slice(12)), true)
ASSERT(state_foreign_set(txn, one, zero, grantor.slice(12)) == 32, 25)
accept('', 0)
}
)[test.hook]"];
HASH_WASM(grantee);
// this is the grantor
TestHook grantor_wasm = jswasm[R"[test.hook](
var ASSERT = (x, code) => {
if (!x) {
trace('error', 0, false)
rollback(x.toString(), code)
}
}
function arrayEqual(arr1, arr2) {
if (arr1.length !== arr2.length) return false
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) return false
}
return true
}
var sfAccount = 524289
var DOESNT_EXIST = -5
var Hook = (arg) => {
const otxnacc = otxn_field(sfAccount)
ASSERT(otxnacc.length === 20, 0)
const hookacc = hook_account()
ASSERT(hookacc.length == 20, 1)
if (arrayEqual(otxnacc, hookacc)) {
const one = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1,
]
const zero = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
]
const y = state_foreign_set(undefined, one, zero, hookacc)
trace('y', y, false)
ASSERT(y === 0 || y === DOESNT_EXIST, 2)
}
accept('', 0)
}
)[test.hook]"];
HASH_WASM(grantor);
// install the grantor hook on alice
{
Json::Value grants{Json::arrayValue};
grants[0U][jss::HookGrant] = Json::Value{};
grants[0U][jss::HookGrant][jss::HookHash] = grantee_hash_str;
grants[0U][jss::HookGrant][jss::Authorize] = bob.human();
Json::Value json = ripple::test::jtx::hook(
alice, {{hsov1(grantor_wasm, 1, HSDROPS, overrideFlag)}}, 0);
json[jss::Hooks][0U][jss::Hook][jss::HookGrants] = grants;
env(json, M("set state_foreign_set"), HSFEE);
env.close();
}
// install the grantee hook on bob
{
// invoice ID contains the grantor account
Json::Value json = ripple::test::jtx::hook(
bob, {{hsov1(grantee_wasm, 1, HSDROPS, overrideFlag)}}, 0);
env(json, M("set state_foreign_set 2"), HSFEE);
env.close();
}
auto const aliceid = Account("alice").id();
auto const nsdirkl = keylet::hookStateDir(aliceid, beast::zero);
std::string const invid = std::string(24, '0') + strHex(alice.id());
auto const one = ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000001");
// ensure there's no way the state or directory exist before we start
{
auto const nsdir = env.le(nsdirkl);
BEAST_REQUIRE(!nsdir);
auto const state1 =
env.le(ripple::keylet::hookState(aliceid, one, beast::zero));
BEAST_REQUIRE(!state1);
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 2);
}
// inovke the grantee hook but supply an account to foreign_set (through
// invoiceid) should definitely fail
{
Json::Value json = pay(cho, bob, XRP(1));
json[jss::InvoiceID] =
"00000000000000000000000000000000000000000000000000000000000000"
"01";
env(json,
fee(XRP(1)),
M("test state_foreign_set 6a"),
ter(tecHOOK_REJECTED));
}
// invoke the grantee hook but supply a valid account for which no
// grants exist
{
Json::Value json = pay(cho, bob, XRP(1));
json[jss::InvoiceID] = std::string(24, '0') + strHex(david.id());
env(json,
fee(XRP(1)),
M("test state_foreign_set 6b"),
ter(tecHOOK_REJECTED));
{
auto const nsdir = env.le(nsdirkl);
BEAST_REQUIRE(!nsdir);
auto const state1 = env.le(
ripple::keylet::hookState(david.id(), one, beast::zero));
BEAST_REQUIRE(!state1);
BEAST_EXPECT((*env.le("david"))[sfOwnerCount] == 0);
}
}
// invoke the grantee hook, this will create the state on the grantor
{
Json::Value json = pay(cho, bob, XRP(1));
json[jss::InvoiceID] = invid;
env(json,
fee(XRP(1)),
M("test state_foreign_set 6"),
ter(tesSUCCESS));
}
// check state
{
auto const nsdir = env.le(nsdirkl);
BEAST_REQUIRE(!!nsdir);
auto const state1 =
env.le(ripple::keylet::hookState(aliceid, one, beast::zero));
BEAST_REQUIRE(!!state1);
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 3);
BEAST_EXPECT(state1->getFieldH256(sfHookStateKey) == one);
auto const data1 = state1->getFieldVL(sfHookStateData);
BEAST_EXPECT(data1.size() == 32);
BEAST_EXPECT(uint256::fromVoid(data1.data()) == env.txid());
}
// invoke the grantor hook, this will delete the state
env(pay(alice, cho, XRP(1)),
M("test state_foreign_set 4"),
fee(XRP(1)));
// check state was removed
{
auto const nsdir = env.le(nsdirkl);
BEAST_REQUIRE(!nsdir);
auto const state1 =
env.le(ripple::keylet::hookState(aliceid, one, beast::zero));
BEAST_REQUIRE(!state1);
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 2);
}
// install grantee hook on david
{
// invoice ID contains the grantor account
Json::Value json = ripple::test::jtx::hook(
david, {{hsov1(grantee_wasm, 1, HSDROPS, overrideFlag)}}, 0);
env(json, M("set state_foreign_set 5"), HSFEE);
env.close();
}
// invoke daivd, expect failure
{
Json::Value json = pay(cho, david, XRP(1));
json[jss::InvoiceID] = invid;
env(json,
fee(XRP(1)),
M("test state_foreign_set 6"),
ter(tecHOOK_REJECTED));
}
// remove sfAuthorize from alice grants
{
Json::Value grants{Json::arrayValue};
grants[0U][jss::HookGrant] = Json::Value{};
grants[0U][jss::HookGrant][jss::HookHash] = grantee_hash_str;
Json::Value json = ripple::test::jtx::hook(
alice, {{hsov1(grantor_wasm, 1, HSDROPS, overrideFlag)}}, 0);
json[jss::Hooks][0U][jss::Hook][jss::HookGrants] = grants;
env(json, M("set state_foreign_set 7"), HSFEE);
env.close();
}
// invoke david again, expect success
{
Json::Value json = pay(cho, david, XRP(1));
json[jss::InvoiceID] = invid;
env(json,
fee(XRP(1)),
M("test state_foreign_set 8"),
ter(tesSUCCESS));
}
// check state
{
auto const nsdir = env.le(nsdirkl);
BEAST_REQUIRE(!!nsdir);
auto const state1 =
env.le(ripple::keylet::hookState(aliceid, one, beast::zero));
BEAST_REQUIRE(!!state1);
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 3);
BEAST_EXPECT(state1->getFieldH256(sfHookStateKey) == one);
auto const data1 = state1->getFieldVL(sfHookStateData);
BEAST_EXPECT(data1.size() == 32);
BEAST_EXPECT(uint256::fromVoid(data1.data()) == env.txid());
}
// change alice's namespace
{
Json::Value json = ripple::test::jtx::hook(
alice, {{hsov1(grantor_wasm, 1, HSDROPS, overrideFlag)}}, 0);
json[jss::Hooks][0U][jss::Hook][jss::HookNamespace] =
"77777777777777777777777777777777777777777777777777777777777777"
"77";
env(json, M("set state_foreign_set 9"), HSFEE);
env.close();
}
// invoke david again, expect failure
{
Json::Value json = pay(cho, david, XRP(1));
json[jss::InvoiceID] = invid;
env(json,
fee(XRP(1)),
M("test state_foreign_set 10"),
ter(tecHOOK_REJECTED));
}
// check reserve exhaustion
TestHook exhaustion_wasm = jswasm[R"[test.hook](
var ASSERT = (x, code) => {
if (!x) {
trace('error', 0, false)
rollback(x.toString(), code)
}
}
var sfInvoiceID = 327697
var Hook = (arg) => {
let txn = otxn_id(0)
ASSERT(txn.length === 32, 0)
trace('txn', txn, true)
const grantor = otxn_field(sfInvoiceID)
ASSERT(grantor.length === 32, 1)
const iterations = grantor[0]
trace('iterations', iterations, false)
const zero = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
]
for (let i = 0; i < iterations; ++i) {
txn[0] = i
trace('exhaustion i: ', i, false)
trace('exhaustion txn: ', txn, false)
trace('exhaustion txn: ', txn, true)
ASSERT(state_foreign_set(txn, txn, zero, grantor.slice(12)) == 32, 2)
}
accept('', 0)
}
)[test.hook]"];
HASH_WASM(exhaustion);
// install the grantor hook on eve
{
Json::Value grants{Json::arrayValue};
grants[0U][jss::HookGrant] = Json::Value{};
grants[0U][jss::HookGrant][jss::HookHash] = exhaustion_hash_str;
grants[0U][jss::HookGrant][jss::Authorize] = bob.human();
Json::Value json = ripple::test::jtx::hook(
eve, {{hsov1(grantor_wasm, 1, HSDROPS, overrideFlag)}}, 0);
json[jss::Hooks][0U][jss::Hook][jss::HookGrants] = grants;
env(json, M("set state_foreign_set 11"), HSFEE);
env.close();
}
// install exhaustion grantee on bob
{
Json::Value json = ripple::test::jtx::hook(
bob, {{hsov1(exhaustion_wasm, 1, HSDROPS, overrideFlag)}}, 0);
env(json, M("set state_foreign_set 12"), HSFEE);
env.close();
}
// now invoke repeatedly until exhaustion is reached
{
Json::Value json = pay(cho, bob, XRP(1));
json[jss::InvoiceID] =
"01" + std::string(22, '0') + strHex(eve.id());
// 2500 xrp less 1 account reserve (200) divided by 50xrp per object
// reserve = 46 objects of these we already have: 1 hook, 1
// sfAuthorize, so 44 objects can be allocated
env(json,
fee(XRP(1)),
M("test state_foreign_set 13"),
ter(tesSUCCESS));
env.close();
BEAST_EXPECT((*env.le("eve"))[sfOwnerCount] == 3);
// now we have allocated 1 state object, so 43 more can be allocated
// try to set 44 state entries, this will fail
json[jss::InvoiceID] =
"2C" + std::string(22, '0') + strHex(eve.id());
env(json,
fee(XRP(1)),
M("test state_foreign_set 14"),
ter(tecHOOK_REJECTED));
env.close();
BEAST_EXPECT((*env.le("eve"))[sfOwnerCount] == 3);
// try to set 43 state objects, this will succeed
json[jss::InvoiceID] =
"2B" + std::string(22, '0') + strHex(eve.id());
env(json,
fee(XRP(1)),
M("test state_foreign_set 15"),
ter(tesSUCCESS));
env.close();
BEAST_EXPECT((*env.le("eve"))[sfOwnerCount] == 46);
// try to set one state object, this will fail
env(json,
fee(XRP(1)),
M("test state_foreign_set 16"),
ter(tecHOOK_REJECTED));
env.close();
BEAST_EXPECT((*env.le("eve"))[sfOwnerCount] == 46);
}
}
void
test_state_set(FeatureBitset features)
{
testcase("Test state_set");
using namespace jtx;
Env env{*this, features};
// Env env{*this, envconfig(), features, nullptr,
// beast::severities::kTrace
// };
auto const bob = Account{"bob"};
auto const alice = Account{"alice"};
auto const cho = Account{"cho"};
auto const david = Account{"david"};
auto const eve = Account{"eve"}; // small balance
auto const frank = Account{"frank"}; // big balance
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
env.fund(XRP(10000), cho);
env.fund(XRP(1000000), david);
env.fund(XRP(2600), eve);
env.fund(XRP(1000000000), frank);
// install a rollback hook on cho
env(ripple::test::jtx::hook(
cho, {{hsov1(rollback_wasm, 1, HSDROPS, overrideFlag)}}, 0),
M("set state_set rollback"),
HSFEE);
env.close();
auto const aliceid = Account("alice").id();
auto const nsdirkl = keylet::hookStateDir(aliceid, beast::zero);
// ensure there's no way the state or directory exist before we start
{
auto const nsdir = env.le(nsdirkl);
BEAST_REQUIRE(!nsdir);
auto const state1 = env.le(
ripple::keylet::hookState(aliceid, beast::zero, beast::zero));
BEAST_REQUIRE(!state1);
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
}
// first hook will set two state objects with different keys and data on
// alice
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var TOO_BIG = -3
var INVALID_ARGUMENT = -7
var a = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1,
]
var ba = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1,
]
var ha = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1,
]
var data2 = [
35, 19, 150, 104, 120, 220, 171, 196, 64, 38, 7, 43, 163, 210, 12, 105, 64,
221, 205, 231, 56, 155, 11, 169, 108, 60, 179, 135, 55, 2, 129, 232, 43, 221,
93, 187, 64, 217, 102, 150, 111, 193, 107, 232, 212, 124, 123, 98, 20, 76,
209, 75, 170, 153, 54, 117, 233, 34, 173, 15, 95, 148, 29, 134, 235, 168, 19,
153, 249, 152, 255, 202, 91, 134, 47, 223, 103, 143, 226, 227, 195, 55, 204,
71, 15, 51, 136, 176, 51, 59, 2, 85, 103, 22, 164, 251, 142, 133, 111, 216,
132, 22, 163, 84, 24, 52, 6, 14, 246, 101, 52, 5, 38, 126, 5, 116, 218, 9,
191, 85, 140, 117, 146, 172, 51, 251, 1, 141,
]
var Hook = (arg) => {
ASSERT(state_set(a, ba) === TOO_BIG, 11)
ASSERT(state_set(a, ha) === TOO_BIG, 12)
const key = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
]
const data = [202, 254, 186, 190]
ASSERT(state_set(data, key) === 4, 13)
const key2 = [1, 2, 3]
ASSERT(state_set(data2, key2) === 128, 14)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set state_set 1"),
HSFEE);
env.close();
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
// invoke the hook with cho (rollback after alice's hooks have
// executed)
env(pay(alice, cho, XRP(1)),
M("test state_set 1 rollback"),
fee(XRP(1)),
ter(tecHOOK_REJECTED));
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
auto const nsdir = env.le(nsdirkl);
BEAST_EXPECT(!nsdir);
auto const state1 = env.le(
ripple::keylet::hookState(aliceid, beast::zero, beast::zero));
BEAST_EXPECT(!state1);
// invoke the hook from bob to alice, this will work
env(pay(bob, alice, XRP(1)), M("test state_set 1"), fee(XRP(1)));
env.close();
}
// check that the state object and namespace exists
{
// owner count should be 1 hook + 2 state objects == 3
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 3);
auto const nsdir = env.le(nsdirkl);
BEAST_REQUIRE(!!nsdir);
BEAST_EXPECT(nsdir->getFieldV256(sfIndexes).size() == 2);
auto const state1 = env.le(
ripple::keylet::hookState(aliceid, beast::zero, beast::zero));
BEAST_REQUIRE(!!state1);
BEAST_EXPECT(state1->getFieldH256(sfHookStateKey) == beast::zero);
auto const data1 = state1->getFieldVL(sfHookStateData);
BEAST_EXPECT(data1.size() == 4);
BEAST_EXPECT(
data1[0] == 0xCAU && data1[1] == 0xFEU && data1[2] == 0xBAU &&
data1[3] == 0xBEU);
uint8_t key2[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3};
auto const state2 = env.le(ripple::keylet::hookState(
aliceid, uint256::fromVoid(key2), beast::zero));
BEAST_REQUIRE(!!state2);
auto const lekey2 = state2->getFieldH256(sfHookStateKey);
BEAST_EXPECT(lekey2 == uint256::fromVoid(key2));
uint8_t data2[128] = {
0x23U, 0x13U, 0x96U, 0x68U, 0x78U, 0xDCU, 0xABU, 0xC4U, 0x40U,
0x26U, 0x07U, 0x2BU, 0xA3U, 0xD2U, 0x0CU, 0x69U, 0x40U, 0xDDU,
0xCDU, 0xE7U, 0x38U, 0x9BU, 0x0BU, 0xA9U, 0x6CU, 0x3CU, 0xB3U,
0x87U, 0x37U, 0x02U, 0x81U, 0xE8U, 0x2BU, 0xDDU, 0x5DU, 0xBBU,
0x40U, 0xD9U, 0x66U, 0x96U, 0x6FU, 0xC1U, 0x6BU, 0xE8U, 0xD4U,
0x7CU, 0x7BU, 0x62U, 0x14U, 0x4CU, 0xD1U, 0x4BU, 0xAAU, 0x99U,
0x36U, 0x75U, 0xE9U, 0x22U, 0xADU, 0x0FU, 0x5FU, 0x94U, 0x1DU,
0x86U, 0xEBU, 0xA8U, 0x13U, 0x99U, 0xF9U, 0x98U, 0xFFU, 0xCAU,
0x5BU, 0x86U, 0x2FU, 0xDFU, 0x67U, 0x8FU, 0xE2U, 0xE3U, 0xC3U,
0x37U, 0xCCU, 0x47U, 0x0FU, 0x33U, 0x88U, 0xB0U, 0x33U, 0x3BU,
0x02U, 0x55U, 0x67U, 0x16U, 0xA4U, 0xFBU, 0x8EU, 0x85U, 0x6FU,
0xD8U, 0x84U, 0x16U, 0xA3U, 0x54U, 0x18U, 0x34U, 0x06U, 0x0EU,
0xF6U, 0x65U, 0x34U, 0x05U, 0x26U, 0x7EU, 0x05U, 0x74U, 0xDAU,
0x09U, 0xBFU, 0x55U, 0x8CU, 0x75U, 0x92U, 0xACU, 0x33U, 0xFBU,
0x01U, 0x8DU};
auto const ledata2 = state2->getFieldVL(sfHookStateData);
BEAST_REQUIRE(ledata2.size() == sizeof(data2));
for (uint32_t i = 0; i < sizeof(data2); ++i)
BEAST_EXPECT(data2[i] == ledata2[i]);
}
// make amother hook to override an existing state and delete an
// existing state
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var Hook = (arg) => {
const key = [0]
const data = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
ASSERT(state_set(data, key) === 16, 21)
const key2 = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 2, 3,
]
ASSERT(state_set(void 0, key2) === 0, 22)
accept('', 0)
}
)[test.hook]"];
TestHook hook2 = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var Hook = (arg) => {
const key = [0]
const data = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
const data_read = state(key)
ASSERT(data_read.length === 16, 31)
for (let i = 0; i < 16; ++i) ASSERT(data[i] === data_read[i], 32)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice,
{{{hsov1(hook, 1, HSDROPS, overrideFlag)},
{},
{},
{hsov1(hook2, 1, HSDROPS, 0)}}},
0),
M("set state_set 2"),
HSFEE);
env.close();
// two hooks + two state objects = 4
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 4);
// this hook will be installed on bob, and it will verify the newly
// updated state is also available on his side. caution must be
// taken because bob's hooks will execute first if bob's is the
// otxn. therefore we will flip to a payment from alice to bob here
TestHook hook3 = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var sfAccount = 524289
var Hook = (arg) => {
hook_again()
const alice = otxn_field(sfAccount)
trace('alice', alice, true)
ASSERT(alice.length === 20, 41)
const key = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
]
const data = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
const data_read = state_foreign(key, key, alice)
ASSERT(data_read.length == 16, 42)
for (let i = 0; i < 16; ++i) ASSERT(data[i] === data_read[i], 43)
accept('', 0)
}
)[test.hook]"];
// install the hook on bob
env(ripple::test::jtx::hook(
bob, {{hsov1(hook3, 1, HSDROPS, overrideFlag)}}, 0),
M("set state_set 3"),
HSFEE);
env.close();
// invoke the hook with cho (rollback after alice's hooks have
// executed)
env(pay(alice, cho, XRP(1)),
M("test state_set 3 rollback"),
fee(XRP(1)),
ter(tecHOOK_REJECTED));
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 4);
// invoke the hook
env(pay(alice, bob, XRP(1)), M("test state_set 3"), fee(XRP(1)));
env.close();
}
// check that the updates have been made
{
// two hooks + one state == 3
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 3);
BEAST_EXPECT((*env.le("bob"))[sfOwnerCount] == 1);
auto const nsdir = env.le(nsdirkl);
BEAST_REQUIRE(!!nsdir);
BEAST_EXPECT(nsdir->getFieldV256(sfIndexes).size() == 1);
auto const state1 = env.le(
ripple::keylet::hookState(aliceid, beast::zero, beast::zero));
BEAST_REQUIRE(!!state1);
BEAST_EXPECT(state1->getFieldH256(sfHookStateKey) == beast::zero);
auto const ledata1 = state1->getFieldVL(sfHookStateData);
BEAST_EXPECT(ledata1.size() == 16);
uint8_t data1[16] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2};
for (uint32_t i = 0; i < sizeof(data1); ++i)
BEAST_EXPECT(data1[i] == ledata1[i]);
uint8_t key2[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3};
auto const state2 = env.le(ripple::keylet::hookState(
aliceid, uint256::fromVoid(key2), beast::zero));
BEAST_REQUIRE(!state2);
}
// create a hook state inside the weak side of an execution, while the
// strong side is rolled back
{
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var Hook = (arg) => {
hook_again()
const key = [255]
const data = [255, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
ASSERT(state_set(data, key) == 16, 2)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice, deleting the other hook
env(ripple::test::jtx::hook(
alice,
{{{hsov1(hook, 1, HSDROPS, overrideFlag)},
{},
{},
{hso_delete()}}},
0),
M("set state_set 4"),
HSFEE);
env.close();
// invoke from alice to cho, this will cause a rollback, however the
// hook state should still be updated because the hook specified
// hook_again, and in the second weak execution the hook is allowed
// to set state
env(pay(alice, cho, XRP(1)),
M("test state_set 4 rollback"),
fee(XRP(1)),
ter(tecHOOK_REJECTED));
uint8_t key[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFFU};
auto const state = env.le(ripple::keylet::hookState(
aliceid, uint256::fromVoid(key), beast::zero));
BEAST_EXPECT(state);
// one hook + two state objects == 3
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 3);
// delete alice's hook
env(ripple::test::jtx::hook(
alice, {{{hso_delete()}, {}, {}, {}}}, 0),
M("set state_set 5 delete"),
HSFEE);
env.close();
// check the state is still present
{
auto const state = env.le(ripple::keylet::hookState(
aliceid, uint256::fromVoid(key), beast::zero));
BEAST_EXPECT(state);
}
// zero hooks + two state objects == 2
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 2);
// put on a different hook
env(ripple::test::jtx::hook(
alice,
{{hsov1(rollback_wasm, 1, HSDROPS, overrideFlag)}},
0),
M("set state_set rollback2"),
HSFEE);
env.close();
// check the state is still present
{
auto const state = env.le(ripple::keylet::hookState(
aliceid, uint256::fromVoid(key), beast::zero));
BEAST_EXPECT(state);
}
// one hooks + two state objects == 3
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 3);
}
// check reserve exhaustion
TestHook exhaustion_wasm = jswasm[R"[test.hook](
var ASSERT = (x, line) => {
if (!x) {
trace('line', line, false)
rollback(x.toString(), line)
}
}
var sfInvoiceID = 327697
var Hook = (arg) => {
const txn = otxn_id(0)
ASSERT(txn.length === 32, 1)
const grantor = otxn_field(sfInvoiceID)
ASSERT(grantor.length === 32, 2)
const iterations = (grantor[0] << 8) + grantor[1]
const p = hook_pos()
for (let i = 0; i < iterations; ++i) {
txn[0] = i & 255
txn[1] = (i >> 8) & 255
txn[2] = p
ASSERT(state_set(txn, txn) === 32, 3)
}
accept('', 0)
}
)[test.hook]"];
HASH_WASM(exhaustion);
// install the exhaustion hook on eve
{
Json::Value json = ripple::test::jtx::hook(
eve, {{hsov1(exhaustion_wasm, 1, HSDROPS, overrideFlag)}}, 0);
env(json, M("set state_set 6"), HSFEE);
env.close();
}
// now invoke repeatedly until exhaustion is reached
{
Json::Value json = pay(david, eve, XRP(1));
json[jss::InvoiceID] = "0001" + std::string(60, '0');
// 2500 xrp less 1 account reserve (200) divided by 50xrp per object
// reserve = 46 objects of these we already have: 1 hook, so 45
// objects can be allocated
env(json, fee(XRP(1)), M("test state_set 7"), ter(tesSUCCESS));
env.close();
BEAST_EXPECT((*env.le("eve"))[sfOwnerCount] == 2);
// now we have allocated 1 state object, so 44 more can be allocated
// try to set 45 state entries, this will fail
json[jss::InvoiceID] = "002D" + std::string(60, '0');
env(json,
fee(XRP(1)),
M("test state_set 8"),
ter(tecHOOK_REJECTED));
env.close();
BEAST_EXPECT((*env.le("eve"))[sfOwnerCount] == 2);
// try to set 44 state objects, this will succeed
json[jss::InvoiceID] = "002C" + std::string(60, '0');
env(json, fee(XRP(1)), M("test state_set 9"), ter(tesSUCCESS));
env.close();
BEAST_EXPECT((*env.le("eve"))[sfOwnerCount] == 46);
// try to set one state object, this will fail
env(json,
fee(XRP(1)),
M("test state_set 10"),
ter(tecHOOK_REJECTED));
env.close();
BEAST_EXPECT((*env.le("eve"))[sfOwnerCount] == 46);
}
// test maximum state modification
{
// install the hook into every position on frank
env(ripple::test::jtx::hook(
frank,
{{hsov1(exhaustion_wasm, 1, HSDROPS),
hsov1(exhaustion_wasm, 1, HSDROPS),
hsov1(exhaustion_wasm, 1, HSDROPS),
hsov1(exhaustion_wasm, 1, HSDROPS)}},
0),
M("set state_set 11"),
HSFEE,
ter(tesSUCCESS));
Json::Value json = pay(david, frank, XRP(1));
// we can modify 256 entries at a time with the hook, but first we
// want to test too many modifications so we will do 65 which times
// 4 executions is 260
json[jss::InvoiceID] = "0041" + std::string(60, '0');
env(json,
fee(XRP(1)),
M("test state_set 12"),
ter(tecHOOK_REJECTED));
env.close();
BEAST_EXPECT((*env.le("frank"))[sfOwnerCount] == 4);
// now we will do 64 which is exactly 256, which should be accepted
json[jss::InvoiceID] = "0040" + std::string(60, '0');
env(json, fee(XRP(1)), M("test state_set 13"), ter(tesSUCCESS));
env.close();
BEAST_EXPECT((*env.le("frank"))[sfOwnerCount] == 260);
}
// RH TODO:
// check state can be set on emit callback
// check namespacing provides for non-collision of same key
}
void
test_trace(FeatureBitset features)
{
testcase("Test trace");
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);
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
rollback(x.toString(), code)
}
}
const Hook = (arg) => {
ASSERT(trace('', 0, false) === 0)
ASSERT(trace('', [18], true) === 0)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set trace"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test trace"), fee(XRP(1)));
}
void
test_util_accid(FeatureBitset features)
{
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);
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
rollback(x.toString(), code)
}
}
const Hook = (arg) => {
let addr = 'rMEGJtK2SttrtAfoKaqKUpCrDCi9saNuLg'
let b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0xde &&
b[1] === 0x15 &&
b[2] === 0x1e &&
b[3] === 0x2f &&
b[4] === 0xb2 &&
b[5] === 0xaa &&
b[6] === 0xbd &&
b[7] === 0x1a &&
b[8] === 0x5b &&
b[9] === 0xd0 &&
b[10] === 0x2f &&
b[11] === 0x63 &&
b[12] === 0x68 &&
b[13] === 0x26 &&
b[14] === 0xdf &&
b[15] === 0x43 &&
b[16] === 0x50 &&
b[17] === 0xc0 &&
b[18] === 0x40 &&
b[19] === 0xde
)
addr = 'rNo8xzUAauXENpvsMVJ9Q9w5LtVxCVFN4p'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x97 &&
b[1] === 0x73 &&
b[2] === 0x23 &&
b[3] === 0xaa &&
b[4] === 0x33 &&
b[5] === 0x7c &&
b[6] === 0xb6 &&
b[7] === 0x82 &&
b[8] === 0x37 &&
b[9] === 0x83 &&
b[10] === 0x58 &&
b[11] === 0x3a &&
b[12] === 0x7a &&
b[13] === 0xdf &&
b[14] === 0x4e &&
b[15] === 0xd8 &&
b[16] === 0x52 &&
b[17] === 0x2c &&
b[18] === 0xa8 &&
b[19] === 0xf0
)
addr = 'rUpwuJR1xLH18aHLP5nEm4Hw215tmkq6V7'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x78 &&
b[1] === 0xe2 &&
b[2] === 0x10 &&
b[3] === 0xac &&
b[4] === 0x98 &&
b[5] === 0x38 &&
b[6] === 0xf2 &&
b[7] === 0x5a &&
b[8] === 0x3b &&
b[9] === 0x7e &&
b[10] === 0xde &&
b[11] === 0x51 &&
b[12] === 0x37 &&
b[13] === 0x13 &&
b[14] === 0x94 &&
b[15] === 0xed &&
b[16] === 0x80 &&
b[17] === 0x77 &&
b[18] === 0x89 &&
b[19] === 0x48
)
addr = 'ravUPmVUQ65qeuNSFiN6W2U88smjJYHBJm'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x40 &&
b[1] === 0xe8 &&
b[2] === 0x2f &&
b[3] === 0x55 &&
b[4] === 0xc7 &&
b[5] === 0x3a &&
b[6] === 0xeb &&
b[7] === 0xcf &&
b[8] === 0xc9 &&
b[9] === 0x1d &&
b[10] === 0x3b &&
b[11] === 0xf4 &&
b[12] === 0x77 &&
b[13] === 0x76 &&
b[14] === 0x50 &&
b[15] === 0x2b &&
b[16] === 0x49 &&
b[17] === 0x7b &&
b[18] === 0x12 &&
b[19] === 0x2c
)
addr = 'rPXQ8PW1C382oewiEyJrAWtDQBNsQhAtWA'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0xf7 &&
b[1] === 0x13 &&
b[2] === 0x19 &&
b[3] === 0x49 &&
b[4] === 0x3f &&
b[5] === 0xa6 &&
b[6] === 0xa3 &&
b[7] === 0xdb &&
b[8] === 0x62 &&
b[9] === 0xae &&
b[10] === 0x12 &&
b[11] === 0x1b &&
b[12] === 0x12 &&
b[13] === 0x6c &&
b[14] === 0xfe &&
b[15] === 0x81 &&
b[16] === 0x49 &&
b[17] === 0x5a &&
b[18] === 0x49 &&
b[19] === 0x16
)
addr = 'rnZbUT8tpm48KEdfELCxRjJJhNV1JNYcg5'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x32 &&
b[1] === 0x0a &&
b[2] === 0x5c &&
b[3] === 0x53 &&
b[4] === 0x61 &&
b[5] === 0x5b &&
b[6] === 0x4b &&
b[7] === 0x57 &&
b[8] === 0x1d &&
b[9] === 0xc4 &&
b[10] === 0x6f &&
b[11] === 0x13 &&
b[12] === 0xbd &&
b[13] === 0x4f &&
b[14] === 0x31 &&
b[15] === 0x70 &&
b[16] === 0x84 &&
b[17] === 0xd1 &&
b[18] === 0xb1 &&
b[19] === 0x68
)
addr = 'rPghxri3jhBaxBfWGAHrVC4KANoRBe6dcM'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0xf8 &&
b[1] === 0xb6 &&
b[2] === 0x49 &&
b[3] === 0x2b &&
b[4] === 0x5b &&
b[5] === 0x21 &&
b[6] === 0xc8 &&
b[7] === 0xda &&
b[8] === 0xbd &&
b[9] === 0x0f &&
b[10] === 0x1d &&
b[11] === 0x2f &&
b[12] === 0xd9 &&
b[13] === 0xf4 &&
b[14] === 0x5b &&
b[15] === 0xde &&
b[16] === 0xcc &&
b[17] === 0x6a &&
b[18] === 0xeb &&
b[19] === 0x91
)
addr = 'r4Tck2QJcfcwBuTgVJXYb4QbrKP6mT1acM'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0xeb &&
b[1] === 0x63 &&
b[2] === 0x4c &&
b[3] === 0xd6 &&
b[4] === 0xf9 &&
b[5] === 0xbf &&
b[6] === 0x50 &&
b[7] === 0xc1 &&
b[8] === 0xd9 &&
b[9] === 0x79 &&
b[10] === 0x30 &&
b[11] === 0x84 &&
b[12] === 0x1b &&
b[13] === 0xfc &&
b[14] === 0x35 &&
b[15] === 0x32 &&
b[16] === 0xbd &&
b[17] === 0x6d &&
b[18] === 0xc0 &&
b[19] === 0x75
)
addr = 'rETHUL5T1SzM6AMotnsK5V3J5XMwJ9UhZ2'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x9e &&
b[1] === 0x8a &&
b[2] === 0x18 &&
b[3] === 0x66 &&
b[4] === 0x92 &&
b[5] === 0x0e &&
b[6] === 0xe5 &&
b[7] === 0xed &&
b[8] === 0xfa &&
b[9] === 0xe3 &&
b[10] === 0x23 &&
b[11] === 0x15 &&
b[12] === 0xcb &&
b[13] === 0x83 &&
b[14] === 0xef &&
b[15] === 0x73 &&
b[16] === 0xe4 &&
b[17] === 0x91 &&
b[18] === 0x0b &&
b[19] === 0xca
)
addr = 'rh9CggaWiY6QdD55ZkbbnrFpHJkKSauLfC'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x22 &&
b[1] === 0x8b &&
b[2] === 0xff &&
b[3] === 0x31 &&
b[4] === 0xb4 &&
b[5] === 0x93 &&
b[6] === 0xf6 &&
b[7] === 0xc1 &&
b[8] === 0x12 &&
b[9] === 0xea &&
b[10] === 0xd6 &&
b[11] === 0xdf &&
b[12] === 0xc4 &&
b[13] === 0x05 &&
b[14] === 0xb3 &&
b[15] === 0x7d &&
b[16] === 0xc0 &&
b[17] === 0x65 &&
b[18] === 0x21 &&
b[19] === 0x34
)
addr = 'r9sYGdPCGuJauy8QVG4CHnvp5U4eu3yY2B'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x58 &&
b[1] === 0x3b &&
b[2] === 0xf0 &&
b[3] === 0xcb &&
b[4] === 0x95 &&
b[5] === 0x80 &&
b[6] === 0xde &&
b[7] === 0xa0 &&
b[8] === 0xb3 &&
b[9] === 0x71 &&
b[10] === 0xd0 &&
b[11] === 0x18 &&
b[12] === 0x17 &&
b[13] === 0x1a &&
b[14] === 0xbb &&
b[15] === 0x98 &&
b[16] === 0x1f &&
b[17] === 0xcc &&
b[18] === 0x7c &&
b[19] === 0x68
)
addr = 'r4yJX9eU65WHfmKz6xXmSRf9CZN6bXfpWb'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0xf1 &&
b[1] === 0x00 &&
b[2] === 0x8f &&
b[3] === 0x64 &&
b[4] === 0x0f &&
b[5] === 0x99 &&
b[6] === 0x19 &&
b[7] === 0xda &&
b[8] === 0xcf &&
b[9] === 0x48 &&
b[10] === 0x18 &&
b[11] === 0x1c &&
b[12] === 0x35 &&
b[13] === 0x2e &&
b[14] === 0xe4 &&
b[15] === 0x3e &&
b[16] === 0x37 &&
b[17] === 0x7c &&
b[18] === 0x01 &&
b[19] === 0xf6
)
addr = 'rBkXoWoXPHuZy2nHbE7L1zJfqAvb4jHRrK'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x75 &&
b[1] === 0xec &&
b[2] === 0xdb &&
b[3] === 0x3b &&
b[4] === 0x9a &&
b[5] === 0x71 &&
b[6] === 0xd9 &&
b[7] === 0xef &&
b[8] === 0xd6 &&
b[9] === 0x55 &&
b[10] === 0x15 &&
b[11] === 0xdd &&
b[12] === 0xea &&
b[13] === 0xd2 &&
b[14] === 0x36 &&
b[15] === 0x7a &&
b[16] === 0x05 &&
b[17] === 0x6f &&
b[18] === 0x4e &&
b[19] === 0x5f
)
addr = 'rnaUBeEBNuyv57Jk127DsApEQoR8JqWpie'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x2c &&
b[1] === 0xdb &&
b[2] === 0xeb &&
b[3] === 0x1f &&
b[4] === 0x5e &&
b[5] === 0xc5 &&
b[6] === 0xd7 &&
b[7] === 0x5f &&
b[8] === 0xac &&
b[9] === 0xbd &&
b[10] === 0x19 &&
b[11] === 0xc8 &&
b[12] === 0x3f &&
b[13] === 0x45 &&
b[14] === 0x3b &&
b[15] === 0xa8 &&
b[16] === 0xa0 &&
b[17] === 0x1c &&
b[18] === 0xdb &&
b[19] === 0x0f
)
addr = 'rJHmUPMQ6qYdaqMizDZY8FKcCqCJxYYnb3'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0xbd &&
b[1] === 0xa5 &&
b[2] === 0xaf &&
b[3] === 0xda &&
b[4] === 0x5f &&
b[5] === 0x04 &&
b[6] === 0xe7 &&
b[7] === 0xef &&
b[8] === 0x16 &&
b[9] === 0x7a &&
b[10] === 0x35 &&
b[11] === 0x94 &&
b[12] === 0x6e &&
b[13] === 0xef &&
b[14] === 0x19 &&
b[15] === 0xfa &&
b[16] === 0x12 &&
b[17] === 0xf3 &&
b[18] === 0x1c &&
b[19] === 0x64
)
addr = 'rpJtt64FNNtaEBgqbJcrrunucUWJSdKJa2'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x0e &&
b[1] === 0x5a &&
b[2] === 0x83 &&
b[3] === 0x89 &&
b[4] === 0xc0 &&
b[5] === 0x5e &&
b[6] === 0x56 &&
b[7] === 0xd1 &&
b[8] === 0x50 &&
b[9] === 0xbc &&
b[10] === 0x45 &&
b[11] === 0x7b &&
b[12] === 0x86 &&
b[13] === 0x46 &&
b[14] === 0xf1 &&
b[15] === 0xcf &&
b[16] === 0xb7 &&
b[17] === 0xd0 &&
b[18] === 0xbf &&
b[19] === 0xd4
)
addr = 'rUC2XjZURBYQ8r6i5sqWnhtDmFFdJFobb9'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x7f &&
b[1] === 0xf5 &&
b[2] === 0x2d &&
b[3] === 0xf4 &&
b[4] === 0x98 &&
b[5] === 0x2b &&
b[6] === 0x7c &&
b[7] === 0x14 &&
b[8] === 0x7e &&
b[9] === 0x9a &&
b[10] === 0x8b &&
b[11] === 0xeb &&
b[12] === 0x1a &&
b[13] === 0x53 &&
b[14] === 0x60 &&
b[15] === 0x34 &&
b[16] === 0x95 &&
b[17] === 0x42 &&
b[18] === 0x4a &&
b[19] === 0x44
)
addr = 'rKEsw1ExpKaukXyyPCxeZdAF5V68kPSAVZ'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0xc8 &&
b[1] === 0x19 &&
b[2] === 0xe6 &&
b[3] === 0x2a &&
b[4] === 0xdd &&
b[5] === 0x42 &&
b[6] === 0x48 &&
b[7] === 0xd6 &&
b[8] === 0x7d &&
b[9] === 0xa5 &&
b[10] === 0x56 &&
b[11] === 0x66 &&
b[12] === 0x55 &&
b[13] === 0xb4 &&
b[14] === 0xbf &&
b[15] === 0xde &&
b[16] === 0x99 &&
b[17] === 0xcf &&
b[18] === 0xed &&
b[19] === 0x96
)
addr = 'rEXhVGVWdte28r1DUzfgKLjNiHi1Tn6R7X'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x9f &&
b[1] === 0x41 &&
b[2] === 0x26 &&
b[3] === 0xa3 &&
b[4] === 0x6d &&
b[5] === 0x56 &&
b[6] === 0x01 &&
b[7] === 0xc8 &&
b[8] === 0x09 &&
b[9] === 0x63 &&
b[10] === 0x76 &&
b[11] === 0xed &&
b[12] === 0x4c &&
b[13] === 0x45 &&
b[14] === 0x66 &&
b[15] === 0x63 &&
b[16] === 0x16 &&
b[17] === 0xc9 &&
b[18] === 0x5c &&
b[19] === 0x80
)
addr = 'r3TcfPNEvidJ2LkNoFojffcCd7RgT53Thg'
b = util_accid(addr)
ASSERT(20 === b.length)
ASSERT(
b[0] === 0x51 &&
b[1] === 0xd1 &&
b[2] === 0x00 &&
b[3] === 0xff &&
b[4] === 0x0d &&
b[5] === 0x92 &&
b[6] === 0x18 &&
b[7] === 0x73 &&
b[8] === 0x80 &&
b[9] === 0x30 &&
b[10] === 0xc5 &&
b[11] === 0x1a &&
b[12] === 0xf2 &&
b[13] === 0x9f &&
b[14] === 0x52 &&
b[15] === 0x8e &&
b[16] === 0xb8 &&
b[17] === 0x63 &&
b[18] === 0x08 &&
b[19] === 0x7c
)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set util_accid"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test util_accid"), fee(XRP(1)));
}
void
test_util_keylet(FeatureBitset features)
{
testcase("Test util_keylet");
using namespace jtx;
Env env{*this, features};
// Env env{*this, envconfig(), features, nullptr,
// 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 = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
trace('error', 0, false)
rollback(x.toString(), code)
}
}
const ASSERT_KL_EQ = (m, n) => {
ASSERT(n[0] === m[0] && n[1] === m[1] && n[2] === m[2] && n[3] === m[3], 0)
}
//C5D0F34B0A1905BC3B29AA1BE139FE04D60C8694D3950A8D80251D10B563A822
const ns = [
0xc5, 0xd0, 0xf3, 0x4b, 0x0a, 0x19, 0x05, 0xbc, 0x3b, 0x29, 0xaa, 0x1b, 0xe1,
0x39, 0xfe, 0x04, 0xd6, 0x0c, 0x86, 0x94, 0xd3, 0x95, 0x0a, 0x8d, 0x80, 0x25,
0x1d, 0x10, 0xb5, 0x63, 0xa8, 0x22,
]
// 2D0CB3CD60DA33B5AA7FEA321F111663EAED32481C6B700E484550F45AD96223
const klkey = [
0x00, 0x00, 0x2d, 0x0c, 0xb3, 0xcd, 0x60, 0xda, 0x33, 0xb5, 0xaa, 0x7f, 0xea,
0x32, 0x1f, 0x11, 0x16, 0x63, 0xea, 0xed, 0x32, 0x48, 0x1c, 0x6b, 0x70, 0x0e,
0x48, 0x45, 0x50, 0xf4, 0x5a, 0xd9, 0x62, 0x23,
]
const key = [
0x2d, 0x0c, 0xb3, 0xcd, 0x60, 0xda, 0x33, 0xb5, 0xaa, 0x7f, 0xea, 0x32, 0x1f,
0x11, 0x16, 0x63, 0xea, 0xed, 0x32, 0x48, 0x1c, 0x6b, 0x70, 0x0e, 0x48, 0x45,
0x50, 0xf4, 0x5a, 0xd9, 0x62, 0x23,
]
const cur = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55,
0x53, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00,
]
//rB6v18pQ765Z9DH5RQsTFevoQPFmRtBqhT
const a = [
0x75, 0x6e, 0xde, 0x88, 0xa9, 0x07, 0xd4, 0xcc, 0xf3, 0x8d, 0x6a, 0xdb, 0x9f,
0xc7, 0x94, 0x64, 0x19, 0xf0, 0xc4, 0x1d,
]
//raKM1bZkGmASBqN5v2swrf2uAPJ32Cd8GV
const b = [
0x3a, 0x51, 0x8a, 0x22, 0x53, 0x81, 0x60, 0x84, 0x1c, 0x14, 0x32, 0xfe, 0x6f,
0x3e, 0x6d, 0x6e, 0x76, 0x29, 0xfb, 0xba,
]
const KEYLET_HOOK = 1
const KEYLET_HOOK_STATE = 2
const KEYLET_ACCOUNT = 3
const KEYLET_AMENDMENTS = 4
const KEYLET_CHILD = 5
const KEYLET_SKIP = 6
const KEYLET_FEES = 7
const KEYLET_NEGATIVE_UNL = 8
const KEYLET_LINE = 9
const KEYLET_OFFER = 10
const KEYLET_QUALITY = 11
const KEYLET_EMITTED_DIR = 12
const KEYLET_TICKET = 13
const KEYLET_SIGNERS = 14
const KEYLET_CHECK = 15
const KEYLET_DEPOSIT_PREAUTH = 16
const KEYLET_UNCHECKED = 17
const KEYLET_OWNER_DIR = 18
const KEYLET_PAGE = 19
const KEYLET_ESCROW = 20
const KEYLET_PAYCHAN = 21
const KEYLET_EMITTED = 22
const KEYLET_NFT_OFFER = 23
const KEYLET_HOOK_DEFINITION = 24
const KEYLET_HOOK_STATE_DIR = 25
const KEYLET_URITOKEN = 26
const INVALID_ARGUMENT = -7
const Hook = (arg) => {
// Test one of each type
let e, ans
// KEYLET_HOOK
e = util_keylet(KEYLET_HOOK, a)
ASSERT(34 === e.length, 1)
ans = [
0x00, 0x48, 0x6c, 0x4b, 0x29, 0xc6, 0x0f, 0x40, 0x5d, 0xb7, 0x6e, 0x87,
0x65, 0x4a, 0x2f, 0x15, 0x4b, 0xab, 0x99, 0xc7, 0x62, 0x29, 0x80, 0x10,
0xa1, 0x89, 0x78, 0x52, 0x90, 0x80, 0x2f, 0x78, 0xbd, 0xcc,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_HOOK_STATE
e = util_keylet(KEYLET_HOOK_STATE, b, key, ns)
ASSERT(34 === e.length, 2)
ans = [
0x00, 0x76, 0x28, 0xaf, 0xcc, 0x25, 0x0a, 0x64, 0x41, 0x8e, 0xb7, 0x83,
0x68, 0xeb, 0x4e, 0xc5, 0x52, 0x4a, 0xeb, 0x97, 0x54, 0xab, 0xc1, 0x0b,
0x13, 0x06, 0x7f, 0xfb, 0x9f, 0x4b, 0xd8, 0x38, 0x62, 0xf2,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_ACCOUNT
e = util_keylet(KEYLET_ACCOUNT, b)
ASSERT(34 === e.length, 3)
ans = [
0x00, 0x61, 0xc6, 0x55, 0xdd, 0x8d, 0x8e, 0xd3, 0xba, 0xb4, 0xa0, 0xf1,
0xec, 0x2d, 0xa9, 0x99, 0xf4, 0x1b, 0xa6, 0x82, 0xc6, 0x84, 0xf9, 0x99,
0x66, 0xb9, 0x3c, 0x9a, 0xc3, 0xe3, 0x5c, 0x9a, 0x81, 0x6d,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_AMENDMENTS
e = util_keylet(KEYLET_AMENDMENTS)
ASSERT(34 === e.length, 4)
ans = [
0x00, 0x66, 0x7d, 0xb0, 0x78, 0x8c, 0x02, 0x0f, 0x02, 0x78, 0x0a, 0x67,
0x3d, 0xc7, 0x47, 0x57, 0xf2, 0x38, 0x23, 0xfa, 0x30, 0x14, 0xc1, 0x86,
0x6e, 0x72, 0xcc, 0x4c, 0xd8, 0xb2, 0x26, 0xcd, 0x6e, 0xf4,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_CHILD
e = util_keylet(KEYLET_CHILD, key)
ASSERT(34 === e.length, 5)
ans = [
0x1c, 0xd2, 0x2d, 0x0c, 0xb3, 0xcd, 0x60, 0xda, 0x33, 0xb5, 0xaa, 0x7f,
0xea, 0x32, 0x1f, 0x11, 0x16, 0x63, 0xea, 0xed, 0x32, 0x48, 0x1c, 0x6b,
0x70, 0x0e, 0x48, 0x45, 0x50, 0xf4, 0x5a, 0xd9, 0x62, 0x23,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_SKIP
e = util_keylet(KEYLET_SKIP)
ASSERT(34 === e.length, 6)
ans = [
0x00, 0x68, 0xb4, 0x97, 0x9a, 0x36, 0xcd, 0xc7, 0xf3, 0xd3, 0xd5, 0xc3,
0x1a, 0x4e, 0xae, 0x2a, 0xc7, 0xd7, 0x20, 0x9d, 0xda, 0x87, 0x75, 0x88,
0xb9, 0xaf, 0xc6, 0x67, 0x99, 0x69, 0x2a, 0xb0, 0xd6, 0x6b,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_FEES
e = util_keylet(KEYLET_FEES)
ASSERT(34 === e.length, 7)
ans = [
0x00, 0x73, 0x4b, 0xc5, 0x0c, 0x9b, 0x0d, 0x85, 0x15, 0xd3, 0xea, 0xae,
0x1e, 0x74, 0xb2, 0x9a, 0x95, 0x80, 0x43, 0x46, 0xc4, 0x91, 0xee, 0x1a,
0x95, 0xbf, 0x25, 0xe4, 0xaa, 0xb8, 0x54, 0xa6, 0xa6, 0x51,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_NEGATIVE_UNL
e = util_keylet(KEYLET_NEGATIVE_UNL)
ASSERT(34 === e.length, 8)
ans = [
0x00, 0x4e, 0x2e, 0x8a, 0x59, 0xaa, 0x9d, 0x3b, 0x5b, 0x18, 0x6b, 0x0b,
0x9e, 0x0f, 0x62, 0xe6, 0xc0, 0x25, 0x87, 0xca, 0x74, 0xa4, 0xd7, 0x78,
0x93, 0x8e, 0x95, 0x7b, 0x63, 0x57, 0xd3, 0x64, 0xb2, 0x44,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_LINE
e = util_keylet(KEYLET_LINE, a, b, cur)
ASSERT(34 === e.length, 9)
ans = [
0x00, 0x72, 0x0e, 0xb8, 0x2a, 0xdd, 0x5e, 0x15, 0x59, 0x1b, 0xf6, 0xe3,
0x6d, 0xbc, 0x3c, 0x12, 0xd3, 0x07, 0x6d, 0x43, 0xa8, 0x53, 0xf8, 0xf9,
0xe8, 0xa7, 0xd8, 0x4f, 0xe1, 0xe9, 0x7a, 0x2a, 0xc7, 0x3d,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_OFFER
e = util_keylet(KEYLET_OFFER, a, 1)
ASSERT(34 === e.length, 10)
ans = [
0x00, 0x6f, 0x60, 0x14, 0x48, 0x80, 0x97, 0x5f, 0x76, 0x6a, 0xb2, 0x2c,
0x32, 0x2f, 0x10, 0x8e, 0x03, 0x43, 0x51, 0xde, 0x89, 0x6c, 0xf4, 0x9f,
0x6b, 0x4a, 0xc7, 0x2c, 0x54, 0xf7, 0x27, 0x29, 0x9b, 0xe8,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_OFFER
e = util_keylet(KEYLET_OFFER, a, ns)
ASSERT(34 === e.length, 11)
ans = [
0x00, 0x6f, 0x23, 0x61, 0x7f, 0x44, 0x91, 0x1c, 0xba, 0x3b, 0x5c, 0xbe,
0xe9, 0x42, 0x22, 0xac, 0xa4, 0x29, 0xf4, 0xd6, 0x60, 0x01, 0xa8, 0xab,
0x9b, 0x98, 0x5e, 0xb8, 0xb8, 0x42, 0x9f, 0x1e, 0x91, 0x4b,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_QUALITY
klkey[0] = 0
klkey[1] = 0x65
e = util_keylet(KEYLET_QUALITY, klkey, 0, 1)
ASSERT(INVALID_ARGUMENT === e, 12)
// ans = [
// 0x00, 0x6f, 0x23, 0x61, 0x7f, 0x44, 0x91, 0x1c, 0xba, 0x3b, 0x5c, 0xbe,
// 0xe9, 0x42, 0x22, 0xac, 0xa4, 0x29, 0xf4, 0xd6, 0x60, 0x01, 0xa8, 0xab,
// 0x9b, 0x98, 0x5e, 0xb8, 0xb8, 0x42, 0x9f, 0x1e, 0x91, 0x4b,
// ]
// ASSERT_KL_EQ(e, ans)
// KEYLET_QUALITY
klkey[1] = 0x64
e = util_keylet(KEYLET_QUALITY, klkey, 0, 1)
ASSERT(34 === e.length, 13)
ans = [
0x00, 0x64, 0x2d, 0x0c, 0xb3, 0xcd, 0x60, 0xda, 0x33, 0xb5, 0xaa, 0x7f,
0xea, 0x32, 0x1f, 0x11, 0x16, 0x63, 0xea, 0xed, 0x32, 0x48, 0x1c, 0x6b,
0x70, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_EMITTED_DIR
e = util_keylet(KEYLET_EMITTED_DIR)
ASSERT(34 === e.length, 14)
ans = [
0x00, 0x64, 0xb4, 0xde, 0x82, 0x30, 0x55, 0xd0, 0x0b, 0xc1, 0x2c, 0xd7,
0x8f, 0xe1, 0xaa, 0xf7, 0x4e, 0xe6, 0x06, 0x21, 0x95, 0xb2, 0x62, 0x9f,
0x49, 0xa2, 0x59, 0x15, 0xa3, 0x9c, 0x64, 0xbe, 0x19, 0x00,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_SIGNERS
e = util_keylet(KEYLET_SIGNERS, a)
ASSERT(34 === e.length, 15)
ans = [
0x00, 0x53, 0xdf, 0x8f, 0xf0, 0xce, 0x41, 0x1a, 0x3b, 0x8f, 0x1b, 0xb5,
0xbb, 0x32, 0x78, 0x17, 0x15, 0xd6, 0x77, 0x42, 0xf5, 0xb5, 0x63, 0xb8,
0x77, 0xb3, 0x3b, 0x07, 0x76, 0xf6, 0xf7, 0xbc, 0xda, 0x1d,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_CHECK
e = util_keylet(KEYLET_CHECK, a, 1)
ASSERT(34 === e.length, 16)
ans = [
0x00, 0x43, 0x08, 0x1f, 0x26, 0xff, 0x79, 0x1a, 0xf7, 0x54, 0x26, 0xed,
0xf9, 0xeb, 0x08, 0x44, 0x85, 0x28, 0x58, 0x2c, 0xb1, 0xa4, 0xef, 0x4f,
0xd0, 0xb4, 0x49, 0x9b, 0x76, 0x82, 0xe7, 0x69, 0xa6, 0xb5,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_CHECK
e = util_keylet(KEYLET_CHECK, a, ns)
ASSERT(34 === e.length, 17)
ans = [
0x00, 0x43, 0x94, 0xe3, 0x6f, 0x0d, 0xd3, 0xed, 0xc0, 0x2c, 0x49, 0xa5,
0xaa, 0x0e, 0xcc, 0x49, 0x18, 0x39, 0x92, 0xab, 0x57, 0xc3, 0x2d, 0x9e,
0x45, 0x51, 0x04, 0x78, 0x49, 0x49, 0xd1, 0xe6, 0xd2, 0x01,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_DEPOSIT_PREAUTH
e = util_keylet(KEYLET_DEPOSIT_PREAUTH, a, b)
ASSERT(34 === e.length, 18)
ans = [
0x00, 0x70, 0x88, 0x90, 0x0f, 0x27, 0x66, 0x57, 0xbc, 0xc0, 0x5d, 0xa1,
0x67, 0x40, 0xab, 0x9d, 0x33, 0x01, 0x8e, 0x45, 0x71, 0x7b, 0x0e, 0xc4,
0x2e, 0x4d, 0x11, 0xbd, 0x6d, 0xbd, 0x94, 0x03, 0x48, 0xe0,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_UNCHECKED
klkey[0] = 0
klkey[1] = 0
e = util_keylet(KEYLET_UNCHECKED, key)
ASSERT(34 === e.length, 19)
ans = klkey
ASSERT_KL_EQ(e, ans)
// KEYLET_OWNER_DIR
e = util_keylet(KEYLET_OWNER_DIR, a)
ASSERT(34 === e.length, 20)
ans = [
0x00, 0x64, 0xc8, 0x5e, 0x01, 0x29, 0x06, 0x7b, 0x75, 0xad, 0x30, 0xb0,
0xaa, 0x1c, 0xc2, 0x5b, 0x0a, 0x82, 0xc7, 0xf9, 0xaa, 0xbd, 0xee, 0x05,
0xff, 0x01, 0x66, 0x69, 0xef, 0x9d, 0x82, 0xdc, 0xec, 0x30,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_PAGE
e = util_keylet(KEYLET_PAGE, ns, 0, 1)
ASSERT(34 === e.length, 21)
ans = [
0x00, 0x64, 0x61, 0xe6, 0x05, 0x1a, 0xb0, 0x49, 0x89, 0x2e, 0x75, 0xc9,
0x3d, 0x67, 0xfb, 0x7a, 0x63, 0xf1, 0xef, 0x56, 0xdd, 0xaf, 0x3e, 0x6b,
0x43, 0x6f, 0x57, 0x6e, 0x8c, 0x01, 0x81, 0x98, 0x2e, 0x48,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_ESCROW
e = util_keylet(KEYLET_ESCROW, a, 1)
ASSERT(34 === e.length, 22)
ans = [
0x00, 0x75, 0x13, 0xef, 0x04, 0xcd, 0x33, 0x6a, 0xad, 0xf6, 0x3d, 0x0c,
0x7e, 0x05, 0x6c, 0x84, 0x9a, 0x7c, 0xf6, 0x72, 0x5e, 0x99, 0xbc, 0x93,
0x80, 0x1e, 0xf5, 0x78, 0xa0, 0x32, 0x72, 0x5b, 0x84, 0xfe,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_ESCROW
e = util_keylet(KEYLET_ESCROW, a, ns)
ASSERT(34 === e.length, 23)
ans = [
0x00, 0x75, 0xc1, 0xc6, 0xc5, 0x23, 0x74, 0x87, 0x12, 0x56, 0xaa, 0x7a,
0x1f, 0xb3, 0x29, 0x7a, 0x0a, 0x55, 0x88, 0x7d, 0x16, 0x6a, 0xcf, 0x85,
0x28, 0x59, 0x88, 0xc2, 0xda, 0x81, 0x7f, 0x03, 0x90, 0x43,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_PAYCHAN
e = util_keylet(KEYLET_PAYCHAN, a, b, 1)
ASSERT(34 === e.length, 24)
ans = [
0x00, 0x78, 0xed, 0x04, 0xce, 0x27, 0x20, 0x21, 0x55, 0x2b, 0xbf, 0xa1,
0xe5, 0xff, 0xbb, 0x53, 0xb6, 0x45, 0xa2, 0xff, 0x8a, 0x44, 0x66, 0xd5,
0x76, 0x24, 0xb5, 0x71, 0xe6, 0x44, 0x9e, 0xeb, 0xfc, 0x5a,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_PAYCHAN
e = util_keylet(KEYLET_PAYCHAN, a, b, ns)
ASSERT(34 === e.length, 25)
ans = [
0x00, 0x78, 0x7d, 0xe1, 0x01, 0xf6, 0x2b, 0xb0, 0x55, 0x80, 0xb9, 0xd6,
0xb0, 0x3f, 0x3b, 0xb0, 0x01, 0xbd, 0xe6, 0x9b, 0x89, 0x0f, 0x8a, 0xcd,
0xbe, 0x71, 0x73, 0x5e, 0xc3, 0x63, 0xf8, 0xc5, 0x4b, 0x9b,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_EMITTED
e = util_keylet(KEYLET_EMITTED, ns)
ASSERT(34 === e.length, 26)
ans = [
0x00, 0x45, 0xf3, 0x51, 0x2d, 0x1c, 0x80, 0xa3, 0xc0, 0xb1, 0x46, 0x04,
0xe1, 0xad, 0xdb, 0x90, 0x1c, 0x66, 0x32, 0x10, 0x08, 0xcc, 0xd0, 0xab,
0xd2, 0xdb, 0xbe, 0xc4, 0x08, 0xa6, 0x0f, 0x6a, 0x62, 0xe9,
]
ASSERT_KL_EQ(e, ans)
// KEYLET_NFT_OFFER
e = util_keylet(KEYLET_NFT_OFFER, a, 1)
ASSERT(34 === e.length, 27)
// KEYLET_NFT_OFFER
e = util_keylet(KEYLET_NFT_OFFER, a, ns)
ASSERT(34 === e.length, 28)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set util_keylet"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test util_keylet"), fee(XRP(1)));
}
void
test_util_raddr(FeatureBitset features)
{
testcase("Test util_raddr");
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);
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
rollback(x.toString(), code)
}
}
function toHex(v) {
const a = []
for (let i = 0; i < v.length; i++) {
a.push(v.charCodeAt(i))
}
return a
}
const Hook = (arg) => {
let raw = [
0x6b, 0x30, 0xe2, 0x94, 0xf3, 0x40, 0x3f, 0xf8, 0x7c, 0xef, 0x9e, 0x72,
0x21, 0x7f, 0xf7, 0xeb, 0x4a, 0x6a, 0x43, 0xf4,
]
let addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x77 &&
addr[2] === 0x6d &&
addr[3] === 0x6d &&
addr[4] === 0x31 &&
addr[5] === 0x33 &&
addr[6] === 0x70 &&
addr[7] === 0x37 &&
addr[8] === 0x56 &&
addr[9] === 0x67 &&
addr[10] === 0x36 &&
addr[11] === 0x4b &&
addr[12] === 0x6d &&
addr[13] === 0x6e &&
addr[14] === 0x71 &&
addr[15] === 0x4b &&
addr[16] === 0x52 &&
addr[17] === 0x77 &&
addr[18] === 0x44 &&
addr[19] === 0x7a &&
addr[20] === 0x78 &&
addr[21] === 0x76 &&
addr[22] === 0x69 &&
addr[23] === 0x35 &&
addr[24] === 0x58 &&
addr[25] === 0x70 &&
addr[26] === 0x36 &&
addr[27] === 0x77 &&
addr[28] === 0x6e &&
addr[29] === 0x48 &&
addr[30] === 0x4d &&
addr[31] === 0x44 &&
addr[32] === 0x44 &&
addr[33] === 0x68
)
raw = [
0xe4, 0x0f, 0xa3, 0x4e, 0x3e, 0x66, 0x15, 0x36, 0x64, 0x89, 0x4f, 0xcb,
0xfb, 0xfc, 0xfe, 0x2d, 0x2d, 0x19, 0x0d, 0x69,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x4d &&
addr[2] === 0x38 &&
addr[3] === 0x31 &&
addr[4] === 0x6f &&
addr[5] === 0x48 &&
addr[6] === 0x77 &&
addr[7] === 0x68 &&
addr[8] === 0x37 &&
addr[9] === 0x35 &&
addr[10] === 0x39 &&
addr[11] === 0x34 &&
addr[12] === 0x6a &&
addr[13] === 0x48 &&
addr[14] === 0x38 &&
addr[15] === 0x70 &&
addr[16] === 0x36 &&
addr[17] === 0x31 &&
addr[18] === 0x57 &&
addr[19] === 0x65 &&
addr[20] === 0x31 &&
addr[21] === 0x73 &&
addr[22] === 0x64 &&
addr[23] === 0x58 &&
addr[24] === 0x46 &&
addr[25] === 0x42 &&
addr[26] === 0x35 &&
addr[27] === 0x48 &&
addr[28] === 0x52 &&
addr[29] === 0x52 &&
addr[30] === 0x79 &&
addr[31] === 0x4b &&
addr[32] === 0x76 &&
addr[33] === 0x4a
)
raw = [
0x0c, 0x90, 0x4b, 0x4f, 0xa5, 0x59, 0xbf, 0x10, 0x6a, 0xae, 0xb5, 0x28,
0x6c, 0x94, 0xba, 0x34, 0x18, 0xfd, 0xf3, 0x53,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x70 &&
addr[2] === 0x39 &&
addr[3] === 0x52 &&
addr[4] === 0x79 &&
addr[5] === 0x73 &&
addr[6] === 0x42 &&
addr[7] === 0x63 &&
addr[8] === 0x55 &&
addr[9] === 0x42 &&
addr[10] === 0x59 &&
addr[11] === 0x63 &&
addr[12] === 0x76 &&
addr[13] === 0x4a &&
addr[14] === 0x4a &&
addr[15] === 0x4b &&
addr[16] === 0x38 &&
addr[17] === 0x54 &&
addr[18] === 0x48 &&
addr[19] === 0x45 &&
addr[20] === 0x79 &&
addr[21] === 0x6f &&
addr[22] === 0x79 &&
addr[23] === 0x74 &&
addr[24] === 0x74 &&
addr[25] === 0x6b &&
addr[26] === 0x57 &&
addr[27] === 0x58 &&
addr[28] === 0x39 &&
addr[29] === 0x4b &&
addr[30] === 0x52 &&
addr[31] === 0x62 &&
addr[32] === 0x39 &&
addr[33] === 0x4d
)
raw = [
0x75, 0x82, 0xfb, 0x27, 0x10, 0x8c, 0x0f, 0x9a, 0xf2, 0x67, 0x35, 0xcc,
0x7b, 0x22, 0x6b, 0xd2, 0x2f, 0xdf, 0x4f, 0x92,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x42 &&
addr[2] === 0x35 &&
addr[3] === 0x4c &&
addr[4] === 0x79 &&
addr[5] === 0x77 &&
addr[6] === 0x6b &&
addr[7] === 0x54 &&
addr[8] === 0x4c &&
addr[9] === 0x31 &&
addr[10] === 0x34 &&
addr[11] === 0x51 &&
addr[12] === 0x64 &&
addr[13] === 0x55 &&
addr[14] === 0x64 &&
addr[15] === 0x77 &&
addr[16] === 0x43 &&
addr[17] === 0x78 &&
addr[18] === 0x70 &&
addr[19] === 0x6e &&
addr[20] === 0x65 &&
addr[21] === 0x46 &&
addr[22] === 0x32 &&
addr[23] === 0x7a &&
addr[24] === 0x63 &&
addr[25] === 0x7a &&
addr[26] === 0x46 &&
addr[27] === 0x66 &&
addr[28] === 0x44 &&
addr[29] === 0x7a &&
addr[30] === 0x57 &&
addr[31] === 0x46 &&
addr[32] === 0x38 &&
addr[33] === 0x50
)
raw = [
0x6c, 0xb6, 0x51, 0x1f, 0x20, 0xec, 0xca, 0x1e, 0x98, 0x03, 0xfc, 0xfa,
0x6f, 0x3e, 0x56, 0x75, 0x72, 0x29, 0x51, 0x97,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x77 &&
addr[2] === 0x75 &&
addr[3] === 0x46 &&
addr[4] === 0x50 &&
addr[5] === 0x4b &&
addr[6] === 0x34 &&
addr[7] === 0x48 &&
addr[8] === 0x51 &&
addr[9] === 0x4e &&
addr[10] === 0x73 &&
addr[11] === 0x59 &&
addr[12] === 0x42 &&
addr[13] === 0x47 &&
addr[14] === 0x74 &&
addr[15] === 0x46 &&
addr[16] === 0x52 &&
addr[17] === 0x4b &&
addr[18] === 0x77 &&
addr[19] === 0x45 &&
addr[20] === 0x6d &&
addr[21] === 0x75 &&
addr[22] === 0x41 &&
addr[23] === 0x68 &&
addr[24] === 0x63 &&
addr[25] === 0x4b &&
addr[26] === 0x63 &&
addr[27] === 0x48 &&
addr[28] === 0x39 &&
addr[29] === 0x5a &&
addr[30] === 0x32 &&
addr[31] === 0x59 &&
addr[32] === 0x7a &&
addr[33] === 0x58
)
raw = [
0xa5, 0x31, 0x30, 0x28, 0xf9, 0x62, 0xe4, 0x80, 0x48, 0x94, 0x3b, 0x1a,
0x59, 0xbb, 0x5e, 0x36, 0x96, 0xb3, 0x44, 0x35,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x47 &&
addr[2] === 0x68 &&
addr[3] === 0x54 &&
addr[4] === 0x52 &&
addr[5] === 0x4a &&
addr[6] === 0x5a &&
addr[7] === 0x31 &&
addr[8] === 0x56 &&
addr[9] === 0x4d &&
addr[10] === 0x51 &&
addr[11] === 0x74 &&
addr[12] === 0x36 &&
addr[13] === 0x6a &&
addr[14] === 0x44 &&
addr[15] === 0x72 &&
addr[16] === 0x66 &&
addr[17] === 0x4e &&
addr[18] === 0x63 &&
addr[19] === 0x6f &&
addr[20] === 0x4a &&
addr[21] === 0x34 &&
addr[22] === 0x39 &&
addr[23] === 0x6a &&
addr[24] === 0x34 &&
addr[25] === 0x43 &&
addr[26] === 0x67 &&
addr[27] === 0x71 &&
addr[28] === 0x4b &&
addr[29] === 0x6d &&
addr[30] === 0x52 &&
addr[31] === 0x32 &&
addr[32] === 0x6f &&
addr[33] === 0x36
)
raw = [
0xbf, 0x04, 0x6c, 0x79, 0xa0, 0x96, 0xde, 0x80, 0x66, 0xd3, 0x74, 0xc8,
0xdf, 0x94, 0x5f, 0x89, 0xf2, 0x3e, 0x9a, 0x27,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x4a &&
addr[2] === 0x52 &&
addr[3] === 0x72 &&
addr[4] === 0x34 &&
addr[5] === 0x72 &&
addr[6] === 0x4c &&
addr[7] === 0x32 &&
addr[8] === 0x43 &&
addr[9] === 0x4a &&
addr[10] === 0x48 &&
addr[11] === 0x67 &&
addr[12] === 0x46 &&
addr[13] === 0x47 &&
addr[14] === 0x56 &&
addr[15] === 0x67 &&
addr[16] === 0x31 &&
addr[17] === 0x6a &&
addr[18] === 0x61 &&
addr[19] === 0x66 &&
addr[20] === 0x39 &&
addr[21] === 0x4a &&
addr[22] === 0x48 &&
addr[23] === 0x51 &&
addr[24] === 0x70 &&
addr[25] === 0x56 &&
addr[26] === 0x6d &&
addr[27] === 0x68 &&
addr[28] === 0x76 &&
addr[29] === 0x45 &&
addr[30] === 0x37 &&
addr[31] === 0x68 &&
addr[32] === 0x61 &&
addr[33] === 0x62
)
raw = [
0xe2, 0x07, 0xab, 0xd3, 0x7d, 0xc2, 0xcd, 0xd4, 0x6d, 0x15, 0x7b, 0x67,
0x5a, 0xc8, 0x3e, 0x0e, 0x05, 0x9b, 0x08, 0x62,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x4d &&
addr[2] === 0x63 &&
addr[3] === 0x33 &&
addr[4] === 0x75 &&
addr[5] === 0x4d &&
addr[6] === 0x6b &&
addr[7] === 0x4b &&
addr[8] === 0x31 &&
addr[9] === 0x62 &&
addr[10] === 0x62 &&
addr[11] === 0x32 &&
addr[12] === 0x64 &&
addr[13] === 0x4b &&
addr[14] === 0x7a &&
addr[15] === 0x5a &&
addr[16] === 0x64 &&
addr[17] === 0x56 &&
addr[18] === 0x71 &&
addr[19] === 0x35 &&
addr[20] === 0x75 &&
addr[21] === 0x59 &&
addr[22] === 0x54 &&
addr[23] === 0x55 &&
addr[24] === 0x37 &&
addr[25] === 0x5a &&
addr[26] === 0x76 &&
addr[27] === 0x4e &&
addr[28] === 0x45 &&
addr[29] === 0x41 &&
addr[30] === 0x32 &&
addr[31] === 0x33 &&
addr[32] === 0x67 &&
addr[33] === 0x44
)
raw = [
0x2a, 0x56, 0x74, 0x25, 0x84, 0x8d, 0x41, 0x6d, 0xf1, 0x06, 0x01, 0x6c,
0x2a, 0xb1, 0x13, 0xc3, 0x1e, 0x65, 0x63, 0x80,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x68 &&
addr[2] === 0x69 &&
addr[3] === 0x69 &&
addr[4] === 0x41 &&
addr[5] === 0x78 &&
addr[6] === 0x79 &&
addr[7] === 0x59 &&
addr[8] === 0x41 &&
addr[9] === 0x43 &&
addr[10] === 0x67 &&
addr[11] === 0x45 &&
addr[12] === 0x52 &&
addr[13] === 0x4b &&
addr[14] === 0x47 &&
addr[15] === 0x51 &&
addr[16] === 0x4d &&
addr[17] === 0x72 &&
addr[18] === 0x53 &&
addr[19] === 0x5a &&
addr[20] === 0x57 &&
addr[21] === 0x43 &&
addr[22] === 0x74 &&
addr[23] === 0x6b &&
addr[24] === 0x4d &&
addr[25] === 0x6f &&
addr[26] === 0x69 &&
addr[27] === 0x58 &&
addr[28] === 0x48 &&
addr[29] === 0x34 &&
addr[30] === 0x64 &&
addr[31] === 0x48 &&
addr[32] === 0x6e &&
addr[33] === 0x6f
)
raw = [
0x24, 0xbb, 0xa9, 0xc3, 0x95, 0x74, 0x9a, 0x88, 0x04, 0x12, 0xc0, 0x91,
0xe7, 0x13, 0x41, 0x7f, 0x9a, 0xd5, 0x74, 0x43,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x68 &&
addr[2] === 0x4d &&
addr[3] === 0x4e &&
addr[4] === 0x33 &&
addr[5] === 0x79 &&
addr[6] === 0x4e &&
addr[7] === 0x50 &&
addr[8] === 0x4e &&
addr[9] === 0x74 &&
addr[10] === 0x4b &&
addr[11] === 0x70 &&
addr[12] === 0x78 &&
addr[13] === 0x6b &&
addr[14] === 0x71 &&
addr[15] === 0x4c &&
addr[16] === 0x78 &&
addr[17] === 0x51 &&
addr[18] === 0x32 &&
addr[19] === 0x63 &&
addr[20] === 0x33 &&
addr[21] === 0x55 &&
addr[22] === 0x68 &&
addr[23] === 0x6f &&
addr[24] === 0x41 &&
addr[25] === 0x7a &&
addr[26] === 0x66 &&
addr[27] === 0x75 &&
addr[28] === 0x59 &&
addr[29] === 0x35 &&
addr[30] === 0x75 &&
addr[31] === 0x35 &&
addr[32] === 0x4a &&
addr[33] === 0x7a
)
raw = [
0x49, 0x53, 0x9e, 0x65, 0x21, 0x8a, 0xcf, 0x37, 0x85, 0x2b, 0xff, 0x87,
0x14, 0x76, 0xda, 0x1a, 0x62, 0x3a, 0xea, 0x80,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x66 &&
addr[2] === 0x67 &&
addr[3] === 0x35 &&
addr[4] === 0x56 &&
addr[5] === 0x41 &&
addr[6] === 0x44 &&
addr[7] === 0x41 &&
addr[8] === 0x4d &&
addr[9] === 0x4d &&
addr[10] === 0x42 &&
addr[11] === 0x78 &&
addr[12] === 0x46 &&
addr[13] === 0x51 &&
addr[14] === 0x76 &&
addr[15] === 0x44 &&
addr[16] === 0x78 &&
addr[17] === 0x5a &&
addr[18] === 0x54 &&
addr[19] === 0x32 &&
addr[20] === 0x52 &&
addr[21] === 0x6a &&
addr[22] === 0x55 &&
addr[23] === 0x64 &&
addr[24] === 0x47 &&
addr[25] === 0x69 &&
addr[26] === 0x64 &&
addr[27] === 0x59 &&
addr[28] === 0x61 &&
addr[29] === 0x35 &&
addr[30] === 0x76 &&
addr[31] === 0x69 &&
addr[32] === 0x37 &&
addr[33] === 0x5a
)
raw = [
0xe7, 0xd3, 0x03, 0xbc, 0xae, 0xbd, 0x62, 0x20, 0xae, 0xc2, 0xe1, 0x7e,
0x0b, 0xff, 0xdc, 0x21, 0x24, 0x34, 0x50, 0x82,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x34 &&
addr[2] === 0x33 &&
addr[3] === 0x6d &&
addr[4] === 0x31 &&
addr[5] === 0x31 &&
addr[6] === 0x66 &&
addr[7] === 0x74 &&
addr[8] === 0x36 &&
addr[9] === 0x79 &&
addr[10] === 0x6f &&
addr[11] === 0x50 &&
addr[12] === 0x69 &&
addr[13] === 0x6d &&
addr[14] === 0x36 &&
addr[15] === 0x56 &&
addr[16] === 0x44 &&
addr[17] === 0x78 &&
addr[18] === 0x64 &&
addr[19] === 0x55 &&
addr[20] === 0x76 &&
addr[21] === 0x63 &&
addr[22] === 0x46 &&
addr[23] === 0x77 &&
addr[24] === 0x36 &&
addr[25] === 0x57 &&
addr[26] === 0x38 &&
addr[27] === 0x41 &&
addr[28] === 0x77 &&
addr[29] === 0x78 &&
addr[30] === 0x78 &&
addr[31] === 0x4b &&
addr[32] === 0x35 &&
addr[33] === 0x58
)
raw = [
0xc3, 0xe1, 0x5f, 0xab, 0xc0, 0x0a, 0x79, 0x73, 0x71, 0xd0, 0x55, 0xc0,
0x80, 0x79, 0xae, 0x45, 0x71, 0x0f, 0xa0, 0x97,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x4a &&
addr[2] === 0x69 &&
addr[3] === 0x35 &&
addr[4] === 0x6b &&
addr[5] === 0x65 &&
addr[6] === 0x58 &&
addr[7] === 0x79 &&
addr[8] === 0x33 &&
addr[9] === 0x31 &&
addr[10] === 0x4a &&
addr[11] === 0x31 &&
addr[12] === 0x34 &&
addr[13] === 0x52 &&
addr[14] === 0x4b &&
addr[15] === 0x73 &&
addr[16] === 0x4e &&
addr[17] === 0x59 &&
addr[18] === 0x41 &&
addr[19] === 0x46 &&
addr[20] === 0x31 &&
addr[21] === 0x51 &&
addr[22] === 0x36 &&
addr[23] === 0x6a &&
addr[24] === 0x4d &&
addr[25] === 0x56 &&
addr[26] === 0x69 &&
addr[27] === 0x45 &&
addr[28] === 0x52 &&
addr[29] === 0x55 &&
addr[30] === 0x51 &&
addr[31] === 0x71 &&
addr[32] === 0x59 &&
addr[33] === 0x36
)
raw = [
0x95, 0x15, 0x7f, 0x2a, 0xaf, 0xe3, 0x2f, 0x7f, 0x2e, 0xf1, 0xa0, 0xf5,
0xea, 0xc3, 0x07, 0x06, 0xa1, 0xd3, 0xf5, 0xd9,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x4e &&
addr[2] === 0x62 &&
addr[3] === 0x48 &&
addr[4] === 0x53 &&
addr[5] === 0x55 &&
addr[6] === 0x6d &&
addr[7] === 0x66 &&
addr[8] === 0x4b &&
addr[9] === 0x61 &&
addr[10] === 0x34 &&
addr[11] === 0x71 &&
addr[12] === 0x31 &&
addr[13] === 0x51 &&
addr[14] === 0x78 &&
addr[15] === 0x44 &&
addr[16] === 0x45 &&
addr[17] === 0x5a &&
addr[18] === 0x4c &&
addr[19] === 0x6e &&
addr[20] === 0x54 &&
addr[21] === 0x67 &&
addr[22] === 0x46 &&
addr[23] === 0x56 &&
addr[24] === 0x45 &&
addr[25] === 0x4c &&
addr[26] === 0x78 &&
addr[27] === 0x39 &&
addr[28] === 0x6d &&
addr[29] === 0x57 &&
addr[30] === 0x45 &&
addr[31] === 0x43 &&
addr[32] === 0x6b &&
addr[33] === 0x41
)
raw = [
0xf0, 0xec, 0x0f, 0x86, 0x31, 0xbb, 0x2c, 0xbf, 0x8f, 0xb7, 0xe3, 0x1c,
0x82, 0xa0, 0xa3, 0x50, 0xd5, 0xe0, 0xfe, 0x6b,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x34 &&
addr[2] === 0x78 &&
addr[3] === 0x31 &&
addr[4] === 0x78 &&
addr[5] === 0x46 &&
addr[6] === 0x32 &&
addr[7] === 0x42 &&
addr[8] === 0x47 &&
addr[9] === 0x73 &&
addr[10] === 0x42 &&
addr[11] === 0x41 &&
addr[12] === 0x7a &&
addr[13] === 0x77 &&
addr[14] === 0x77 &&
addr[15] === 0x61 &&
addr[16] === 0x4b &&
addr[17] === 0x61 &&
addr[18] === 0x70 &&
addr[19] === 0x4b &&
addr[20] === 0x6f &&
addr[21] === 0x6f &&
addr[22] === 0x35 &&
addr[23] === 0x57 &&
addr[24] === 0x65 &&
addr[25] === 0x31 &&
addr[26] === 0x59 &&
addr[27] === 0x53 &&
addr[28] === 0x6e &&
addr[29] === 0x52 &&
addr[30] === 0x50 &&
addr[31] === 0x57 &&
addr[32] === 0x75 &&
addr[33] === 0x39
)
raw = [
0x8d, 0xa4, 0x7d, 0xab, 0xd1, 0x19, 0xdc, 0xc4, 0x45, 0x5f, 0xaa, 0xe2,
0x1c, 0x39, 0xca, 0x19, 0x34, 0xf1, 0x86, 0x16,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x44 &&
addr[2] === 0x75 &&
addr[3] === 0x41 &&
addr[4] === 0x4c &&
addr[5] === 0x6e &&
addr[6] === 0x52 &&
addr[7] === 0x79 &&
addr[8] === 0x76 &&
addr[9] === 0x77 &&
addr[10] === 0x38 &&
addr[11] === 0x36 &&
addr[12] === 0x43 &&
addr[13] === 0x63 &&
addr[14] === 0x55 &&
addr[15] === 0x5a &&
addr[16] === 0x39 &&
addr[17] === 0x74 &&
addr[18] === 0x52 &&
addr[19] === 0x55 &&
addr[20] === 0x45 &&
addr[21] === 0x6d &&
addr[22] === 0x35 &&
addr[23] === 0x43 &&
addr[24] === 0x61 &&
addr[25] === 0x65 &&
addr[26] === 0x50 &&
addr[27] === 0x46 &&
addr[28] === 0x66 &&
addr[29] === 0x33 &&
addr[30] === 0x74 &&
addr[31] === 0x36 &&
addr[32] === 0x61 &&
addr[33] === 0x31
)
raw = [
0xa9, 0x94, 0x5a, 0xe3, 0x5a, 0x43, 0xad, 0xbe, 0xba, 0xa4, 0x13, 0x94,
0xf5, 0xdc, 0x8f, 0x3b, 0x01, 0x14, 0xff, 0xfe,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x47 &&
addr[2] === 0x54 &&
addr[3] === 0x65 &&
addr[4] === 0x76 &&
addr[5] === 0x4a &&
addr[6] === 0x71 &&
addr[7] === 0x76 &&
addr[8] === 0x6b &&
addr[9] === 0x5a &&
addr[10] === 0x76 &&
addr[11] === 0x48 &&
addr[12] === 0x73 &&
addr[13] === 0x58 &&
addr[14] === 0x5a &&
addr[15] === 0x71 &&
addr[16] === 0x55 &&
addr[17] === 0x78 &&
addr[18] === 0x43 &&
addr[19] === 0x48 &&
addr[20] === 0x4c &&
addr[21] === 0x68 &&
addr[22] === 0x73 &&
addr[23] === 0x43 &&
addr[24] === 0x53 &&
addr[25] === 0x38 &&
addr[26] === 0x57 &&
addr[27] === 0x68 &&
addr[28] === 0x79 &&
addr[29] === 0x4d &&
addr[30] === 0x74 &&
addr[31] === 0x7a &&
addr[32] === 0x6e &&
addr[33] === 0x5a
)
raw = [
0xc1, 0xe6, 0x7f, 0x17, 0xd3, 0x00, 0x9b, 0x80, 0x6c, 0x85, 0x74, 0x9c,
0x80, 0x40, 0xaf, 0x64, 0xce, 0x09, 0x7e, 0x2e,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x4a &&
addr[2] === 0x67 &&
addr[3] === 0x45 &&
addr[4] === 0x59 &&
addr[5] === 0x55 &&
addr[6] === 0x37 &&
addr[7] === 0x36 &&
addr[8] === 0x45 &&
addr[9] === 0x55 &&
addr[10] === 0x34 &&
addr[11] === 0x59 &&
addr[12] === 0x41 &&
addr[13] === 0x5a &&
addr[14] === 0x41 &&
addr[15] === 0x44 &&
addr[16] === 0x79 &&
addr[17] === 0x61 &&
addr[18] === 0x37 &&
addr[19] === 0x6b &&
addr[20] === 0x37 &&
addr[21] === 0x62 &&
addr[22] === 0x71 &&
addr[23] === 0x38 &&
addr[24] === 0x4e &&
addr[25] === 0x76 &&
addr[26] === 0x64 &&
addr[27] === 0x65 &&
addr[28] === 0x4b &&
addr[29] === 0x41 &&
addr[30] === 0x48 &&
addr[31] === 0x69 &&
addr[32] === 0x32 &&
addr[33] === 0x50
)
raw = [
0xd8, 0x74, 0xcf, 0x61, 0x0d, 0x97, 0xe4, 0xab, 0x76, 0xa0, 0x70, 0x60,
0xb7, 0xc5, 0x9c, 0x9a, 0x88, 0x86, 0x62, 0xaa,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x4c &&
addr[2] === 0x6a &&
addr[3] === 0x57 &&
addr[4] === 0x74 &&
addr[5] === 0x59 &&
addr[6] === 0x52 &&
addr[7] === 0x61 &&
addr[8] === 0x6e &&
addr[9] === 0x61 &&
addr[10] === 0x6b &&
addr[11] === 0x33 &&
addr[12] === 0x74 &&
addr[13] === 0x52 &&
addr[14] === 0x43 &&
addr[15] === 0x5a &&
addr[16] === 0x42 &&
addr[17] === 0x69 &&
addr[18] === 0x61 &&
addr[19] === 0x38 &&
addr[20] === 0x64 &&
addr[21] === 0x33 &&
addr[22] === 0x70 &&
addr[23] === 0x7a &&
addr[24] === 0x78 &&
addr[25] === 0x6b &&
addr[26] === 0x6e &&
addr[27] === 0x63 &&
addr[28] === 0x73 &&
addr[29] === 0x7a &&
addr[30] === 0x6f &&
addr[31] === 0x33 &&
addr[32] === 0x33 &&
addr[33] === 0x38
)
raw = [
0x8e, 0xad, 0xb4, 0xbb, 0x71, 0x2a, 0x29, 0x1b, 0x53, 0x43, 0xe0, 0x03,
0x1f, 0x97, 0x6b, 0x0d, 0xa9, 0xed, 0x39, 0xc2,
]
addr = util_raddr(raw)
ASSERT(34 === addr.length)
addr = toHex(addr)
ASSERT(
addr[0] === 0x72 &&
addr[1] === 0x4e &&
addr[2] === 0x72 &&
addr[3] === 0x52 &&
addr[4] === 0x73 &&
addr[5] === 0x59 &&
addr[6] === 0x57 &&
addr[7] === 0x69 &&
addr[8] === 0x4a &&
addr[9] === 0x53 &&
addr[10] === 0x64 &&
addr[11] === 0x39 &&
addr[12] === 0x47 &&
addr[13] === 0x4a &&
addr[14] === 0x50 &&
addr[15] === 0x50 &&
addr[16] === 0x36 &&
addr[17] === 0x51 &&
addr[18] === 0x71 &&
addr[19] === 0x33 &&
addr[20] === 0x4a &&
addr[21] === 0x61 &&
addr[22] === 0x44 &&
addr[23] === 0x43 &&
addr[24] === 0x37 &&
addr[25] === 0x53 &&
addr[26] === 0x48 &&
addr[27] === 0x61 &&
addr[28] === 0x57 &&
addr[29] === 0x66 &&
addr[30] === 0x68 &&
addr[31] === 0x32 &&
addr[32] === 0x33 &&
addr[33] === 0x4b
)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set util_raddr"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test util_raddr"), fee(XRP(1)));
}
void
test_util_sha512h(FeatureBitset features)
{
testcase("Test util_sha512h");
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);
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
trace('error', 0, false)
rollback(x.toString(), code)
}
}
const Hook = (arg) => {
let raw = [
0x72, 0x4e, 0x36, 0x53, 0x59, 0x77, 0x72, 0x32, 0x64, 0x54, 0x56, 0x43,
0x7a, 0x45, 0x71, 0x39, 0x57, 0x43, 0x77, 0x4a,
]
let hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x42 &&
hash[1] == 0x5c &&
hash[2] == 0x4c &&
hash[3] == 0x01 &&
hash[4] == 0x84 &&
hash[5] == 0xa5 &&
hash[6] == 0x76 &&
hash[7] == 0x79 &&
hash[8] == 0xdc &&
hash[9] == 0x6d &&
hash[10] == 0xff &&
hash[11] == 0x40 &&
hash[12] == 0x8c &&
hash[13] == 0x29 &&
hash[14] == 0x06 &&
hash[15] == 0x6b &&
hash[16] == 0x0f &&
hash[17] == 0xb9 &&
hash[18] == 0xea &&
hash[19] == 0x34
)
raw = [
0x72, 0x4b, 0x4b, 0x75, 0x52, 0x36, 0x36, 0x46, 0x62, 0x38, 0x33, 0x76,
0x35, 0x71, 0x79, 0x41, 0x34, 0x48, 0x67, 0x6a,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x36 &&
hash[1] == 0x2c &&
hash[2] == 0x32 &&
hash[3] == 0x1d &&
hash[4] == 0x8d &&
hash[5] == 0xdd &&
hash[6] == 0xaf &&
hash[7] == 0x2d &&
hash[8] == 0x3c &&
hash[9] == 0xe6 &&
hash[10] == 0x94 &&
hash[11] == 0x12 &&
hash[12] == 0x20 &&
hash[13] == 0xda &&
hash[14] == 0x62 &&
hash[15] == 0xa6 &&
hash[16] == 0x98 &&
hash[17] == 0x41 &&
hash[18] == 0x04 &&
hash[19] == 0x5e
)
raw = [
0x72, 0x42, 0x54, 0x33, 0x58, 0x57, 0x43, 0x76, 0x61, 0x38, 0x48, 0x55,
0x4e, 0x4e, 0x5a, 0x46, 0x6a, 0x5a, 0x43, 0x55,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0xcf &&
hash[1] == 0xfd &&
hash[2] == 0x6f &&
hash[3] == 0x01 &&
hash[4] == 0x95 &&
hash[5] == 0x76 &&
hash[6] == 0x7d &&
hash[7] == 0xfb &&
hash[8] == 0xca &&
hash[9] == 0x41 &&
hash[10] == 0xfd &&
hash[11] == 0x24 &&
hash[12] == 0x23 &&
hash[13] == 0xd6 &&
hash[14] == 0x82 &&
hash[15] == 0x20 &&
hash[16] == 0x76 &&
hash[17] == 0xdd &&
hash[18] == 0xc9 &&
hash[19] == 0xec
)
raw = [
0x72, 0x4c, 0x52, 0x4c, 0x41, 0x6e, 0x61, 0x62, 0x56, 0x6f, 0x46, 0x62,
0x37, 0x47, 0x68, 0x79, 0x58, 0x75, 0x42, 0x53,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x02 &&
hash[1] == 0xeb &&
hash[2] == 0x2f &&
hash[3] == 0x30 &&
hash[4] == 0xfc &&
hash[5] == 0x73 &&
hash[6] == 0x34 &&
hash[7] == 0xe7 &&
hash[8] == 0x89 &&
hash[9] == 0xa2 &&
hash[10] == 0x58 &&
hash[11] == 0xd6 &&
hash[12] == 0xb0 &&
hash[13] == 0x55 &&
hash[14] == 0x32 &&
hash[15] == 0x96 &&
hash[16] == 0xb5 &&
hash[17] == 0x2e &&
hash[18] == 0x97 &&
hash[19] == 0x81
)
raw = [
0x72, 0x4c, 0x37, 0x33, 0x39, 0x47, 0x4b, 0x35, 0x75, 0x36, 0x79, 0x78,
0x76, 0x43, 0x73, 0x6f, 0x68, 0x43, 0x32, 0x43,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x9f &&
hash[1] == 0xd4 &&
hash[2] == 0x7c &&
hash[3] == 0x25 &&
hash[4] == 0xde &&
hash[5] == 0x23 &&
hash[6] == 0x97 &&
hash[7] == 0x57 &&
hash[8] == 0xed &&
hash[9] == 0x25 &&
hash[10] == 0xd0 &&
hash[11] == 0x98 &&
hash[12] == 0xf7 &&
hash[13] == 0x83 &&
hash[14] == 0x70 &&
hash[15] == 0xf6 &&
hash[16] == 0x5f &&
hash[17] == 0x3d &&
hash[18] == 0xb5 &&
hash[19] == 0x43
)
raw = [
0x72, 0x4d, 0x4d, 0x45, 0x57, 0x74, 0x75, 0x4b, 0x43, 0x77, 0x54, 0x43,
0x36, 0x31, 0x78, 0x41, 0x78, 0x35, 0x55, 0x46,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x77 &&
hash[1] == 0x59 &&
hash[2] == 0x43 &&
hash[3] == 0x6b &&
hash[4] == 0x4d &&
hash[5] == 0x11 &&
hash[6] == 0x6b &&
hash[7] == 0xe5 &&
hash[8] == 0xf8 &&
hash[9] == 0x90 &&
hash[10] == 0x07 &&
hash[11] == 0x00 &&
hash[12] == 0xb3 &&
hash[13] == 0xb2 &&
hash[14] == 0x6b &&
hash[15] == 0x8a &&
hash[16] == 0xc8 &&
hash[17] == 0xf2 &&
hash[18] == 0x82 &&
hash[19] == 0xb7
)
raw = [
0x72, 0x66, 0x48, 0x6a, 0x66, 0x31, 0x6b, 0x70, 0x4b, 0x6a, 0x39, 0x66,
0x6a, 0x39, 0x35, 0x58, 0x6a, 0x59, 0x69, 0x51,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0xbd &&
hash[1] == 0x1b &&
hash[2] == 0xdd &&
hash[3] == 0x9d &&
hash[4] == 0x10 &&
hash[5] == 0xde &&
hash[6] == 0x24 &&
hash[7] == 0xa1 &&
hash[8] == 0xb2 &&
hash[9] == 0x6c &&
hash[10] == 0x24 &&
hash[11] == 0xbc &&
hash[12] == 0xf9 &&
hash[13] == 0x97 &&
hash[14] == 0x50 &&
hash[15] == 0xde &&
hash[16] == 0x93 &&
hash[17] == 0x39 &&
hash[18] == 0x58 &&
hash[19] == 0x21
)
raw = [
0x72, 0x66, 0x6e, 0x75, 0x57, 0x38, 0x77, 0x6f, 0x4b, 0x62, 0x6e, 0x57,
0x4b, 0x6b, 0x6b, 0x75, 0x39, 0x6a, 0x79, 0x64,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x3b &&
hash[1] == 0x89 &&
hash[2] == 0xed &&
hash[3] == 0x68 &&
hash[4] == 0x0d &&
hash[5] == 0x13 &&
hash[6] == 0x3b &&
hash[7] == 0x1d &&
hash[8] == 0x43 &&
hash[9] == 0xfe &&
hash[10] == 0xae &&
hash[11] == 0x3e &&
hash[12] == 0xc3 &&
hash[13] == 0x90 &&
hash[14] == 0xe8 &&
hash[15] == 0x0e &&
hash[16] == 0x17 &&
hash[17] == 0x14 &&
hash[18] == 0x23 &&
hash[19] == 0x71
)
raw = [
0x72, 0x70, 0x79, 0x64, 0x52, 0x39, 0x55, 0x32, 0x67, 0x66, 0x75, 0x6b,
0x34, 0x5a, 0x72, 0x53, 0x66, 0x48, 0x61, 0x71,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x2b &&
hash[1] == 0x01 &&
hash[2] == 0x00 &&
hash[3] == 0x05 &&
hash[4] == 0xf1 &&
hash[5] == 0x60 &&
hash[6] == 0x71 &&
hash[7] == 0x62 &&
hash[8] == 0x7c &&
hash[9] == 0x4a &&
hash[10] == 0xcc &&
hash[11] == 0x03 &&
hash[12] == 0x2a &&
hash[13] == 0x89 &&
hash[14] == 0x40 &&
hash[15] == 0x5a &&
hash[16] == 0x03 &&
hash[17] == 0xdc &&
hash[18] == 0x83 &&
hash[19] == 0xc8
)
raw = [
0x72, 0x4c, 0x4c, 0x45, 0x36, 0x34, 0x74, 0x44, 0x4c, 0x78, 0x59, 0x37,
0x47, 0x6f, 0x41, 0x41, 0x57, 0x66, 0x73, 0x36,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0xdf &&
hash[1] == 0xe3 &&
hash[2] == 0x14 &&
hash[3] == 0xf0 &&
hash[4] == 0x5f &&
hash[5] == 0x95 &&
hash[6] == 0x8c &&
hash[7] == 0x57 &&
hash[8] == 0x2f &&
hash[9] == 0x9d &&
hash[10] == 0x45 &&
hash[11] == 0xdc &&
hash[12] == 0x12 &&
hash[13] == 0x77 &&
hash[14] == 0x39 &&
hash[15] == 0xac &&
hash[16] == 0xea &&
hash[17] == 0x4a &&
hash[18] == 0xb0 &&
hash[19] == 0x8f
)
raw = [
0x72, 0x50, 0x64, 0x50, 0x58, 0x77, 0x76, 0x75, 0x39, 0x4e, 0x4c, 0x59,
0x46, 0x50, 0x34, 0x69, 0x56, 0x64, 0x56, 0x70,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0xd1 &&
hash[1] == 0x9f &&
hash[2] == 0x25 &&
hash[3] == 0x93 &&
hash[4] == 0xa3 &&
hash[5] == 0xca &&
hash[6] == 0xea &&
hash[7] == 0x10 &&
hash[8] == 0x06 &&
hash[9] == 0x78 &&
hash[10] == 0xfc &&
hash[11] == 0x58 &&
hash[12] == 0xa4 &&
hash[13] == 0x99 &&
hash[14] == 0x3c &&
hash[15] == 0x6e &&
hash[16] == 0xc4 &&
hash[17] == 0x2d &&
hash[18] == 0x6d &&
hash[19] == 0x53
)
raw = [
0x72, 0x44, 0x65, 0x44, 0x32, 0x5a, 0x71, 0x53, 0x48, 0x35, 0x44, 0x70,
0x51, 0x4d, 0x78, 0x76, 0x36, 0x36, 0x52, 0x6b,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x4d &&
hash[1] == 0x5b &&
hash[2] == 0xdd &&
hash[3] == 0x31 &&
hash[4] == 0xde &&
hash[5] == 0xb9 &&
hash[6] == 0xf5 &&
hash[7] == 0xb8 &&
hash[8] == 0xbd &&
hash[9] == 0x17 &&
hash[10] == 0xe1 &&
hash[11] == 0x51 &&
hash[12] == 0xaa &&
hash[13] == 0x51 &&
hash[14] == 0x9c &&
hash[15] == 0x5b &&
hash[16] == 0xe0 &&
hash[17] == 0x15 &&
hash[18] == 0x61 &&
hash[19] == 0x2c
)
raw = [
0x72, 0x55, 0x34, 0x78, 0x54, 0x52, 0x75, 0x6f, 0x32, 0x34, 0x62, 0x52,
0x6f, 0x65, 0x41, 0x48, 0x33, 0x53, 0x55, 0x66,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0xbd &&
hash[1] == 0xa1 &&
hash[2] == 0x62 &&
hash[3] == 0x1e &&
hash[4] == 0x84 &&
hash[5] == 0x12 &&
hash[6] == 0xb3 &&
hash[7] == 0xcc &&
hash[8] == 0x58 &&
hash[9] == 0x19 &&
hash[10] == 0x9a &&
hash[11] == 0x22 &&
hash[12] == 0xcf &&
hash[13] == 0x6a &&
hash[14] == 0x0a &&
hash[15] == 0x43 &&
hash[16] == 0xde &&
hash[17] == 0xb5 &&
hash[18] == 0xba &&
hash[19] == 0x50
)
raw = [
0x72, 0x6e, 0x6d, 0x6f, 0x6a, 0x57, 0x46, 0x6f, 0x41, 0x58, 0x72, 0x76,
0x71, 0x75, 0x62, 0x6f, 0x45, 0x77, 0x4e, 0x4e,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x5f &&
hash[1] == 0x26 &&
hash[2] == 0xf9 &&
hash[3] == 0x0a &&
hash[4] == 0xc7 &&
hash[5] == 0xd5 &&
hash[6] == 0x40 &&
hash[7] == 0x2d &&
hash[8] == 0x1f &&
hash[9] == 0x9e &&
hash[10] == 0x46 &&
hash[11] == 0xaa &&
hash[12] == 0x6d &&
hash[13] == 0x9c &&
hash[14] == 0x64 &&
hash[15] == 0x88 &&
hash[16] == 0x87 &&
hash[17] == 0xf3 &&
hash[18] == 0x29 &&
hash[19] == 0x72
)
raw = [
0x72, 0x61, 0x33, 0x57, 0x65, 0x64, 0x69, 0x71, 0x58, 0x37, 0x34, 0x79,
0x42, 0x42, 0x68, 0x48, 0x4c, 0x44, 0x51, 0x4d,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x25 &&
hash[1] == 0x70 &&
hash[2] == 0x5f &&
hash[3] == 0x6d &&
hash[4] == 0xa8 &&
hash[5] == 0x60 &&
hash[6] == 0x54 &&
hash[7] == 0xba &&
hash[8] == 0xd8 &&
hash[9] == 0x33 &&
hash[10] == 0x41 &&
hash[11] == 0x48 &&
hash[12] == 0x95 &&
hash[13] == 0x52 &&
hash[14] == 0xa6 &&
hash[15] == 0x22 &&
hash[16] == 0x9d &&
hash[17] == 0x82 &&
hash[18] == 0xa0 &&
hash[19] == 0x87
)
raw = [
0x72, 0x45, 0x47, 0x57, 0x33, 0x6b, 0x6f, 0x34, 0x41, 0x31, 0x69, 0x50,
0x43, 0x5a, 0x54, 0x78, 0x6d, 0x77, 0x6a, 0x44,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0xd4 &&
hash[1] == 0xda &&
hash[2] == 0xe0 &&
hash[3] == 0xc7 &&
hash[4] == 0x40 &&
hash[5] == 0xc4 &&
hash[6] == 0x28 &&
hash[7] == 0x59 &&
hash[8] == 0xa9 &&
hash[9] == 0x6d &&
hash[10] == 0x91 &&
hash[11] == 0xdc &&
hash[12] == 0x34 &&
hash[13] == 0x0d &&
hash[14] == 0xb9 &&
hash[15] == 0xe6 &&
hash[16] == 0xe9 &&
hash[17] == 0x9d &&
hash[18] == 0x04 &&
hash[19] == 0x0b
)
raw = [
0x72, 0x68, 0x52, 0x46, 0x71, 0x54, 0x35, 0x45, 0x39, 0x7a, 0x63, 0x69,
0x70, 0x68, 0x4c, 0x54, 0x39, 0x78, 0x6a, 0x52,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x61 &&
hash[1] == 0x5b &&
hash[2] == 0xfe &&
hash[3] == 0x17 &&
hash[4] == 0x6e &&
hash[5] == 0x81 &&
hash[6] == 0x42 &&
hash[7] == 0xff &&
hash[8] == 0xee &&
hash[9] == 0xd7 &&
hash[10] == 0x1a &&
hash[11] == 0x6d &&
hash[12] == 0x14 &&
hash[13] == 0x5d &&
hash[14] == 0x64 &&
hash[15] == 0xa8 &&
hash[16] == 0x20 &&
hash[17] == 0x1a &&
hash[18] == 0x33 &&
hash[19] == 0xc3
)
raw = [
0x72, 0x70, 0x61, 0x4a, 0x69, 0x34, 0x4c, 0x62, 0x55, 0x36, 0x55, 0x63,
0x4a, 0x45, 0x78, 0x62, 0x38, 0x39, 0x35, 0x5a,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x01 &&
hash[1] == 0x61 &&
hash[2] == 0xa4 &&
hash[3] == 0x8e &&
hash[4] == 0x6d &&
hash[5] == 0x20 &&
hash[6] == 0xba &&
hash[7] == 0x20 &&
hash[8] == 0x72 &&
hash[9] == 0x72 &&
hash[10] == 0x8f &&
hash[11] == 0x4f &&
hash[12] == 0x3f &&
hash[13] == 0xe1 &&
hash[14] == 0xe1 &&
hash[15] == 0xe7 &&
hash[16] == 0xeb &&
hash[17] == 0x15 &&
hash[18] == 0xa8 &&
hash[19] == 0x4c
)
raw = [
0x72, 0x34, 0x59, 0x78, 0x47, 0x46, 0x71, 0x51, 0x64, 0x47, 0x70, 0x71,
0x6e, 0x4c, 0x59, 0x65, 0x4d, 0x38, 0x56, 0x52,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0x42 &&
hash[1] == 0xc5 &&
hash[2] == 0x2f &&
hash[3] == 0x3b &&
hash[4] == 0xb7 &&
hash[5] == 0xd4 &&
hash[6] == 0x54 &&
hash[7] == 0xb4 &&
hash[8] == 0x97 &&
hash[9] == 0xb4 &&
hash[10] == 0xfc &&
hash[11] == 0xb0 &&
hash[12] == 0x46 &&
hash[13] == 0xba &&
hash[14] == 0xb6 &&
hash[15] == 0xad &&
hash[16] == 0x93 &&
hash[17] == 0x8d &&
hash[18] == 0xeb &&
hash[19] == 0x7d
)
raw = [
0x72, 0x33, 0x76, 0x71, 0x75, 0x79, 0x72, 0x45, 0x39, 0x55, 0x53, 0x70,
0x68, 0x62, 0x43, 0x55, 0x6d, 0x65, 0x4b, 0x55,
]
hash = util_sha512h(raw)
ASSERT(32 == hash.length)
ASSERT(
hash[0] == 0xd5 &&
hash[1] == 0x6b &&
hash[2] == 0x6b &&
hash[3] == 0x45 &&
hash[4] == 0x30 &&
hash[5] == 0xf0 &&
hash[6] == 0x34 &&
hash[7] == 0x76 &&
hash[8] == 0x31 &&
hash[9] == 0x56 &&
hash[10] == 0x8c &&
hash[11] == 0x38 &&
hash[12] == 0x0c &&
hash[13] == 0x1a &&
hash[14] == 0xaf &&
hash[15] == 0xab &&
hash[16] == 0x42 &&
hash[17] == 0x16 &&
hash[18] == 0x21 &&
hash[19] == 0x42
)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set util_sha512h"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test util_sha512h"), fee(XRP(1)));
}
void
test_util_verify(FeatureBitset features)
{
testcase("Test util_verify");
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);
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x, code) => {
if (!x) {
trace('error', 0, false)
rollback(x.toString(), code)
}
}
// secp256k1
const pubkey_sec = [
0x02, 0xc7, 0x38, 0x7f, 0xfc, 0x25, 0xc1, 0x56, 0xca, 0x7f, 0x8a, 0x6d, 0x76,
0x0c, 0x8d, 0x01, 0xef, 0x64, 0x2c, 0xee, 0x9c, 0xe4, 0x68, 0x0c, 0x33, 0xff,
0xb3, 0xff, 0x39, 0xaf, 0xec, 0xfe, 0x70,
]
const sig_sec = [
0x30, 0x45, 0x02, 0x21, 0x00, 0x95, 0x6e, 0x7d, 0x1f, 0x01, 0x16, 0xf1, 0x65,
0x00, 0xd2, 0xcc, 0xd8, 0x8d, 0x2a, 0x2f, 0xef, 0xf6, 0x52, 0x16, 0x85, 0x42,
0xf4, 0x4e, 0x43, 0xdb, 0xe6, 0xf4, 0x53, 0xe8, 0x03, 0xb8, 0x4f, 0x02, 0x20,
0x0a, 0xb6, 0xc3, 0x4b, 0x5f, 0x0c, 0xc6, 0x6b, 0x4f, 0x1f, 0x83, 0xe9, 0x89,
0x74, 0xb8, 0x80, 0xa2, 0x2f, 0xae, 0x52, 0x91, 0x6b, 0xa2, 0xce, 0x96, 0xa3,
0x61, 0x05, 0x3f, 0xff, 0x81, 0xe9,
]
// ed25519
const pubkey_ed = [
0xed, 0xd9, 0xb3, 0x59, 0x98, 0x02, 0xb2, 0x14, 0xa9, 0x9d, 0x75, 0x77, 0x12,
0xd6, 0xab, 0xdf, 0x72, 0xf8, 0x3c, 0x63, 0xbb, 0xd5, 0x38, 0x61, 0x41, 0x17,
0x90, 0xb1, 0x3d, 0x04, 0xb2, 0xc5, 0xc9,
]
const sig_ed = [
0x56, 0x68, 0x80, 0x76, 0x70, 0xfe, 0xce, 0x60, 0x34, 0xaf, 0xd6, 0xcd, 0x1b,
0xb4, 0xc6, 0x60, 0xae, 0x08, 0x39, 0x6d, 0x6d, 0x8b, 0x7d, 0x22, 0x71, 0x3b,
0xda, 0x26, 0x43, 0xc1, 0xe1, 0x91, 0xc4, 0xe4, 0x4d, 0x8e, 0x02, 0xe8, 0x57,
0x8b, 0x20, 0x45, 0xda, 0xd4, 0x8f, 0x97, 0xfc, 0x16, 0xf8, 0x92, 0x5b, 0x6b,
0x51, 0xfb, 0x3b, 0xe5, 0x0f, 0xb0, 0x4b, 0x3a, 0x20, 0x4c, 0x53, 0x04,
]
const msg = [0xde, 0xad, 0xbe, 0xef]
const bmsg = [0xad, 0xbe, 0xef]
const Hook = (arg) => {
// test secp256k1 verification
ASSERT(util_verify(msg, sig_sec, pubkey_sec) === 1)
ASSERT(util_verify(bmsg, sig_sec, pubkey_sec) === 0)
// test ed25519 verification
ASSERT(util_verify(msg, sig_ed, pubkey_ed) === 1)
ASSERT(util_verify(bmsg, sig_ed, pubkey_ed) === 0)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set util_verify"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test util_verify"), fee(XRP(1)));
}
void
test_sto_emplace(FeatureBitset features)
{
testcase("Test sto_emplace");
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);
TestHook hook = jswasm[R"[test.hook](
var ASSERT = (x) => {
if (!x) {
rollback(x.toString(), 0)
}
}
var sto = [
17, 0, 97, 34, 0, 0, 0, 0, 36, 4, 31, 148, 217, 37, 4, 94, 132, 183, 45, 0, 0,
0, 0, 85, 19, 64, 179, 37, 134, 49, 150, 181, 111, 65, 245, 137, 235, 125, 47,
217, 76, 13, 125, 184, 14, 75, 44, 103, 167, 120, 42, 214, 194, 176, 119, 80,
98, 64, 0, 0, 0, 0, 164, 121, 148, 129, 20, 55, 223, 68, 7, 231, 170, 7, 241,
213, 201, 145, 242, 211, 111, 158, 184, 199, 52, 175, 108,
]
var ins = [
86, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
]
var ans = [
17, 0, 97, 34, 0, 0, 0, 0, 36, 4, 31, 148, 217, 37, 4, 94, 132, 183, 45, 0, 0,
0, 0, 85, 19, 64, 179, 37, 134, 49, 150, 181, 111, 65, 245, 137, 235, 125, 47,
217, 76, 13, 125, 184, 14, 75, 44, 103, 167, 120, 42, 214, 194, 176, 119, 80,
86, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 98, 64, 0, 0, 0, 0,
164, 121, 148, 129, 20, 55, 223, 68, 7, 231, 170, 7, 241, 213, 201, 145, 242,
211, 111, 158, 184, 199, 52, 175, 108,
]
var ans2 = [
17, 0, 97, 34, 0, 0, 0, 0, 36, 4, 31, 148, 217, 37, 4, 94, 132, 183, 45, 0, 0,
0, 0, 84, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 85, 19, 64, 179,
37, 134, 49, 150, 181, 111, 65, 245, 137, 235, 125, 47, 217, 76, 13, 125, 184,
14, 75, 44, 103, 167, 120, 42, 214, 194, 176, 119, 80, 98, 64, 0, 0, 0, 0,
164, 121, 148, 129, 20, 55, 223, 68, 7, 231, 170, 7, 241, 213, 201, 145, 242,
211, 111, 158, 184, 199, 52, 175, 108,
]
var INVALID_ARGUMENT = -7
var TOO_SMALL = -4
var PARSE_ERROR = -18
var Hook = (arg) => {
ASSERT(sto_emplace(void 0, 1, 1) == INVALID_ARGUMENT)
ASSERT(sto_emplace(1, 1, void 0) == INVALID_ARGUMENT)
ASSERT(sto_emplace(1, 1, -1) == INVALID_ARGUMENT)
ASSERT(sto_emplace(1, 1, 4294967215) == INVALID_ARGUMENT)
ASSERT(sto_emplace(1, void 0, 1) == INVALID_ARGUMENT)
ASSERT(sto_emplace(1, false, 1) == INVALID_ARGUMENT)
ASSERT(sto_emplace([0, 1], [0, 1], 327686) == PARSE_ERROR)
{
ASSERT(sto_emplace([0], [0, 1], 327686) == TOO_SMALL)
ASSERT(sto_emplace([0, 1], [0], 327686) == TOO_SMALL)
ASSERT(
sto_emplace([0, 1], new Array(4097).fill(0), 327686) == INVALID_ARGUMENT
)
ASSERT(
sto_emplace(new Array(16385).fill(0), [0, 1], 327686) == INVALID_ARGUMENT
)
}
let buf = []
{
buf = sto_emplace(sto, ins, 327686)
ASSERT(buf.length === sto.length + ins.length)
for (let i = 0; i < ans.length; ++i) ASSERT(ans[i] == buf[i])
ins[0] = 84
buf = sto_emplace(sto, ins, 327684)
ASSERT(buf.length == sto.length + ins.length)
for (let i = 0; i < ans2.length; ++i) ASSERT(ans2[i] == buf[i])
}
{
const sto2 = [34, 0, 0, 0, 0]
const ins2 = [17, 17, 17]
const buf2 = sto_emplace(sto2, ins2, 65537)
ASSERT(buf2.length == sto2.length + ins2.length)
const ans3 = [17, 17, 17, 34, 0, 0, 0, 0]
for (let i = 0; i < ans3.length; ++i) ASSERT(ans3[i] == buf2[i])
}
{
const sto2 = [34, 0, 0, 0, 0]
const ins2 = [49, 17, 17, 17, 17, 18, 34, 34, 34]
buf = sto_emplace(sto2, ins2, 196609)
ASSERT(buf.length == sto2.length + ins2.length)
const ans3 = [34, 0, 0, 0, 0, 49, 17, 17, 17, 17, 18, 34, 34, 34]
for (let i = 0; i < ans3.length; ++i) ASSERT(ans3[i] == buf[i])
}
{
const rep = [34, 16, 32, 48, 64]
buf = sto_emplace(sto, rep, 131074)
ASSERT(buf.length == sto.length)
ASSERT(buf[0] == sto[0] && buf[1] == sto[1] && buf[2] == sto[2])
for (let i = 3; i < rep.length + 3; ++i) ASSERT(buf[i] == rep[i - 3])
for (let i = rep.length + 3; i < sto.length; ++i) ASSERT(sto[i] == buf[i])
}
return accept(0, 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set sto_emplace"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_emplace"), fee(XRP(1)));
}
void
test_sto_erase(FeatureBitset features)
{
testcase("Test sto_erase");
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);
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x) => {
if (!x) rollback(x.toString(), 0)
}
const DOESNT_EXIST = -5
const sto = [
0x11, 0x00, 0x61, 0x22, 0x00, 0x00, 0x00, 0x00, 0x24, 0x04, 0x1f, 0x94, 0xd9,
0x25, 0x04, 0x5e, 0x84, 0xb7, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x55, 0x13, 0x40,
0xb3, 0x25, 0x86, 0x31, 0x96, 0xb5, 0x6f, 0x41, 0xf5, 0x89, 0xeb, 0x7d, 0x2f,
0xd9, 0x4c, 0x0d, 0x7d, 0xb8, 0x0e, 0x4b, 0x2c, 0x67, 0xa7, 0x78, 0x2a, 0xd6,
0xc2, 0xb0, 0x77, 0x50, 0x62, 0x40, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x79, 0x94,
0x81, 0x14, 0x37, 0xdf, 0x44, 0x07, 0xe7, 0xaa, 0x07, 0xf1, 0xd5, 0xc9, 0x91,
0xf2, 0xd3, 0x6f, 0x9e, 0xb8, 0xc7, 0x34, 0xaf, 0x6c,
]
// test_sto_erase
const Hook = (arg) => {
// erase field 22
{
const buf = sto_erase(sto, 0x20002)
ASSERT(buf.length === sto.length - 5)
ASSERT(buf[0] === sto[0] && buf[1] === sto[1] && buf[2] === sto[2])
for (let i = 3; i < sto.length - 5; i++) {
ASSERT(sto[i + 5] === buf[i])
}
}
// test front erasure
{
const buf = sto_erase(sto, 0x10001)
ASSERT(buf.length === sto.length - 3)
for (let i = 3; i < sto.length - 3; i++) {
ASSERT(sto[i] === buf[i - 3])
}
}
// test back erasure
{
const buf = sto_erase(sto, 0x80001)
ASSERT(buf.length === sto.length - 22)
for (let i = 0; i < sto.length - 22; i++) {
ASSERT(sto[i] === buf[i])
}
}
// test not found
{
const buf = sto_erase(sto, 0x80002)
ASSERT(buf === DOESNT_EXIST)
}
// test total erasure
{
const rep = [0x22, 0x10, 0x20, 0x30, 0x40]
const buf = sto_erase(rep, 0x20002)
ASSERT(buf.length === 0)
}
accept('success', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set sto_erase"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_erase"), fee(XRP(1)));
}
void
test_sto_subarray(FeatureBitset features)
{
testcase("Test sto_subarray");
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);
TestHook hook = jswasm[R"[test.hook](
const DOESNT_EXIST = -5
const INVALID_ARGUMENT = -7
const ASSERT = (x) => {
if (!x) rollback(x.toString(), 0)
}
const sto = [
0xf4, 0xeb, 0x13, 0x00, 0x01, 0x81, 0x14, 0x20, 0x42, 0x88, 0xd2, 0xe4, 0x7f,
0x8e, 0xf6, 0xc9, 0x9b, 0xcc, 0x45, 0x79, 0x66, 0x32, 0x0d, 0x12, 0x40, 0x97,
0x11, 0xe1, 0xeb, 0x13, 0x00, 0x01, 0x81, 0x14, 0x3e, 0x9d, 0x4a, 0x2b, 0x8a,
0xa0, 0x78, 0x0f, 0x68, 0x2d, 0x13, 0x6f, 0x7a, 0x56, 0xd6, 0x72, 0x4e, 0xf5,
0x37, 0x54, 0xe1, 0xf1,
]
const Hook = (reserved) => {
// Test invalid arg
ASSERT(sto_subarray(undefined, 0) === INVALID_ARGUMENT)
ASSERT(sto_subarray(sto, undefined) === INVALID_ARGUMENT)
ASSERT(sto_subarray(sto, -1) === INVALID_ARGUMENT)
ASSERT(sto_subarray(sto, 0xffffffff + 1) === INVALID_ARGUMENT)
// Test index 0, should be position 1 length 27
// ASSERT(sto_subarray(sto, 0) === (1 << 32) + 27)
ASSERT(sto_subarray(sto, 0) === 2 ** 32 + 27)
// Test index 1, should be position 28 length 27
// ASSERT(sto_subarray(sto, 1) === (28 << 32) + 27)
ASSERT(sto_subarray(sto, 1) === 28 * 2 ** 32 + 27)
// Test index 2, doesn't exist
ASSERT(sto_subarray(sto, 2) === DOESNT_EXIST)
accept('success', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set sto_subarray"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_subarray"), fee(XRP(1)));
}
void
test_sto_subfield(FeatureBitset features)
{
testcase("Test sto_subfield");
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);
TestHook hook = jswasm[R"[test.hook](
const DOESNT_EXIST = -5
const INVALID_ARGUMENT = -7
const ASSERT = (x) => {
if (!x) rollback(x.toString(), 0)
}
const sto = [
0x11, 0x00, 0x53, 0x22, 0x00, 0x00, 0x00, 0x00, 0x25, 0x01, 0x52, 0x70, 0x1a,
0x20, 0x23, 0x00, 0x00, 0x00, 0x02, 0x20, 0x26, 0x00, 0x00, 0x00, 0x00, 0x34,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x09, 0xa9, 0xc8, 0x6b,
0xf2, 0x06, 0x95, 0x73, 0x5a, 0xb0, 0x36, 0x20, 0xeb, 0x1c, 0x32, 0x60, 0x66,
0x35, 0xac, 0x3d, 0xa0, 0xb7, 0x02, 0x82, 0xf3, 0x7c, 0x67, 0x4f, 0xc8, 0x89,
0xef, 0xe7,
]
const Hook = (reserved) => {
// Test invalid arg
ASSERT(sto_subfield(undefined, 0x10001) === INVALID_ARGUMENT)
ASSERT(sto_subfield(sto, undefined) === INVALID_ARGUMENT)
ASSERT(sto_subfield(sto, -1) === INVALID_ARGUMENT)
ASSERT(sto_subfield(sto, 0xffffffff + 1) === INVALID_ARGUMENT)
// ASSERT(sto_subfield(sto, 0x10001) === (1 << 32) + 2)
ASSERT(sto_subfield(sto, 0x10001) === 2 ** 32 + 2)
// Test subfield 0x11, should be position 0 length 3, payload pos 1, len 2
// ASSERT(sto_subfield(sto, 0x10001) === (1 << 32) + 2)
ASSERT(sto_subfield(sto, 0x10001) === 2 ** 32 + 2)
// Test subfield 0x22, should be position 3 length 5, payload pos 4, len 4
// ASSERT(sto_subfield(sto, 0x20002) === (4 << 32) + 4)
ASSERT(sto_subfield(sto, 0x20002) === 4 * 2 ** 32 + 4)
// Test subfield 0x34, should be at position 25, length = 9, payload pos 26, len 8
// ASSERT(sto_subfield(sto, 0x30004) === (26 << 32) + 8)
ASSERT(sto_subfield(sto, 0x30004) === 26 * 2 ** 32 + 8)
// Test final subfield, position 34, length 33, payload pos 35, len 32
// ASSERT(sto_subfield(sto, 0x50005) === (35 << 32) + 32)
ASSERT(sto_subfield(sto, 0x50005) === 35 * 2 ** 32 + 32)
// Test not found
ASSERT(sto_subfield(sto, 0x90009) === DOESNT_EXIST)
accept('success', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set sto_subfield"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_subfield"), fee(XRP(1)));
}
void
test_sto_validate(FeatureBitset features)
{
testcase("Test sto_validate");
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);
TestHook hook = jswasm[R"[test.hook](
const INVALID_ARGUMENT = -7
const ASSERT = (x) => {
if (!x) rollback(x.toString(), 0)
}
const sto = [
0x11, 0x00, 0x61, 0x22, 0x00, 0x00, 0x00, 0x00, 0x24, 0x04, 0x1f, 0x94, 0xd9,
0x25, 0x04, 0x5e, 0x84, 0xb7, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x55, 0x13, 0x40,
0xb3, 0x25, 0x86, 0x31, 0x96, 0xb5, 0x6f, 0x41, 0xf5, 0x89, 0xeb, 0x7d, 0x2f,
0xd9, 0x4c, 0x0d, 0x7d, 0xb8, 0x0e, 0x4b, 0x2c, 0x67, 0xa7, 0x78, 0x2a, 0xd6,
0xc2, 0xb0, 0x77, 0x50, 0x62, 0x40, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x79, 0x94,
0x81, 0x14, 0x37, 0xdf, 0x44, 0x07, 0xe7, 0xaa, 0x07, 0xf1, 0xd5, 0xc9, 0x91,
0xf2, 0xd3, 0x6f, 0x9e, 0xb8, 0xc7, 0x34, 0xaf, 0x6c,
]
const Hook = (reserved) => {
// Test arg check
ASSERT(sto_validate(undefined) === INVALID_ARGUMENT)
// Test validation
ASSERT(sto_validate(sto) === 1)
// Invalidate
sto[0] = 0x22
ASSERT(sto_validate(sto) === 0)
// Fix
sto[0] = 0x11
// Invalidate somewhere else
sto[3] = 0x40
ASSERT(sto_validate(sto) === 0)
// test small validation
{
const sto = [0x22, 0x00, 0x00, 0x00, 0x00]
ASSERT(sto_validate(sto) === 1)
}
accept('success', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set sto_validate"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_validate"), fee(XRP(1)));
}
void
test_sto_to_json(FeatureBitset features)
{
testcase("Test sto_to_json");
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);
TestHook hook = jswasm[R"[test.hook](
const INVALID_ARGUMENT = -7
const ASSERT = (x) => {
if (!x) rollback(x.toString(), 0)
}
const sto = [
0x11, 0x00, 0x61, 0x22, 0x00, 0x00, 0x00, 0x00, 0x24, 0x04, 0x1f, 0x94, 0xd9,
0x25, 0x04, 0x5e, 0x84, 0xb7, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x55, 0x13, 0x40,
0xb3, 0x25, 0x86, 0x31, 0x96, 0xb5, 0x6f, 0x41, 0xf5, 0x89, 0xeb, 0x7d, 0x2f,
0xd9, 0x4c, 0x0d, 0x7d, 0xb8, 0x0e, 0x4b, 0x2c, 0x67, 0xa7, 0x78, 0x2a, 0xd6,
0xc2, 0xb0, 0x77, 0x50, 0x62, 0x40, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x79, 0x94,
0x81, 0x14, 0x37, 0xdf, 0x44, 0x07, 0xe7, 0xaa, 0x07, 0xf1, 0xd5, 0xc9, 0x91,
0xf2, 0xd3, 0x6f, 0x9e, 0xb8, 0xc7, 0x34, 0xaf, 0x6c,
]
const sto_json = {
LedgerEntryType: 'AccountRoot',
Flags: 0,
Sequence: 69178585,
PreviousTxnLgrSeq: 73303223,
OwnerCount: 0,
PreviousTxnID:
'1340B325863196B56F41F589EB7D2FD94C0D7DB80E4B2C67A7782AD6C2B07750',
Balance: '10779028',
Account: 'raaRdKAobgyv8VqpRV2UZRjT74FL9PHuei',
}
const Hook = (reserved) => {
// Test arg check
ASSERT(sto_to_json(undefined) === INVALID_ARGUMENT)
// Test ledger entry
const json = sto_to_json(sto)
ASSERT(typeof json === 'object')
trace('2', '', false)
trace('JSON.stringify(json)', json, false)
ASSERT(Object.keys(json).length === Object.keys(sto_json).length)
for (const key of Object.keys(json)) {
ASSERT(sto_json[key] === json[key])
}
// test transaction
ASSERT(otxn_slot(1) === 1)
const otxn_sto = slot(1)
ASSERT(typeof sto_to_json(otxn_sto) === 'object')
// Invalid
sto[0] = 0x22
ASSERT(sto_to_json(sto) === INVALID_ARGUMENT)
// Fix
sto[0] = 0x11
// Invalid length
sto.push(0x00)
ASSERT(sto_to_json(sto) === INVALID_ARGUMENT)
accept('success', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set sto_to_json"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_to_json"), fee(XRP(1)));
}
void
test_sto_from_json(FeatureBitset features)
{
testcase("Test sto_from_json");
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);
TestHook hook = jswasm[R"[test.hook](
const INVALID_ARGUMENT = -7
const ASSERT = (x) => {
if (!x) rollback(x.toString(), 0)
}
const accRoot = {
LedgerEntryType: 'AccountRoot',
Flags: 0,
Sequence: 69178585,
PreviousTxnLgrSeq: 73303223,
OwnerCount: 0,
PreviousTxnID:
'1340B325863196B56F41F589EB7D2FD94C0D7DB80E4B2C67A7782AD6C2B07750',
Balance: '10779028',
Account: 'raaRdKAobgyv8VqpRV2UZRjT74FL9PHuei',
}
const sto = [
0x11, 0x00, 0x61, 0x22, 0x00, 0x00, 0x00, 0x00, 0x24, 0x04, 0x1f, 0x94, 0xd9,
0x25, 0x04, 0x5e, 0x84, 0xb7, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x55, 0x13, 0x40,
0xb3, 0x25, 0x86, 0x31, 0x96, 0xb5, 0x6f, 0x41, 0xf5, 0x89, 0xeb, 0x7d, 0x2f,
0xd9, 0x4c, 0x0d, 0x7d, 0xb8, 0x0e, 0x4b, 0x2c, 0x67, 0xa7, 0x78, 0x2a, 0xd6,
0xc2, 0xb0, 0x77, 0x50, 0x62, 0x40, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x79, 0x94,
0x81, 0x14, 0x37, 0xdf, 0x44, 0x07, 0xe7, 0xaa, 0x07, 0xf1, 0xd5, 0xc9, 0x91,
0xf2, 0xd3, 0x6f, 0x9e, 0xb8, 0xc7, 0x34, 0xaf, 0x6c,
]
const Hook = (reserved) => {
// Test arg check
ASSERT(sto_from_json(undefined) === INVALID_ARGUMENT)
// Test ledger entry
const accRootSto = sto_from_json(accRoot)
ASSERT(Array.isArray(accRootSto))
ASSERT(accRootSto.length === sto.length)
ASSERT(accRootSto.every((v, i) => v === sto[i]))
// test transaction
const txn_json = otxn_json()
ASSERT(otxn_slot(1) === 1)
const otxn_sto = slot(1)
ASSERT(sto_from_json(txn_json).length === otxn_sto.length)
ASSERT(sto_from_json(txn_json).every((v, i) => v === otxn_sto[i]))
// Invalid field
ASSERT(
sto_from_json({ ...accRoot, SomeInvalidField: '123' }) === INVALID_ARGUMENT
)
// Invalid value type
// TODO: Should be invalid https://github.com/Xahau/xahaud/issues/458
// ASSERT(sto_from_json({ ...accRoot, Balance: 123 }) === INVALID_ARGUMENT)
accept('success', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set sto_from_json"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_from_json"), fee(XRP(1)));
}
void
test_js_date(FeatureBitset features)
{
testcase("Test JS Date");
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);
TestHook hook = jswasm[R"[test.hook](
const ASSERT = (x) => {
if (!x) rollback(x.toString(), -1)
}
const Hook = (reserve) => {
const data = new Date()
ASSERT(data.getTime() === (ledger_last_time() + 946684800) * 1000)
ASSERT(Date.now() === (ledger_last_time() + 946684800) * 1000)
accept('', 0)
}
)[test.hook]"];
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hsov1(hook, 1, HSDROPS, overrideFlag)}}, 0),
M("set js Date"),
HSFEE);
env.close();
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test js Date"), fee(XRP(1)));
}
void
testWithFeatures(FeatureBitset features, const std::string& target)
{
Json::Value a(Json::arrayValue);
if (target == ":log-test-names:")
{
testcase(target);
}
TEST_TARGET_PATTERN(a, target, testHooksOwnerDir, features);
TEST_TARGET_PATTERN(a, target, testHooksDisabled, features);
TEST_TARGET_PATTERN(a, target, testTxStructure, features);
// // testInferHookSetOperation(); // Not Version Specific
// // TEST_TARGET_PATTERN(a, target, testParams, features); // Not
// Version Specific
// // TEST_TARGET_PATTERN(a, target, testGrants, features); // Not
// Version Specific
TEST_TARGET_PATTERN(a, target, testInstall, features);
TEST_TARGET_PATTERN(a, target, testDelete, features);
TEST_TARGET_PATTERN(a, target, testNSDelete, features);
TEST_TARGET_PATTERN(a, target, testCreate, features);
TEST_TARGET_PATTERN(a, target, testUpdate, features);
TEST_TARGET_PATTERN(a, target, testWithTickets, features);
// // DA TODO: illegalfunc_wasm
// // TEST_TARGET_PATTERN(a, target, testWasm, features);
TEST_TARGET_PATTERN(a, target, test_accept, features);
TEST_TARGET_PATTERN(a, target, test_rollback, features);
// TEST_TARGET_PATTERN(a, target, testGuards, features); // Not Used in
// JSHooks
TEST_TARGET_PATTERN(a, target, test_emit, features); //
// TEST_TARGET_PATTERN(a, target, test_prepare, features); // JS ONLY
// tested above TEST_TARGET_PATTERN(a, target, test_etxn_burden,
// features);
// // tested above TEST_TARGET_PATTERN(a, target, test_etxn_generation,
// features); // tested above TEST_TARGET_PATTERN(a, target,
// test_otxn_burden, features); // tested above
// TEST_TARGET_PATTERN(a, target, test_otxn_generation, features); //
// tested above
TEST_TARGET_PATTERN(a, target, test_etxn_details, features); //
TEST_TARGET_PATTERN(a, target, test_etxn_fee_base, features); //
TEST_TARGET_PATTERN(a, target, test_etxn_nonce, features); //
TEST_TARGET_PATTERN(a, target, test_etxn_reserve, features); //
TEST_TARGET_PATTERN(a, target, test_fee_base, features); //
TEST_TARGET_PATTERN(a, target, test_otxn_field, features); //
TEST_TARGET_PATTERN(a, target, test_ledger_keylet, features); //
TEST_TARGET_PATTERN(a, target, test_float_compare, features); //
TEST_TARGET_PATTERN(a, target, test_float_divide, features); //
TEST_TARGET_PATTERN(a, target, test_float_int, features); //
TEST_TARGET_PATTERN(a, target, test_float_invert, features); //
TEST_TARGET_PATTERN(a, target, test_float_log, features); //
TEST_TARGET_PATTERN(a, target, test_float_mantissa, features); //
TEST_TARGET_PATTERN(a, target, test_float_mulratio, features); //
TEST_TARGET_PATTERN(a, target, test_float_multiply, features); //
TEST_TARGET_PATTERN(a, target, test_float_negate, features); //
TEST_TARGET_PATTERN(a, target, test_float_one, features); //
TEST_TARGET_PATTERN(a, target, test_float_root, features); //
TEST_TARGET_PATTERN(a, target, test_float_set, features); //
TEST_TARGET_PATTERN(a, target, test_float_sign, features); //
TEST_TARGET_PATTERN(a, target, test_float_sto, features); //
TEST_TARGET_PATTERN(a, target, test_float_sto_set, features); //
TEST_TARGET_PATTERN(a, target, test_float_sum, features); //
TEST_TARGET_PATTERN(a, target, test_hook_account, features); //
TEST_TARGET_PATTERN(a, target, test_hook_again, features); //
TEST_TARGET_PATTERN(a, target, test_hook_hash, features); //
TEST_TARGET_PATTERN(a, target, test_hook_param, features); //
TEST_TARGET_PATTERN(a, target, test_hook_param_set, features); //
TEST_TARGET_PATTERN(a, target, test_hook_pos, features); //
TEST_TARGET_PATTERN(a, target, test_hook_skip, features); //
TEST_TARGET_PATTERN(a, target, test_ledger_last_hash, features); //
TEST_TARGET_PATTERN(a, target, test_ledger_last_time, features); //
TEST_TARGET_PATTERN(a, target, test_ledger_nonce, features); //
TEST_TARGET_PATTERN(a, target, test_ledger_seq, features); //
TEST_TARGET_PATTERN(a, target, test_meta_slot, features); //
TEST_TARGET_PATTERN(a, target, test_xpop_slot, features); //
TEST_TARGET_PATTERN(a, target, test_otxn_id, features); //
TEST_TARGET_PATTERN(a, target, test_otxn_slot, features); //
TEST_TARGET_PATTERN(a, target, test_otxn_type, features);
TEST_TARGET_PATTERN(a, target, test_otxn_param, features); //
TEST_TARGET_PATTERN(a, target, test_otxn_json, features); // JS ONLY
TEST_TARGET_PATTERN(a, target, test_slot, features);
TEST_TARGET_PATTERN(a, target, test_slot_clear, features); //
TEST_TARGET_PATTERN(a, target, test_slot_count, features); //
TEST_TARGET_PATTERN(a, target, test_slot_float, features); //
TEST_TARGET_PATTERN(a, target, test_slot_set, features); //
TEST_TARGET_PATTERN(a, target, test_slot_size, features); //
TEST_TARGET_PATTERN(a, target, test_slot_subarray, features); //
TEST_TARGET_PATTERN(a, target, test_slot_subfield, features); //
TEST_TARGET_PATTERN(a, target, test_slot_type, features); //
TEST_TARGET_PATTERN(a, target, test_slot_json, features); // JS ONLY
TEST_TARGET_PATTERN(a, target, test_state, features); //
TEST_TARGET_PATTERN(a, target, test_state_foreign, features); //
TEST_TARGET_PATTERN(a, target, test_state_foreign_set, features); //
// TEST_TARGET_PATTERN(a, target, test_state_foreign_set_max, features);
// // Not Version Specific
TEST_TARGET_PATTERN(a, target, test_state_set, features); //
TEST_TARGET_PATTERN(a, target, test_sto_emplace, features); //
TEST_TARGET_PATTERN(a, target, test_sto_erase, features); //
TEST_TARGET_PATTERN(a, target, test_sto_subarray, features); //
TEST_TARGET_PATTERN(a, target, test_sto_subfield, features); //
TEST_TARGET_PATTERN(a, target, test_sto_validate, features); //
TEST_TARGET_PATTERN(a, target, test_sto_to_json, features); // JS ONLY
TEST_TARGET_PATTERN(
a, target, test_sto_from_json, features); // JS ONLY
TEST_TARGET_PATTERN(a, target, test_trace, features); //
// TEST_TARGET_PATTERN(a, target, test_trace_float, features); // C
// ONLY TEST_TARGET_PATTERN(a, target, test_trace_num, features); //
// C ONLY
TEST_TARGET_PATTERN(a, target, test_util_accid, features); //
TEST_TARGET_PATTERN(a, target, test_util_keylet, features); //
TEST_TARGET_PATTERN(a, target, test_util_raddr, features); //
TEST_TARGET_PATTERN(a, target, test_util_sha512h, features); //
TEST_TARGET_PATTERN(a, target, test_util_verify, features); //
TEST_TARGET_PATTERN(a, target, test_js_date, features); //
if (target == ":log-test-names:")
{
std::cout << "\nTEST-NAMES-START\n"
<< Json::FastWriter().write(a) << "\nTEST-NAMES-END\n"
<< std::endl;
pass();
}
}
void
run() override
{
using namespace test::jtx;
auto const sa = supported_amendments();
testWithFeatures(sa, arg());
}
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](
const Hook = (arg) => {
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