From 70bc2d3283d94cfb607425155a7d3a4d2734eef1 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 22 May 2024 23:12:18 +0200 Subject: [PATCH] more js hook tests --- src/test/app/SetJSHook_test.cpp | 2046 ++++++++++++++++++++++++++++++- src/test/app/SetJSHook_wasm.h | 169 ++- 2 files changed, 2190 insertions(+), 25 deletions(-) diff --git a/src/test/app/SetJSHook_test.cpp b/src/test/app/SetJSHook_test.cpp index b8c9e5597..d2e6ccf53 100644 --- a/src/test/app/SetJSHook_test.cpp +++ b/src/test/app/SetJSHook_test.cpp @@ -99,13 +99,13 @@ public: env.close(); env(ripple::test::jtx::hook( - alice, {{hso(accept_wasm, 1, overrideFlag)}}, 0), + alice, {{hsov1(accept_wasm, 1, overrideFlag)}}, 0), HSFEE, ter(tesSUCCESS)); env.close(); env(ripple::test::jtx::hook( - alice, {{hso(accept_wasm, 1, overrideFlag)}}, 0), + alice, {{hsov1(accept_wasm, 1, overrideFlag)}}, 0), HSFEE, ter(tesSUCCESS)); env.close(); @@ -125,10 +125,1996 @@ public: env.close(); } + void + testHooksDisabled(FeatureBitset features) + { + testcase("Check for disabled amendment"); + using namespace jtx; + Env env{*this, features - featureHooks}; + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + + env(ripple::test::jtx::hook( + alice, {{hsov1(accept_wasm, 1, overrideFlag)}}, 0), + M("Hooks Disabled"), + HSFEE, + ter(temDISABLED)); + } + + void + testTxStructure(FeatureBitset features) + { + testcase("Checks malformed transactions"); + using namespace jtx; + Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + // Test outer structure + + env(ripple::test::jtx::hook(alice, {}, 0), + M("Must have a hooks field"), + HSFEE, + ter(temMALFORMED)); + + env(ripple::test::jtx::hook(alice, {{}}, 0), + M("Must have a non-empty hooks field"), + HSFEE, + ter(temMALFORMED)); + + env(ripple::test::jtx::hook( + alice, + {{hsov1(accept_wasm, 1), + hsov1(accept_wasm, 1), + hsov1(accept_wasm, 1), + hsov1(accept_wasm, 1), + hsov1(accept_wasm, 1), + hsov1(accept_wasm, 1), + hsov1(accept_wasm, 1), + hsov1(accept_wasm, 1), + hsov1(accept_wasm, 1), + hsov1(accept_wasm, 1), + hsov1(accept_wasm, 1)}}, + 0), + M("Must have fewer than 11 entries"), + HSFEE, + ter(temMALFORMED)); + + { + Json::Value jv; + jv[jss::Account] = alice.human(); + jv[jss::TransactionType] = jss::SetHook; + jv[jss::Flags] = 0; + jv[jss::Hooks] = Json::Value{Json::arrayValue}; + + Json::Value iv; + iv[jss::MemoData] = "DEADBEEF"; + iv[jss::MemoFormat] = ""; + iv[jss::MemoType] = ""; + jv[jss::Hooks][0U][jss::Memo] = iv; + env(jv, + M("Hooks Array must contain Hook objects"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + } + + void + testInstall(FeatureBitset features) + { + testcase("Checks malformed install operation"); + using namespace jtx; + Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + + auto const bob = Account{"bob"}; + env.fund(XRP(10000), bob); + + // create a hook that we can then install + { + env(ripple::test::jtx::hook( + bob, {{hsov1(accept_wasm, 1), hsov1(rollback_wasm, 1)}}, 0), + M("First set = tesSUCCESS"), + HSFEE, + ter(tesSUCCESS)); + } + + Json::Value jv; + jv[jss::Account] = alice.human(); + jv[jss::TransactionType] = jss::SetHook; + jv[jss::Flags] = 0; + jv[jss::Hooks] = Json::Value{Json::arrayValue}; + + // can't set api version + { + Json::Value iv; + iv[jss::HookHash] = accept_hash_str; + iv[jss::HookApiVersion] = 0U; + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, + M("Hook Install operation cannot set apiversion"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + // can't set non-existent hook + { + Json::Value iv; + iv[jss::HookHash] = + "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBE" + "EF"; + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, + M("Hook Install operation cannot set non existent hook hash"), + HSFEE, + ter(terNO_HOOK)); + env.close(); + } + + // can set extant hook + { + Json::Value iv; + iv[jss::HookHash] = accept_hash_str; + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, + M("Hook Install operation can set extant hook hash"), + HSFEE, + ter(tesSUCCESS)); + env.close(); + } + + // can't set extant hook over other hook without override flag + { + Json::Value iv; + iv[jss::HookHash] = rollback_hash_str; + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, + M("Hook Install operation can set extant hook hash"), + HSFEE, + ter(tecREQUIRES_FLAG)); + env.close(); + } + + // can set extant hook over other hook with override flag + { + Json::Value iv; + iv[jss::HookHash] = rollback_hash_str; + iv[jss::Flags] = hsfOVERRIDE; + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, + M("Hook Install operation can set extant hook hash"), + HSFEE, + ter(tesSUCCESS)); + env.close(); + } + } + + void + testDelete(FeatureBitset features) + { + testcase("Checks malformed delete operation"); + using namespace jtx; + Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + + Json::Value jv; + jv[jss::Account] = alice.human(); + jv[jss::TransactionType] = jss::SetHook; + jv[jss::Flags] = 0; + jv[jss::Hooks] = Json::Value{Json::arrayValue}; + + // flag required + { + Json::Value iv; + iv[jss::CreateCode] = ""; + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, + M("Hook DELETE operation must include hsfOVERRIDE flag"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + // invalid flags + { + Json::Value iv; + iv[jss::CreateCode] = ""; + iv[jss::Flags] = "2147483648"; + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, + M("Hook DELETE operation must include hsfOVERRIDE flag"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + // grants, parameters, hookon, hookapiversion, hooknamespace keys must + // be absent + for (auto const& [key, value] : JSSMap{ + {jss::HookGrants, Json::arrayValue}, + {jss::HookParameters, Json::arrayValue}, + {jss::HookOn, + "000000000000000000000000000000000000000000000000000000000000" + "0000"}, + {jss::HookApiVersion, "1"}, + {jss::HookNamespace, to_string(uint256{beast::zero})}}) + { + Json::Value iv; + iv[jss::CreateCode] = ""; + iv[key] = value; + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, + M("Hook DELETE operation cannot include: grants, params, " + "hookon, apiversion, namespace"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + // create and delete single hook + { + { + Json::Value jv = ripple::test::jtx::hook( + alice, {{hsov1(accept_wasm, 1)}}, 0); + env(jv, M("Normal accept create"), HSFEE, ter(tesSUCCESS)); + env.close(); + } + + BEAST_REQUIRE(env.le(accept_keylet)); + + Json::Value iv; + iv[jss::CreateCode] = ""; + iv[jss::Flags] = hsfOVERRIDE; + jv[jss::Hooks][0U][jss::Hook] = iv; + + env(jv, M("Normal hook DELETE"), HSFEE); + env.close(); + + // check to ensure definition is deleted and hooks object too + auto const def = env.le(accept_keylet); + auto const hook = env.le(keylet::hook(Account("alice").id())); + + BEAST_EXPECT(!def); + BEAST_EXPECT(!hook); + } + + // create four hooks then delete the second last one + { + // create + { + Json::Value jv = ripple::test::jtx::hook( + alice, + {{hsov1(accept_wasm, 1), + hsov1(makestate_wasm, 1), + hsov1(rollback_wasm, 1), + hsov1(accept2_wasm, 1)}}, + 0); + env(jv, M("Create four"), HSFEE, ter(tesSUCCESS)); + env.close(); + } + + // delete third and check + { + Json::Value iv; + iv[jss::CreateCode] = ""; + iv[jss::Flags] = hsfOVERRIDE; + for (uint8_t i = 0; i < 4; ++i) + jv[jss::Hooks][i][jss::Hook] = Json::Value{}; + jv[jss::Hooks][2U][jss::Hook] = iv; + + env(jv, M("Normal hooki DELETE (third pos)"), HSFEE); + env.close(); + + // check the hook definitions are consistent with reference + // count dropping to zero on the third + auto const accept_def = env.le(accept_keylet); + auto const rollback_def = env.le(rollback_keylet); + auto const makestate_def = env.le(makestate_keylet); + auto const accept2_def = env.le(accept2_keylet); + + BEAST_REQUIRE(accept_def); + BEAST_EXPECT(!rollback_def); + BEAST_REQUIRE(makestate_def); + BEAST_REQUIRE(accept2_def); + + // check the hooks array is correct + auto const hook = env.le(keylet::hook(Account("alice").id())); + BEAST_REQUIRE(hook); + + auto const& hooks = hook->getFieldArray(sfHooks); + BEAST_REQUIRE(hooks.size() == 4); + + // make sure only the third is deleted + BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookHash)); + BEAST_REQUIRE(hooks[1].isFieldPresent(sfHookHash)); + BEAST_EXPECT(!hooks[2].isFieldPresent(sfHookHash)); + BEAST_REQUIRE(hooks[3].isFieldPresent(sfHookHash)); + + // check hashes on the three remaining + BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash); + BEAST_EXPECT( + hooks[1].getFieldH256(sfHookHash) == makestate_hash); + BEAST_EXPECT(hooks[3].getFieldH256(sfHookHash) == accept2_hash); + } + + // delete rest and check + { + Json::Value iv; + iv[jss::CreateCode] = ""; + iv[jss::Flags] = hsfOVERRIDE; + for (uint8_t i = 0; i < 4; ++i) + { + if (i != 2U) + jv[jss::Hooks][i][jss::Hook] = iv; + else + jv[jss::Hooks][i][jss::Hook] = Json::Value{}; + } + + env(jv, + M("Normal hook DELETE (first, second, fourth pos)"), + HSFEE); + env.close(); + + // check the hook definitions are consistent with reference + // count dropping to zero on the third + auto const accept_def = env.le(accept_keylet); + auto const rollback_def = env.le(rollback_keylet); + auto const makestate_def = env.le(makestate_keylet); + auto const accept2_def = env.le(accept2_keylet); + + BEAST_EXPECT(!accept_def); + BEAST_EXPECT(!rollback_def); + BEAST_EXPECT(!makestate_def); + BEAST_EXPECT(!accept2_def); + + // check the hooks object is gone + auto const hook = env.le(keylet::hook(Account("alice").id())); + BEAST_EXPECT(!hook); + } + } + } + + void + testNSDelete(FeatureBitset features) + { + testcase("Checks malformed nsdelete operation"); + using namespace jtx; + Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + + auto const bob = Account{"bob"}; + env.fund(XRP(10000), bob); + + Json::Value jv; + jv[jss::Account] = alice.human(); + jv[jss::TransactionType] = jss::SetHook; + jv[jss::Flags] = 0; + jv[jss::Hooks] = Json::Value{Json::arrayValue}; + + for (auto const& [key, value] : JSSMap{ + {jss::HookGrants, Json::arrayValue}, + {jss::HookParameters, Json::arrayValue}, + {jss::HookOn, + "000000000000000000000000000000000000000000000000000000000000" + "0000"}, + {jss::HookApiVersion, "1"}, + }) + { + Json::Value iv; + iv[key] = value; + iv[jss::Flags] = hsfNSDELETE; + iv[jss::HookNamespace] = to_string(uint256{beast::zero}); + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, + M("Hook NSDELETE operation cannot include: grants, params, " + "hookon, apiversion"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + auto const key = uint256::fromVoid( + (std::array{ + 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{ + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU}) + .data()); + + auto const stateKeylet = + keylet::hookState(Account("alice").id(), key, ns); + + // create a namespace + std::string ns_str = + "CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE"; + { + // create hook + Json::Value jv = + ripple::test::jtx::hook(alice, {{hsov1(makestate_wasm, 1)}}, 0); + + jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str; + env(jv, M("Create makestate hook"), HSFEE, ter(tesSUCCESS)); + env.close(); + + // run hook + env(pay(bob, alice, XRP(1)), + M("Run create state hook"), + fee(XRP(1))); + env.close(); + + // check if the hookstate object was created + auto const hookstate = env.le(stateKeylet); + BEAST_EXPECT(!!hookstate); + + // check if the value was set correctly + auto const& data = hookstate->getFieldVL(sfHookStateData); + + BEAST_REQUIRE(data.size() == 6); + BEAST_EXPECT( + data[0] == 'v' && data[1] == 'a' && data[2] == 'l' && + data[3] == 'u' && data[4] == 'e' && data[5] == '\0'); + } + + // delete the namespace + { + Json::Value iv; + iv[jss::Flags] = hsfNSDELETE; + iv[jss::HookNamespace] = ns_str; + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, M("Normal NSDELETE operation"), HSFEE, ter(tesSUCCESS)); + env.close(); + + // ensure the hook is still installed + auto const hook = env.le(keylet::hook(Account("alice").id())); + BEAST_REQUIRE(hook); + + BEAST_REQUIRE(hook->isFieldPresent(sfHooks)); + auto const& hooks = hook->getFieldArray(sfHooks); + BEAST_EXPECT(hooks.size() > 0); + BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash)); + BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == makestate_hash); + + // ensure the directory is gone + auto const dirKeylet = + keylet::hookStateDir(Account("alice").id(), ns); + BEAST_EXPECT(!env.le(dirKeylet)); + + // ensure the state object is gone + BEAST_EXPECT(!env.le(stateKeylet)); + } + } + + void + testCreate(FeatureBitset features) + { + testcase("Checks malformed create operation"); + using namespace jtx; + Env env{*this, features}; + + auto const bob = Account{"bob"}; + env.fund(XRP(10000), bob); + + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + + // test normal create and missing override flag + { + env(ripple::test::jtx::hook(bob, {{hsov1(accept_wasm, 1)}}, 0), + M("First set = tesSUCCESS"), + HSFEE, + ter(tesSUCCESS)); + + env(ripple::test::jtx::hook(bob, {{hsov1(accept_wasm, 1)}}, 0), + M("Second set = tecREQUIRES_FLAG"), + HSFEE, + ter(tecREQUIRES_FLAG)); + env.close(); + } + + Json::Value jv; + jv[jss::Account] = alice.human(); + jv[jss::TransactionType] = jss::SetHook; + jv[jss::Flags] = 0; + jv[jss::Hooks] = Json::Value{Json::arrayValue}; + + // DA TODO: FAILING + // // payload too large + // { + // env(ripple::test::jtx::hook(alice, {{hsov1(long_wasm, 1)}}, 0), + // M("If CreateCode is present, then it must be less than 64kib"), + // HSFEE, + // ter(temMALFORMED)); + // env.close(); + // } + + // namespace missing + { + Json::Value iv; + iv[jss::CreateCode] = strHex(accept_wasm); + iv[jss::HookApiVersion] = 1U; + iv[jss::HookOn] = + "00000000000000000000000000000000000000000000000000000000000000" + "00"; + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = iv; + + env(jv, + M("HSO Create operation must contain namespace"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + // api version missing + { + Json::Value iv; + iv[jss::CreateCode] = strHex(accept_wasm); + iv[jss::HookNamespace] = to_string(uint256{beast::zero}); + iv[jss::HookOn] = + "00000000000000000000000000000000000000000000000000000000000000" + "00"; + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = iv; + + env(jv, + M("HSO Create operation must contain api version"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + // api version wrong + { + Json::Value iv; + iv[jss::CreateCode] = strHex(accept_wasm); + iv[jss::HookNamespace] = to_string(uint256{beast::zero}); + iv[jss::HookApiVersion] = 2U; + iv[jss::HookOn] = + "00000000000000000000000000000000000000000000000000000000000000" + "00"; + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = iv; + + env(jv, + M("HSO Create operation must contain valid api version"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + // hookon missing + { + Json::Value iv; + iv[jss::CreateCode] = strHex(accept_wasm); + iv[jss::HookNamespace] = to_string(uint256{beast::zero}); + iv[jss::HookApiVersion] = 1U; + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = iv; + + env(jv, + M("HSO Create operation must contain hookon"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + // hook hash present + { + Json::Value jv = + ripple::test::jtx::hook(alice, {{hsov1(accept_wasm, 1)}}, 0); + Json::Value iv = jv[jss::Hooks][0U]; + iv[jss::Hook][jss::HookHash] = to_string(uint256{beast::zero}); + jv[jss::Hooks][0U] = iv; + env(jv, + M("Cannot have both CreateCode and HookHash"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + // correctly formed + { + Json::Value jv = + ripple::test::jtx::hook(alice, {{hsov1(accept_wasm, 1)}}, 0); + env(jv, M("Normal accept"), HSFEE, ter(tesSUCCESS)); + env.close(); + + auto const def = env.le(accept_keylet); + auto const hook = env.le(keylet::hook(Account("alice").id())); + + // check if the hook definition exists + BEAST_EXPECT(!!def); + + // check if the user account has a hooks object + BEAST_EXPECT(!!hook); + + // check if the hook is correctly set at position 1 + BEAST_EXPECT(hook->isFieldPresent(sfHooks)); + auto const& hooks = hook->getFieldArray(sfHooks); + BEAST_EXPECT(hooks.size() > 0); + BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash)); + BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash); + + // check if the wasm binary was correctly set + BEAST_EXPECT(def->isFieldPresent(sfCreateCode)); + auto const& wasm = def->getFieldVL(sfCreateCode); + auto const wasm_hash = + sha512Half_s(ripple::Slice(wasm.data(), wasm.size())); + BEAST_EXPECT(wasm_hash == accept_hash); + } + + // add a second hook + { + Json::Value jv = + ripple::test::jtx::hook(alice, {{hsov1(accept_wasm, 1)}}, 0); + Json::Value iv = jv[jss::Hooks][0U]; + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = Json::Value{}; + jv[jss::Hooks][1U] = iv; + env(jv, + M("Normal accept, second position"), + HSFEE, + ter(tesSUCCESS)); + env.close(); + + auto const def = env.le(accept_keylet); + auto const hook = env.le(keylet::hook(Account("alice").id())); + + // check if the hook definition exists + BEAST_EXPECT(!!def); + + // check if the user account has a hooks object + BEAST_EXPECT(!!hook); + + // check if the hook is correctly set at position 2 + BEAST_EXPECT(hook->isFieldPresent(sfHooks)); + auto const& hooks = hook->getFieldArray(sfHooks); + BEAST_EXPECT(hooks.size() > 1); + BEAST_EXPECT(hooks[1].isFieldPresent(sfHookHash)); + BEAST_EXPECT(hooks[1].getFieldH256(sfHookHash) == accept_hash); + + // check if the reference count was correctly incremented + BEAST_EXPECT(def->isFieldPresent(sfReferenceCount)); + // two references from alice, one from bob (first test above) + BEAST_EXPECT(def->getFieldU64(sfReferenceCount) == 3ULL); + } + + auto const rollback_hash = ripple::sha512Half_s( + ripple::Slice(rollback_wasm.data(), rollback_wasm.size())); + + // test override + { + Json::Value jv = + ripple::test::jtx::hook(alice, {{hsov1(rollback_wasm, 1)}}, 0); + jv[jss::Hooks][0U][jss::Hook][jss::Flags] = hsfOVERRIDE; + env(jv, M("Rollback override"), HSFEE, ter(tesSUCCESS)); + env.close(); + + auto const rollback_def = env.le(rollback_keylet); + auto const accept_def = env.le(accept_keylet); + auto const hook = env.le(keylet::hook(Account("alice").id())); + + // check if the hook definition exists + BEAST_EXPECT(rollback_def); + BEAST_EXPECT(accept_def); + + // check if the user account has a hooks object + BEAST_EXPECT(hook); + + // check if the hook is correctly set at position 1 + BEAST_EXPECT(hook->isFieldPresent(sfHooks)); + auto const& hooks = hook->getFieldArray(sfHooks); + BEAST_EXPECT(hooks.size() > 1); + BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash)); + BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == rollback_hash); + BEAST_EXPECT(hooks[1].isFieldPresent(sfHookHash)); + BEAST_EXPECT(hooks[1].getFieldH256(sfHookHash) == accept_hash); + + // check if the wasm binary was correctly set + BEAST_EXPECT(rollback_def->isFieldPresent(sfCreateCode)); + auto const& wasm = rollback_def->getFieldVL(sfCreateCode); + auto const wasm_hash = + sha512Half_s(ripple::Slice(wasm.data(), wasm.size())); + BEAST_EXPECT(wasm_hash == rollback_hash); + + // check if the reference count was correctly incremented + BEAST_EXPECT(rollback_def->isFieldPresent(sfReferenceCount)); + BEAST_EXPECT(rollback_def->getFieldU64(sfReferenceCount) == 1ULL); + + // check if the reference count was correctly decremented + BEAST_EXPECT(accept_def->isFieldPresent(sfReferenceCount)); + BEAST_EXPECT(accept_def->getFieldU64(sfReferenceCount) == 2ULL); + } + } + + void + testUpdate(FeatureBitset features) + { + testcase("Checks malformed update operation"); + using namespace jtx; + Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + + auto const bob = Account{"bob"}; + env.fund(XRP(10000), bob); + + Json::Value jv; + jv[jss::Account] = alice.human(); + jv[jss::TransactionType] = jss::SetHook; + jv[jss::Flags] = 0; + jv[jss::Hooks] = Json::Value{Json::arrayValue}; + + // first create the hook + { + Json::Value iv; + iv[jss::CreateCode] = strHex(accept_wasm); + iv[jss::HookNamespace] = to_string(uint256{beast::zero}); + iv[jss::HookApiVersion] = 1U; + iv[jss::HookOn] = + "00000000000000000000000000000000000000000000000000000000000000" + "00"; + iv[jss::HookParameters] = Json::Value{Json::arrayValue}; + iv[jss::HookParameters][0U] = Json::Value{}; + iv[jss::HookParameters][0U][jss::HookParameter] = Json::Value{}; + iv[jss::HookParameters][0U][jss::HookParameter] + [jss::HookParameterName] = "AAAAAAAAAAAA"; + iv[jss::HookParameters][0U][jss::HookParameter] + [jss::HookParameterValue] = "BBBBBB"; + + iv[jss::HookParameters][1U] = Json::Value{}; + iv[jss::HookParameters][1U][jss::HookParameter] = Json::Value{}; + iv[jss::HookParameters][1U][jss::HookParameter] + [jss::HookParameterName] = "CAFE"; + iv[jss::HookParameters][1U][jss::HookParameter] + [jss::HookParameterValue] = "FACADE"; + + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, M("Create accept"), HSFEE, ter(tesSUCCESS)); + env.close(); + } + + // all alice operations below are then updates + + // must not specify override flag + { + Json::Value iv; + iv[jss::Flags] = hsfOVERRIDE; + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = iv; + + env(jv, + M("Override flag not allowed on update"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + // must not specify NSDELETE unless also Namespace + { + Json::Value iv; + iv[jss::Flags] = hsfNSDELETE; + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = iv; + + env(jv, + M("NSDELETE flag not allowed on update unless HookNamespace " + "also present"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + // api version not allowed in update + { + Json::Value iv; + iv[jss::HookApiVersion] = 1U; + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = iv; + + env(jv, + M("ApiVersion not allowed in update"), + HSFEE, + ter(temMALFORMED)); + env.close(); + } + + // try individually updating the various allowed fields + { + Json::Value params{Json::arrayValue}; + params[0U][jss::HookParameter] = Json::Value{}; + params[0U][jss::HookParameter][jss::HookParameterName] = "CAFE"; + params[0U][jss::HookParameter][jss::HookParameterValue] = "BABE"; + + Json::Value grants{Json::arrayValue}; + grants[0U][jss::HookGrant] = Json::Value{}; + grants[0U][jss::HookGrant][jss::HookHash] = accept_hash_str; + + for (auto const& [key, value] : JSSMap{ + {jss::HookOn, + "00000000000000000000000000000000000000000000000000000000" + "00000001"}, + {jss::HookNamespace, + "CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE" + "CAFECAFE"}, + {jss::HookParameters, params}, + {jss::HookGrants, grants}}) + { + Json::Value iv; + iv[key] = value; + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = iv; + + env(jv, M("Normal update"), HSFEE, ter(tesSUCCESS)); + env.close(); + } + + // ensure hook still exists + auto const hook = env.le(keylet::hook(Account("alice").id())); + BEAST_REQUIRE(hook); + BEAST_REQUIRE(hook->isFieldPresent(sfHooks)); + auto const& hooks = hook->getFieldArray(sfHooks); + BEAST_EXPECT(hooks.size() == 1); + BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash)); + BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash); + + // check all fields were updated to correct values + BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookOn)); + BEAST_EXPECT(hooks[0].getFieldH256(sfHookOn) == UINT256_BIT[0]); + + auto const ns = uint256::fromVoid( + (std::array{ + 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 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 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 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 already; + for (uint8_t i = 0; i < params.size(); ++i) + { + const auto pn = p[i].getFieldVL(sfHookParameterName); + const auto pv = p[i].getFieldVL(sfHookParameterValue); + + // make sure it's not a duplicate entry + BEAST_EXPECT(already.find(pn) == already.end()); + + // make sure it exists + BEAST_EXPECT(params.find(pn) != params.end()); + + // make sure the value matches + BEAST_EXPECT(params[pn] == pv); + already.emplace(pn); + } + } + + // try to delete all parameters (reset to defaults) using EMA (Empty + // Parameters Array) + { + Json::Value iv; + iv[jss::HookParameters] = Json::Value{Json::arrayValue}; + + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, M("Unset all params on hook"), HSFEE, ter(tesSUCCESS)); + env.close(); + + // ensure hook still exists + auto const hook = env.le(keylet::hook(Account("alice").id())); + BEAST_REQUIRE(hook); + BEAST_REQUIRE(hook->isFieldPresent(sfHooks)); + auto const& hooks = hook->getFieldArray(sfHooks); + BEAST_EXPECT(hooks.size() == 1); + BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash)); + BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash); + + // check there right number of parameters exist + BEAST_REQUIRE(!hooks[0].isFieldPresent(sfHookParameters)); + } + + // try to set each type of field on a non existent hook + { + Json::Value params{Json::arrayValue}; + params[0U][jss::HookParameter] = Json::Value{}; + params[0U][jss::HookParameter][jss::HookParameterName] = "CAFE"; + params[0U][jss::HookParameter][jss::HookParameterValue] = "BABE"; + + Json::Value grants{Json::arrayValue}; + grants[0U][jss::HookGrant] = Json::Value{}; + grants[0U][jss::HookGrant][jss::HookHash] = accept_hash_str; + + for (auto const& [key, value] : JSSMap{ + {jss::HookOn, + "00000000000000000000000000000000000000000000000000000000" + "00000001"}, + {jss::HookNamespace, + "CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE" + "CAFECAFE"}, + {jss::HookParameters, params}, + {jss::HookGrants, grants}}) + { + Json::Value iv; + iv[key] = value; + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = Json::Value{}; + jv[jss::Hooks][1U] = Json::Value{}; + jv[jss::Hooks][1U][jss::Hook] = iv; + + env(jv, + M("Invalid update on non existent hook"), + HSFEE, + ter(tecNO_ENTRY)); + env.close(); + } + + // ensure hook still exists and that there was no created new entry + auto const hook = env.le(keylet::hook(Account("alice").id())); + BEAST_REQUIRE(hook); + BEAST_REQUIRE(hook->isFieldPresent(sfHooks)); + auto const& hooks = hook->getFieldArray(sfHooks); + BEAST_EXPECT(hooks.size() == 1); + BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash)); + BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash); + } + + // test adding multiple grants + { + { + // add a second hook + env(ripple::test::jtx::hook( + alice, {{{}, hsov1(accept_wasm, 1)}}, 0), + M("Add second hook"), + HSFEE, + ter(tesSUCCESS)); + } + + Json::Value grants{Json::arrayValue}; + grants[0U][jss::HookGrant] = Json::Value{}; + grants[0U][jss::HookGrant][jss::HookHash] = rollback_hash_str; + grants[0U][jss::HookGrant][jss::Authorize] = bob.human(); + + grants[1U][jss::HookGrant] = Json::Value{}; + grants[1U][jss::HookGrant][jss::HookHash] = accept_hash_str; + + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = Json::objectValue; + jv[jss::Hooks][1U] = Json::Value{}; + jv[jss::Hooks][1U][jss::Hook] = Json::Value{}; + jv[jss::Hooks][1U][jss::Hook][jss::HookGrants] = grants; + + env(jv, M("Add grants"), HSFEE); + env.close(); + + // ensure hook still exists + auto const hook = env.le(keylet::hook(Account("alice").id())); + BEAST_REQUIRE(hook); + + BEAST_REQUIRE(hook->isFieldPresent(sfHooks)); + auto const& hooks = hook->getFieldArray(sfHooks); + BEAST_EXPECT(hooks.size() == 2); + BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash)); + BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash); + + // check there right number of grants exist + // hook 0 should have 1 grant + BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookGrants)); + BEAST_REQUIRE(hooks[0].getFieldArray(sfHookGrants).size() == 1); + // hook 1 should have 2 grants + { + BEAST_REQUIRE(hooks[1].isFieldPresent(sfHookGrants)); + auto const& grants = hooks[1].getFieldArray(sfHookGrants); + BEAST_REQUIRE(grants.size() == 2); + + BEAST_REQUIRE(grants[0].isFieldPresent(sfHookHash)); + BEAST_REQUIRE(grants[0].isFieldPresent(sfAuthorize)); + BEAST_REQUIRE(grants[1].isFieldPresent(sfHookHash)); + BEAST_EXPECT(!grants[1].isFieldPresent(sfAuthorize)); + + BEAST_EXPECT( + grants[0].getFieldH256(sfHookHash) == rollback_hash); + BEAST_EXPECT(grants[0].getAccountID(sfAuthorize) == bob.id()); + + BEAST_EXPECT(grants[1].getFieldH256(sfHookHash) == accept_hash); + } + } + + // update grants + { + Json::Value grants{Json::arrayValue}; + grants[0U][jss::HookGrant] = Json::Value{}; + grants[0U][jss::HookGrant][jss::HookHash] = makestate_hash_str; + + jv[jss::Hooks][0U] = Json::Value{}; + jv[jss::Hooks][0U][jss::Hook] = Json::objectValue; + jv[jss::Hooks][1U] = Json::Value{}; + jv[jss::Hooks][1U][jss::Hook] = Json::Value{}; + jv[jss::Hooks][1U][jss::Hook][jss::HookGrants] = grants; + + env(jv, M("update grants"), HSFEE); + env.close(); + + // ensure hook still exists + auto const hook = env.le(keylet::hook(Account("alice").id())); + BEAST_REQUIRE(hook); + + BEAST_REQUIRE(hook->isFieldPresent(sfHooks)); + auto const& hooks = hook->getFieldArray(sfHooks); + BEAST_EXPECT(hooks.size() == 2); + BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash)); + BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash); + + // check there right number of grants exist + // hook 1 should have 1 grant + { + BEAST_REQUIRE(hooks[1].isFieldPresent(sfHookGrants)); + auto const& grants = hooks[1].getFieldArray(sfHookGrants); + BEAST_REQUIRE(grants.size() == 1); + BEAST_REQUIRE(grants[0].isFieldPresent(sfHookHash)); + BEAST_EXPECT( + grants[0].getFieldH256(sfHookHash) == makestate_hash); + } + } + + // use an empty grants array to reset the grants + { + jv[jss::Hooks][0U] = Json::objectValue; + jv[jss::Hooks][0U][jss::Hook] = Json::objectValue; + jv[jss::Hooks][1U] = Json::Value{}; + jv[jss::Hooks][1U][jss::Hook] = Json::Value{}; + jv[jss::Hooks][1U][jss::Hook][jss::HookGrants] = Json::arrayValue; + + env(jv, M("clear grants"), HSFEE); + env.close(); + + // ensure hook still exists + auto const hook = env.le(keylet::hook(Account("alice").id())); + BEAST_REQUIRE(hook); + + BEAST_REQUIRE(hook->isFieldPresent(sfHooks)); + auto const& hooks = hook->getFieldArray(sfHooks); + BEAST_EXPECT(hooks.size() == 2); + BEAST_EXPECT(hooks[0].isFieldPresent(sfHookHash)); + BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == accept_hash); + + // check there right number of grants exist + // hook 1 should have 0 grants + BEAST_REQUIRE(!hooks[1].isFieldPresent(sfHookGrants)); + } + } + + void + testWithTickets(FeatureBitset features) + { + testcase("with tickets"); + using namespace jtx; + + Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + + std::uint32_t aliceTicketSeq{env.seq(alice) + 1}; + env(ticket::create(alice, 10)); + std::uint32_t const aliceSeq{env.seq(alice)}; + env.require(owners(alice, 10)); + + env(ripple::test::jtx::hook(alice, {{hsov1(accept_wasm, 1)}}, 0), + HSFEE, + ticket::use(aliceTicketSeq++), + ter(tesSUCCESS)); + + env.require(tickets(alice, env.seq(alice) - aliceTicketSeq)); + BEAST_EXPECT(env.seq(alice) == aliceSeq); + env.require(owners(alice, 9 + 1)); + } + + // void + // testWasm(FeatureBitset features) + // { + // testcase("Checks malformed hook binaries"); + // using namespace jtx; + // Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10000), alice); + + // env(ripple::test::jtx::hook(alice, {{hsov1(illegalfunc_wasm, 1)}}, 0), + // M("Must only contain hook and cbak"), + // HSFEE, + // ter(temMALFORMED)); + // } + + void + test_accept(FeatureBitset features) + { + testcase("Test accept() hookapi"); + using namespace jtx; + Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10000), alice); + env.fund(XRP(10000), bob); + + env(ripple::test::jtx::hook(alice, {{hsov1(accept_wasm, 1)}}, 0), + M("Install Accept Hook"), + HSFEE); + env.close(); + + env(pay(bob, alice, XRP(1)), M("Test Accept Hook"), fee(XRP(1))); + env.close(); + } + + void + test_rollback(FeatureBitset features) + { + testcase("Test rollback() hookapi"); + using namespace jtx; + Env env{*this, features}; + + auto const bob = Account{"bob"}; + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + env.fund(XRP(10000), bob); + + env(ripple::test::jtx::hook(alice, {{hsov1(rollback_wasm, 1)}}, 0), + M("Install Rollback Hook"), + HSFEE); + env.close(); + + env(pay(bob, alice, XRP(1)), + M("Test Rollback Hook"), + fee(XRP(1)), + ter(tecHOOK_REJECTED)); + env.close(); + } + + void + test_emit(FeatureBitset features) + { + testcase("Test float_emit"); + using namespace jtx; + Env env{ + *this, envconfig(), features, nullptr, beast::severities::kWarning + // beast::severities::kTrace + }; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10000), alice); + env.fund(XRP(10000), bob); + + TestHook hook = wasm[R"[test.hook]( + #include + extern int32_t _g(uint32_t, uint32_t); + extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t emit (uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t etxn_reserve(uint32_t); + extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t hook_account(uint32_t, uint32_t); + extern int64_t otxn_field ( + uint32_t write_ptr, + uint32_t write_len, + uint32_t field_id + ); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + #define OUT_OF_BOUNDS (-1) + #define ttPAYMENT 0 + #define tfCANONICAL 0x80000000UL + #define amAMOUNT 1U + #define amFEE 8U + #define atACCOUNT 1U + #define DOESNT_EXIST (-5) + #define atDESTINATION 3U + #define SBUF(x) (uint32_t)x,sizeof(x) + + #define PREREQUISITE_NOT_MET -9 + #define ENCODE_DROPS_SIZE 9 + #define ENCODE_DROPS(buf_out, drops, amount_type ) \ + {\ + uint8_t uat = amount_type; \ + uint64_t udrops = drops; \ + buf_out[0] = 0x60U +(uat & 0x0FU ); \ + buf_out[1] = 0b01000000 + (( udrops >> 56 ) & 0b00111111 ); \ + buf_out[2] = (udrops >> 48) & 0xFFU; \ + buf_out[3] = (udrops >> 40) & 0xFFU; \ + buf_out[4] = (udrops >> 32) & 0xFFU; \ + buf_out[5] = (udrops >> 24) & 0xFFU; \ + buf_out[6] = (udrops >> 16) & 0xFFU; \ + buf_out[7] = (udrops >> 8) & 0xFFU; \ + buf_out[8] = (udrops >> 0) & 0xFFU; \ + buf_out += ENCODE_DROPS_SIZE; \ + } + + #define _06_XX_ENCODE_DROPS(buf_out, drops, amount_type )\ + ENCODE_DROPS(buf_out, drops, amount_type ); + + #define ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS(buf_out, drops, amAMOUNT ); + #define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS_AMOUNT(buf_out, drops ); + + #define ENCODE_DROPS_FEE(buf_out, drops )\ + ENCODE_DROPS(buf_out, drops, amFEE ); + #define _06_08_ENCODE_DROPS_FEE(buf_out, drops )\ + ENCODE_DROPS_FEE(buf_out, drops ); + + #define ENCODE_TT_SIZE 3 + #define ENCODE_TT(buf_out, tt )\ + {\ + uint8_t utt = tt;\ + buf_out[0] = 0x12U;\ + buf_out[1] =(utt >> 8 ) & 0xFFU;\ + buf_out[2] =(utt >> 0 ) & 0xFFU;\ + buf_out += ENCODE_TT_SIZE; \ + } + #define _01_02_ENCODE_TT(buf_out, tt)\ + ENCODE_TT(buf_out, tt); + + + #define ENCODE_ACCOUNT_SIZE 22 + #define ENCODE_ACCOUNT(buf_out, account_id, account_type)\ + {\ + uint8_t uat = account_type;\ + buf_out[0] = 0x80U + uat;\ + buf_out[1] = 0x14U;\ + *(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\ + *(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\ + *(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\ + buf_out += ENCODE_ACCOUNT_SIZE;\ + } + #define _08_XX_ENCODE_ACCOUNT(buf_out, account_id, account_type)\ + ENCODE_ACCOUNT(buf_out, account_id, account_type); + + #define ENCODE_ACCOUNT_SRC_SIZE 22 + #define ENCODE_ACCOUNT_SRC(buf_out, account_id)\ + ENCODE_ACCOUNT(buf_out, account_id, atACCOUNT); + #define _08_01_ENCODE_ACCOUNT_SRC(buf_out, account_id)\ + ENCODE_ACCOUNT_SRC(buf_out, account_id); + + #define ENCODE_ACCOUNT_DST_SIZE 22 + #define ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION); + #define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT_DST(buf_out, account_id); + + #define ENCODE_ACCOUNT_OWNER_SIZE 22 + #define ENCODE_ACCOUNT_OWNER(buf_out, account_id) \ + ENCODE_ACCOUNT(buf_out, account_id, atOWNER); + #define _08_02_ENCODE_ACCOUNT_OWNER(buf_out, account_id) \ + ENCODE_ACCOUNT_OWNER(buf_out, account_id); + + #define ENCODE_UINT32_COMMON_SIZE 5U + #define ENCODE_UINT32_COMMON(buf_out, i, field)\ + {\ + uint32_t ui = i; \ + uint8_t uf = field; \ + buf_out[0] = 0x20U +(uf & 0x0FU); \ + buf_out[1] =(ui >> 24 ) & 0xFFU; \ + buf_out[2] =(ui >> 16 ) & 0xFFU; \ + buf_out[3] =(ui >> 8 ) & 0xFFU; \ + buf_out[4] =(ui >> 0 ) & 0xFFU; \ + buf_out += ENCODE_UINT32_COMMON_SIZE; \ + } + #define _02_XX_ENCODE_UINT32_COMMON(buf_out, i, field)\ + ENCODE_UINT32_COMMON(buf_out, i, field)\ + + #define ENCODE_UINT32_UNCOMMON_SIZE 6U + #define ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + {\ + uint32_t ui = i; \ + uint8_t uf = field; \ + buf_out[0] = 0x20U; \ + buf_out[1] = uf; \ + buf_out[2] =(ui >> 24 ) & 0xFFU; \ + buf_out[3] =(ui >> 16 ) & 0xFFU; \ + buf_out[4] =(ui >> 8 ) & 0xFFU; \ + buf_out[5] =(ui >> 0 ) & 0xFFU; \ + buf_out += ENCODE_UINT32_UNCOMMON_SIZE; \ + } + #define _02_XX_ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + + #define ENCODE_LLS_SIZE 6U + #define ENCODE_LLS(buf_out, lls )\ + ENCODE_UINT32_UNCOMMON(buf_out, lls, 0x1B ); + #define _02_27_ENCODE_LLS(buf_out, lls )\ + ENCODE_LLS(buf_out, lls ); + + #define ENCODE_FLS_SIZE 6U + #define ENCODE_FLS(buf_out, fls )\ + ENCODE_UINT32_UNCOMMON(buf_out, fls, 0x1A ); + #define _02_26_ENCODE_FLS(buf_out, fls )\ + ENCODE_FLS(buf_out, fls ); + + #define ENCODE_TAG_SRC_SIZE 5 + #define ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x3U ); + #define _02_03_ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_TAG_SRC(buf_out, tag ); + + #define ENCODE_TAG_DST_SIZE 5 + #define ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0xEU ); + #define _02_14_ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_TAG_DST(buf_out, tag ); + + #define ENCODE_SEQUENCE_SIZE 5 + #define ENCODE_SEQUENCE(buf_out, sequence )\ + ENCODE_UINT32_COMMON(buf_out, sequence, 0x4U ); + #define _02_04_ENCODE_SEQUENCE(buf_out, sequence )\ + ENCODE_SEQUENCE(buf_out, sequence ); + + #define ENCODE_FLAGS_SIZE 5 + #define ENCODE_FLAGS(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x2U ); + #define _02_02_ENCODE_FLAGS(buf_out, tag )\ + ENCODE_FLAGS(buf_out, tag ); + + #define ENCODE_SIGNING_PUBKEY_SIZE 35 + #define ENCODE_SIGNING_PUBKEY(buf_out, pkey )\ + {\ + buf_out[0] = 0x73U;\ + buf_out[1] = 0x21U;\ + *(uint64_t*)(buf_out + 2) = *(uint64_t*)(pkey + 0);\ + *(uint64_t*)(buf_out + 10) = *(uint64_t*)(pkey + 8);\ + *(uint64_t*)(buf_out + 18) = *(uint64_t*)(pkey + 16);\ + *(uint64_t*)(buf_out + 26) = *(uint64_t*)(pkey + 24);\ + buf[34] = pkey[32];\ + buf_out += ENCODE_SIGNING_PUBKEY_SIZE;\ + } + + #define _07_03_ENCODE_SIGNING_PUBKEY(buf_out, pkey )\ + ENCODE_SIGNING_PUBKEY(buf_out, pkey ); + + #define ENCODE_SIGNING_PUBKEY_NULL_SIZE 35 + #define ENCODE_SIGNING_PUBKEY_NULL(buf_out )\ + {\ + buf_out[0] = 0x73U;\ + buf_out[1] = 0x21U;\ + *(uint64_t*)(buf_out+2) = 0;\ + *(uint64_t*)(buf_out+10) = 0;\ + *(uint64_t*)(buf_out+18) = 0;\ + *(uint64_t*)(buf_out+25) = 0;\ + buf_out += ENCODE_SIGNING_PUBKEY_NULL_SIZE;\ + } + + #define _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out )\ + ENCODE_SIGNING_PUBKEY_NULL(buf_out ); + + extern int64_t etxn_fee_base ( + uint32_t read_ptr, + uint32_t read_len + ); + extern int64_t etxn_details ( + uint32_t write_ptr, + uint32_t write_len + ); + extern int64_t ledger_seq (void); + + #define PREPARE_PAYMENT_SIMPLE_SIZE 270U + #define PREPARE_PAYMENT_SIMPLE(buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\ + {\ + uint8_t* buf_out = buf_out_master;\ + uint8_t acc[20];\ + uint64_t drops_amount = (drops_amount_raw);\ + uint32_t dest_tag = (dest_tag_raw);\ + uint32_t src_tag = (src_tag_raw);\ + uint32_t cls = (uint32_t)ledger_seq();\ + hook_account(SBUF(acc));\ + _01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \ + _02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \ + _02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \ + _02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* uint32 | size 5 */ \ + _02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \ + _02_26_ENCODE_FLS (buf_out, cls + 1 ); /* uint32 | size 6 */ \ + _02_27_ENCODE_LLS (buf_out, cls + 5 ); /* uint32 | size 6 */ \ + _06_01_ENCODE_DROPS_AMOUNT (buf_out, drops_amount ); /* amount | size 9 */ \ + uint8_t* fee_ptr = buf_out;\ + _06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \ + _07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \ + _08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \ + _08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \ + int64_t edlen = etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_SIZE); /* emitdet | size 1?? */ \ + int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \ + _06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \ + } + + #define UINT16_FROM_BUF(buf)\ + (((uint64_t)((buf)[0]) << 8U) +\ + ((uint64_t)((buf)[1]) << 0U)) + + #define BUFFER_EQUAL_32(buf1, buf2)\ + (\ + *(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\ + *(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\ + *(((uint64_t*)(buf1)) + 2) == *(((uint64_t*)(buf2)) + 2) &&\ + *(((uint64_t*)(buf1)) + 3) == *(((uint64_t*)(buf2)) + 3) &&\ + *(((uint64_t*)(buf1)) + 4) == *(((uint64_t*)(buf2)) + 4) &&\ + *(((uint64_t*)(buf1)) + 5) == *(((uint64_t*)(buf2)) + 5) &&\ + *(((uint64_t*)(buf1)) + 6) == *(((uint64_t*)(buf2)) + 6) &&\ + *(((uint64_t*)(buf1)) + 7) == *(((uint64_t*)(buf2)) + 7)) + + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x,sizeof(#x),__LINE__) + + #define sfDestination ((8U << 16U) + 3U) + + extern int64_t etxn_generation(void); + extern int64_t otxn_generation(void); + extern int64_t otxn_burden(void); + extern int64_t etxn_burden(void); + + int64_t cbak(uint32_t r) + { + // on callback we emit 2 more txns + uint8_t bob[20]; + ASSERT(otxn_field(SBUF(bob), sfDestination) == 20); + + ASSERT(otxn_generation() + 1 == etxn_generation()); + + ASSERT(etxn_burden() == PREREQUISITE_NOT_MET); + + ASSERT(etxn_reserve(2) == 2); + + ASSERT(otxn_burden() > 0); + ASSERT(etxn_burden() == otxn_burden() * 2); + + uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE]; + PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0); + + uint8_t hash1[32]; + ASSERT(emit(SBUF(hash1), SBUF(tx)) == 32); + + ASSERT(etxn_details(tx + 132, 138) == 138); + uint8_t hash2[32]; + ASSERT(emit(SBUF(hash2), SBUF(tx)) == 32); + + ASSERT(!BUFFER_EQUAL_32(hash1, hash2)); + + return accept(0,0,0); + } + + int64_t hook(uint32_t r) + { + _g(1,1); + + etxn_reserve(1); + + // bounds checks + ASSERT(emit(1000000, 32, 0, 32) == OUT_OF_BOUNDS); + ASSERT(emit(0,1000000, 0, 32) == OUT_OF_BOUNDS); + ASSERT(emit(0,32, 1000000, 32) == OUT_OF_BOUNDS); + ASSERT(emit(0,32, 0, 1000000) == OUT_OF_BOUNDS); + + ASSERT(otxn_generation() == 0); + ASSERT(otxn_burden == 1); + + uint8_t bob[20]; + ASSERT(otxn_param(SBUF(bob), "bob", 3) == 20); + + uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE]; + PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0); + + uint8_t hash[32]; + ASSERT(emit(SBUF(hash), SBUF(tx)) == 32); + + return accept(0,0,0); + } + )[test.hook]"]; + + env(ripple::test::jtx::hook(alice, {{hsov1(hook, 1, overrideFlag)}}, 0), + M("set emit"), + HSFEE); + env.close(); + + Json::Value invoke; + invoke[jss::TransactionType] = "Invoke"; + invoke[jss::Account] = alice.human(); + + Json::Value params{Json::arrayValue}; + params[0U][jss::HookParameter][jss::HookParameterName] = + strHex(std::string("bob")); + params[0U][jss::HookParameter][jss::HookParameterValue] = + strHex(bob.id()); + + invoke[jss::HookParameters] = params; + + env(invoke, M("test emit"), fee(XRP(1))); + + bool const fixV2 = env.current()->rules().enabled(fixXahauV2); + + std::optional 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(node) + .getField(sfNewFields) + .downcast(); + + auto const& et = const_cast(nf) + .getField(sfEmittedTxn) + .downcast(); + + auto const& em = const_cast(et) + .getField(sfEmitDetails) + .downcast(); + + BEAST_EXPECT(em.getFieldU32(sfEmitGeneration) == 1); + BEAST_EXPECT(em.getFieldU64(sfEmitBurden) == 1); + + Blob txBlob = et.getSerializer().getData(); + auto const tx = std::make_unique( + 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(*(i.first)) + .getField(sfEmitDetails) + .downcast(); + BEAST_EXPECT(em.getFieldU64(sfEmitBurden) == burden_expected); + BEAST_EXPECT(em.getFieldU32(sfEmitGeneration) == j + 2); + BEAST_REQUIRE(i.second->isFieldPresent(sfHookExecutions)); + auto const hookExecutions = + i.second->getFieldArray(sfHookExecutions); + BEAST_EXPECT(hookExecutions.size() == 1); + BEAST_EXPECT( + hookExecutions[0].getFieldU64(sfHookReturnCode) == 0); + BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3); + BEAST_EXPECT( + hookExecutions[0].getFieldU16(sfHookEmitCount) == 2); + if (fixV2) + BEAST_EXPECT(hookExecutions[0].getFieldU32(sfFlags) == 2); + } + env.close(); + burden_expected *= 2U; + } + + { + auto const ledger = env.closed(); + int txcount = 0; + for (auto& i : ledger->txs) + { + txcount++; + auto const& em = const_cast(*(i.first)) + .getField(sfEmitDetails) + .downcast(); + BEAST_EXPECT(em.getFieldU64(sfEmitBurden) == 256); + BEAST_EXPECT(em.getFieldU32(sfEmitGeneration) == 9); + BEAST_REQUIRE(i.second->isFieldPresent(sfHookExecutions)); + auto const hookExecutions = + i.second->getFieldArray(sfHookExecutions); + BEAST_EXPECT(hookExecutions.size() == 1); + BEAST_EXPECT( + hookExecutions[0].getFieldU64(sfHookReturnCode) == + 283); // emission failure on first emit + if (fixV2) + BEAST_EXPECT(hookExecutions[0].getFieldU32(sfFlags) == 2); + } + BEAST_EXPECT(txcount == 256); + } + + // next close will lead to zero transactions + env.close(); + { + auto const ledger = env.closed(); + int txcount = 0; + for ([[maybe_unused]] auto& i : ledger->txs) + txcount++; + BEAST_EXPECT(txcount == 0); + } + } + void testWithFeatures(FeatureBitset features) { testHooksOwnerDir(features); + testHooksDisabled(features); + testTxStructure(features); + // testInferHookSetOperation(); // Not Version Specific + // testParams(features); // Not Version Specific + // testGrants(features); // Not Version Specific + + testInstall(features); + testDelete(features); + testNSDelete(features); + testCreate(features); + testUpdate(features); + testWithTickets(features); + + // DA TODO: illegalfunc_wasm + // testWasm(features); + test_accept(features); + test_rollback(features); + + // testGuards(features); // Not Used in JSHooks + + // test_emit(features); // + // // test_etxn_burden(features); // tested above + // // test_etxn_generation(features); // tested above + // // test_otxn_burden(features); // tested above + // // test_otxn_generation(features); // tested above + // test_etxn_details(features); // + // test_etxn_fee_base(features); // + // test_etxn_nonce(features); // + // test_etxn_reserve(features); // + // test_fee_base(features); // + + // test_otxn_field(features); // + + // test_ledger_keylet(features); // + + // test_float_compare(features); // + // test_float_divide(features); // + // test_float_int(features); // + // test_float_invert(features); // + // test_float_log(features); // + // test_float_mantissa(features); // + // test_float_mulratio(features); // + // test_float_multiply(features); // + // test_float_negate(features); // + // test_float_one(features); // + // test_float_root(features); // + // test_float_set(features); // + // test_float_sign(features); // + // test_float_sto(features); // + // test_float_sto_set(features); // + // test_float_sum(features); // + + // test_hook_account(features); // + // test_hook_again(features); // + // test_hook_hash(features); // + // test_hook_param(features); // + // test_hook_param_set(features); // + // test_hook_pos(features); // + // test_hook_skip(features); // + + // test_ledger_last_hash(features); // + // test_ledger_last_time(features); // + // test_ledger_nonce(features); // + // test_ledger_seq(features); // + + // test_meta_slot(features); // + + // test_otxn_id(features); // + // test_otxn_slot(features); // + // test_otxn_type(features); // + // test_otxn_param(features); // + + // test_slot(features); // + // test_slot_clear(features); // + // test_slot_count(features); // + // test_slot_float(features); // + // test_slot_set(features); // + // test_slot_size(features); // + // test_slot_subarray(features); // + // test_slot_subfield(features); // + // test_slot_type(features); // + + // test_state(features); // + // test_state_foreign(features); // + // test_state_foreign_set(features); // + // test_state_foreign_set_max(features); // + // test_state_set(features); // + + // test_sto_emplace(features); // + // test_sto_erase(features); // + // test_sto_subarray(features); // + // test_sto_subfield(features); // + // test_sto_validate(features); // + + // test_trace(features); // + // test_trace_float(features); // + // test_trace_num(features); // + + // test_util_accid(features); // + // test_util_keylet(features); // + // test_util_raddr(features); // + // test_util_sha512h(features); // + // test_util_verify(features); // } void @@ -143,13 +2129,65 @@ private: TestHook accept_wasm = // WASM: 0 jswasm[ R"[test.hook]( - const accept = (msg, code) => {} const Hook = (arg) => { - return accept("0",0); + return accept("0", 0); } )[test.hook]"]; HASH_WASM(accept); + + TestHook rollback_wasm = // WASM: 1 + jswasm[ + R"[test.hook]( + const Hook = (arg) => { + return rollback("0", 0); + } + )[test.hook]"]; + + HASH_WASM(rollback); + + TestHook illegalfunc_wasm = // WASM: 3 + jswasm[ + R"[test.hook]( + console.log("HERE"); + return accept(ret, 0); + } + )[test.hook]"]; + + TestHook long_wasm = // WASM: 4 + jswasm[ + R"[test.hook]( + const M_REPEAT_10 = (X) => X.repeat(10); + const M_REPEAT_100 = (X) => M_REPEAT_10(X).repeat(10); + const M_REPEAT_1000 = (X) => M_REPEAT_100(X).repeat(10); + const Hook = (arg) => { + const ret = M_REPEAT_1000("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz01234567890123"); + return accept(ret, 0); + } + )[test.hook]"]; + + TestHook makestate_wasm = // WASM: 5 + jswasm[ + R"[test.hook]( + const Hook = (arg) => { + const test_key = "0000000000000000000000000000000000000000000000006b657900"; + const test_value = "76616C756500"; + return accept("0", state_set(test_value, test_key)); + } + )[test.hook]"]; + + HASH_WASM(makestate); + + // this is just used as a second small hook with a unique hash + TestHook accept2_wasm = // WASM: 6 + jswasm[ + R"[test.hook]( + const Hook = (arg) => { + return accept("0", 2); + } + )[test.hook]"]; + + HASH_WASM(accept2); }; BEAST_DEFINE_TESTSUITE(SetJSHook, app, ripple); } // namespace test diff --git a/src/test/app/SetJSHook_wasm.h b/src/test/app/SetJSHook_wasm.h index f8aced925..5cd05f781 100644 --- a/src/test/app/SetJSHook_wasm.h +++ b/src/test/app/SetJSHook_wasm.h @@ -11,30 +11,157 @@ namespace test { std::map> jswasm = { /* ==== WASM: 0 ==== */ {R"[test.hook]( - const accept = (msg, code) => {} const Hook = (arg) => { - return accept("0",0); + return accept("0", 0); } )[test.hook]", - {0x43U, 0x06U, 0x0cU, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x08U, - 0x48U, 0x6fU, 0x6fU, 0x6bU, 0x28U, 0x77U, 0x61U, 0x73U, 0x6dU, 0x6aU, - 0x73U, 0x2fU, 0x74U, 0x65U, 0x73U, 0x74U, 0x2dU, 0x30U, 0x2dU, 0x67U, - 0x65U, 0x6eU, 0x2eU, 0x6aU, 0x73U, 0x06U, 0x6dU, 0x73U, 0x67U, 0x08U, - 0x63U, 0x6fU, 0x64U, 0x65U, 0x06U, 0x61U, 0x72U, 0x67U, 0x0cU, 0x00U, - 0x06U, 0x00U, 0xa2U, 0x01U, 0x00U, 0x01U, 0x00U, 0x01U, 0x00U, 0x02U, - 0x32U, 0x01U, 0xa4U, 0x01U, 0x00U, 0x00U, 0x00U, 0x3fU, 0xe3U, 0x00U, - 0x00U, 0x00U, 0x80U, 0x3fU, 0xe4U, 0x00U, 0x00U, 0x00U, 0x80U, 0x3eU, - 0xe3U, 0x00U, 0x00U, 0x00U, 0x80U, 0x3eU, 0xe4U, 0x00U, 0x00U, 0x00U, - 0x80U, 0xc2U, 0x00U, 0x4dU, 0xe3U, 0x00U, 0x00U, 0x00U, 0x3aU, 0xe3U, - 0x00U, 0x00U, 0x00U, 0xc2U, 0x01U, 0x4dU, 0xe4U, 0x00U, 0x00U, 0x00U, - 0x3aU, 0xe4U, 0x00U, 0x00U, 0x00U, 0xc7U, 0x28U, 0xcaU, 0x03U, 0x01U, - 0x03U, 0x3dU, 0x40U, 0x41U, 0x0cU, 0x02U, 0x06U, 0x00U, 0x00U, 0x02U, - 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x01U, 0x02U, 0xccU, 0x03U, 0x00U, - 0x01U, 0x00U, 0xceU, 0x03U, 0x00U, 0x01U, 0x00U, 0x29U, 0xcaU, 0x03U, - 0x02U, 0x00U, 0x0cU, 0x02U, 0x06U, 0x00U, 0x00U, 0x01U, 0x00U, 0x01U, - 0x03U, 0x00U, 0x01U, 0x0bU, 0x01U, 0xd0U, 0x03U, 0x00U, 0x01U, 0x00U, - 0x38U, 0xe3U, 0x00U, 0x00U, 0x00U, 0xc1U, 0x00U, 0xb7U, 0x23U, 0x02U, - 0x00U, 0xcaU, 0x03U, 0x03U, 0x01U, 0x03U, 0x07U, 0x02U, 0x30U}}, + {0x43U, 0x04U, 0x08U, 0x48U, 0x6fU, 0x6fU, 0x6bU, 0x28U, 0x77U, 0x61U, + 0x73U, 0x6dU, 0x6aU, 0x73U, 0x2fU, 0x74U, 0x65U, 0x73U, 0x74U, 0x2dU, + 0x30U, 0x2dU, 0x67U, 0x65U, 0x6eU, 0x2eU, 0x6aU, 0x73U, 0x06U, 0x61U, + 0x72U, 0x67U, 0x0cU, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x0cU, + 0x00U, 0x06U, 0x00U, 0xa2U, 0x01U, 0x00U, 0x01U, 0x00U, 0x01U, 0x00U, + 0x01U, 0x1aU, 0x01U, 0xa4U, 0x01U, 0x00U, 0x00U, 0x00U, 0x3fU, 0xe3U, + 0x00U, 0x00U, 0x00U, 0x80U, 0x3eU, 0xe3U, 0x00U, 0x00U, 0x00U, 0x80U, + 0xc2U, 0x00U, 0x4dU, 0xe3U, 0x00U, 0x00U, 0x00U, 0x3aU, 0xe3U, 0x00U, + 0x00U, 0x00U, 0xc7U, 0x28U, 0xc8U, 0x03U, 0x01U, 0x04U, 0x1fU, 0x00U, + 0x06U, 0x08U, 0x0cU, 0x02U, 0x06U, 0x00U, 0x00U, 0x01U, 0x00U, 0x01U, + 0x03U, 0x00U, 0x01U, 0x0bU, 0x01U, 0xcaU, 0x03U, 0x00U, 0x01U, 0x00U, + 0x38U, 0xe6U, 0x00U, 0x00U, 0x00U, 0xc1U, 0x00U, 0xb7U, 0x23U, 0x02U, + 0x00U, 0xc8U, 0x03U, 0x02U, 0x01U, 0x03U, 0x07U, 0x02U, 0x30U}}, + + /* ==== WASM: 1 ==== */ + {R"[test.hook]( + const Hook = (arg) => { + return rollback("0", 0); + } + )[test.hook]", + {0x43U, 0x04U, 0x08U, 0x48U, 0x6fU, 0x6fU, 0x6bU, 0x28U, 0x77U, 0x61U, + 0x73U, 0x6dU, 0x6aU, 0x73U, 0x2fU, 0x74U, 0x65U, 0x73U, 0x74U, 0x2dU, + 0x31U, 0x2dU, 0x67U, 0x65U, 0x6eU, 0x2eU, 0x6aU, 0x73U, 0x06U, 0x61U, + 0x72U, 0x67U, 0x10U, 0x72U, 0x6fU, 0x6cU, 0x6cU, 0x62U, 0x61U, 0x63U, + 0x6bU, 0x0cU, 0x00U, 0x06U, 0x00U, 0xa2U, 0x01U, 0x00U, 0x01U, 0x00U, + 0x01U, 0x00U, 0x01U, 0x1aU, 0x01U, 0xa4U, 0x01U, 0x00U, 0x00U, 0x00U, + 0x3fU, 0xe3U, 0x00U, 0x00U, 0x00U, 0x80U, 0x3eU, 0xe3U, 0x00U, 0x00U, + 0x00U, 0x80U, 0xc2U, 0x00U, 0x4dU, 0xe3U, 0x00U, 0x00U, 0x00U, 0x3aU, + 0xe3U, 0x00U, 0x00U, 0x00U, 0xc7U, 0x28U, 0xc8U, 0x03U, 0x01U, 0x04U, + 0x1fU, 0x00U, 0x06U, 0x08U, 0x0cU, 0x02U, 0x06U, 0x00U, 0x00U, 0x01U, + 0x00U, 0x01U, 0x03U, 0x00U, 0x01U, 0x0bU, 0x01U, 0xcaU, 0x03U, 0x00U, + 0x01U, 0x00U, 0x38U, 0xe6U, 0x00U, 0x00U, 0x00U, 0xc1U, 0x00U, 0xb7U, + 0x23U, 0x02U, 0x00U, 0xc8U, 0x03U, 0x02U, 0x01U, 0x03U, 0x07U, 0x02U, + 0x30U}}, + + /* ==== WASM: 2 ==== */ + {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]", + {0x43U, 0x0bU, 0x16U, 0x4dU, 0x5fU, 0x52U, 0x45U, 0x50U, 0x45U, 0x41U, + 0x54U, 0x5fU, 0x31U, 0x30U, 0x18U, 0x4dU, 0x5fU, 0x52U, 0x45U, 0x50U, + 0x45U, 0x41U, 0x54U, 0x5fU, 0x31U, 0x30U, 0x30U, 0x1aU, 0x4dU, 0x5fU, + 0x52U, 0x45U, 0x50U, 0x45U, 0x41U, 0x54U, 0x5fU, 0x31U, 0x30U, 0x30U, + 0x30U, 0x08U, 0x48U, 0x6fU, 0x6fU, 0x6bU, 0x28U, 0x77U, 0x61U, 0x73U, + 0x6dU, 0x6aU, 0x73U, 0x2fU, 0x74U, 0x65U, 0x73U, 0x74U, 0x2dU, 0x32U, + 0x2dU, 0x67U, 0x65U, 0x6eU, 0x2eU, 0x6aU, 0x73U, 0x02U, 0x58U, 0x0cU, + 0x72U, 0x65U, 0x70U, 0x65U, 0x61U, 0x74U, 0x06U, 0x61U, 0x72U, 0x67U, + 0x06U, 0x72U, 0x65U, 0x74U, 0x84U, 0x01U, 0x61U, 0x62U, 0x63U, 0x64U, + 0x65U, 0x66U, 0x67U, 0x68U, 0x69U, 0x6aU, 0x6bU, 0x6cU, 0x6dU, 0x6eU, + 0x6fU, 0x70U, 0x71U, 0x72U, 0x73U, 0x74U, 0x75U, 0x76U, 0x77U, 0x78U, + 0x79U, 0x7aU, 0x61U, 0x62U, 0x63U, 0x64U, 0x65U, 0x66U, 0x67U, 0x68U, + 0x69U, 0x6aU, 0x6bU, 0x6cU, 0x6dU, 0x6eU, 0x6fU, 0x70U, 0x71U, 0x72U, + 0x73U, 0x74U, 0x75U, 0x76U, 0x77U, 0x78U, 0x79U, 0x7aU, 0x30U, 0x31U, + 0x32U, 0x33U, 0x34U, 0x35U, 0x36U, 0x37U, 0x38U, 0x39U, 0x30U, 0x31U, + 0x32U, 0x33U, 0x0cU, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x0cU, + 0x00U, 0x06U, 0x00U, 0xa2U, 0x01U, 0x00U, 0x01U, 0x00U, 0x01U, 0x00U, + 0x04U, 0x62U, 0x01U, 0xa4U, 0x01U, 0x00U, 0x00U, 0x00U, 0x3fU, 0xe3U, + 0x00U, 0x00U, 0x00U, 0x80U, 0x3fU, 0xe4U, 0x00U, 0x00U, 0x00U, 0x80U, + 0x3fU, 0xe5U, 0x00U, 0x00U, 0x00U, 0x80U, 0x3fU, 0xe6U, 0x00U, 0x00U, + 0x00U, 0x80U, 0x3eU, 0xe3U, 0x00U, 0x00U, 0x00U, 0x80U, 0x3eU, 0xe4U, + 0x00U, 0x00U, 0x00U, 0x80U, 0x3eU, 0xe5U, 0x00U, 0x00U, 0x00U, 0x80U, + 0x3eU, 0xe6U, 0x00U, 0x00U, 0x00U, 0x80U, 0xc2U, 0x00U, 0x4dU, 0xe3U, + 0x00U, 0x00U, 0x00U, 0x3aU, 0xe3U, 0x00U, 0x00U, 0x00U, 0xc2U, 0x01U, + 0x4dU, 0xe4U, 0x00U, 0x00U, 0x00U, 0x3aU, 0xe4U, 0x00U, 0x00U, 0x00U, + 0xc2U, 0x02U, 0x4dU, 0xe5U, 0x00U, 0x00U, 0x00U, 0x3aU, 0xe5U, 0x00U, + 0x00U, 0x00U, 0xc2U, 0x03U, 0x4dU, 0xe6U, 0x00U, 0x00U, 0x00U, 0x3aU, + 0xe6U, 0x00U, 0x00U, 0x00U, 0xc7U, 0x28U, 0xceU, 0x03U, 0x01U, 0x07U, + 0x79U, 0x7cU, 0x3fU, 0x3fU, 0x00U, 0x0cU, 0x08U, 0x0cU, 0x02U, 0x06U, + 0x00U, 0x00U, 0x01U, 0x00U, 0x01U, 0x03U, 0x00U, 0x00U, 0x0bU, 0x01U, + 0xd0U, 0x03U, 0x00U, 0x01U, 0x00U, 0xd3U, 0x42U, 0xe9U, 0x00U, 0x00U, + 0x00U, 0xbfU, 0x0aU, 0x25U, 0x01U, 0x00U, 0xceU, 0x03U, 0x02U, 0x00U, + 0x0cU, 0x02U, 0x06U, 0x00U, 0x00U, 0x01U, 0x00U, 0x01U, 0x03U, 0x00U, + 0x00U, 0x11U, 0x01U, 0xd0U, 0x03U, 0x00U, 0x01U, 0x00U, 0x38U, 0xe3U, + 0x00U, 0x00U, 0x00U, 0xd3U, 0xf1U, 0x42U, 0xe9U, 0x00U, 0x00U, 0x00U, + 0xbfU, 0x0aU, 0x25U, 0x01U, 0x00U, 0xceU, 0x03U, 0x03U, 0x00U, 0x0cU, + 0x02U, 0x06U, 0x00U, 0x00U, 0x01U, 0x00U, 0x01U, 0x03U, 0x00U, 0x00U, + 0x11U, 0x01U, 0xd0U, 0x03U, 0x00U, 0x01U, 0x00U, 0x38U, 0xe4U, 0x00U, + 0x00U, 0x00U, 0xd3U, 0xf1U, 0x42U, 0xe9U, 0x00U, 0x00U, 0x00U, 0xbfU, + 0x0aU, 0x25U, 0x01U, 0x00U, 0xceU, 0x03U, 0x04U, 0x00U, 0x0cU, 0x02U, + 0x06U, 0x00U, 0x00U, 0x01U, 0x01U, 0x01U, 0x03U, 0x00U, 0x00U, 0x1bU, + 0x02U, 0xd4U, 0x03U, 0x00U, 0x01U, 0x00U, 0xd6U, 0x03U, 0x01U, 0x00U, + 0x30U, 0x61U, 0x00U, 0x00U, 0x38U, 0xe5U, 0x00U, 0x00U, 0x00U, 0x04U, + 0xecU, 0x00U, 0x00U, 0x00U, 0xf1U, 0xcbU, 0x38U, 0xedU, 0x00U, 0x00U, + 0x00U, 0x62U, 0x00U, 0x00U, 0xb7U, 0x23U, 0x02U, 0x00U, 0xceU, 0x03U, + 0x05U, 0x02U, 0x12U, 0x3fU}}, + + /* ==== WASM: 3 ==== */ + {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]", + {0x43U, 0x09U, 0x08U, 0x48U, 0x6fU, 0x6fU, 0x6bU, 0x28U, 0x77U, 0x61U, + 0x73U, 0x6dU, 0x6aU, 0x73U, 0x2fU, 0x74U, 0x65U, 0x73U, 0x74U, 0x2dU, + 0x33U, 0x2dU, 0x67U, 0x65U, 0x6eU, 0x2eU, 0x6aU, 0x73U, 0x06U, 0x61U, + 0x72U, 0x67U, 0x10U, 0x74U, 0x65U, 0x73U, 0x74U, 0x5fU, 0x6bU, 0x65U, + 0x79U, 0x14U, 0x74U, 0x65U, 0x73U, 0x74U, 0x5fU, 0x76U, 0x61U, 0x6cU, + 0x75U, 0x65U, 0x70U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, + 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, + 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, + 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, + 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, + 0x30U, 0x36U, 0x62U, 0x36U, 0x35U, 0x37U, 0x39U, 0x30U, 0x30U, 0x18U, + 0x37U, 0x36U, 0x36U, 0x31U, 0x36U, 0x43U, 0x37U, 0x35U, 0x36U, 0x35U, + 0x30U, 0x30U, 0x0cU, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x12U, + 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5fU, 0x73U, 0x65U, 0x74U, 0x0cU, + 0x00U, 0x06U, 0x00U, 0xa2U, 0x01U, 0x00U, 0x01U, 0x00U, 0x01U, 0x00U, + 0x01U, 0x1aU, 0x01U, 0xa4U, 0x01U, 0x00U, 0x00U, 0x00U, 0x3fU, 0xe3U, + 0x00U, 0x00U, 0x00U, 0x80U, 0x3eU, 0xe3U, 0x00U, 0x00U, 0x00U, 0x80U, + 0xc2U, 0x00U, 0x4dU, 0xe3U, 0x00U, 0x00U, 0x00U, 0x3aU, 0xe3U, 0x00U, + 0x00U, 0x00U, 0xc7U, 0x28U, 0xc8U, 0x03U, 0x01U, 0x04U, 0x1fU, 0x00U, + 0x06U, 0x0cU, 0x0cU, 0x02U, 0x06U, 0x00U, 0x00U, 0x01U, 0x02U, 0x01U, + 0x05U, 0x00U, 0x01U, 0x28U, 0x03U, 0xcaU, 0x03U, 0x00U, 0x01U, 0x00U, + 0xccU, 0x03U, 0x01U, 0x00U, 0x30U, 0xceU, 0x03U, 0x01U, 0x01U, 0x30U, + 0x61U, 0x01U, 0x00U, 0x61U, 0x00U, 0x00U, 0x04U, 0xe8U, 0x00U, 0x00U, + 0x00U, 0xcbU, 0x04U, 0xe9U, 0x00U, 0x00U, 0x00U, 0xccU, 0x38U, 0xeaU, + 0x00U, 0x00U, 0x00U, 0xc1U, 0x00U, 0x38U, 0xebU, 0x00U, 0x00U, 0x00U, + 0x62U, 0x01U, 0x00U, 0x62U, 0x00U, 0x00U, 0xf2U, 0x23U, 0x02U, 0x00U, + 0xc8U, 0x03U, 0x02U, 0x03U, 0x21U, 0x21U, 0x21U, 0x07U, 0x02U, 0x30U}}, + + /* ==== WASM: 4 ==== */ + {R"[test.hook]( + const Hook = (arg) => { + return accept("0", 2); + } + )[test.hook]", + {0x43U, 0x04U, 0x08U, 0x48U, 0x6fU, 0x6fU, 0x6bU, 0x28U, 0x77U, 0x61U, + 0x73U, 0x6dU, 0x6aU, 0x73U, 0x2fU, 0x74U, 0x65U, 0x73U, 0x74U, 0x2dU, + 0x34U, 0x2dU, 0x67U, 0x65U, 0x6eU, 0x2eU, 0x6aU, 0x73U, 0x06U, 0x61U, + 0x72U, 0x67U, 0x0cU, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x0cU, + 0x00U, 0x06U, 0x00U, 0xa2U, 0x01U, 0x00U, 0x01U, 0x00U, 0x01U, 0x00U, + 0x01U, 0x1aU, 0x01U, 0xa4U, 0x01U, 0x00U, 0x00U, 0x00U, 0x3fU, 0xe3U, + 0x00U, 0x00U, 0x00U, 0x80U, 0x3eU, 0xe3U, 0x00U, 0x00U, 0x00U, 0x80U, + 0xc2U, 0x00U, 0x4dU, 0xe3U, 0x00U, 0x00U, 0x00U, 0x3aU, 0xe3U, 0x00U, + 0x00U, 0x00U, 0xc7U, 0x28U, 0xc8U, 0x03U, 0x01U, 0x04U, 0x1fU, 0x00U, + 0x06U, 0x08U, 0x0cU, 0x02U, 0x06U, 0x00U, 0x00U, 0x01U, 0x00U, 0x01U, + 0x03U, 0x00U, 0x01U, 0x0bU, 0x01U, 0xcaU, 0x03U, 0x00U, 0x01U, 0x00U, + 0x38U, 0xe6U, 0x00U, 0x00U, 0x00U, 0xc1U, 0x00U, 0xb9U, 0x23U, 0x02U, + 0x00U, 0xc8U, 0x03U, 0x02U, 0x01U, 0x03U, 0x07U, 0x02U, 0x30U}}, }; }