//------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled Copyright (c) 2012-2016 Ripple Labs Inc. 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 #include #include #include #include #include #include #include #include #include #include #include namespace ripple { namespace test { #define DEBUG_TESTS 1 using TestHook = std::vector const&; class JSSHasher { public: size_t operator()(const Json::StaticString& n) const { return std::hash{}(n.c_str()); } }; class JSSEq { public: bool operator()(const Json::StaticString& a, const Json::StaticString& b) const { return a == b; } }; using JSSMap = std::unordered_map; // 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); class SetHook0_test : public beast::unit_test::suite { private: // helper void static overrideFlag(Json::Value& jv) { jv[jss::Flags] = hsfOVERRIDE; } public: // This is a large fee, large enough that we can set most small test hooks // without running into fee issues we only want to test fee code specifically in // fee unit tests, the rest of the time we want to ignore it. #define HSFEE fee(100'000'000) #define M(m) memo(m, "", "") std::unique_ptr makePageCapConfig( FeatureBitset features, uint32_t networkID, std::string fee, std::string a_res, std::string o_res, uint32_t ledgerID) { using namespace jtx; Json::Value jsonValue; Json::Reader reader; std::string base_genesis = R"json({ "ledger": { "accepted": true, "accountState": [ { "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "Balance": "100000000000000000", "Flags": 0, "LedgerEntryType": "AccountRoot", "OwnerCount": 0, "PreviousTxnID": "A92EF82C3C68F771927E3892A2F708F12CBD492EF68A860F042E4053C8EC6C8D", "PreviousTxnLgrSeq": 0, "Sequence": 1, "index": "2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8" }, { "Amendments": [], "Flags": 0, "LedgerEntryType": "Amendments", "index": "7DB0788C020F02780A673DC74757F23823FA3014C1866E72CC4CD8B226CD6EF4" }, { "BaseFee": "A", "Flags": 0, "LedgerEntryType": "FeeSettings", "ReferenceFeeUnits": 10, "ReserveBase": 1000000, "ReserveIncrement": 200000, "XahauActivationLgrSeq": 0, "index": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A651" }, { "Flags": 0, "IndexNext": "40000", "IndexPrevious": "3fffe", "Indexes": [], "LedgerEntryType": "DirectoryNode", "Owner": "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn", "RootIndex": "EC1A88838E9ADA27E8ED583917C1530D2F48C3A3C93F50EDAD662D662E9BCC76", "index": "EC1A88838E9ADA27E8ED583917C1530D2F48C3A3C93F50EDAD662D662E9BCC76" }, { "Flags": 0, "IndexNext": "3fffe", "IndexPrevious": "3fffd", "Indexes": [], "LedgerEntryType": "DirectoryNode", "Owner": "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn", "RootIndex": "EC1A88838E9ADA27E8ED583917C1530D2F48C3A3C93F50EDAD662D662E9BCC76", "index": "4A5F3F9E6762A4F89FFCD385FF2309E1F7D1309321BFEEA61D5C9ACB768DB61B" }, { "Flags": 0, "Indexes": [], "LedgerEntryType": "DirectoryNode", "Owner": "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn", "RootIndex": "A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D", "index": "A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D" }, { "Account": "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn", "Balance": "99999899000000", "Flags": 8388608, "LedgerEntryType": "AccountRoot", "HookNamespaces": [ "0000000000000000000000000000000000000000000000000000000000000000" ], "HookStateCount": 8388576, "OwnerCount": 8388577, "PreviousTxnID": "A92EF82C3C68F771927E3892A2F708F12CBD492EF68A860F042E4053C8EC6C8D", "PreviousTxnLgrSeq": 0, "Sequence": 3, "index": "92FA6A9FC8EA6018D5D16532D7795C91BFB0831355BDFDA177E86C8BF997985F" } ], "account_hash": "5DF3A98772FB73E782B8740E87885C6BAD9BA486422E3626DEF968AD2CB2C514", "close_flags": 0, "close_time": 0, "close_time_human": "2000-Jan-01 00:00:00.000000", "close_time_resolution": 10, "closed": true, "hash": "56DA0940767AC2F17F0E384F04816002403D0756432B9D503DDA20128A2AAF11", "ledger_hash": "56DA0940767AC2F17F0E384F04816002403D0756432B9D503DDA20128A2AAF11", "ledger_index": "0", "parent_close_time": 0, "parent_hash": "56DA0940767AC2F17F0E384F04816002403D0756432B9D503DDA20128A2AAF11", "seqNum": "5", "totalCoins": "100000000000000000", "total_coins": "100000000000000000", "transaction_hash": "9A77D1D1A4B36DA77B9C4DC63FDEB8F821741D157802F9C42A6ED86003D8B4A0", "transactions": [] }, "ledger_current_index": 0, "status": "success", "validated": true })json"; reader.parse(base_genesis, jsonValue); foreachFeature(features, [&](uint256 const& feature) { std::string featureName = featureToName(feature); std::optional featureHash = getRegisteredFeature(featureName); if (featureHash.has_value()) { std::string hashString = to_string(featureHash.value()); jsonValue["ledger"]["accountState"][1]["Amendments"].append( hashString); } }); jsonValue["ledger_current_index"] = ledgerID; jsonValue["ledger"]["ledger_index"] = to_string(ledgerID); jsonValue["ledger"]["seqNum"] = to_string(ledgerID); return envconfig([&](std::unique_ptr cfg) { cfg->NETWORK_ID = networkID; cfg->START_LEDGER = jsonValue.toStyledString(); cfg->START_UP = Config::LOAD_JSON; Section config; config.append( {"reference_fee = " + fee, "account_reserve = " + a_res, "owner_reserve = " + o_res}); auto setup = setup_FeeVote(config); cfg->FEES = setup; return cfg; }); } 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, {{hso(accept_wasm, overrideFlag)}}, 0), HSFEE, ter(tesSUCCESS)); env.close(); env(ripple::test::jtx::hook( alice, {{hso(accept_wasm, 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; bool const fixV1 = env.current()->rules().enabled(fixXahauV1); auto const txResult = fixV1 ? ter(tesSUCCESS) : ter(tefBAD_LEDGER); env(jv, HSFEE, txResult); 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); // RH TODO: does it matter that passing malformed txn here gives back // temMALFORMED (and not disabled)? env(ripple::test::jtx::hook( alice, {{hso(accept_wasm, 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, std::vector{}, 0), M("Must have a non-empty hooks field"), HSFEE, ter(temMALFORMED)); env(ripple::test::jtx::hook( alice, {{hso(accept_wasm), hso(accept_wasm), hso(accept_wasm), hso(accept_wasm), hso(accept_wasm), hso(accept_wasm), hso(accept_wasm), hso(accept_wasm), hso(accept_wasm), hso(accept_wasm), hso(accept_wasm)}}, 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 testInvalidTxFlags(FeatureBitset features) { testcase("Checks invalid tx flags"); using namespace jtx; for (bool const withFixInvalidTxFlags : {false, true}) { Env env{ *this, withFixInvalidTxFlags ? features : features - fixInvalidTxFlags}; auto const alice = Account{"alice"}; env.fund(XRP(10000), alice); env.close(); Json::Value jv = ripple::test::jtx::hook(alice, {{{hso_delete()}}}, 0); jv[jss::Flags] = tfUniversalMask; env(jv, M("Invalid SetHook flags"), HSFEE, withFixInvalidTxFlags ? ter(temINVALID_FLAG) : ter(tesSUCCESS)); env.close(); } } void testGrants(FeatureBitset features) { testcase("Checks malformed grants on install 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}; // check too many grants { Json::Value iv; iv[jss::HookHash] = to_string(uint256{beast::zero}); Json::Value grants{Json::arrayValue}; for (uint32_t i = 0; i < 9; ++i) { Json::Value pv; Json::Value piv; piv[jss::HookHash] = to_string(uint256{i}); pv[jss::HookGrant] = piv; grants[i] = pv; } iv[jss::HookGrants] = grants; jv[jss::Hooks][0U][jss::Hook] = iv; env(jv, M("HSO must not include more than 8 grants"), HSFEE, ter(temMALFORMED)); env.close(); } // check wrong inner type { Json::Value iv; iv[jss::HookHash] = to_string(uint256{beast::zero}); Json::Value grants{Json::arrayValue}; grants[0U] = Json::Value{}; grants[0U][jss::Memo] = Json::Value{}; grants[0U][jss::Memo][jss::MemoFormat] = strHex(std::string(12, 'a')); grants[0U][jss::Memo][jss::MemoData] = strHex(std::string(12, 'a')); iv[jss::HookGrants] = grants; jv[jss::Hooks][0U][jss::Hook] = iv; env(jv, M("HSO grant array can only contain HookGrant objects"), HSFEE, ter(temMALFORMED)); env.close(); } } void testParams(FeatureBitset features) { testcase("Checks malformed params on install 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}; // check too many parameters { Json::Value iv; iv[jss::HookHash] = to_string(uint256{beast::zero}); Json::Value params{Json::arrayValue}; for (uint32_t i = 0; i < 17; ++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("HSO must not include more than 16 parameters"), HSFEE, ter(temMALFORMED)); env.close(); } // check repeat parameters { Json::Value iv; iv[jss::HookHash] = to_string(uint256{beast::zero}); Json::Value params{Json::arrayValue}; for (uint32_t i = 0; i < 2; ++i) { params[i] = Json::Value{}; params[i][jss::HookParameter] = Json::Value{}; params[i][jss::HookParameter][jss::HookParameterName] = strHex(std::string{"param"}); } iv[jss::HookParameters] = params; jv[jss::Hooks][0U][jss::Hook] = iv; env(jv, M("HSO must not repeat parameter names"), HSFEE, ter(temMALFORMED)); env.close(); } // check too long parameter name { Json::Value iv; iv[jss::HookHash] = to_string(uint256{beast::zero}); Json::Value params{Json::arrayValue}; params[0U] = Json::Value{}; params[0U][jss::HookParameter] = Json::Value{}; params[0U][jss::HookParameter][jss::HookParameterName] = strHex(std::string(33, 'a')); iv[jss::HookParameters] = params; jv[jss::Hooks][0U][jss::Hook] = iv; env(jv, M("HSO must must not contain parameter names longer than 32 " "bytes"), HSFEE, ter(temMALFORMED)); env.close(); } // check too long parameter value { Json::Value iv; iv[jss::HookHash] = to_string(uint256{beast::zero}); Json::Value params{Json::arrayValue}; params[0U] = Json::Value{}; params[0U][jss::HookParameter] = Json::Value{}; params[0U][jss::HookParameter][jss::HookParameterName] = strHex(std::string(32, 'a')); params[0U][jss::HookParameter][jss::HookParameterValue] = strHex(std::string(257, 'a')); iv[jss::HookParameters] = params; jv[jss::Hooks][0U][jss::Hook] = iv; env(jv, M("HSO must must not contain parameter values longer than 256 " "bytes"), HSFEE, ter(temMALFORMED)); env.close(); } // wrong object type { Json::Value iv; iv[jss::HookHash] = to_string(uint256{beast::zero}); Json::Value params{Json::arrayValue}; params[0U] = Json::Value{}; params[0U][jss::Memo] = Json::Value{}; params[0U][jss::Memo][jss::MemoFormat] = strHex(std::string(12, 'a')); params[0U][jss::Memo][jss::MemoData] = strHex(std::string(12, 'a')); iv[jss::HookParameters] = params; jv[jss::Hooks][0U][jss::Hook] = iv; env(jv, M("HSO parameter array can only contain HookParameter 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, {{hso(accept_wasm), hso(rollback_wasm)}}, 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}; bool const hasHookCanEmit = env.current()->rules().enabled(featureHookCanEmit); 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, hookcanemit, 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::HookCanEmit, "000000000000000000000000000000000000000000000000000000000000" "0000"}, {jss::HookApiVersion, "0"}, {jss::HookNamespace, to_string(uint256{beast::zero})}}) { if (!hasHookCanEmit && key == jss::HookCanEmit) continue; 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, hookcanemit, apiversion, namespace"), HSFEE, ter(temMALFORMED)); env.close(); } // create and delete single hook { { Json::Value jv = ripple::test::jtx::hook(alice, {{hso(accept_wasm)}}, 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, {{hso(accept_wasm), hso(makestate_wasm), hso(rollback_wasm), hso(accept2_wasm)}}, 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}; bool const fixNS = env.current()->rules().enabled(fixNSDelete); bool const hasHookCanEmit = env.current()->rules().enabled(featureHookCanEmit); auto const alice = Account{"alice"}; env.fund(XRP(10000), alice); auto const bob = Account{"bob"}; env.fund(XRP(10000), bob); auto const carol = Account{"carol"}; env.fund(XRP(10000), carol); 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::HookCanEmit, "000000000000000000000000000000000000000000000000000000000000" "0000"}, {jss::HookApiVersion, "0"}, }) { if (!hasHookCanEmit && key == jss::HookCanEmit) continue; 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, hookcanemit, 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, {{hso(makestate_wasm)}}, 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'); BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2); BEAST_EXPECT((*env.le(alice))[sfHookStateCount] == 1); } // 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)); BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == (fixNS ? 1 : 2)); BEAST_EXPECT(!(env.le("alice")->isFieldPresent(sfHookStateCount))); } if (env.current()->rules().enabled(featureExtendedHookState)) { // Test hook with scaled state data TestHook scaled_state_wasm = wasm[ R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 state_set (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len); extern int64_t util_keylet(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); extern int64_t slot_set(uint32_t, uint32_t, uint32_t); extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t); extern int64_t slot(uint32_t, uint32_t, uint32_t); extern int64_t hook_account(uint32_t, uint32_t); extern int64_t util_keylet(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); #define SBUF(x) x, sizeof(x) #define TOO_BIG -3 #define DOESNT_EXIST -5 #define KEYLET_ACCOUNT 3 #define sfHookStateScale ((1U << 16U) + 21U) #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x,sizeof(#x),__LINE__) int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t hook_acc[20]; ASSERT(hook_account(hook_acc, 20) == 20); uint8_t account_keylet[34]; ASSERT(util_keylet(account_keylet, 34, KEYLET_ACCOUNT, hook_acc, 20, 0,0,0,0) == 34); ASSERT(slot_set(account_keylet, 34, 1) == 1); slot_subfield(1, sfHookStateScale, 2); int64_t scale = slot(0,0,2); if (scale == 5) { ASSERT(state_set(0, 256, SBUF("test1")) == 256); ASSERT(state_set(0, 256*2, SBUF("test2")) == 256*2); ASSERT(state_set(0, 256*3, SBUF("test3")) == 256*3); ASSERT(state_set(0, 256*4, SBUF("test4")) == 256*4); ASSERT(state_set(0, 256*5, SBUF("test5")) == 256*5); ASSERT(state_set(0, 256*5+1, SBUF("test")) == TOO_BIG); accept(0,0,scale); } rollback(0,0,scale); } )[test.hook]"]; HASH_WASM(scaled_state); BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount)); // Install hook on carol Json::Value jv = ripple::test::jtx::hook(carol, {{hso(scaled_state_wasm)}}, 0); jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str; jv[jss::Hooks][0U][jss::Hook][jss::HookOn] = to_string(UINT256_BIT[ttACCOUNT_SET]); env(jv, M("Create scaled state hook"), HSFEE, ter(tesSUCCESS)); env.close(); BEAST_EXPECT((*env.le(carol))[sfOwnerCount] == 1); BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount)); { // HookStateScale => 5 Json::Value jv = noop(carol); jv[sfHookStateScale.fieldName] = 5; env(jv, HSFEE); env.close(); BEAST_EXPECT((*env.le(carol))[sfOwnerCount] == 1); BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount)); Json::Value invoke = invoke::invoke(carol); env(invoke, HSFEE); env.close(); BEAST_EXPECT((*env.le(carol))[sfOwnerCount] == 26); BEAST_EXPECT((*env.le(carol))[sfHookStateCount] == 5); } // Delete namespace to clean up state Json::Value iv; iv[jss::Flags] = hsfNSDELETE; iv[jss::HookNamespace] = ns_str; jv[jss::Hooks][0U][jss::Hook] = iv; env(jv, M("Delete namespace"), HSFEE); env.close(); // Verify state cleanup BEAST_EXPECT( (*env.le(carol))[sfOwnerCount] == features[fixNSDelete] ? 1 : 26); BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount)); } } void testNSDeletePartial(FeatureBitset features) { testcase("Checks partial nsdelete operation"); using namespace jtx; static const std::vector hook = { 0x00U, 0x61U, 0x73U, 0x6dU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x21U, 0x05U, 0x60U, 0x02U, 0x7fU, 0x7fU, 0x01U, 0x7fU, 0x60U, 0x02U, 0x7fU, 0x7fU, 0x01U, 0x7eU, 0x60U, 0x03U, 0x7fU, 0x7fU, 0x7eU, 0x01U, 0x7eU, 0x60U, 0x04U, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x01U, 0x7eU, 0x60U, 0x01U, 0x7fU, 0x01U, 0x7eU, 0x02U, 0x55U, 0x06U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x02U, 0x5fU, 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x0cU, 0x68U, 0x6fU, 0x6fU, 0x6bU, 0x5fU, 0x61U, 0x63U, 0x63U, 0x6fU, 0x75U, 0x6eU, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x08U, 0x72U, 0x6fU, 0x6cU, 0x6cU, 0x62U, 0x61U, 0x63U, 0x6bU, 0x00U, 0x02U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x05U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x00U, 0x03U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x09U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5fU, 0x73U, 0x65U, 0x74U, 0x00U, 0x03U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x04U, 0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x2bU, 0x07U, 0x7fU, 0x01U, 0x41U, 0x80U, 0x88U, 0x04U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x80U, 0x88U, 0x04U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x00U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x01U, 0x0bU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6fU, 0x6fU, 0x6bU, 0x00U, 0x06U, 0x0aU, 0x94U, 0x81U, 0x00U, 0x01U, 0x90U, 0x81U, 0x00U, 0x02U, 0x02U, 0x7fU, 0x01U, 0x7eU, 0x23U, 0x00U, 0x41U, 0xd0U, 0x00U, 0x6bU, 0x22U, 0x01U, 0x24U, 0x00U, 0x20U, 0x01U, 0x20U, 0x00U, 0x36U, 0x02U, 0x4cU, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1aU, 0x20U, 0x01U, 0x41U, 0x30U, 0x6aU, 0x41U, 0x14U, 0x10U, 0x01U, 0x42U, 0x14U, 0x52U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x0cU, 0x10U, 0x02U, 0x1aU, 0x0bU, 0x20U, 0x01U, 0x41U, 0x28U, 0x6aU, 0x22U, 0x00U, 0x41U, 0x08U, 0x20U, 0x01U, 0x41U, 0x30U, 0x6aU, 0x22U, 0x02U, 0x41U, 0x14U, 0x10U, 0x03U, 0x1aU, 0x20U, 0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0x28U, 0x42U, 0x01U, 0x7cU, 0x37U, 0x03U, 0x28U, 0x20U, 0x01U, 0x41U, 0xabU, 0x01U, 0x3aU, 0x00U, 0x09U, 0x20U, 0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0x28U, 0x3cU, 0x00U, 0x0aU, 0x20U, 0x00U, 0x41U, 0x08U, 0x20U, 0x02U, 0x41U, 0x14U, 0x10U, 0x04U, 0x1aU, 0x20U, 0x00U, 0x41U, 0x08U, 0x20U, 0x01U, 0x41U, 0x20U, 0x10U, 0x04U, 0x1aU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x05U, 0x20U, 0x01U, 0x41U, 0xd0U, 0x00U, 0x6aU, 0x24U, 0x00U, 0x0bU}; Env env{*this, features}; bool const fixNS = env.current()->rules().enabled(fixNSDelete); auto const bob = Account{"bob"}; auto const alice = Account{"alice"}; env.fund(XRP(10000000), alice); env.fund(XRP(10000000), bob); // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), M("set state_set_max"), HSFEE); env.close(); // invoke the hook for (uint32_t i = 0; i < 256; ++i) { env(pay(bob, alice, XRP(1)), M("test state_set_max"), fee(XRP(1))); env.close(); } BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 258); // delete the namespace pass 1 { 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] = hsfNSDELETE; iv[jss::HookNamespace] = to_string(uint256{beast::zero}); jv[jss::Hooks][0U][jss::Hook] = iv; env(jv, HSFEE, ter(fixNS ? tesPARTIAL : tesSUCCESS)); env.close(); // ensure the directory is still there auto const dirKeylet = keylet::hookStateDir( Account("alice").id(), uint256{beast::zero}); if (fixNS) { BEAST_EXPECT(env.le(dirKeylet)); BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2); } else { BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 258); BEAST_EXPECT(!env.le(dirKeylet)); } } // delete the namespace pass 2 if (fixNS) { 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] = hsfNSDELETE; iv[jss::HookNamespace] = to_string(uint256{beast::zero}); jv[jss::Hooks][0U][jss::Hook] = iv; env(jv, HSFEE, ter(tesSUCCESS)); env.close(); // ensure the directory is gone auto const dirKeylet = keylet::hookStateDir( Account("alice").id(), uint256{beast::zero}); BEAST_EXPECT(!env.le(dirKeylet)); // ensure the owner count is 1 BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 1); } } void testPageCap(FeatureBitset features) { testcase("Test page cap"); using namespace jtx; test::jtx::Env env{ *this, makePageCapConfig(features, 21337, "10", "1000000", "200000", 0), features}; bool const hasFix = env.current()->rules().enabled(fixPageCap); auto const alice = Account{"alice"}; env.memoize(alice); auto const bob = Account{"bob"}; env.fund(XRP(10000000), bob); auto const preHookCount = (*env.le(alice))[sfHookStateCount]; std::string hook = "0061736D01000000012A0660057F7F7F7F7F017E60027F7F017E60027F7F017F60" "047F7F7F7F017E60037F7F7E017E60017F017E02520603656E7605747261636500" "0003656E760C686F6F6B5F6163636F756E74000103656E76025F67000203656E76" "057374617465000303656E760973746174655F736574000303656E760661636365" "70740004030201050503010002062B077F0141B088040B7F004180080B7F0041A2" "080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B" "00060ABF830001BB830002017F017E230041D0006B220124002001200036024C41" "900841114180084110410010001A200141306A2200411410011A4101410110021A" "200141286A41082000411410031A2001200131002F200131002842388620013100" "294230867C200131002A4228867C200131002B4220867C200131002C4218867C20" "0131002D4210867C200131002E4208867C7C3703202001410036021C0340419280" "80807841C90110021A200128021C41C8014E4504402001200134021C2001290320" "42C8017E7C370310200141106A220041082000410810041A2001200128021C4101" "6A36021C0C010B0B2001200129032042017C3703202001200141286A220036020C" "200128020C200129032042388842FF01833C0000200128020C2001290320423088" "42FF01833C0001200128020C200129032042288842FF01833C0002200128020C20" "0129032042208842FF01833C0003200128020C200129032042188842FF01833C00" "04200128020C200129032042108842FF01833C0005200128020C20012903204208" "8842FF01833C0006200128020C200129032042FF01833C00072000410820014130" "6A411410041A4180084110421C1005200141D0006A24000B0B2801004180080B21" "426173652E633A2043616C6C65642E0022426173652E633A2043616C6C65642E2" "2"; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), M("set fix_page_cap"), HSFEE); env.close(); env(invoke::invoke(alice), M("test simple"), fee(XRP(1)), ter(tesSUCCESS)); env.close(); BEAST_EXPECT( (*env.le(alice))[sfHookStateCount] == hasFix ? preHookCount + 200 : preHookCount + 64); BEAST_EXPECT( (*env.le(alice))[sfOwnerCount] == hasFix ? preHookCount + 202 : preHookCount + 66); } void testFillCopy(FeatureBitset features) { testcase("Test fill/copy"); // a hook containing memory.fill instruction std::string hookFill = "0061736d0100000001130360027f7f017f60037f7f7e017e60017f017e02" "170203656e76025f67000003656e76066163636570740001030201020503" "0100020621057f01418088040b7f004180080b7f004180080b7f00418088" "040b7f004180080b07080104686f6f6b00020aa4800001a0800001017e23" "01412a41e400fc0b004101410110001a41004100420010011a20010b"; // a hook containing memory.copy instruction std::string hookCopy = "0061736d0100000001130360027f7f017f60037f7f7e017e60017f017e02" "170203656e76025f67000003656e76066163636570740001030201020503" "0100020621057f01418088040b7f004180080b7f004180080b7f00418088" "040b7f004180080b07080104686f6f6b00020aa5800001a1800001017e23" "00230141e400fc0a00004101410110001a41004100420010011a20010b"; using namespace jtx; for (int withFix = 0; withFix < 2; ++withFix) { auto f = withFix ? features : features - fix20250131; Env env{*this, f}; auto const alice = Account{"alice"}; env.fund(XRP(10000), alice); auto const bob = Account{"bob"}; env.fund(XRP(10000), bob); env(ripple::test::jtx::hook(alice, {{hso(hookFill)}}, 0), M(withFix ? "hookFill - with fix" : "hookFill - zonder fix"), HSFEE, withFix ? ter(temMALFORMED) : ter(tesSUCCESS)); env(ripple::test::jtx::hook(bob, {{hso(hookCopy)}}, 0), M(withFix ? "hookCopy - with fix" : "hookCopy - zonder fix"), HSFEE, withFix ? ter(temMALFORMED) : ter(tesSUCCESS)); env.close(); } } void testCreate(FeatureBitset features) { testcase("Checks malformed create operation"); using namespace jtx; Env env{*this, features}; bool const hasHookCanEmit = env.current()->rules().enabled(featureHookCanEmit); 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, {{hso(accept_wasm)}}, 0), M("First set = tesSUCCESS"), HSFEE, ter(tesSUCCESS)); env(ripple::test::jtx::hook(bob, {{hso(accept_wasm)}}, 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}; // payload too large { env(ripple::test::jtx::hook(alice, {{hso(long_wasm)}}, 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] = 0U; iv[jss::HookOn] = "00000000000000000000000000000000000000000000000000000000000000" "00"; if (hasHookCanEmit) iv[jss::HookCanEmit] = "0000000000000000000000000000000000000000000000000000000000" "000000"; 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"; if (hasHookCanEmit) iv[jss::HookCanEmit] = "0000000000000000000000000000000000000000000000000000000000" "000000"; 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] = 1U; iv[jss::HookOn] = "00000000000000000000000000000000000000000000000000000000000000" "00"; if (hasHookCanEmit) iv[jss::HookCanEmit] = "0000000000000000000000000000000000000000000000000000000000" "000000"; 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] = 0U; if (hasHookCanEmit) iv[jss::HookCanEmit] = "0000000000000000000000000000000000000000000000000000000000" "000000"; 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, {{hso(accept_wasm)}}, 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, {{hso(accept_wasm)}}, 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, {{hso(accept_wasm)}}, 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, {{hso(rollback_wasm)}}, 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}; bool const hasHookCanEmit = env.current()->rules().enabled(featureHookCanEmit); 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] = 0U; iv[jss::HookOn] = "00000000000000000000000000000000000000000000000000000000000000" "00"; if (hasHookCanEmit) iv[jss::HookCanEmit] = "0000000000000000000000000000000000000000000000000000000000" "000000"; 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] = 0U; 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::HookCanEmit, "00000000000000000000000000000000000000000000000000000000" "00000001"}, {jss::HookNamespace, "CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE" "CAFECAFE"}, {jss::HookParameters, params}, {jss::HookGrants, grants}}) { if (!hasHookCanEmit && key == jss::HookCanEmit) continue; 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]); if (hasHookCanEmit) { BEAST_REQUIRE(hooks[0].isFieldPresent(sfHookCanEmit)); BEAST_EXPECT( hooks[0].getFieldH256(sfHookCanEmit) == ripple::uint256("000000000000000000000000000000000000000000" "0000000000000000000001")); } 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, hookcanemit, and namespace to defaults { for (auto const& [key, value] : JSSMap{ {jss::HookOn, "00000000000000000000000000000000000000000000000000000000" "00000000"}, {jss::HookCanEmit, "00000000000000000000000000000000000000000000000000000000" "00000000"}, {jss::HookNamespace, to_string(uint256{beast::zero})}}) { if (key == jss::HookCanEmit && !hasHookCanEmit) continue; 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(sfHookCanEmit)); 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::HookCanEmit, "00000000000000000000000000000000000000000000000000000000" "00000001"}, {jss::HookNamespace, "CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE" "CAFECAFE"}, {jss::HookParameters, params}, {jss::HookGrants, grants}}) { if (key == jss::HookCanEmit && !hasHookCanEmit) continue; 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, {{{}, hso(accept_wasm)}}, 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, {{hso(accept_wasm)}}, 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 testInferHookSetOperation() { testcase("Test operation inference"); // hsoNOOP { STObject hso{sfHook}; BEAST_EXPECT(SetHook::inferOperation(hso) == hsoNOOP); } // hsoCREATE { STObject hso{sfHook}; hso.setFieldVL(sfCreateCode, {1}); // non-empty create code BEAST_EXPECT(SetHook::inferOperation(hso) == hsoCREATE); } // hsoDELETE { STObject hso{sfHook}; hso.setFieldVL(sfCreateCode, ripple::Blob{}); // empty create code BEAST_EXPECT(SetHook::inferOperation(hso) == hsoDELETE); } // hsoINSTALL { STObject hso{sfHook}; hso.setFieldH256( sfHookHash, uint256{beast::zero}); // all zeros hook hash BEAST_EXPECT(SetHook::inferOperation(hso) == hsoINSTALL); } // hsoNSDELETE { STObject hso{sfHook}; hso.setFieldH256( sfHookNamespace, uint256{beast::zero}); // all zeros hook hash hso.setFieldU32(sfFlags, hsfNSDELETE); BEAST_EXPECT(SetHook::inferOperation(hso) == hsoNSDELETE); } // hsoUPDATE { STObject hso{sfHook}; hso.setFieldH256(sfHookOn, UINT256_BIT[0]); BEAST_EXPECT(SetHook::inferOperation(hso) == hsoUPDATE); } // hsoINVALID { STObject hso{sfHook}; hso.setFieldVL(sfCreateCode, {1}); // non-empty create code hso.setFieldH256( sfHookHash, uint256{beast::zero}); // all zeros hook hash BEAST_EXPECT(SetHook::inferOperation(hso) == hsoINVALID); } } 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, {{hso(noguard_wasm)}}, 0), M("Must import guard"), HSFEE, ter(temMALFORMED)); env(ripple::test::jtx::hook(alice, {{hso(illegalfunc_wasm)}}, 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, {{hso(accept_wasm)}}, 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, {{hso(rollback_wasm)}}, 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 testGuards(FeatureBitset features) { testcase("Test guards"); 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); // test a simple loop without a guard call { TestHook hook = wasm[R"[test.hook]( (module (type (;0;) (func (param i32 i32) (result i64))) (type (;1;) (func (param i32 i32) (result i32))) (type (;2;) (func (param i32) (result i64))) (import "env" "hook_account" (func (;0;) (type 0))) (import "env" "_g" (func (;1;) (type 1))) (func (;2;) (type 2) (param i32) (result i64) (local i32) global.get 0 i32.const 32 i32.sub local.tee 1 global.set 0 loop (result i64) ;; label = @1 local.get 1 i32.const 20 call 0 drop br 0 (;@1;) end) (memory (;0;) 2) (global (;0;) (mut i32) (i32.const 66560)) (global (;1;) i32 (i32.const 1024)) (global (;2;) i32 (i32.const 1024)) (global (;3;) i32 (i32.const 66560)) (global (;4;) i32 (i32.const 1024)) (export "memory" (memory 0)) (export "hook" (func 2))) )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook)}}, 0), M("Loop 1 no guards"), HSFEE, ter(temMALFORMED)); env.close(); } // same loop again but with a guard call { TestHook hook = wasm[R"[test.hook]( (module (type (;0;) (func (param i32 i32) (result i64))) (type (;1;) (func (param i32 i32) (result i32))) (type (;2;) (func (param i32) (result i64))) (import "env" "hook_account" (func (;0;) (type 0))) (import "env" "_g" (func (;1;) (type 1))) (func (;2;) (type 2) (param i32) (result i64) (local i32) global.get 0 i32.const 32 i32.sub local.tee 1 global.set 0 loop (result i64) ;; label = @1 i32.const 1 i32.const 1 call 1 drop local.get 1 i32.const 20 call 0 drop br 0 (;@1;) end) (memory (;0;) 2) (global (;0;) (mut i32) (i32.const 66560)) (global (;1;) i32 (i32.const 1024)) (global (;2;) i32 (i32.const 1024)) (global (;3;) i32 (i32.const 66560)) (global (;4;) i32 (i32.const 1024)) (export "memory" (memory 0)) (export "hook" (func 2))) )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook)}}, 0), M("Loop 1 with guards"), HSFEE); env.close(); } // simple looping, c { TestHook hook = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t hook_account (uint32_t, uint32_t); int64_t hook(uint32_t reserved ) { uint8_t acc[20]; for (int i = 0; GUARD(10), i < 10; ++i) hook_account(acc, 20); return accept(0,0,2); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), M("Loop 2 in C"), HSFEE); env.close(); env(pay(bob, alice, XRP(1)), M("Test Loop 2"), fee(XRP(1))); env.close(); } // complex looping, c { TestHook hook = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t hook_account (uint32_t, uint32_t); int64_t hook(uint32_t reserved) { uint8_t acc[20]; // guards should be computed by: // (this loop iterations + 1) * (each parent loop's iteration's + 0) for (int i = 0; i < 10; ++i) { _g(1, 11); for (int j = 0; j < 2; ++j) { _g(2, 30); for (int k = 0; k < 5; ++k) { _g(3, 120); hook_account(acc, 20); } for (int k = 0; k < 5; ++k) { _g(4, 120); hook_account(acc, 20); } } } return accept(0,0,2); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), M("Loop 3 in C"), HSFEE); env.close(); env(pay(bob, alice, XRP(1)), M("Test Loop 3"), fee(XRP(1))); env.close(); } // complex looping missing a guard { TestHook hook = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t hook_account (uint32_t, uint32_t); int64_t hook(uint32_t reserved) { uint8_t acc[20]; // guards should be computed by: // (this loop iterations + 1) * (each parent loop's iteration's + 0) for (int i = 0; i < acc[0]; ++i) { _g(1, 11); for (int j = 0; j < acc[1]; ++j) { // guard missing here hook_account(acc, 20); for (int k = 0; k < acc[2]; ++k) { _g(3, 120); hook_account(acc, 20); } for (int k = 0; k < acc[3]; ++k) { _g(4, 120); hook_account(acc, 20); } } } return accept(0,0,2); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), M("Loop 4 in C"), HSFEE, ter(temMALFORMED)); 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 = 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, {{hso(hook, 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 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t etxn_details (uint32_t, uint32_t); extern int64_t etxn_reserve(uint32_t); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define PREREQUISITE_NOT_MET -9 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t det[116]; // Test out of bounds check ASSERT(etxn_details(1000000, 116) == OUT_OF_BOUNDS); ASSERT(etxn_details(0, 1000000) == OUT_OF_BOUNDS); ASSERT(etxn_details((uint32_t)det, 115) == TOO_SMALL); ASSERT(etxn_details((uint32_t)det, 116) == PREREQUISITE_NOT_MET); etxn_reserve(1); ASSERT(etxn_details((uint32_t)det, 116) == 116); return accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t etxn_fee_base (uint32_t, uint32_t); extern int64_t etxn_reserve(uint32_t); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define PREREQUISITE_NOT_MET -9 #define INVALID_TXN -37 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reservmaed ) { _g(1,1); uint8_t det[116]; // Test out of bounds check ASSERT(etxn_fee_base(1000000, 116) == OUT_OF_BOUNDS); ASSERT(etxn_fee_base(0, 1000000) == OUT_OF_BOUNDS); ASSERT(etxn_fee_base((uint32_t)det, 116) == PREREQUISITE_NOT_MET); etxn_reserve(1); ASSERT(etxn_fee_base((uint32_t)det, 116) == INVALID_TXN); return accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t etxn_nonce (uint32_t, uint32_t); extern int64_t etxn_reserve(uint32_t); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define TOO_MANY_NONCES -12 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reservmaed ) { _g(1,1); uint8_t nonce[64]; // Test out of bounds check ASSERT(etxn_nonce(1000000, 116) == OUT_OF_BOUNDS); ASSERT(etxn_nonce(0, 1000000) == OUT_OF_BOUNDS); ASSERT(etxn_nonce((uint32_t)nonce, 31) == TOO_SMALL); uint64_t* n1 = (uint64_t*)nonce; uint64_t* n2 = (uint64_t*)(((uint8_t*)nonce) + 32); for (int i = 0; GUARD(256), i < 256; ++i) { ASSERT(etxn_nonce((uint32_t)nonce + ((i % 2) * 32), 32) == 32); ASSERT(!(*(n1 + 0) == *(n2 + 0) && *(n1 + 1) == *(n2 + 1))); } ASSERT(etxn_nonce((uint32_t)nonce, 116) == TOO_MANY_NONCES); return accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t etxn_reserve(uint32_t); #define TOO_BIG -3 #define TOO_SMALL -4 #define ALREADY_SET -8 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reservmaed ) { _g(1,1); 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,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t fee_base(void); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reservmaed ) { _g(1,1); ASSERT(fee_base() == 10); return accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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_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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_one (void); extern int64_t float_compare(int64_t, int64_t, uint32_t); #define EQ 0b001U #define LT 0b010U #define GT 0b100U #define LTE 0b011U #define GTE 0b101U #define NEQ 0b110U #define ASSERT(x)\ if ((x) != 1)\ rollback(0,0,__LINE__) #define INVALID_ARGUMENT -7 #define INVALID_FLOAT -10024 int64_t hook(uint32_t reserved ) { _g(1,1); int64_t result = 0; // test invalid floats { ASSERT(float_compare(-1,-2, EQ) == INVALID_FLOAT); ASSERT(float_compare( 0,-2, EQ) == INVALID_FLOAT); ASSERT(float_compare(-1, 0, EQ) == INVALID_FLOAT); } // test invalid flags { // flag 8 doesnt exist ASSERT(float_compare(0,0, 0b1000U) == INVALID_ARGUMENT); // flag 16 doesnt exist ASSERT(float_compare(0,0, 0b10000U) == INVALID_ARGUMENT); // every flag except the valid ones ASSERT(float_compare(0,0, ~0b111UL) == INVALID_ARGUMENT); // all valid flags combined is invalid too ASSERT(float_compare(0,0, 0b111UL) == INVALID_ARGUMENT); // no flags is also invalid ASSERT(float_compare(0,0, 0) == INVALID_ARGUMENT); } // test logic { 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)); int64_t large_negative = 1622844335003378560LL; /* -154846915 */ int64_t small_negative = 1352229899321148800LL; /* -1.15001111e-7 */ int64_t small_positive = 5713898440837102138LL; /* 3.33411333131321e-21 */ int64_t large_positive = 7749425685711506120LL; /* 3.234326634253e+92 */ // large negative < small negative 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); // large_negative < large positive 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); // small_negative < small_positive 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); // small positive < large positive 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); // small negative < 0 ASSERT(float_compare(small_negative, 0, LT)); // large negative < 0 ASSERT(float_compare(large_negative, 0, LT)); // small positive > 0 ASSERT(float_compare(small_positive, 0, GT)); // large positive > 0 ASSERT(float_compare(large_positive, 0, GT)); } return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_divide (int64_t, int64_t); extern int64_t float_one (void); #define INVALID_FLOAT -10024 #define DIVISION_BY_ZERO -25 #define XFL_OVERFLOW -30 #define ASSERT(x)\ if (!(x))\ rollback(0,0,__LINE__); extern int64_t float_compare(int64_t, int64_t, uint32_t); extern int64_t float_negate(int64_t); extern int64_t float_sum(int64_t, int64_t); extern int64_t float_mantissa(int64_t); #define float_exponent(f) (((int32_t)(((f) >> 54U) & 0xFFU)) - 97) #define ASSERT_EQUAL(x, y)\ {\ int64_t px = (x);\ int64_t py = (y);\ int64_t mx = float_mantissa(px);\ int64_t my = float_mantissa(py);\ int32_t diffexp = float_exponent(px) - float_exponent(py);\ if (diffexp == 1)\ mx *= 10LL;\ if (diffexp == -1)\ my *= 10LL;\ int64_t diffman = mx - my;\ if (diffman < 0) diffman *= -1LL;\ if (diffexp < 0) diffexp *= -1;\ if (diffexp > 1 || diffman > 5000000 || mx < 0 || my < 0)\ rollback((uint32_t) #x, sizeof(#x), __LINE__);\ } int64_t hook(uint32_t reserved ) { _g(1,1); // ensure invalid xfl are not accepted ASSERT(float_divide(-1, float_one()) == INVALID_FLOAT); // divide by 0 ASSERT(float_divide(float_one(), 0) == DIVISION_BY_ZERO); ASSERT(float_divide(0, float_one()) == 0); // check 1 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()); // 1 / 10 = 0.1 ASSERT_EQUAL(float_divide(float_one(), 6107881094714392576LL), 6071852297695428608LL); // 123456789 / 1623 = 76067.0295749 ASSERT_EQUAL(float_divide(6234216452170766464LL, 6144532891733356544LL), 6168530993200328528LL); // -1.245678451111 / 1.3546984132111e+42 = -9.195245517106014e-43 ASSERT_EQUAL(float_divide(1478426356228633688LL, 6846826132016365020LL), 711756787386903390LL); // 9.134546514878452e-81 / 1 ASSERT(float_divide(4638834963451748340LL, float_one()) == 4638834963451748340LL); // 9.134546514878452e-81 / 1.41649684651e+75 = (underflow 0) ASSERT(float_divide(4638834963451748340LL, 7441363081262569392LL) == 0); // 1.3546984132111e+42 / 9.134546514878452e-81 = XFL_OVERFLOW ASSERT(float_divide(6846826132016365020LL, 4638834963451748340LL) == XFL_OVERFLOW); ASSERT_EQUAL( float_divide( 3121244226425810900LL /* -4.753284285427668e+91 */, 2135203055881892282LL /* -9.50403176301817e+36 */), 7066645550312560102LL /* 5.001334595622374e+54 */); ASSERT_EQUAL( float_divide( 2473507938381460320LL /* -5.535342582428512e+55 */, 6365869885731270068LL /* 6787211884129716 */), 2187897766692155363LL /* -8.155547044835299e+39 */); ASSERT_EQUAL( float_divide( 1716271542690607496LL /* -49036842898190.16 */, 3137794549622534856LL /* -3.28920897266964e+92 */), 4667220053951274769LL /* 1.490839995440913e-79 */); ASSERT_EQUAL( float_divide( 1588045991926420391LL /* -2778923.092005799 */, 5933338827267685794LL /* 6.601717648113058e-9 */), 1733591650950017206LL /* -420939403974674.2 */); ASSERT_EQUAL( float_divide( 5880783758174228306LL /* 8.089844083101523e-12 */, 1396720886139976383LL /* -0.00009612200909863615 */), 1341481714205255877LL /* -8.416224503589061e-8 */); ASSERT_EQUAL( float_divide( 5567703563029955929LL /* 1.254423600022873e-29 */, 2184969513100691140LL /* -5.227293453371076e+39 */), 236586937995245543LL /* -2.399757371979751e-69 */); ASSERT_EQUAL( float_divide( 7333313065548121054LL /* 1.452872188953566e+69 */, 1755926008837497886LL /* -8529353417745438 */), 2433647177826281173LL /* -1.703379046213333e+53 */); ASSERT_EQUAL( float_divide( 1172441975040622050LL /* -1.50607192429309e-17 */, 6692015311011173216LL /* 8.673463993357152e+33 */), 560182767210134346LL /* -1.736413416192842e-51 */); ASSERT_EQUAL( float_divide( 577964843368607493LL /* -1.504091065184005e-50 */, 6422931182144699580LL /* 9805312769113276000 */), 235721135837751035LL /* -1.533955214485243e-69 */); ASSERT_EQUAL( float_divide( 6039815413139899240LL /* 0.0049919124634346 */, 2117655488444284242LL /* -9.970862834892113e+35 */), 779625635892827768LL /* -5.006499985102456e-39 */); ASSERT_EQUAL( float_divide( 1353563835098586141LL /* -2.483946887437341e-7 */, 6450909070545770298LL /* 175440415122002600000 */), 992207753070525611LL /* -1.415835049016491e-27 */); ASSERT_EQUAL( float_divide( 6382158843584616121LL /* 50617712279937850 */, 5373794957212741595LL /* 5.504201387110363e-40 */), 7088854809772330055LL /* 9.196195545910343e+55 */); ASSERT_EQUAL( float_divide( 2056891719200540975LL /* -3.250289119594799e+32 */, 1754532627802542730LL /* -7135972382790282 */), 6381651867337939070LL /* 45547949813167340 */); ASSERT_EQUAL( float_divide( 5730152450208688630LL /* 1.573724193417718e-20 */, 1663581695074866883LL /* -62570322025.24355 */), 921249452789827075LL /* -2.515128806245891e-31 */); ASSERT_EQUAL( float_divide( 6234301156018475310LL /* 131927173.7708846 */, 2868710604383082256LL /* -4.4212413754468e+77 */), 219156721749007916LL /* -2.983939635224108e-70 */); ASSERT_EQUAL( float_divide( 2691125731495874243LL /* -6.980353583058627e+67 */, 7394070851520237320LL /* 8.16746263262388e+72 */), 1377640825464715759LL /* -0.000008546538744084975 */); ASSERT_EQUAL( float_divide( 5141867696142208039LL /* 7.764120939842599e-53 */, 5369434678231981897LL /* 1.143922406350665e-40 */), 5861466794943198400LL /* 6.7872793615536e-13 */); ASSERT_EQUAL( float_divide( 638296190872832492LL /* -7.792243040963052e-47 */, 5161669734904371378LL /* 9.551761192523954e-52 */), 1557396184145861422LL /* -81579.12330410798 */); ASSERT_EQUAL( float_divide( 2000727145906286285LL /* -1.128911353786061e+29 */, 2096625200460673392LL /* -6.954973360763248e+34 */), 5982403476503576795LL /* 0.000001623171355558107 */); ASSERT_EQUAL( float_divide( 640472838055334326LL /* -9.968890223464885e-47 */, 5189754252349396763LL /* 1.607481618585371e-50 */), 1537425431139169736LL /* -6201.557833201096 */); return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_int (int64_t, uint32_t, uint32_t); extern int64_t float_one (void); #define INVALID_FLOAT -10024 #define INVALID_ARGUMENT -7 #define CANT_RETURN_NEGATIVE -33 #define TOO_BIG -3 #define ASSERT(x)\ if (!(x))\ rollback(0,0,__LINE__); int64_t hook(uint32_t reserved ) { _g(1,1); // ensure invalid xfl are not accepted ASSERT(float_int(-1,0,0) == INVALID_FLOAT); // check 1 ASSERT(float_int(float_one(), 0, 0) == 1LL); // check 1.23e-20 always returns 0 (too small to display) ASSERT(float_int(5729808726015270912LL,0,0) == 0); ASSERT(float_int(5729808726015270912LL,15,0) == 0); ASSERT(float_int(5729808726015270912LL,16,0) == INVALID_ARGUMENT); ASSERT(float_int(float_one(), 15, 0) == 1000000000000000LL); ASSERT(float_int(float_one(), 14, 0) == 100000000000000LL); ASSERT(float_int(float_one(), 13, 0) == 10000000000000LL); ASSERT(float_int(float_one(), 12, 0) == 1000000000000LL); ASSERT(float_int(float_one(), 11, 0) == 100000000000LL); ASSERT(float_int(float_one(), 10, 0) == 10000000000LL); ASSERT(float_int(float_one(), 9, 0) == 1000000000LL); ASSERT(float_int(float_one(), 8, 0) == 100000000LL); ASSERT(float_int(float_one(), 7, 0) == 10000000LL); ASSERT(float_int(float_one(), 6, 0) == 1000000LL); ASSERT(float_int(float_one(), 5, 0) == 100000LL); ASSERT(float_int(float_one(), 4, 0) == 10000LL); ASSERT(float_int(float_one(), 3, 0) == 1000LL); ASSERT(float_int(float_one(), 2, 0) == 100LL); ASSERT(float_int(float_one(), 1, 0) == 10LL); ASSERT(float_int(float_one(), 0, 0) == 1LL); // normal upper limit on exponent ASSERT(float_int(6360317241828374919LL, 0, 0) == 1234567981234567LL); // ask for one decimal above limit ASSERT(float_int(6360317241828374919LL, 1, 0) == TOO_BIG); // ask for 15 decimals above limit ASSERT(float_int(6360317241828374919LL, 15, 0) == TOO_BIG); // every combination for 1.234567981234567 ASSERT(float_int(6090101264186145159LL, 0, 0) == 1LL); ASSERT(float_int(6090101264186145159LL, 1, 0) == 12LL); ASSERT(float_int(6090101264186145159LL, 2, 0) == 123LL); ASSERT(float_int(6090101264186145159LL, 3, 0) == 1234LL); ASSERT(float_int(6090101264186145159LL, 4, 0) == 12345LL); ASSERT(float_int(6090101264186145159LL, 5, 0) == 123456LL); ASSERT(float_int(6090101264186145159LL, 6, 0) == 1234567LL); ASSERT(float_int(6090101264186145159LL, 7, 0) == 12345679LL); ASSERT(float_int(6090101264186145159LL, 8, 0) == 123456798LL); ASSERT(float_int(6090101264186145159LL, 9, 0) == 1234567981LL); ASSERT(float_int(6090101264186145159LL, 10, 0) == 12345679812LL); ASSERT(float_int(6090101264186145159LL, 11, 0) == 123456798123LL); ASSERT(float_int(6090101264186145159LL, 12, 0) == 1234567981234LL); ASSERT(float_int(6090101264186145159LL, 13, 0) == 12345679812345LL); ASSERT(float_int(6090101264186145159LL, 14, 0) == 123456798123456LL); ASSERT(float_int(6090101264186145159LL, 15, 0) == 1234567981234567LL); // same with absolute parameter ASSERT(float_int(1478415245758757255LL, 0, 1) == 1LL); ASSERT(float_int(1478415245758757255LL, 1, 1) == 12LL); ASSERT(float_int(1478415245758757255LL, 2, 1) == 123LL); ASSERT(float_int(1478415245758757255LL, 3, 1) == 1234LL); ASSERT(float_int(1478415245758757255LL, 4, 1) == 12345LL); ASSERT(float_int(1478415245758757255LL, 5, 1) == 123456LL); ASSERT(float_int(1478415245758757255LL, 6, 1) == 1234567LL); ASSERT(float_int(1478415245758757255LL, 7, 1) == 12345679LL); ASSERT(float_int(1478415245758757255LL, 8, 1) == 123456798LL); ASSERT(float_int(1478415245758757255LL, 9, 1) == 1234567981LL); ASSERT(float_int(1478415245758757255LL, 10, 1) == 12345679812LL); ASSERT(float_int(1478415245758757255LL, 11, 1) == 123456798123LL); ASSERT(float_int(1478415245758757255LL, 12, 1) == 1234567981234LL); ASSERT(float_int(1478415245758757255LL, 13, 1) == 12345679812345LL); ASSERT(float_int(1478415245758757255LL, 14, 1) == 123456798123456LL); ASSERT(float_int(1478415245758757255LL, 15, 1) == 1234567981234567LL); // neg xfl sans absolute parameter ASSERT(float_int(1478415245758757255LL, 15, 0) == CANT_RETURN_NEGATIVE); // 1.234567981234567e-16 ASSERT(float_int(5819885286543915399LL, 15, 0) == 1LL); for (uint32_t i = 1; GUARD(15), i < 15; ++i) ASSERT(float_int(5819885286543915399LL, i, 0) == 0); return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_invert (int64_t); extern int64_t float_one (void); #define INVALID_FLOAT -10024 #define DIVISION_BY_ZERO -25 #define TOO_BIG -3 #define ASSERT(x)\ if (!(x))\ rollback(0,0,__LINE__); extern int64_t float_compare(int64_t, int64_t, uint32_t); extern int64_t float_negate(int64_t); extern int64_t float_sum(int64_t, int64_t); extern int64_t float_mantissa(int64_t); #define float_exponent(f) (((int32_t)(((f) >> 54U) & 0xFFU)) - 97) #define ASSERT_EQUAL(x, y)\ {\ int64_t px = (x);\ int64_t py = (y);\ int64_t mx = float_mantissa(px);\ int64_t my = float_mantissa(py);\ int32_t diffexp = float_exponent(px) - float_exponent(py);\ if (diffexp == 1)\ mx *= 10LL;\ if (diffexp == -1)\ my *= 10LL;\ int64_t diffman = mx - my;\ if (diffman < 0) diffman *= -1LL;\ if (diffexp < 0) diffexp *= -1;\ if (diffexp > 1 || diffman > 5000000 || mx < 0 || my < 0)\ rollback((uint32_t) #x, sizeof(#x), __LINE__);\ } int64_t hook(uint32_t reserved ) { _g(1,1); // divide by 0 ASSERT(float_invert(0) == DIVISION_BY_ZERO); // ensure invalid xfl are not accepted ASSERT(float_invert(-1) == INVALID_FLOAT); // check 1 ASSERT(float_invert(float_one()) == float_one()); // 1 / 10 = 0.1 ASSERT_EQUAL(float_invert(6107881094714392576LL), 6071852297695428608LL); // 1 / 123 = 0.008130081300813009 ASSERT_EQUAL(float_invert(6126125493223874560LL), 6042953581977277649LL); // 1 / 1234567899999999 = 8.100000008100007e-16 ASSERT_EQUAL(float_invert(6360317241747140351LL), 5808736320061298855LL); // 1/ 1*10^-81 = 10**81 ASSERT_EQUAL(float_invert(4630700416936869888LL), 7540018576963469311LL); return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_log (int64_t float1); extern int64_t float_one (void); #define INVALID_ARGUMENT -7 #define COMPLEX_NOT_SUPPORTED -39 extern int64_t float_mantissa(int64_t); #define float_exponent(f) (((int32_t)(((f) >> 54U) & 0xFFU)) - 97) #define ASSERT_EQUAL(x, y)\ {\ int64_t px = (x);\ int64_t py = (y);\ int64_t mx = float_mantissa(px);\ int64_t my = float_mantissa(py);\ int32_t diffexp = float_exponent(px) - float_exponent(py);\ if (diffexp == 1)\ mx *= 10LL;\ if (diffexp == -1)\ my *= 10LL;\ int64_t diffman = mx - my;\ if (diffman < 0) diffman *= -1LL;\ if (diffexp < 0) diffexp *= -1;\ if (diffexp > 1 || diffman > 5000000 || mx < 0 || my < 0)\ rollback((uint32_t) #x, sizeof(#x), __LINE__);\ } int64_t hook(uint32_t reserved ) { _g(1,1); // check 0 is not allowed if (float_log(0) != INVALID_ARGUMENT) rollback(0,0,__LINE__); // log10( 846513684968451 ) = 14.92763398342338 ASSERT_EQUAL(float_log(6349533412187342878LL), 6108373858112734914LL); // log10 ( -1000 ) = invalid (complex not supported) if (float_log(1532223873305968640LL) != COMPLEX_NOT_SUPPORTED) rollback(0,0,__LINE__); // log10 (1000) == 3 ASSERT_EQUAL(float_log(6143909891733356544LL), 6091866696204910592LL); // log10 (0.112381) == -0.949307107740766 ASSERT_EQUAL(float_log(6071976107695428608LL), 1468659350345448364LL); // log10 (0.00000000000000001123) = -16.94962024373854221 ASSERT_EQUAL(float_log(5783744921543716864LL), 1496890038311378526LL); // log10 (100000000000000000000000000000000000000000000000000000000000000) = 62 ASSERT_EQUAL(float_log(7206759403792793600LL), 6113081094714392576LL); return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_one (void); extern int64_t float_mantissa(int64_t); extern int64_t float_negate(int64_t); #define ASSERT_EQUAL(x,y)\ if ((x) != (y))\ rollback(0,0,__LINE__); #define ASSERT(x)\ if (!(x))\ rollback(0,0,__LINE__); #define INVALID_FLOAT -10024 int64_t hook(uint32_t reserved ) { _g(1,1); int64_t result = 0; // test invalid floats { ASSERT(float_mantissa(-1) == INVALID_FLOAT); ASSERT(float_mantissa(-11010191919LL) == INVALID_FLOAT); } // test canonical zero ASSERT(float_mantissa(0) == 0); // test one, negative one { ASSERT(float_mantissa(float_one()) == 1000000000000000LL); ASSERT(float_mantissa(float_negate(float_one())) == 1000000000000000LL); } // test random numbers { ASSERT_EQUAL( float_mantissa(4763370308433150973LL /* 7.569101929907197e-74 */), 7569101929907197LL); ASSERT_EQUAL( float_mantissa(668909658849475214LL /* -2.376913998641806e-45 */), 2376913998641806LL); ASSERT_EQUAL( float_mantissa(962271544155031248LL /* -7.508423152486096e-29 */), 7508423152486096LL); ASSERT_EQUAL( float_mantissa(7335644976228470276LL /* 3.784782869302788e+69 */), 3784782869302788LL); ASSERT_EQUAL( float_mantissa(2837780149340315954LL /* -9.519583351644467e+75 */), 9519583351644466LL); ASSERT_EQUAL( float_mantissa(2614004940018599738LL /* -1.917156143712058e+63 */), 1917156143712058LL); ASSERT_EQUAL( float_mantissa(4812250541755005603LL /* 2.406139723315875e-71 */), 2406139723315875LL); ASSERT_EQUAL( float_mantissa(5140304866732560580LL /* 6.20129153019514e-53 */), 6201291530195140LL); ASSERT_EQUAL( float_mantissa(1124677839589482624LL /* -7.785132001599617e-20 */), 7785132001599616LL); ASSERT_EQUAL( float_mantissa(5269336076015865585LL /* 9.131711247126257e-46 */), 9131711247126257LL); ASSERT_EQUAL( float_mantissa(2296179634826760368LL /* -8.3510241225484e+45 */), 8351024122548400LL); ASSERT_EQUAL( float_mantissa(1104028240398536470LL /* -5.149931320135446e-21 */), 5149931320135446LL); ASSERT_EQUAL( float_mantissa(2691222059222981864LL /* -7.076681310166248e+67 */), 7076681310166248LL); ASSERT_EQUAL( float_mantissa(6113256168823855946LL /* 63.7507410946337 */), 6375074109463370LL); ASSERT_EQUAL( float_mantissa(311682216630003626LL /* -5.437441968809898e-65 */), 5437441968809898LL); ASSERT_EQUAL( float_mantissa(794955605753965262LL /* -2.322071336757966e-38 */), 2322071336757966LL); ASSERT_EQUAL( float_mantissa(204540636400815950LL /* -6.382252796514126e-71 */), 6382252796514126LL); ASSERT_EQUAL( float_mantissa(5497195278343034975LL /* 2.803732951029855e-33 */), 2803732951029855LL); ASSERT_EQUAL( float_mantissa(1450265914369875626LL /* -0.09114033611316906 */), 9114033611316906LL); ASSERT_EQUAL( float_mantissa(7481064015089962668LL /* 5.088633654939308e+77 */), 5088633654939308LL); } return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_mulratio (int64_t, uint32_t, uint32_t, uint32_t); extern int64_t float_one (void); #define INVALID_FLOAT -10024 #define DIVISION_BY_ZERO -25 #define XFL_OVERFLOW -30 #define ASSERT(x)\ if (!(x))\ rollback(0,0,__LINE__); extern int64_t float_compare(int64_t, int64_t, uint32_t); extern int64_t float_negate(int64_t); extern int64_t float_sum(int64_t, int64_t); extern int64_t float_mantissa(int64_t); #define float_exponent(f) (((int32_t)(((f) >> 54U) & 0xFFU)) - 97) #define ASSERT_EQUAL(x, y)\ {\ int64_t px = (x);\ int64_t py = (y);\ int64_t mx = float_mantissa(px);\ int64_t my = float_mantissa(py);\ int32_t diffexp = float_exponent(px) - float_exponent(py);\ if (diffexp == 1)\ mx *= 10LL;\ if (diffexp == -1)\ my *= 10LL;\ int64_t diffman = mx - my;\ if (diffman < 0) diffman *= -1LL;\ if (diffexp < 0) diffexp *= -1;\ if (diffexp > 1 || diffman > 5000000 || mx < 0 || my < 0)\ rollback((uint32_t) #x, sizeof(#x), __LINE__);\ } int64_t hook(uint32_t reserved ) { _g(1,1); // ensure invalid xfl are not accepted ASSERT(float_mulratio(-1, 0, 1, 1) == INVALID_FLOAT); // multiply by 0 ASSERT(float_mulratio(float_one(), 0, 0, 1) == 0); ASSERT(float_mulratio(0, 0, 1, 1) == 0); // check 1 ASSERT(float_mulratio(float_one(), 0, 1, 1) == float_one()); ASSERT(float_mulratio(float_negate(float_one()), 0, 1, 1) == float_negate(float_one())); // check overflow // 1e+95 * 1e+95 ASSERT(float_mulratio(7801234554605699072LL, 0, 0xFFFFFFFFUL, 1) == XFL_OVERFLOW); // 1e+95 * 10 ASSERT(float_mulratio(7801234554605699072LL, 0, 10, 1) == XFL_OVERFLOW); // -1e+95 * 10 ASSERT(float_mulratio(3189548536178311168LL, 0, 10, 1) == XFL_OVERFLOW); // identity ASSERT_EQUAL(float_mulratio(3189548536178311168LL, 0, 1, 1), 3189548536178311168LL); // random mulratios ASSERT_EQUAL( float_mulratio(2296131684119423544LL, 0U, 2210828011U, 2814367554U), 2294351094683836182LL); ASSERT_EQUAL( float_mulratio(565488225163275031LL, 0U, 2373474507U, 4203973264U), 562422045628095449LL); ASSERT_EQUAL( float_mulratio(2292703263479286183LL, 0U, 3170020147U, 773892643U), 2307839765178024100LL); ASSERT_EQUAL( float_mulratio(758435948837102675LL, 0U, 3802740780U, 1954123588U), 760168290112163547LL); ASSERT_EQUAL( float_mulratio(3063742137774439410LL, 0U, 2888815591U, 4122448592U), 3053503824756415637LL); ASSERT_EQUAL( float_mulratio(974014561126802184LL, 0U, 689168634U, 3222648522U), 957408554638995792LL); ASSERT_EQUAL( float_mulratio(2978333847445611553LL, 0U, 1718558513U, 2767410870U), 2976075722223325259LL); ASSERT_EQUAL( float_mulratio(6577058837932757648LL, 0U, 1423256719U, 1338068927U), 6577173649752398013LL); ASSERT_EQUAL( float_mulratio(2668681541248816636LL, 0U, 345215754U, 4259223936U), 2650183845127530219LL); ASSERT_EQUAL( float_mulratio(651803640367065917LL, 0U, 327563234U, 1191613855U), 639534906402789368LL); ASSERT_EQUAL( float_mulratio(3154958130393015979LL, 0U, 1304112625U, 3024066701U), 3153571282364880740LL); ASSERT_EQUAL( float_mulratio(1713286099776800976LL, 0U, 1902151138U, 2927030061U), 1712614441093927706LL); ASSERT_EQUAL( float_mulratio(2333142120591277120LL, 0U, 914099656U, 108514965U), 2349692988167140475LL); ASSERT_EQUAL( float_mulratio(995968561418010814LL, 0U, 1334462574U, 846156977U), 998955931389416094LL); ASSERT_EQUAL( float_mulratio(6276035843030312442LL, 0U, 2660687613U, 236740983U), 6294920527635363073LL); ASSERT_EQUAL( float_mulratio(7333118474702086419LL, 0U, 46947714U, 2479204760U), 7298214153648998535LL); ASSERT_EQUAL( float_mulratio(2873297486994296492LL, 0U, 880591893U, 436034100U), 2884122995598532757LL); ASSERT_EQUAL( float_mulratio(1935815261812737573LL, 0U, 3123665800U, 3786746543U), 1934366328810191207LL); ASSERT_EQUAL( float_mulratio(7249556282125616118LL, 0U, 2378803159U, 2248850590U), 7250005170160875417LL); ASSERT_EQUAL( float_mulratio(311005347529659996LL, 0U, 992915590U, 2433548552U), 308187142737041830LL); // today: round up test return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_multiply (int64_t, int64_t); extern int64_t float_one (void); #define INVALID_FLOAT -10024 #define DIVISION_BY_ZERO -25 #define XFL_OVERFLOW -30 #define ASSERT(x)\ if (!(x))\ rollback(0,0,__LINE__); extern int64_t float_compare(int64_t, int64_t, uint32_t); extern int64_t float_negate(int64_t); extern int64_t float_sum(int64_t, int64_t); extern int64_t float_mantissa(int64_t); #define float_exponent(f) (((int32_t)(((f) >> 54U) & 0xFFU)) - 97) #define ASSERT_EQUAL(x, y)\ {\ int64_t px = (x);\ int64_t py = (y);\ int64_t mx = float_mantissa(px);\ int64_t my = float_mantissa(py);\ int32_t diffexp = float_exponent(px) - float_exponent(py);\ if (diffexp == 1)\ mx *= 10LL;\ if (diffexp == -1)\ my *= 10LL;\ int64_t diffman = mx - my;\ if (diffman < 0) diffman *= -1LL;\ if (diffexp < 0) diffexp *= -1;\ if (diffexp > 1 || diffman > 5000000 || mx < 0 || my < 0)\ rollback((uint32_t) #x, sizeof(#x), __LINE__);\ } int64_t hook(uint32_t reserved ) { _g(1,1); // ensure invalid xfl are not accepted ASSERT(float_multiply(-1, float_one()) == INVALID_FLOAT); // multiply by 0 ASSERT(float_multiply(float_one(), 0) == 0); ASSERT(float_multiply(0, float_one()) == 0); // check 1 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()); // check overflow // 1e+95 * 1e+95 ASSERT(float_multiply(7801234554605699072LL, 7801234554605699072LL) == XFL_OVERFLOW); // 1e+95 * 10 ASSERT(float_multiply(7801234554605699072LL, 6107881094714392576LL) == XFL_OVERFLOW); ASSERT(float_multiply(6107881094714392576LL, 7801234554605699072LL) == XFL_OVERFLOW); // -1e+95 * 10 ASSERT(float_multiply(3189548536178311168LL, 6107881094714392576LL) == XFL_OVERFLOW); // identity ASSERT_EQUAL(float_multiply(3189548536178311168LL, float_one()), 3189548536178311168LL); ASSERT_EQUAL(float_multiply(float_one(), 3189548536178311168LL), 3189548536178311168LL); // random multiplications ASSERT_EQUAL( float_multiply( 7791757438262485039LL /* 9.537282166267951e+94 */, 4759088999670263908LL /* 3.287793167020132e-74 */), 6470304726017852129LL /* 3.135661113819873e+21 */); ASSERT_EQUAL( float_multiply( 7534790022873909775LL /* 4.771445910440463e+80 */, 1017891960669847079LL /* -9.085644138855975e-26 */), 2472307761756037979LL /* -4.335165957006171e+55 */); ASSERT_EQUAL( float_multiply( 2813999069907898454LL /* -3.75290242870895e+74 */, 4962524721184225460LL /* 8.56513107667986e-63 */), 1696567870013294731LL /* -3214410121988.235 */); ASSERT_EQUAL( float_multiply( 2151742066453140308LL /* -8.028643824784212e+37 */, 437647738130579252LL /* -5.302173903011636e-58 */), 5732835652591705549LL /* 4.256926576434637e-20 */); ASSERT_EQUAL( float_multiply( 5445302332922546340LL /* 4.953983058987172e-36 */, 7770966530708354172LL /* 6.760773121619068e+93 */), 7137051085305881332LL /* 3.349275551015668e+58 */); ASSERT_EQUAL( float_multiply( 2542989542826132533LL /* -2.959352989172789e+59 */, 6308418769944702613LL /* 3379291626008.213 */), 2775217422137696934LL /* -1.000051677471398e+72 */); ASSERT_EQUAL( float_multiply( 5017652318929433511LL /* 9.649533293441959e-60 */, 6601401767766764916LL /* 8.131913296358772e+28 */), 5538267259220228820LL /* 7.846916809259732e-31 */); ASSERT_EQUAL( float_multiply( 892430323307269235LL /* -9.724796342652019e-33 */, 1444078017997143500LL /* -0.0292613723858478 */), 5479222755754111850LL /* 2.845608871588714e-34 */); ASSERT_EQUAL( float_multiply( 7030632722283214253LL /* 5.017303585240493e+52 */, 297400838197636668LL /* -9.170462045924924e-66 */), 1247594596364389994LL /* -4.601099210133098e-13 */); ASSERT_EQUAL( float_multiply( 1321751204165279730LL /* -6.700112973094898e-9 */, 2451801790748530375LL /* -1.843593458980551e+54 */), 6918764256086244704LL /* 1.235228445162848e+46 */); ASSERT_EQUAL( float_multiply( 2055496484261758590LL /* -1.855054180812414e+32 */, 2079877890137711361LL /* -8.222061547283201e+33 */), 7279342234795540005LL /* 1.525236964818469e+66 */); ASSERT_EQUAL( float_multiply( 2439875962311968674LL /* -7.932163531900834e+53 */, 4707485682591872793LL /* 5.727671617074969e-77 */), 1067392794851803610LL /* -4.543282792366554e-23 */); ASSERT_EQUAL( float_multiply( 6348574818322812800LL /* 750654298515443.2 */, 6474046245013515838LL /* 6.877180109483582e+21 */), 6742547427357110773LL /* 5.162384810848757e+36 */); ASSERT_EQUAL( float_multiply( 1156137305783593424LL /* -3.215801176746448e-18 */, 351790564990861307LL /* -9.516993310703611e-63 */), 4650775291275116747LL /* 3.060475828764875e-80 */); ASSERT_EQUAL( float_multiply( 5786888485280994123LL /* 4.266563737277259e-17 */, 6252137323085080394LL /* 1141040294.831946 */), 5949619829273756852LL /* 4.868321144702132e-8 */); ASSERT_EQUAL( float_multiply( 2078182880999439640LL /* -6.52705240901148e+33 */, 1662438186251269392LL /* -51135233789.26864 */), 6884837854131013998LL /* 3.33762350889611e+44 */); ASSERT_EQUAL( float_multiply( 1823781083140711248LL /* -43268336830308640000 */, 1120252241608199010LL /* -3.359534020316002e-20 */), 6090320310700749729LL /* 1.453614495839137 */); ASSERT_EQUAL( float_multiply( 6617782604883935174LL /* 6.498351904047046e+29 */, 6185835042802056262LL /* 689635.404973575 */), 6723852137583788319LL /* 4.481493547008287e+35 */); ASSERT_EQUAL( float_multiply( 333952667495151166LL /* -9.693494324475454e-64 */, 1556040883317758614LL /* -68026.1150230799 */), 5032611291744396930LL /* 6.594107598923394e-59 */); ASSERT_EQUAL( float_multiply( 2326968399632616779LL /* -3.110991909440843e+47 */, 707513695207834635LL /* -4.952153338037259e-43 */), 6180479299649214949LL /* 154061.0896894437 */); ASSERT_EQUAL( float_multiply( 1271003508324696477LL /* -9.995612660957597e-12 */, 5321949753651889765LL /* 7.702193354704484e-43 */), 512101972406838314LL /* -7.698814141342762e-54 */); ASSERT_EQUAL( float_multiply( 1928646740923345323LL /* -1.106100408773035e+25 */, 4639329980209973352LL /* 9.629563273103463e-81 */), 487453886143282122LL /* -1.065126387268554e-55 */); ASSERT_EQUAL( float_multiply( 6023906813956669432LL /* 0.0007097711789686777 */, 944348444470060009LL /* -7.599721976996842e-30 */), 888099590592064434LL /* -5.394063627447218e-33 */); ASSERT_EQUAL( float_multiply( 6580290597764062787LL /* 5.035141803138627e+27 */, 6164319297265300034LL /* 33950.07022461506 */), 6667036882686408593LL /* 1.709434178074513e+32 */); ASSERT_EQUAL( float_multiply( 2523439530503240484LL /* -1.423739175762724e+58 */, 5864448766677980801LL /* 9.769251096336e-13 */), 2307233895764065602LL /* -1.39088655037165e+46 */); ASSERT_EQUAL( float_multiply( 6760707453987140465LL /* 5.308012931396465e+37 */, 5951641080643457645LL /* 6.889572514402925e-8 */), 6632955645489194550LL /* 3.656993999824438e+30 */); ASSERT_EQUAL( float_multiply( 6494270716308443375LL /* 9.087252894929135e+22 */, 564752637895553836LL /* -6.306284101612332e-51 */), 978508199357889360LL /* -5.730679845862224e-28 */); ASSERT_EQUAL( float_multiply( 6759145618427534062LL /* 3.746177371790062e+37 */, 4721897842483633304LL /* 2.125432999353496e-76 */), 5394267403342547165LL /* 7.962249007433949e-39 */); ASSERT_EQUAL( float_multiply( 1232673571201806425LL /* -7.694472557031513e-14 */, 6884256144221925318LL /* 2.75591359980743e+44 */), 2037747561727791012LL /* -2.12053015632682e+31 */); ASSERT_EQUAL( float_multiply( 1427694775835421031LL /* -0.004557293586344295 */, 4883952867277976402LL /* 2.050871208358738e-67 */), 225519204318055258LL /* -9.34642220427145e-70 */); ASSERT_EQUAL( float_multiply( 5843509949864662087LL /* 6.84483279249927e-14 */, 5264483986612843822LL /* 4.279621844104494e-46 */), 5028946513739275800LL /* 2.929329593802264e-59 */); ASSERT_EQUAL( float_multiply( 6038444022009738988LL /* 0.003620521333274348 */, 7447499078040748850LL /* 7.552493624689458e+75 */), 7406652183825856093LL /* 2.734396428760669e+73 */); ASSERT_EQUAL( float_multiply( 939565473697468970LL /* -2.816751204405802e-30 */, 1100284903077087966LL /* -1.406593998686942e-21 */), 5174094397561240825LL /* 3.962025339911417e-51 */); ASSERT_EQUAL( float_multiply( 5694071830210473617LL /* 1.521901214166673e-22 */, 5536709154363579683LL /* 6.288811952610595e-31 */), 5143674525748709391LL /* 9.570950546343951e-53 */); ASSERT_EQUAL( float_multiply( 600729862341871819LL /* -6.254711528966347e-49 */, 6330630279715378440LL /* 75764028872020.56 */), 851415551394320910LL /* -4.738821448667662e-35 */); ASSERT_EQUAL( float_multiply( 1876763139233864902LL /* -3.265694247738566e+22 */, 4849561230315278754LL /* 3.688031264625058e-69 */), 649722744589988028LL /* -1.204398248636604e-46 */); ASSERT_EQUAL( float_multiply( 3011947542126279863LL /* -3.542991042788535e+85 */, 1557732559110376235LL /* -84942.87294925611 */), 7713172080438368541LL /* 3.009518380079389e+90 */); ASSERT_EQUAL( float_multiply( 5391579936313268788LL /* 5.274781978155572e-39 */, 1018647290024655822LL /* -9.840973493664718e-26 */), 329450072133864644LL /* -5.190898963188932e-64 */); ASSERT_EQUAL( float_multiply( 2815029221608845312LL /* -4.783054129655808e+74 */, 4943518985822088837LL /* 7.57379422402522e-64 */), 1678961648155863225LL /* -362258677403.8713 */); ASSERT_EQUAL( float_multiply( 1377509900308195934LL /* -0.00000841561358756515 */, 7702104197062186199LL /* 9.95603351337903e+89 */), 2998768765665354000LL /* -8.378613091344656e+84 */); return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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_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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_one (void); extern int64_t float_negate(int64_t); #define ASSERT(x)\ if ((x) != 1)\ rollback(0,0,__LINE__) #define INVALID_FLOAT -10024 int64_t hook(uint32_t reserved ) { _g(1,1); int64_t result = 0; // test invalid floats { ASSERT(float_negate(-1) == INVALID_FLOAT); ASSERT(float_negate(-11010191919LL) == INVALID_FLOAT); } // test canonical zero ASSERT(float_negate(0) == 0); // test double negation { ASSERT(float_negate(float_one()) != float_one()); ASSERT(float_negate(float_negate(float_one())) == float_one()); } // test random numbers { // +/- 3.463476342523e+22 ASSERT(float_negate(6488646939756037240LL) == 1876960921328649336LL); ASSERT(float_negate(float_one()) == 1478180677777522688LL); ASSERT(float_negate(1838620299498162368LL) == 6450306317925550272LL); } return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_one (void); int64_t hook(uint32_t reserved ) { _g(1,1); int64_t f = float_one(); return f == 6089866696204910592ULL ? accept(0,0,2) : rollback(0,0,1); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_root (int64_t float1, uint32_t n); extern int64_t float_one (void); extern int64_t float_compare(int64_t, int64_t, uint32_t); extern int64_t float_negate(int64_t); extern int64_t float_sum(int64_t, int64_t); extern int64_t float_mantissa(int64_t); #define float_exponent(f) (((int32_t)(((f) >> 54U) & 0xFFU)) - 97) #define ASSERT_EQUAL(x, y)\ {\ int64_t px = (x);\ int64_t py = (y);\ int64_t mx = float_mantissa(px);\ int64_t my = float_mantissa(py);\ int32_t diffexp = float_exponent(px) - float_exponent(py);\ if (diffexp == 1)\ mx *= 10LL;\ if (diffexp == -1)\ my *= 10LL;\ int64_t diffman = mx - my;\ if (diffman < 0) diffman *= -1LL;\ if (diffexp < 0) diffexp *= -1;\ if (diffexp > 1 || diffman > 5000000 || mx < 0 || my < 0)\ rollback((uint32_t) #x, sizeof(#x), __LINE__);\ } int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT_EQUAL(float_root(float_one(), 2), float_one()); // sqrt 9 is 3 ASSERT_EQUAL(float_root(6097866696204910592LL, 2), 6091866696204910592LL); // cube root of 1000 is 10 ASSERT_EQUAL(float_root(6143909891733356544LL, 3), 6107881094714392576LL); // sqrt of negative is "complex not supported error" if (float_root(1478180677777522688LL, 2) != -39) rollback(0,0,__LINE__); // tenth root of 0 is 0 if (float_root(0, 10) != 0) rollback(0,0,__LINE__); return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 float_set (int32_t, int64_t); #define INVALID_FLOAT -10024 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reserved ) { _g(1,1); // zero mantissa should return canonical zero ASSERT(float_set(-5, 0) == 0); ASSERT(float_set(50, 0) == 0); ASSERT(float_set(-50, 0) == 0); ASSERT(float_set(0, 0) == 0); // an exponent lower than -96 should produce an invalid float error ASSERT(float_set(-97, 1) == INVALID_FLOAT); // an exponent larger than +96 should produce an invalid float error ASSERT(float_set(+97, 1) == INVALID_FLOAT); ASSERT(float_set(-5,6541432897943971LL) == 6275552114197674403LL); ASSERT(float_set(-83,7906202688397446LL) == 4871793800248533126LL); ASSERT(float_set(76,4760131426754533LL) == 7732937091994525669LL); ASSERT(float_set(37,-8019384286534438LL) == 2421948784557120294LL); ASSERT(float_set(50,5145342538007840LL) == 7264947941859247392LL); ASSERT(float_set(-70,4387341302202416LL) == 5102462119485603888LL); ASSERT(float_set(-26,-1754544005819476LL) == 1280776838179040340LL); ASSERT(float_set(36,8261761545780560LL) == 7015862781734272336LL); ASSERT(float_set(35,7975622850695472LL) == 6997562244529705264LL); ASSERT(float_set(17,-4478222822793996LL) == 2058119652903740172LL); ASSERT(float_set(-53,5506604247857835LL) == 5409826157092453035LL); ASSERT(float_set(-60,5120164869507050LL) == 5283338928147728362LL); ASSERT(float_set(41,5176113875683063LL) == 7102849126611584759LL); ASSERT(float_set(-54,-3477931844992923LL) == 778097067752718235LL); ASSERT(float_set(21,6345031894305479LL) == 6743730074440567495LL); ASSERT(float_set(-23,5091583691147091LL) == 5949843091820201811LL); ASSERT(float_set(-33,7509684078851678LL) == 5772117207113086558LL); ASSERT(float_set(-72,-1847771838890268LL) == 452207734575939868LL); ASSERT(float_set(71,-9138413713437220LL) == 3035557363306410532LL); ASSERT(float_set(28,4933894067102586LL) == 6868419726179738490LL); return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_one (void); extern int64_t float_sign(int64_t); extern int64_t float_negate(int64_t); #define ASSERT(x)\ if ((x) != 1)\ rollback(0,0,__LINE__) #define ASSERT_EQUAL(x,y) ASSERT((x) == (y)) #define INVALID_FLOAT -10024 int64_t hook(uint32_t reserved ) { _g(1,1); int64_t result = 0; // test invalid floats { ASSERT(float_sign(-1) == INVALID_FLOAT); ASSERT(float_sign(-11010191919LL) == INVALID_FLOAT); } // test canonical zero ASSERT(float_sign(0) == 0); // test one ASSERT(float_sign(float_one()) == 0); ASSERT(float_sign(float_negate(float_one())) == 1); // test random numbers ASSERT_EQUAL( float_sign(7248434512952957686LL /* 6.646312141200119e+64 */), 0LL); ASSERT_EQUAL( float_sign(889927818394811978LL /* -7.222291430194763e-33 */), 1LL); ASSERT_EQUAL(float_sign(5945816149233111421LL /* 1.064641104056701e-8 */), 0LL); ASSERT_EQUAL(float_sign(6239200145838704863LL /* 621826155.7938399 */), 0LL); ASSERT_EQUAL( float_sign(6992780785042190360LL /* 3.194163363180568e+50 */), 0LL); ASSERT_EQUAL( float_sign(6883099933108789087LL /* 1.599702486671199e+44 */), 0LL); ASSERT_EQUAL( float_sign(890203738162163464LL /* -7.498211197546248e-33 */), 1LL); ASSERT_EQUAL(float_sign(4884803073052080964LL /* 2.9010769824633e-67 */), 0LL); ASSERT_EQUAL( float_sign(2688292350356944394LL /* -4.146972444128778e+67 */), 1LL); ASSERT_EQUAL( float_sign(4830109852288093280LL /* 2.251051746921568e-70 */), 0LL); ASSERT_EQUAL( float_sign(294175951907940320LL /* -5.945575756228576e-66 */), 1LL); ASSERT_EQUAL( float_sign(7612037404955382316LL /* 9.961233953985069e+84 */), 0LL); ASSERT_EQUAL(float_sign(7520840929603658997LL /* 8.83675114967167e+79 */), 0LL); ASSERT_EQUAL( float_sign(4798982086157926282LL /* 7.152082635718538e-72 */), 0LL); ASSERT_EQUAL( float_sign(689790136568817905LL /* -5.242993208502513e-44 */), 1LL); ASSERT_EQUAL( float_sign(5521738045011558042LL /* 9.332101110070938e-32 */), 0LL); ASSERT_EQUAL( float_sign(728760820583452906LL /* -8.184880204173546e-42 */), 1LL); ASSERT_EQUAL( float_sign(2272937984362856794LL /* -3.12377216812681e+44 */), 1LL); ASSERT_EQUAL(float_sign(1445723661896317830LL /* -0.0457178113775911 */), 1LL); ASSERT_EQUAL( float_sign(5035721527359772724LL /* 9.704343214299189e-59 */), 0LL); return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t hook_account (uint32_t, uint32_t); extern int64_t float_sto ( uint32_t write_ptr, uint32_t write_len, uint32_t cread_ptr, uint32_t cread_len, uint32_t iread_ptr, uint32_t iread_len, int64_t float1, uint32_t field_code ); extern int64_t float_sto_set ( uint32_t read_ptr, uint32_t read_len ); #define OUT_OF_BOUNDS (-1) #define INVALID_FLOAT (-10024) #define INVALID_ARGUMENT (-7) #define TOO_SMALL (-4) #define XFL_OVERFLOW (-30) #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) x, sizeof(x) #define sfAmount ((6U << 16U) + 1U) #define sfDeliveredAmount ((6U << 16U) + 18U) uint8_t cur1[3] = {'U','S','D'}; uint8_t cur1full[20] = { 0,0,0,0,0,0,0,0,0,0,0,0, 'U', 'S', 'D', 0,0,0,0,0 }; #define BUFFER_EQUAL_20(buf1, buf2)\ (\ *(((uint64_t*)(buf1)) + 0)) == *(((uint64_t*)(buf2)) + 0) &&\ *(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\ *(((uint32_t*)(buf1)) + 4) == *(((uint32_t*)(buf2)) + 4) int64_t hook(uint32_t reserved ) { _g(1,1); int64_t y; uint8_t cur2[20]; for (int i =0; GUARD(20), i < 20; ++i) cur2[i] = i; uint8_t iss[20]; ASSERT(hook_account(SBUF(iss)) == 20); uint8_t buf[50]; // the three buffers must be bounds checked ASSERT((y=float_sto(1000000, 50, 0,0,0,0,0,0)) == OUT_OF_BOUNDS); ASSERT((y=float_sto(0, 1000000, 0,0,0,0,0,0)) == OUT_OF_BOUNDS); ASSERT((y=float_sto(SBUF(buf), 1000000, 20, 1,20,0,0)) == OUT_OF_BOUNDS); ASSERT((y=float_sto(SBUF(buf), 1, 1000000, 1,20,0,0)) == INVALID_ARGUMENT); ASSERT((y=float_sto(SBUF(buf), 1,20, 1000000, 20, 0,0)) == OUT_OF_BOUNDS); ASSERT((y=float_sto(SBUF(buf), 1,20, 1, 1000000, 0,0)) == INVALID_ARGUMENT); // zero issuer/currency pointers must be accompanied by 0 length ASSERT((y=float_sto(SBUF(buf), 0, 1, 0,0, 0,0)) == INVALID_ARGUMENT); ASSERT((y=float_sto(SBUF(buf), 0, 0, 0,1, 0,0)) == INVALID_ARGUMENT); // zero issuer/currency lengths mus tbe accompanied by 0 pointers ASSERT((y=float_sto(SBUF(buf), 1, 0, 0,0, 0,0)) == INVALID_ARGUMENT); ASSERT((y=float_sto(SBUF(buf), 0, 0, 1,0, 0,0)) == INVALID_ARGUMENT); // issuer without currency is invalid ASSERT((y=float_sto(SBUF(buf), 0,0, SBUF(iss), 0, sfAmount)) == INVALID_ARGUMENT); // currency without issuer is invalid ASSERT((y=float_sto(SBUF(buf), SBUF(cur1), 0,0, 0, sfAmount)) == INVALID_ARGUMENT); // currency and issuer with field code 0 = XRP is invalid ASSERT((y=float_sto(SBUF(buf), SBUF(cur1), SBUF(iss), 0, 0)) == INVALID_ARGUMENT); // invalid XFL ASSERT((y=float_sto(SBUF(buf), SBUF(cur2), SBUF(iss), -1, sfAmount)) == INVALID_FLOAT); // valid XFL, currency and issuer { // currency and issuer with field code not XRP is valid (XFL = 1234567.0) // try with a three letter currency ASSERT((y=float_sto(SBUF(buf), SBUF(cur1), SBUF(iss), 6198187654261802496ULL, sfAmount)) == 49); // check the output contains the correct currency code ASSERT(BUFFER_EQUAL_20(buf + 9, cur1full)); // again with a 20 byte currency ASSERT((y=float_sto(SBUF(buf), SBUF(cur2), SBUF(iss), 6198187654261802496ULL, sfAmount)) == 49); // check the output contains the correct currency code ASSERT(BUFFER_EQUAL_20(buf + 9, cur2)); // check the output contains the correct issuer ASSERT(BUFFER_EQUAL_20(buf + 29, iss)); // check the field code is correct ASSERT(buf[0] == 0x61U); // sfAmount // reverse the operation and check the XFL amount is correct ASSERT((y=float_sto_set(buf, 49)) == 6198187654261802496ULL); // test 0 ASSERT((y=float_sto(SBUF(buf), SBUF(cur2), SBUF(iss), 0, sfAmount)) == 49); ASSERT((y=float_sto_set(buf, 49)) == 0); } // the same again but with a field-uncommon fieldcode { ASSERT((y=float_sto(SBUF(buf), SBUF(cur2), SBUF(iss), 6198187654261802496ULL, sfDeliveredAmount)) == 50); // check the first 2 bytes ASSERT(buf[0] == 0x60U && buf[1] == 0x12U); // same checks as above moved along one // check the output contains the correct currency code ASSERT(BUFFER_EQUAL_20(buf + 10, cur2)); // check the output contains the correct issuer ASSERT(BUFFER_EQUAL_20(buf + 30, iss)); // reverse the operation and check the XFL amount is correct ASSERT((y=float_sto_set(SBUF(buf))) == 6198187654261802496ULL); } // and the same again except use -1 as field code to supress field type bytes { // zero the serialized amount bytes *((uint64_t*)(buf + 2)) = 0; // request fieldcode -1 = only serialize the number ASSERT((y=float_sto(buf + 2, 8, 0,0,0,0, 6198187654261802496ULL, 0xFFFFFFFFUL)) == 8); // reverse the operation and check the XFL amount is correct ASSERT((y=float_sto_set(SBUF(buf))) == 6198187654261802496ULL); // try again with some different xfls ASSERT((y=float_sto(buf + 2, 8, 0,0,0,0, 1244912689067196128ULL, 0xFFFFFFFFUL)) == 8); ASSERT((y=float_sto_set(SBUF(buf))) == 1244912689067196128ULL); // test 0 ASSERT((y=float_sto(buf + 2, 8, 0,0,0,0, 0, 0xFFFFFFFFUL)) == 8); ASSERT((y=float_sto_set(SBUF(buf))) == 0); } // finally test xrp { // zero the serialized amount bytes *((uint64_t*)(buf)) = 0; // request fieldcode 0 = xrp amount serialized ASSERT((y=float_sto(buf + 1, 8, 0,0,0,0, 6198187654261802496ULL, 0)) == 8); buf[0] = 0x61U; ASSERT((y=float_sto_set(buf, 9)) == 6198187654261802496ULL); // test 0 ASSERT((y=float_sto(buf + 1, 8, 0,0,0,0, 0, 0)) == 8); ASSERT((y=float_sto_set(buf, 9)) == 0); //6198187654373024496 } return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t hook_account (uint32_t, uint32_t); extern int64_t float_sto_set ( uint32_t read_ptr, uint32_t read_len ); #define NOT_AN_OBJECT (-23) #define OUT_OF_BOUNDS (-1) #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) x, sizeof(x) #define BUFFER_EQUAL_20(buf1, buf2)\ (\ *(((uint64_t*)(buf1)) + 0)) == *(((uint64_t*)(buf2)) + 0) &&\ *(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\ *(((uint32_t*)(buf1)) + 4) == *(((uint32_t*)(buf2)) + 4) // 1234567000000000 * 10**-9, currency USD, issuer 7C4C8D5B2FDA1D16E9A4F5BB579AC2926C146235 (alice) uint8_t iou[] = //6198187654261802496 { 0x61U,0xD6U,0x04U,0x62U,0xD5U,0x07U,0x7CU,0x86U,0x00U,0x00U, 0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U, 0x00U,0x55U,0x53U,0x44U,0x00U,0x00U,0x00U,0x00U,0x00U,0x7CU, 0x4CU,0x8DU,0x5BU,0x2FU,0xDAU,0x1DU,0x16U,0xE9U,0xA4U,0xF5U, 0xBBU,0x57U,0x9AU,0xC2U,0x92U,0x6CU,0x14U,0x62U,0x35U }; // as above but value is negative uint8_t iou_neg[] = //1586501635834414592 { 0x61U,0x96U,0x04U,0x62U,0xD5U,0x07U,0x7CU,0x86U,0x00U,0x00U, 0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U, 0x00U,0x55U,0x53U,0x44U,0x00U,0x00U,0x00U,0x00U,0x00U,0x7CU, 0x4CU,0x8DU,0x5BU,0x2FU,0xDAU,0x1DU,0x16U,0xE9U,0xA4U,0xF5U, 0xBBU,0x57U,0x9AU,0xC2U,0x92U,0x6CU,0x14U,0x62U,0x35U }; // as above but value is 0 uint8_t iou_zero[] = // 0 { 0x61U,0x80U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U, 0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U, 0x00U,0x55U,0x53U,0x44U,0x00U,0x00U,0x00U,0x00U,0x00U,0x7CU, 0x4CU,0x8DU,0x5BU,0x2FU,0xDAU,0x1DU,0x16U,0xE9U,0xA4U,0xF5U, 0xBBU,0x57U,0x9AU,0xC2U,0x92U,0x6CU,0x14U,0x62U,0x35U }; // XRP short code 1234567 drops uint8_t xrp_short[] = //6198187654261802496 { 0x61U,0x40U,0x00U,0x00U,0x00U,0x00U,0x12U,0xD6U,0x87U }; // XRP long code 755898701447 drops uint8_t xrp_long[] = //6294584066823682416 { 0x60U,0x11U,0x40U,0x00U,0x00U,0xAFU,0xFFU,0x12U,0xD6U,0x87U }; // XRP negative 1234567 drops uint8_t xrp_neg[] = //1586501635834414592 { 0x61U,0x00U,0x00U,0x00U,0x00U,0x00U,0x12U,0xD6U,0x87U }; // XRP negative zero uint8_t xrp_neg_zero[] = // 0 { 0x61U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U }; // XRP positive zero uint8_t xrp_pos_zero[] = // 0 { 0x61U,0x40U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U }; int64_t hook(uint32_t reserved ) { _g(1,1); int64_t y; // bounds check ASSERT((y=float_sto_set(1000000, 50)) == OUT_OF_BOUNDS); ASSERT((y=float_sto_set(0, 1000000)) == OUT_OF_BOUNDS); ASSERT((y=float_sto_set(1000000, 1000000)) == OUT_OF_BOUNDS); // too small check ASSERT((y=float_sto_set(0, 7)) == NOT_AN_OBJECT); // garbage check ASSERT((y=float_sto_set(0, 9)) == NOT_AN_OBJECT); ASSERT((y=float_sto_set(0, 8)) == 0); ASSERT((y=float_sto_set(SBUF(iou))) == 6198187654261802496ULL); ASSERT((y=float_sto_set(SBUF(xrp_short))) == 6198187654261802496ULL); ASSERT((y=float_sto_set(SBUF(iou_neg))) == 1586501635834414592ULL); ASSERT((y=float_sto_set(SBUF(xrp_neg))) == 1586501635834414592ULL); ASSERT((y=float_sto_set(SBUF(xrp_pos_zero))) == 0); ASSERT((y=float_sto_set(SBUF(xrp_neg_zero))) == 0); ASSERT((y=float_sto_set(SBUF(iou_zero))) == 0); ASSERT((y=float_sto_set(SBUF(xrp_long))) == 6294584066823682416ULL); return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t float_one (void); extern int64_t float_compare(int64_t, int64_t, uint32_t); extern int64_t float_negate(int64_t); extern int64_t float_sum(int64_t, int64_t); extern int64_t float_mantissa(int64_t); #define float_exponent(f) (((int32_t)(((f) >> 54U) & 0xFFU)) - 97) #define ASSERT_EQUAL(x, y)\ {\ int64_t px = (x);\ int64_t py = (y);\ int64_t mx = float_mantissa(px);\ int64_t my = float_mantissa(py);\ int32_t diffexp = float_exponent(px) - float_exponent(py);\ if (diffexp == 1)\ mx *= 10LL;\ if (diffexp == -1)\ my *= 10LL;\ int64_t diffman = mx - my;\ if (diffman < 0) diffman *= -1LL;\ if (diffexp < 0) diffexp *= -1;\ if (diffexp > 1 || diffman > 5000000 || mx < 0 || my < 0)\ rollback((uint32_t) #x, sizeof(#x), __LINE__);\ } int64_t hook(uint32_t reserved ) { _g(1,1); // 1 + 1 = 2 ASSERT_EQUAL(6090866696204910592LL, float_sum(float_one(), float_one())); // 1 - 1 = 0 ASSERT_EQUAL(0, float_sum(float_one(), float_negate(float_one()))); // 45678 + 0.345678 = 45678.345678 ASSERT_EQUAL(6165492124810638528LL, float_sum(6165492090242838528LL, 6074309077695428608LL)); // -151864512641 + 100000000000000000 = 99999848135487359 ASSERT_EQUAL( 6387097057170171072LL, float_sum(1676857706508234512LL, 6396111470866104320LL)); // auto generated random sums ASSERT_EQUAL( float_sum( 95785354843184473 /* -5.713362295774553e-77 */, 7607324992379065667 /* 5.248821377668419e+84 */), 7607324992379065667 /* 5.248821377668419e+84 */); ASSERT_EQUAL( float_sum( 1011203427860697296 /* -2.397111329706192e-26 */, 7715811566197737722 /* 5.64900413944857e+90 */), 7715811566197737722 /* 5.64900413944857e+90 */); ASSERT_EQUAL( float_sum( 6507979072644559603 /* 4.781210721563379e+23 */, 422214339164556094 /* -7.883173446470462e-59 */), 6507979072644559603 /* 4.781210721563379e+23 */); ASSERT_EQUAL( float_sum( 129493221419941559 /* -3.392431853567671e-75 */, 6742079437952459317 /* 4.694395406197301e+36 */), 6742079437952459317 /* 4.694395406197301e+36 */); ASSERT_EQUAL( float_sum( 5172806703808250354 /* 2.674331586920946e-51 */, 3070396690523275533 /* -7.948943911338253e+88 */), 3070396690523275533 /* -7.948943911338253e+88 */); ASSERT_EQUAL( float_sum( 2440992231195047997 /* -9.048432414980156e+53 */, 4937813945440933271 /* 1.868753842869655e-64 */), 2440992231195047996 /* -9.048432414980156e+53 */); ASSERT_EQUAL( float_sum( 7351918685453062372 /* 2.0440935844129e+70 */, 6489541496844182832 /* 4.358033430668592e+22 */), 7351918685453062372 /* 2.0440935844129e+70 */); ASSERT_EQUAL( float_sum( 4960621423606196948 /* 6.661833498651348e-63 */, 6036716382996689576 /* 0.001892882320224936 */), 6036716382996689576 /* 0.001892882320224936 */); ASSERT_EQUAL( float_sum( 1342689232407435206 /* -9.62374270576839e-8 */, 5629833007898276923 /* 9.340672939897915e-26 */), 1342689232407435206 /* -9.62374270576839e-8 */); ASSERT_EQUAL( float_sum( 7557687707019793516 /* 9.65473154684222e+81 */, 528084028396448719 /* -5.666471621471183e-53 */), 7557687707019793516 /* 9.65473154684222e+81 */); ASSERT_EQUAL( float_sum( 130151633377050812 /* -4.050843810676924e-75 */, 2525286695563827336 /* -3.270904236349576e+58 */), 2525286695563827336 /* -3.270904236349576e+58 */); ASSERT_EQUAL( float_sum( 5051914485221832639 /* 7.88290256687712e-58 */, 7518727241611221951 /* 6.723063157234623e+79 */), 7518727241611221951 /* 6.723063157234623e+79 */); ASSERT_EQUAL( float_sum( 3014788764095798870 /* -6.384213012307542e+85 */, 7425019819707800346 /* 3.087633801222938e+74 */), 3014788764095767995 /* -6.384213012276667e+85 */); ASSERT_EQUAL( float_sum( 4918950856932792129 /* 1.020063844210497e-65 */, 7173510242188034581 /* 3.779635414204949e+60 */), 7173510242188034581 /* 3.779635414204949e+60 */); ASSERT_EQUAL( float_sum( 20028000442705357 /* -2.013601933223373e-81 */, 95248745393457140 /* -5.17675284604722e-77 */), 95248946753650462 /* -5.176954206240542e-77 */); ASSERT_EQUAL( float_sum( 5516870225060928024 /* 4.46428115944092e-32 */, 7357202055584617194 /* 7.327463715967722e+70 */), 7357202055584617194 /* 7.327463715967722e+70 */); ASSERT_EQUAL( float_sum( 2326103538819088036 /* -2.2461310959121e+47 */, 1749360946246242122 /* -1964290826489674 */), 2326103538819088036 /* -2.2461310959121e+47 */); ASSERT_EQUAL( float_sum( 1738010758208819410 /* -862850129854894.6 */, 2224610859005732191 /* -8.83984233944816e+41 */), 2224610859005732192 /* -8.83984233944816e+41 */); ASSERT_EQUAL( float_sum( 4869534730307487904 /* 5.647132747352224e-68 */, 2166841923565712115 /* -5.114102427874035e+38 */), 2166841923565712115 /* -5.114102427874035e+38 */); ASSERT_EQUAL( float_sum( 1054339559322014937 /* -9.504445772059864e-24 */, 1389511416678371338 /* -0.0000240273144825857 */), 1389511416678371338 /* -0.0000240273144825857 */); return accept(0,0,0); } )[test.hook]"]; env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t hook_account (uint32_t, uint32_t); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t acc[20]; // Test out of bounds check ASSERT(hook_account(1000000, 20) == OUT_OF_BOUNDS); ASSERT(hook_account(0, 1000000) == OUT_OF_BOUNDS); ASSERT(hook_account((uint32_t)acc, 19) == TOO_SMALL); ASSERT(hook_account((uint32_t)acc, 20) == 20); // return the accid as the return string accept((uint32_t)acc, 20, 0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 retStr = hookExecutions[0].getFieldVL(sfHookReturnString); // 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, {{hso(hook, 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 retStr = hookExecutions[0].getFieldVL(sfHookReturnString); // 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 retStr = hookExecutions[1].getFieldVL(sfHookReturnString); // 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 hook_again (void); #define PREREQUISITE_NOT_MET (-9) #define ALREADY_SET (-8) int64_t hook(uint32_t r) { _g(1,1); if (r > 0) { if (hook_again() != PREREQUISITE_NOT_MET) return rollback(0,0,253); return accept(0,0,1); } if (hook_again() != 1) return rollback(0,0,254); if (hook_again() != ALREADY_SET) return rollback(0,0,255); return accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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}; 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 id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t hook_hash (uint32_t, uint32_t, int32_t); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t hash[32]; // Test out of bounds check ASSERT(hook_hash(1000000, 32, -1) == OUT_OF_BOUNDS); ASSERT(hook_hash((uint32_t)hash, 31, -1) == TOO_SMALL); ASSERT(hook_hash((uint32_t)hash, 32, -1) == 32); // return the hash as the return string accept((uint32_t)hash, 32, 0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 retStr = hookExecutions[0].getFieldVL(sfHookReturnString); // 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t hook_hash (uint32_t, uint32_t, int32_t); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reserved ) { _g(1,2); uint8_t hash[32]; // Test out of bounds check ASSERT(hook_hash(1000000, 32, -1) == OUT_OF_BOUNDS); ASSERT(hook_hash((uint32_t)hash, 31, -1) == TOO_SMALL); ASSERT(hook_hash((uint32_t)hash, 32, -1) == 32); // return the hash as the return string accept((uint32_t)hash, 32, 0); } )[test.hook]"]; // install a slightly different hook on bob env(ripple::test::jtx::hook(bob, {{hso(hook2, 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 retStr1 = hookExecutions[0].getFieldVL(sfHookReturnString); // 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 retStr2 = hookExecutions[1].getFieldVL(sfHookReturnString); // 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}; Account const alice{"alice"}; Account const bob{"bob"}; env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); TestHook hook = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 hook_param(uint32_t, uint32_t, uint32_t, uint32_t); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define OUT_OF_BOUNDS (-1) #define TOO_BIG (-3) #define TOO_SMALL (-4) #define DOESNT_EXIST (-5) uint8_t* names[] = { "param0", "param1", "param2", "param3", "param4", "param5", "param6", "param7", "param8", "param9", "param10", "param11", "param12", "param13", "param14", "param15" }; uint8_t* values[] = { "value0", "value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8", "value9", "value10", "value11", "value12", "value13", "value14", "value15" }; #define SBUF(x) x,sizeof(x) #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(hook_param(0, 1000000, 0,32) == OUT_OF_BOUNDS); ASSERT(hook_param(1000000, 32, 0, 32) == OUT_OF_BOUNDS); ASSERT(hook_param(0, 32, 1000000, 32) == OUT_OF_BOUNDS); ASSERT(hook_param(0, 32, 0, 1000000) == OUT_OF_BOUNDS); ASSERT(hook_param(0, 32, 0, 33) == TOO_BIG); ASSERT(hook_param(0, 32, 0, 0) == TOO_SMALL); ASSERT(hook_param(0, 32, 0, 32) == DOESNT_EXIST); uint8_t buf[32]; for (int i = 0; GUARD(16), i < 16; ++i) { int s = 6 + (i < 10 ? 0 : 1); int64_t v = hook_param(SBUF(buf), names[i], s); ASSERT(v == s); ASSERT(buf[0] == 'v' && buf[1] == 'a' && buf[2] == 'l' && buf[3] == 'u' && buf[4] == 'e'); ASSERT(*(buf + v - 1) == *(values[i] + v - 1)); ASSERT(*(buf + v - 2) == *(values[i] + v - 2)); } accept(0,0,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::HookApiVersion] = 0U; 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 hook_param(uint32_t, uint32_t, uint32_t, uint32_t); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define OUT_OF_BOUNDS (-1) #define TOO_BIG (-3) #define TOO_SMALL (-4) #define DOESNT_EXIST (-5) #define INVALID_ARGUMENT (-7) uint8_t* names[] = { "param0", "param1", "param2", "param3", }; #define SBUF(x) x,sizeof(x) #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) int64_t hook(uint32_t reserved ) { _g(1,1); // this entry should be deleted by the setter uint8_t checker_hash[32]; ASSERT(hook_param(SBUF(checker_hash), "checker", 7) == DOESNT_EXIST); uint8_t buf[32]; // this entry should havebeen added by the setter ASSERT(hook_param(SBUF(buf), "hello", 5) == 5); ASSERT(buf[0] == 'w' && buf[1] == 'o' && buf[2] == 'r' && buf[3] == 'l' && buf[4] == 'd'); // these pre-existing entries should be modified by the setter for (int i = 0; GUARD(4), i < 4; ++i) { ASSERT(hook_param(SBUF(buf), names[i], 6) == 6); ASSERT(buf[0] == 'v' && buf[1] == 'a' && buf[2] == 'l' && buf[3] == 'u' && buf[4] == 'e' && buf[5] == '0' + i); } accept(0,0,0); } )[test.hook]"]; TestHook setter_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 hook_param (uint32_t, uint32_t, uint32_t, uint32_t); extern int64_t hook_param_set ( uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t hread_ptr, uint32_t hread_len ); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define OUT_OF_BOUNDS (-1) #define TOO_BIG (-3) #define TOO_SMALL (-4) #define DOESNT_EXIST (-5) #define INVALID_ARGUMENT (-7) uint8_t* names[] = { "param0", "param1", "param2", "param3", }; uint8_t* values[] = { "value0", "value1", "value2", "value3", }; #define SBUF(x) x,sizeof(x) #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(hook_param_set(1000000, 32, 0, 32, 0, 32) == OUT_OF_BOUNDS); ASSERT(hook_param_set(0, 1000000, 0, 32, 0, 32) == OUT_OF_BOUNDS); ASSERT(hook_param_set(0, 32, 1000000, 32, 0, 32) == OUT_OF_BOUNDS); ASSERT(hook_param_set(0, 32, 0, 1000000, 0, 32) == OUT_OF_BOUNDS); ASSERT(hook_param_set(0, 32, 0, 32, 1000000, 32) == OUT_OF_BOUNDS); ASSERT(hook_param_set(0, 32, 0, 32, 0, 1000000) == OUT_OF_BOUNDS); ASSERT(hook_param_set(0, 32, 0, 0, 0, 32) == TOO_SMALL); ASSERT(hook_param_set(0, 32, 0, 33, 0, 32) == TOO_BIG); ASSERT(hook_param_set(0, 32, 0, 32, 0, 33) == INVALID_ARGUMENT); ASSERT(hook_param_set(0, 32, 0, 32, 0, 31) == INVALID_ARGUMENT); ASSERT(hook_param_set(0, 32, 0, 32, 0, 0) == INVALID_ARGUMENT); ASSERT(hook_param_set(0, 257, 0, 32, 0, 32) == TOO_BIG); uint8_t checker_hash[32]; ASSERT(hook_param(SBUF(checker_hash), "checker", 7) == 32); for (int i = 0; GUARD(4), i < 4; ++i) { ASSERT(hook_param_set(values[i], 6, names[i], 6, SBUF(checker_hash)) == 6); } // "delete" the checker entry" for when the checker runs ASSERT(hook_param_set(0,0,"checker", 7, SBUF(checker_hash)) == 0); // add a parameter that did not previously exist ASSERT(hook_param_set("world", 5,"hello", 5, SBUF(checker_hash)) == 5); // ensure this hook's parameters did not change uint8_t buf[32]; for (int i = 0; GUARD(4), i < 4; ++i) { ASSERT(hook_param(SBUF(buf), names[i], 6) == 6); ASSERT(buf[0] == 'v' && buf[1] == 'a' && buf[2] == 'l' && buf[3] == 'u' && buf[4] == 'e' && buf[5] == '0'); } accept(0,0,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::HookApiVersion] = 0U; 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t hook_pos (void); int64_t hook(uint32_t reserved ) { _g(1,1); accept(0,0,hook_pos()); } )[test.hook]"]; // install the hook on alice in all four spots env(ripple::test::jtx::hook( alice, {{hso(hook), hso(hook), hso(hook), hso(hook)}}, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 hook_skip (uint32_t, uint32_t, uint32_t); extern int64_t otxn_field (uint32_t, uint32_t, uint32_t); extern int64_t hook_hash (uint32_t, uint32_t, int32_t); extern int64_t hook_pos(void); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__ << 8U); #define sfInvoiceID ((5U << 16U) + 17U) #define SBUF(x) x,sizeof(x) #define OUT_OF_BOUNDS (-1) #define DOESNT_EXIST (-5) #define INVALID_ARGUMENT (-7) int64_t hook(uint32_t reserved ) { _g(1,1); // bounds checks ASSERT(hook_skip(0, 1000000, 0) == OUT_OF_BOUNDS); ASSERT(hook_skip(1000000, 32, 0) == OUT_OF_BOUNDS); ASSERT(hook_skip(1000000, 100000, 0) == OUT_OF_BOUNDS); ASSERT(hook_skip(0, 33, 0) == INVALID_ARGUMENT); ASSERT(hook_skip(0, 1000000, 1) == OUT_OF_BOUNDS); ASSERT(hook_skip(1000000, 32, 1) == OUT_OF_BOUNDS); ASSERT(hook_skip(1000000, 100000, 1) == OUT_OF_BOUNDS); ASSERT(hook_skip(0, 33, 1) == INVALID_ARGUMENT); // garbage check ASSERT(hook_skip(0, 32, 0) == DOESNT_EXIST); ASSERT(hook_skip(0, 32, 1) == DOESNT_EXIST); ASSERT(hook_skip(0, 32, 2) == INVALID_ARGUMENT); // the hook to skip is passed in by invoice id uint8_t skip[32]; ASSERT(otxn_field(SBUF(skip), sfInvoiceID) == 32); // get this hook's hash uint8_t hash[32]; ASSERT(hook_hash(SBUF(hash), (uint32_t)hook_pos()) == 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(SBUF(hash), 1) == DOESNT_EXIST); ASSERT(hook_skip(SBUF(hash), 0) == 1); ASSERT(hook_skip(SBUF(hash), 1) == 1); // finally skip the hook hash indicated by invoice id ASSERT(hook_skip(SBUF(skip), 0)); accept(0,0,hook_pos()); } )[test.hook]"]; TestHook pos_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t hook_pos (void); int64_t hook(uint32_t reserved ) { _g(1,1); accept(0,0,255); } )[test.hook]"]; HASH_WASM(pos); // install the hook on alice in one places env(ripple::test::jtx::hook( alice, {{hso(skip_wasm), hso(pos_wasm), hso(pos_wasm), hso(skip_wasm)}}, 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_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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 ledger_keylet(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); extern int64_t slot_set(uint32_t, uint32_t, uint32_t); #define ASSERT(x)\ if (!(x))\ {\ rollback((uint32_t)#x, sizeof(#x), __LINE__);\ } #define SBUF(x) x, sizeof(x) #define OUT_OF_BOUNDS (-1) #define TOO_SMALL (-4) #define TOO_BIG (-3) #define INVALID_ARGUMENT (-7) #define DOESNT_EXIST (-5) #define DOES_NOT_MATCH (-40) int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(ledger_keylet(1000000, 34, 0, 34, 0, 34) == OUT_OF_BOUNDS); ASSERT(ledger_keylet(0, 1000000, 0, 34, 0, 34) == OUT_OF_BOUNDS); ASSERT(ledger_keylet(0, 34, 1000000, 34, 0, 34) == OUT_OF_BOUNDS); ASSERT(ledger_keylet(0, 34, 0, 1000000, 0, 34) == OUT_OF_BOUNDS); ASSERT(ledger_keylet(0, 34, 0, 34, 1000000, 34) == OUT_OF_BOUNDS); ASSERT(ledger_keylet(0, 34, 0, 34, 0, 1000000) == OUT_OF_BOUNDS); ASSERT(ledger_keylet(0, 33, 0, 34, 0, 34) == TOO_SMALL); ASSERT(ledger_keylet(0, 34, 0, 33, 0, 34) == TOO_SMALL); ASSERT(ledger_keylet(0, 34, 0, 34, 0, 33) == TOO_SMALL); ASSERT(ledger_keylet(0, 35, 0, 34, 0, 34) == TOO_BIG); ASSERT(ledger_keylet(0, 34, 0, 35, 0, 34) == TOO_BIG); ASSERT(ledger_keylet(0, 34, 0, 34, 0, 35) == TOO_BIG); uint8_t trash[34] = { 1,2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; uint8_t trash2[34] = { 1,2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 10 }; ASSERT(ledger_keylet(0, 34, SBUF(trash2), SBUF(trash)) == DOESNT_EXIST); ASSERT(ledger_keylet(0, 34, SBUF(trash), SBUF(trash2)) == DOESNT_EXIST); uint8_t first[34]; uint8_t last[34] = { 0x00U, 0x01U, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFEU }; uint8_t out[34]; ASSERT(ledger_keylet(SBUF(out), SBUF(first), SBUF(last)) == DOES_NOT_MATCH); last[1] = 0; ASSERT(ledger_keylet(SBUF(out), SBUF(first), SBUF(last)) == 34); ASSERT(slot_set(SBUF(out), 1) == 1); accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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_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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 ledger_last_hash (uint32_t, uint32_t); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t hash[32]; // Test out of bounds check ASSERT(ledger_last_hash(1000000, 32) == OUT_OF_BOUNDS); ASSERT(ledger_last_hash((uint32_t)hash, 31) == TOO_SMALL); ASSERT(ledger_last_hash((uint32_t)hash, 32) == 32); // return the hash accept((uint32_t)hash, 32, 0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 retStr = hookExecutions[0].getFieldVL(sfHookReturnString); // 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t ledger_last_time (void); int64_t hook(uint32_t reserved ) { _g(1,1); accept(0,0,ledger_last_time()); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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( 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t ledger_nonce (uint32_t, uint32_t); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t nonce[64]; // Test out of bounds check ASSERT(ledger_nonce(1000000, 32) == OUT_OF_BOUNDS); ASSERT(ledger_nonce((uint32_t)nonce, 31) == TOO_SMALL); ASSERT(ledger_nonce((uint32_t)nonce, 32) == 32); ASSERT(ledger_nonce((uint32_t)(nonce + 32), 32) == 32); // return the two nonces as the return string accept((uint32_t)nonce, 64, 0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 retStr = hookExecutions[0].getFieldVL(sfHookReturnString); // 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t ledger_seq (void); int64_t hook(uint32_t reserved ) { _g(1,1); accept(0,0,ledger_seq()); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 hook_again (void); extern int64_t slot(uint32_t, uint32_t, uint32_t); extern int64_t trace(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); extern int64_t meta_slot(uint32_t); extern int64_t slot_subfield ( uint32_t parent_slot, uint32_t field_id, uint32_t new_slot ); #define PREREQUISITE_NOT_MET (-9) #define ALREADY_SET (-8) #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define sfHookExecutions ((15U << 16U) + 18U) #define sfTransactionResult ((16U << 16U) + 3U) #define sfAffectedNodes ((15U << 16U) + 8U) #define sfTransactionIndex ((2U << 16U) + 28U) int64_t hook(uint32_t r) { _g(1,1); if (r > 0) { ASSERT(meta_slot(1) == 1); uint8_t buf[1024]; ASSERT(slot(buf, 1024, 1) > 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(0,0,1); } if (hook_again() != 1) return rollback(0,0,254); ASSERT(meta_slot(1) == PREREQUISITE_NOT_MET); return accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t otxn_type(void); extern int64_t otxn_field(uint32_t, uint32_t, uint32_t); extern int64_t otxn_slot(uint32_t); extern int64_t slot(uint32_t, uint32_t, uint32_t); extern int64_t trace(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); extern int64_t xpop_slot(uint32_t, uint32_t); extern int64_t slot_subfield ( uint32_t parent_slot, uint32_t field_id, uint32_t new_slot ); #define ttIMPORT 97 #define DOESNT_EXIST -5 #define NO_FREE_SLOTS -6 #define INVALID_ARGUMENT -7 #define ALREADY_SET -8 #define PREREQUISITE_NOT_MET -9 #define INVALID_TXN -37 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define sfBlob ((7U << 16U) + 26U) #define sfAccount ((8U << 16U) + 1U) #define sfTransactionType ((1U << 16U) + 2U) #define sfHookExecutions ((15U << 16U) + 18U) #define sfTransactionResult ((16U << 16U) + 3U) #define sfAffectedNodes ((15U << 16U) + 8U) #define sfTransactionIndex ((2U << 16U) + 28U) int64_t hook(uint32_t r) { _g(1,1); // invalid tt if (otxn_type() != ttIMPORT) { ASSERT(xpop_slot(1, 2) == PREREQUISITE_NOT_MET); return accept(0,0,1); } // invalid slotno ASSERT(xpop_slot(256, 1) == INVALID_ARGUMENT); ASSERT(xpop_slot(1, 256) == INVALID_ARGUMENT); ASSERT(xpop_slot(1, 1) == INVALID_ARGUMENT); for (int i = 1; GUARD(255), i <= 255; ++i) { otxn_slot(i); } ASSERT(xpop_slot(0, 0) == NO_FREE_SLOTS); 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); return accept(0,0,2); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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_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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t otxn_field (uint32_t write_ptr, uint32_t write_len, uint32_t sfcode); extern int64_t hook_account(uint32_t, uint32_t); #define OUT_OF_BOUNDS -1 #define TOO_BIG -3 #define TOO_SMALL -4 #define INVALID_ARGUMENT -7 #define INVALID_FIELD -17 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) #define sfAccount ((8U << 16U) + 1U) int64_t hook(uint32_t reserved ) { _g(1,1); // bounds check ASSERT(otxn_field(1, 1000000, sfAccount) == OUT_OF_BOUNDS); ASSERT(otxn_field(1000000, 20, sfAccount) == OUT_OF_BOUNDS); // sanity check ASSERT(otxn_field(0, 1, sfAccount) == INVALID_ARGUMENT); // size check ASSERT(otxn_field(0, 0, sfAccount) == TOO_BIG); uint8_t acc[20]; ASSERT(otxn_field(acc, 19, sfAccount) == TOO_SMALL); ASSERT(otxn_field(acc, 20, sfAccount) == 20); ASSERT(otxn_field(acc, 20, 1) == INVALID_FIELD); uint8_t acc2[20]; ASSERT(hook_account(acc2, 20) == 20); for (int i = 0; GUARD(20), i < 20; ++i) ASSERT(acc[i] == acc2[i]); accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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_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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t slot (uint32_t write_ptr, uint32_t write_len, uint32_t slot_no); extern int64_t otxn_id (uint32_t write_ptr, uint32_t write_len, uint32_t flags); extern int64_t util_sha512h(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len ); extern int64_t otxn_slot ( uint32_t slot_no ); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); // bounds check ASSERT(otxn_id(1, 1000000, 0) == OUT_OF_BOUNDS); ASSERT(otxn_id(1000000, 1024, 0) == OUT_OF_BOUNDS); // size check ASSERT(otxn_id(1, 0, 0) == TOO_SMALL); ASSERT(otxn_id(1, 31, 0) == TOO_SMALL); uint8_t id[32]; ASSERT(otxn_id(SBUF(id), 0) == 32); // slot the otxn then generate a canonical hash over it ASSERT(otxn_slot(1) == 1); uint8_t buf[1024]; int64_t size = slot(buf + 4, sizeof(buf) - 4, 1); ASSERT(size > 0); buf[0] = 'T'; buf[1] = 'X'; buf[2] = 'N'; buf[3] = 0; uint8_t hash[32]; ASSERT(util_sha512h(SBUF(hash), buf, size+4) == 32); for (int i = 0; GUARD(32), i < 32; ++i) ASSERT(hash[i] == id[i]); // RH TODO: test the flags = 1 on emitted txn // done! accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t slot (uint32_t write_ptr, uint32_t write_len, uint32_t slot_no); extern int64_t otxn_id (uint32_t write_ptr, uint32_t write_len, uint32_t flags); extern int64_t util_sha512h(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len ); extern int64_t otxn_slot ( uint32_t slot_no ); #define INVALID_ARGUMENT -7 #define NO_FREE_SLOTS -6 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(otxn_slot(256) == INVALID_ARGUMENT); ASSERT(otxn_slot(1) == 1); uint8_t id[32]; ASSERT(otxn_id(SBUF(id), 0) == 32); // slot the otxn then generate a canonical hash over it ASSERT(otxn_slot(1) == 1); uint8_t buf[1024]; int64_t size = slot(buf + 4, sizeof(buf) - 4, 1); ASSERT(size > 0); buf[0] = 'T'; buf[1] = 'X'; buf[2] = 'N'; buf[3] = 0; uint8_t hash[32]; ASSERT(util_sha512h(SBUF(hash), buf, size+4) == 32); for (int i = 0; GUARD(32), i < 32; ++i) ASSERT(hash[i] == id[i]); // slot exhaustion for (int i = 0; GUARD(255), i < 254; ++i) ASSERT(otxn_slot(0) > 0); ASSERT(otxn_slot(0) == NO_FREE_SLOTS); // done! accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t slot (uint32_t write_ptr, uint32_t write_len, uint32_t slot_no); extern int64_t otxn_slot ( uint32_t slot_no ); extern int64_t slot_subfield( uint32_t parent_slot, uint32_t field_id, uint32_t new_slot ); extern int64_t otxn_type(void); #define sfTransactionType ((1U << 16U) + 2U) #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(otxn_slot(1) == 1); ASSERT(slot_subfield(1, sfTransactionType, 2) == 2); int64_t tt = slot(0,0,2); ASSERT(tt == otxn_type()); // done! accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 otxn_param(uint32_t, uint32_t, uint32_t, uint32_t); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define OUT_OF_BOUNDS (-1) #define TOO_BIG (-3) #define TOO_SMALL (-4) #define DOESNT_EXIST (-5) uint8_t* names[] = { "param0", "param1", "param2", "param3", "param4", "param5", "param6", "param7", "param8", "param9", "param10", "param11", "param12", "param13", "param14", "param15" }; uint8_t* values[] = { "value0", "value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8", "value9", "value10", "value11", "value12", "value13", "value14", "value15" }; #define SBUF(x) x,sizeof(x) #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(otxn_param(0, 1000000, 0,32) == OUT_OF_BOUNDS); ASSERT(otxn_param(1000000, 32, 0, 32) == OUT_OF_BOUNDS); ASSERT(otxn_param(0, 32, 1000000, 32) == OUT_OF_BOUNDS); ASSERT(otxn_param(0, 32, 0, 1000000) == OUT_OF_BOUNDS); ASSERT(otxn_param(0, 32, 0, 33) == TOO_BIG); ASSERT(otxn_param(0, 32, 0, 0) == TOO_SMALL); ASSERT(otxn_param(0, 32, 0, 32) == DOESNT_EXIST); uint8_t buf[32]; for (int i = 0; GUARD(16), i < 16; ++i) { int s = 6 + (i < 10 ? 0 : 1); int64_t v = otxn_param(SBUF(buf), names[i], s); ASSERT(v == s); ASSERT(buf[0] == 'v' && buf[1] == 'a' && buf[2] == 'l' && buf[3] == 'u' && buf[4] == 'e'); ASSERT(*(buf + v - 1) == *(values[i] + v - 1)); ASSERT(*(buf + v - 2) == *(values[i] + v - 2)); } accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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_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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t slot (uint32_t write_ptr, uint32_t write_len, uint32_t slot_no); extern int64_t slot_subfield ( uint32_t parent_slot, uint32_t field_id, uint32_t new_slot ); extern int64_t util_keylet ( uint32_t write_ptr, uint32_t write_len, uint32_t keylet_type, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f ); extern int64_t sto_subfield(uint32_t, uint32_t, uint32_t); extern int64_t slot_set(uint32_t, uint32_t, uint32_t); extern int64_t sto_validate(uint32_t, uint32_t); extern int64_t hook_account(uint32_t, uint32_t); extern int64_t slot_size(uint32_t); #define sfBalance ((6U << 16U) + 2U) #define sfFlags ((2U << 16U) + 2U) #define DOESNT_EXIST -5 #define TOO_SMALL -4 #define TOO_BIG -3 #define OUT_OF_BOUNDS -1 #define INVALID_ARGUMENT -7 #define KEYLET_ACCOUNT 3 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); // bounds check ASSERT(slot(1, 1000000, 0) == OUT_OF_BOUNDS); ASSERT(slot(1000000, 1024, 0) == OUT_OF_BOUNDS); // this function can return data as an int64_t, // but requires 0,0 as arguments ASSERT(slot(0,1, 0) == INVALID_ARGUMENT); // slot 0 hasn't been set yet so ASSERT(slot(0,0,0) == DOESNT_EXIST); // grab the hook account uint8_t acc[20]; ASSERT(20 == hook_account(SBUF(acc))); // turn it into account root keylet uint8_t kl[34]; ASSERT(34 == util_keylet(SBUF(kl), KEYLET_ACCOUNT, SBUF(acc), 0,0,0,0)); // slot the account root into a new slot int64_t slot_no = 0; ASSERT((slot_no = slot_set(SBUF(kl), 0)) > 0); int64_t size = 0; ASSERT((size = slot_size(slot_no)) > 0); // the slotted item is too large for return as int64 ASSERT(slot(0,0,slot_no) == TOO_BIG); // big buffer, large enough to hold the account_root uint8_t buf[1024]; // the slot call should return the bytes written which should exactly // match the size of the slotted object ASSERT(slot(SBUF(buf), slot_no) == size); // do a quick sanity check on the object using sto api ASSERT(sto_validate(buf, size) == 1); // grab a field ASSERT(sto_subfield(buf, size, sfBalance) > 0); // subslot a subfield we can return as an int64_t ASSERT(slot_subfield(slot_no, sfBalance, 200) == 200); // retrieve the slotted object as an int64_t ASSERT(slot(0,0,200) > 0); // done! accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t slot_clear(uint32_t slot_no); extern int64_t otxn_slot (uint32_t slot_no); extern int64_t slot_size(uint32_t); #define sfBalance ((6U << 16U) + 2U) #define sfFlags ((2U << 16U) + 2U) #define DOESNT_EXIST -5 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(otxn_slot(1) == 1); ASSERT(slot_size(1) > 0); ASSERT(slot_clear(1) == 1); ASSERT(slot_size(1) == DOESNT_EXIST); ASSERT(slot_clear(1) == DOESNT_EXIST); ASSERT(slot_clear(10) == DOESNT_EXIST); // done! accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t otxn_slot (uint32_t slot_no); extern int64_t slot_size(uint32_t); extern int64_t slot_count(uint32_t); extern int64_t slot_subfield ( uint32_t parent_slot, uint32_t field_id, uint32_t new_slot); #define NOT_AN_ARRAY -22 #define DOESNT_EXIST -5 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) #define sfMemos ((15U << 16U) + 9U) int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(otxn_slot(1) == 1); ASSERT(slot_size(1) > 0); ASSERT(slot_count(1) == NOT_AN_ARRAY); ASSERT(slot_count(0) == DOESNT_EXIST); ASSERT(slot_subfield(1, sfMemos, 1) == 1); ASSERT(slot_size(1) > 0); ASSERT(slot_count(1) == 1); // done! accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t otxn_slot (uint32_t slot_no); extern int64_t slot_size(uint32_t); extern int64_t slot_count(uint32_t); extern int64_t slot_float(uint32_t); extern int64_t float_int ( int64_t float1, uint32_t decimal_places, uint32_t absolute ); extern int64_t slot_subfield ( uint32_t parent_slot, uint32_t field_id, uint32_t new_slot); #define NOT_AN_ARRAY -22 #define DOESNT_EXIST -5 #define NOT_AN_AMOUNT -32 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) #define sfFee ((6U << 16U) + 8U) int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(otxn_slot(1) == 1); ASSERT(slot_size(1) > 0); ASSERT(slot_subfield(1, sfFee, 2) == 2); ASSERT(slot_size(2) > 0); ASSERT(slot_float(0) == DOESNT_EXIST); ASSERT(slot_float(1) == NOT_AN_AMOUNT); int64_t xfl = slot_float(2); ASSERT(xfl > 0); ASSERT(float_int(xfl, 6, 0) == 1000000LL); // done! accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t otxn_id(uint32_t, uint32_t, uint32_t); extern int64_t slot_set(uint32_t, uint32_t, uint32_t); extern int64_t slot_size(uint32_t); #define sfBalance ((6U << 16U) + 2U) #define sfFlags ((2U << 16U) + 2U) #define DOESNT_EXIST -5 #define OUT_OF_BOUNDS -1 #define INVALID_ARGUMENT -7 #define NO_FREE_SLOTS -6 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) // skip keylet uint8_t kl_sk[] = { 0x00U, 0x68U, 0xB4U,0x97U,0x9AU,0x36U,0xCDU,0xC7U,0xF3U,0xD3U,0xD5U,0xC3U, 0x1AU,0x4EU,0xAEU,0x2AU,0xC7U,0xD7U,0x20U,0x9DU,0xDAU,0x87U, 0x75U,0x88U,0xB9U,0xAFU,0xC6U,0x67U,0x99U,0x69U,0x2AU,0xB0U, 0xD6U,0x6BU }; int64_t hook(uint32_t reserved ) { _g(1,1); // bounds check ASSERT(slot_set(1, 1000000, 0) == OUT_OF_BOUNDS); ASSERT(slot_set(1000000, 1024, 0) == OUT_OF_BOUNDS); // read len is only allowed to be 32 (txn id) or 34 (keylet) uint8_t kl_zero[34]; ASSERT(slot_set((uint32_t)kl_zero, 0, 0) == INVALID_ARGUMENT); ASSERT(slot_set((uint32_t)kl_zero, 31, 0) == INVALID_ARGUMENT); ASSERT(slot_set((uint32_t)kl_zero, 33, 0) == INVALID_ARGUMENT); ASSERT(slot_set((uint32_t)kl_zero, 35, 0) == INVALID_ARGUMENT); ASSERT(slot_set((uint32_t)kl_zero, 34, 256) == INVALID_ARGUMENT); // request an invalid keylet ASSERT(slot_set(SBUF(kl_zero), 0) == DOESNT_EXIST); kl_zero[0] = 1; ASSERT(slot_set(SBUF(kl_zero), 0) == DOESNT_EXIST); ASSERT(slot_size(1) == DOESNT_EXIST); // request a valid keylet ASSERT(slot_set(SBUF(kl_sk), 0) > 0); ASSERT(slot_size(1) > 0); // fill up all slots for (uint32_t i = 1; GUARD(257), i < 255; ++i) ASSERT(slot_set(SBUF(kl_sk), 0) > 0); // request a final slot that should fail ASSERT(slot_set(SBUF(kl_sk), 0)); // overwrite an existing slot, should work ASSERT(slot_set(SBUF(kl_sk), 10) == 10); // check all the slots contain an object, the same object uint32_t s = slot_size(1); for (uint32_t i = 2; GUARD(257), i < 256; ++i) ASSERT(s == slot_size(i)); // slot a txn uint8_t txn[32]; ASSERT(otxn_id(SBUF(txn), 0) == 32); ASSERT(slot_set(SBUF(txn), 1) == 1); uint32_t s2 = slot_size(1); // ensure it's not the same object that was there before ASSERT(s != s2 && s2 > 0); // done! accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t slot_set(uint32_t, uint32_t, uint32_t); extern int64_t slot_size(uint32_t); extern int64_t slot (uint32_t write_ptr, uint32_t write_len, uint32_t slot_no); extern int64_t sto_validate(uint32_t, uint32_t); #define sfBalance ((6U << 16U) + 2U) #define sfFlags ((2U << 16U) + 2U) #define DOESNT_EXIST -5 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) // skip keylet uint8_t kl_sk[] = { 0x00U, 0x68U, 0xB4U,0x97U,0x9AU,0x36U,0xCDU,0xC7U,0xF3U,0xD3U,0xD5U,0xC3U, 0x1AU,0x4EU,0xAEU,0x2AU,0xC7U,0xD7U,0x20U,0x9DU,0xDAU,0x87U, 0x75U,0x88U,0xB9U,0xAFU,0xC6U,0x67U,0x99U,0x69U,0x2AU,0xB0U, 0xD6U,0x6BU }; int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(slot_size(1) == DOESNT_EXIST); // request a valid keylet, twice ASSERT(slot_set(SBUF(kl_sk), 1) == 1); ASSERT(slot_set(SBUF(kl_sk), 255) == 255); // check the sizes are equal ASSERT(slot_size(1) == slot_size(255)); // check the sizes are > 0 int64_t s = slot_size(1); ASSERT(s > 0); // pull the object out into a buffer, check the number of bytes written is correct uint8_t buf[4096]; ASSERT(slot(SBUF(buf), 1) == s); // check the object is valid ASSERT(sto_validate(buf, s) == 1); // done! accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t slot_size(uint32_t); extern int64_t slot (uint32_t write_ptr, uint32_t write_len, uint32_t slot_no); extern int64_t slot_subarray(uint32_t, uint32_t, uint32_t); extern int64_t otxn_slot(uint32_t); extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t); extern int64_t slot_count(uint32_t); extern int64_t slot_set(uint32_t, uint32_t, uint32_t); #define sfMemos ((15U << 16U) + 9U) #define sfMemoData ((7U << 16U) + 13U) #define DOESNT_EXIST -5 #define NO_FREE_SLOTS -6 #define NOT_AN_ARRAY -22 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) // skip keylet uint8_t kl_sk[] = { 0x00U, 0x68U, 0xB4U,0x97U,0x9AU,0x36U,0xCDU,0xC7U,0xF3U,0xD3U,0xD5U,0xC3U, 0x1AU,0x4EU,0xAEU,0x2AU,0xC7U,0xD7U,0x20U,0x9DU,0xDAU,0x87U, 0x75U,0x88U,0xB9U,0xAFU,0xC6U,0x67U,0x99U,0x69U,0x2AU,0xB0U, 0xD6U,0x6BU }; int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(slot_subarray(1, 1, 1) == DOESNT_EXIST); // request a valid keylet that doesn't contain an array ASSERT(slot_set(SBUF(kl_sk), 1) == 1); ASSERT(slot_size(1) > 0); ASSERT(slot_subarray(1,1,1) == NOT_AN_ARRAY); // now request an object that contains an array (this txn) ASSERT(otxn_slot(2) == 2); // slot the array ASSERT(slot_subfield(2, sfMemos, 3) == 3); // it should contain 9 entries ASSERT(slot_count(3) == 9); // now index into the array ASSERT(slot_subarray(3, 0, 0) > 0); // take element at index 5 and place it in slot 100 ASSERT(slot_subarray(3, 5, 100) == 100); // override it and replace with element 6 ASSERT(slot_subarray(3, 6, 100) == 100); // check the value is correct ASSERT(slot_subfield(100, sfMemoData, 100) == 100); uint8_t buf[16]; ASSERT(6 == slot(SBUF(buf), 100)); ASSERT( buf[0] == 0x05U && buf[1] == 0xC0U && buf[2] == 0x01U && buf[3] == 0xCAU && buf[4] == 0xFEU && buf[5] == 0x06U); // override it and replace with element 0 ASSERT(slot_subarray(3, 0, 100) == 100); // check the value is correct ASSERT(slot_subfield(100, sfMemoData, 100) == 100); ASSERT(slot(SBUF(buf), 100) == 6); ASSERT( buf[0] == 0x05U && buf[1] == 0xC0U && buf[2] == 0x01U && buf[3] == 0xCAU && buf[4] == 0xFEU && buf[5] == 0x00U); // test slot exhaustion for (int i = 0; GUARD(255), i < 250; ++i) ASSERT(slot_subarray(3, 0, 0) > 0); ASSERT(slot_subarray(3, 0, 0) == NO_FREE_SLOTS); accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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}; auto const bob = Account{"bob"}; auto const alice = Account{"alice"}; env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); TestHook hook = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t slot_size(uint32_t); extern int64_t slot (uint32_t write_ptr, uint32_t write_len, uint32_t slot_no); extern int64_t otxn_slot(uint32_t); extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t); extern int64_t slot_count(uint32_t); extern int64_t slot_set(uint32_t, uint32_t, uint32_t); #define sfMemos ((15U << 16U) + 9U) #define sfMemoData ((7U << 16U) + 13U) #define sfLastLedgerSequence ((2U << 16U) + 0x1BU) #define sfHashes ((19U << 16U) + 2U) #define NOT_AN_OBJECT -23 #define DOESNT_EXIST -5 #define NO_FREE_SLOTS -6 #define INVALID_FIELD -17 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) // skip keylet uint8_t kl_sk[] = { 0x00U, 0x68U, 0xB4U,0x97U,0x9AU,0x36U,0xCDU,0xC7U,0xF3U,0xD3U,0xD5U,0xC3U, 0x1AU,0x4EU,0xAEU,0x2AU,0xC7U,0xD7U,0x20U,0x9DU,0xDAU,0x87U, 0x75U,0x88U,0xB9U,0xAFU,0xC6U,0x67U,0x99U,0x69U,0x2AU,0xB0U, 0xD6U,0x6BU }; int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(slot_subfield(1, 1, 1) == DOESNT_EXIST); ASSERT(slot_set(SBUF(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)); // request a field that is invalid ASSERT(slot_subfield(1, 0xFFFFFFFFUL, 0) == INVALID_FIELD); // request a field that isn't present ASSERT(slot_subfield(1, sfMemos, 0) == DOESNT_EXIST); // request a subfield from something that's not an object ASSERT(slot_subfield(3, sfMemoData, 0) == NOT_AN_OBJECT); // overwrite an existing slot ASSERT(slot_subfield(1, sfLastLedgerSequence, 3) == 3); ASSERT(slot_size(2) == slot_size(3)); // test slot exhaustion for (int i = 0; GUARD(255), i < 252; ++i) ASSERT(slot_subfield(1, sfLastLedgerSequence, 0) > 0); ASSERT(slot_subfield(1, sfLastLedgerSequence, 0) == NO_FREE_SLOTS); accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t slot_size(uint32_t); extern int64_t slot (uint32_t write_ptr, uint32_t write_len, uint32_t slot_no); extern int64_t otxn_slot(uint32_t); extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t); extern int64_t slot_count(uint32_t); extern int64_t slot_type(uint32_t, uint32_t); extern int64_t slot_set(uint32_t, uint32_t, uint32_t); extern int64_t otxn_field ( uint32_t write_ptr, uint32_t write_len, uint32_t field_id ); extern int64_t util_keylet ( uint32_t write_ptr, uint32_t write_len, uint32_t keylet_type, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f ); extern int64_t hook_account(uint32_t, uint32_t); #define sfMemos ((15U << 16U) + 9U) #define sfMemoData ((7U << 16U) + 13U) #define sfLastLedgerSequence ((2U << 16U) + 0x1BU) #define sfHashes ((19U << 16U) + 2U) #define NOT_AN_OBJECT -23 #define DOESNT_EXIST -5 #define NO_FREE_SLOTS -6 #define INVALID_FIELD -17 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) // skip keylet uint8_t kl_sk[] = { 0x00U, 0x68U, 0xB4U,0x97U,0x9AU,0x36U,0xCDU,0xC7U,0xF3U,0xD3U,0xD5U,0xC3U, 0x1AU,0x4EU,0xAEU,0x2AU,0xC7U,0xD7U,0x20U,0x9DU,0xDAU,0x87U, 0x75U,0x88U,0xB9U,0xAFU,0xC6U,0x67U,0x99U,0x69U,0x2AU,0xB0U, 0xD6U,0x6BU }; #define sfLedgerEntry ((10002U << 16U) + 257U) #define sfTransaction ((10001U << 16U) + 257U) #define sfAmount ((6U << 16U) + 1U) #define sfHighLimit ((6U << 16U) + 7U) #define sfAccount ((8U << 16U) + 1U) #define NOT_AN_AMOUNT -32 #define KEYLET_LINE 9 int64_t hook(uint32_t reserved ) { _g(1,1); ASSERT(slot_type(1, 0) == DOESNT_EXIST); ASSERT(slot_set(SBUF(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); // this will determine if the amount is native by returning 1 if it is ASSERT(slot_type(4, 1) == 1); ASSERT(slot_type(3, 1) == NOT_AN_AMOUNT); // there's a trustline between alice and bob // we can find alice and bob's addresses from otxn uint8_t addra[20]; uint8_t addrb[20]; ASSERT(hook_account(SBUF(addra)) == 20); ASSERT(otxn_field(SBUF(addrb), sfAccount) == 20); // build the keylet for the tl uint8_t kl_tr[34]; ASSERT(util_keylet(SBUF(kl_tr), KEYLET_LINE, SBUF(addra), SBUF(addrb), "USD", 3) == 34); // slot the ripplestate object ASSERT(slot_set(SBUF(kl_tr), 5) == 5); // subfield into the high limit ASSERT(slot_subfield(5, sfHighLimit, 6) == 6); // this is a non-native balance so we should get 0 back when testing the amount type ASSERT(slot_type(6, 1) == 0); accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), M("set slot_subfield"), HSFEE); env.close(); // invoke the hook env(pay(bob, alice, XRP(1)), M("test slot_type"), 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t state ( uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len ); extern int64_t state_set(uint32_t,uint32_t,uint32_t, uint32_t); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define TOO_BIG (-3) #define TOO_SMALL (-4) #define OUT_OF_BOUNDS (-1) #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); // set a state object ASSERT(state_set(SBUF("content"), SBUF("key")) == sizeof("content")); ASSERT(state_set(SBUF("content2"), SBUF("key2")) == sizeof("content2")); // Test out of bounds check ASSERT(state(1000000, 32, 0, 32) == OUT_OF_BOUNDS); ASSERT(state(0, 1000000, 0, 32) == OUT_OF_BOUNDS); ASSERT(state(0, 32, 1000000, 32) == OUT_OF_BOUNDS); ASSERT(state(0, 32, 0, 1000000) == TOO_BIG); ASSERT(state(0,0,0,0) == TOO_SMALL); ASSERT(state(0,0,0,33) == TOO_BIG); // read state back uint8_t buf1[32]; uint8_t buf2[32]; int64_t bytes1 = state(SBUF(buf1), SBUF("key")); ASSERT(bytes1 == sizeof("content")); int64_t bytes2 = state(SBUF(buf2), SBUF("key2")); ASSERT(bytes2 == sizeof("content2")); for (int i = 32; GUARD(32), i < bytes1; ++i) ASSERT(buf1[i] == *((uint8_t*)"content" + i)); for (int i = 32; GUARD(32), i < bytes2; ++i) ASSERT(buf2[i] == *((uint8_t*)"content2" + i)); // RH TODO: // - read small state back as int64 return accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t state ( uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len ); extern int64_t state_set(uint32_t,uint32_t,uint32_t, uint32_t); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); // read state back uint8_t buf1[32]; uint8_t buf2[32]; int64_t bytes1 = state(SBUF(buf1), SBUF("key")); ASSERT(bytes1 == sizeof("content")); int64_t bytes2 = state(SBUF(buf2), SBUF("key2")); ASSERT(bytes2 == sizeof("content2")); for (int i = 32; GUARD(32), i < bytes1; ++i) ASSERT(buf1[i] == *((uint8_t*)"content" + i)); for (int i = 32; GUARD(32), i < bytes2; ++i) ASSERT(buf2[i] == *((uint8_t*)"content2" + i)); return accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t state ( uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len ); extern int64_t state_foreign_set( uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); extern int64_t hook_account(uint32_t, uint32_t); extern int64_t state_set(uint32_t,uint32_t,uint32_t, uint32_t); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); // set a state object ASSERT(state_set(SBUF("content"), SBUF("key")) == sizeof("content")); // put the second state object on a different ns uint8_t ns[32]; ns[9] = 0xABU; uint8_t acc[20]; ASSERT(hook_account(SBUF(acc)) == 20); ASSERT(state_foreign_set(SBUF("content2"), SBUF("key2"), SBUF(ns), SBUF(acc)) == sizeof("content2")); return accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t state ( uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len ); extern int64_t state_foreign ( uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t ); extern int64_t hook_account(uint32_t write_ptr, uint32_t write_len); extern int64_t state_set(uint32_t,uint32_t,uint32_t, uint32_t); extern int64_t otxn_field(uint32_t, uint32_t, uint32_t); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define TOO_BIG (-3) #define TOO_SMALL (-4) #define OUT_OF_BOUNDS (-1) #define DOESNT_EXIST (-5) #define INVALID_ARGUMENT (-7) #define SBUF(x) (uint32_t)(x), sizeof(x) #define sfAccount ((8U << 16U) + 1U) int64_t hook(uint32_t reserved ) { _g(1,1); // Test out of bounds check int64_t y; ASSERT((y=state_foreign(1111111, 32, 1, 32, 1, 32, 1, 20)) == OUT_OF_BOUNDS); ASSERT((y=state_foreign(1, 1111111, 1, 32, 1, 32, 1, 20)) == OUT_OF_BOUNDS); ASSERT((y=state_foreign(1, 32, 1111111, 32, 1, 32, 1, 20)) == OUT_OF_BOUNDS); ASSERT((y=state_foreign(1, 32, 1, 1111111, 1, 32, 1, 20)) == TOO_BIG); ASSERT((y=state_foreign(1, 32, 1, 32, 1111111, 32, 1, 20)) == OUT_OF_BOUNDS); ASSERT((y=state_foreign(1, 32, 1, 32, 1, 1111111, 1, 20)) == INVALID_ARGUMENT); ASSERT((y=state_foreign(1, 32, 1, 32, 1, 32, 1111111, 20)) == OUT_OF_BOUNDS); ASSERT((y=state_foreign(1, 32, 1, 32, 1, 32, 1, 1111111)) == INVALID_ARGUMENT); // alice's address is the sender uint8_t acc[20]; ASSERT(otxn_field(SBUF(acc), sfAccount) == 20); // read state back uint8_t buf1[32]; uint8_t buf2[32]; // the namespace of the first obj is all zeros uint8_t ns[32]; int64_t bytes1 = state_foreign(SBUF(buf1), SBUF("key"), SBUF(ns), SBUF(acc)); ASSERT(bytes1 == sizeof("content")); // the namespace of the second obj is all zeros except position 9 which is 0xAB // ensure the namespacing is working by requesting against the wrong namespace first int64_t bytes2 = state_foreign(SBUF(buf2), SBUF("key2"), SBUF(ns), SBUF(acc)); ASSERT(bytes2 == DOESNT_EXIST); ns[9] = 0xABU; bytes2 = state_foreign(SBUF(buf2), SBUF("key2"), SBUF(ns), SBUF(acc)); ASSERT(bytes2 == sizeof("content2")); for (int i = 32; GUARD(32), i < bytes1; ++i) ASSERT(buf1[i] == *((uint8_t*)"content" + i)); for (int i = 32; GUARD(32), i < bytes2; ++i) ASSERT(buf2[i] == *((uint8_t*)"content2" + i)); return accept(0,0,0); } )[test.hook]"]; // install the hook on bob env(ripple::test::jtx::hook(bob, {{hso(hook, 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_max(FeatureBitset features) { testcase("Test state_foreign_set max"); using namespace jtx; static const std::vector ns_maxHook = { 0x00U, 0x61U, 0x73U, 0x6dU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x36U, 0x07U, 0x60U, 0x02U, 0x7fU, 0x7fU, 0x01U, 0x7fU, 0x60U, 0x02U, 0x7fU, 0x7fU, 0x01U, 0x7eU, 0x60U, 0x03U, 0x7fU, 0x7fU, 0x7eU, 0x01U, 0x7eU, 0x60U, 0x04U, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x01U, 0x7eU, 0x60U, 0x05U, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x01U, 0x7eU, 0x60U, 0x08U, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x01U, 0x7eU, 0x60U, 0x01U, 0x7fU, 0x01U, 0x7eU, 0x02U, 0x79U, 0x08U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x02U, 0x5fU, 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x0cU, 0x68U, 0x6fU, 0x6fU, 0x6bU, 0x5fU, 0x61U, 0x63U, 0x63U, 0x6fU, 0x75U, 0x6eU, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x08U, 0x72U, 0x6fU, 0x6cU, 0x6cU, 0x62U, 0x61U, 0x63U, 0x6bU, 0x00U, 0x02U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x05U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x00U, 0x03U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x05U, 0x74U, 0x72U, 0x61U, 0x63U, 0x65U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x11U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5fU, 0x66U, 0x6fU, 0x72U, 0x65U, 0x69U, 0x67U, 0x6eU, 0x5fU, 0x73U, 0x65U, 0x74U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x09U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5fU, 0x73U, 0x65U, 0x74U, 0x00U, 0x03U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x06U, 0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x2bU, 0x07U, 0x7fU, 0x01U, 0x41U, 0xc0U, 0x88U, 0x04U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0xb2U, 0x08U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0xc0U, 0x88U, 0x04U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x00U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x01U, 0x0bU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6fU, 0x6fU, 0x6bU, 0x00U, 0x08U, 0x0aU, 0xd4U, 0x81U, 0x00U, 0x01U, 0xd0U, 0x81U, 0x00U, 0x02U, 0x02U, 0x7fU, 0x01U, 0x7eU, 0x23U, 0x00U, 0x41U, 0xe0U, 0x00U, 0x6bU, 0x22U, 0x01U, 0x24U, 0x00U, 0x20U, 0x01U, 0x20U, 0x00U, 0x36U, 0x02U, 0x5cU, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1aU, 0x20U, 0x01U, 0x41U, 0x40U, 0x6bU, 0x41U, 0x14U, 0x10U, 0x01U, 0x42U, 0x14U, 0x52U, 0x04U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x0cU, 0x10U, 0x02U, 0x1aU, 0x0bU, 0x20U, 0x01U, 0x41U, 0x38U, 0x6aU, 0x41U, 0x08U, 0x20U, 0x01U, 0x41U, 0x40U, 0x6bU, 0x22U, 0x00U, 0x41U, 0x14U, 0x10U, 0x03U, 0x1aU, 0x20U, 0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0x38U, 0x42U, 0x01U, 0x7cU, 0x37U, 0x03U, 0x38U, 0x20U, 0x01U, 0x41U, 0xabU, 0x01U, 0x3aU, 0x00U, 0x19U, 0x20U, 0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0x38U, 0x3cU, 0x00U, 0x1aU, 0x41U, 0x80U, 0x08U, 0x41U, 0x02U, 0x20U, 0x01U, 0x41U, 0x10U, 0x6aU, 0x22U, 0x02U, 0x41U, 0x20U, 0x41U, 0x01U, 0x10U, 0x04U, 0x1aU, 0x20U, 0x01U, 0x41U, 0xa9U, 0x08U, 0x41U, 0x09U, 0x41U, 0xa4U, 0x08U, 0x41U, 0x05U, 0x20U, 0x02U, 0x41U, 0x20U, 0x20U, 0x00U, 0x41U, 0x14U, 0x10U, 0x05U, 0x37U, 0x03U, 0x08U, 0x20U, 0x01U, 0x29U, 0x03U, 0x08U, 0x42U, 0x00U, 0x53U, 0x04U, 0x40U, 0x41U, 0x83U, 0x08U, 0x41U, 0x21U, 0x20U, 0x01U, 0x29U, 0x03U, 0x08U, 0x10U, 0x02U, 0x1aU, 0x0bU, 0x20U, 0x01U, 0x41U, 0x38U, 0x6aU, 0x41U, 0x08U, 0x20U, 0x01U, 0x41U, 0x40U, 0x6bU, 0x41U, 0x14U, 0x10U, 0x06U, 0x1aU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x08U, 0x10U, 0x07U, 0x20U, 0x01U, 0x41U, 0xe0U, 0x00U, 0x6aU, 0x24U, 0x00U, 0x0bU, 0x0bU, 0x38U, 0x01U, 0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, 0x31U, 0x6eU, 0x73U, 0x00U, 0x6eU, 0x73U, 0x5fU, 0x6dU, 0x61U, 0x78U, 0x2eU, 0x63U, 0x3aU, 0x20U, 0x4dU, 0x61U, 0x78U, 0x20U, 0x4eU, 0x61U, 0x6dU, 0x65U, 0x73U, 0x70U, 0x61U, 0x63U, 0x65U, 0x73U, 0x20U, 0x52U, 0x65U, 0x61U, 0x63U, 0x68U, 0x65U, 0x64U, 0x00U, 0x6bU, 0x65U, 0x79U, 0x32U, 0x00U, 0x63U, 0x6fU, 0x6eU, 0x74U, 0x65U, 0x6eU, 0x74U, 0x32U}; Env env{*this, features}; auto const bob = Account{"bob"}; auto const alice = Account{"alice"}; env.fund(XRP(10000000), alice); env.fund(XRP(10000000), bob); // install the hook on alice env(ripple::test::jtx::hook( alice, {{hso(ns_maxHook, overrideFlag)}}, 0), M("set state_foreign_set_max"), HSFEE); env.close(); // invoke the hook for (uint32_t i = 0; i < 255; ++i) { env(pay(bob, alice, XRP(1)), M("test state_foreign_set_max"), fee(XRP(1))); } // fixXahauV1 bool const fixV1 = env.current()->rules().enabled(fixXahauV1); auto const txResult = fixV1 ? ter(tecHOOK_REJECTED) : ter(tesSUCCESS); env(pay(bob, alice, XRP(1)), M("test state_foreign_set_max"), fee(XRP(1)), ter(txResult)); env.close(); // verify hook result // TOO_MANY_NAMESPACES / -45 std::string const hookResult = fixV1 ? "800000000000002d" : "9"; Json::Value params; params[jss::transaction] = env.tx()->getJson(JsonOptions::none)[jss::hash]; auto const jrr = env.rpc("json", "tx", to_string(params)); auto const meta = jrr[jss::result][jss::meta]; auto const executions = meta[sfHookExecutions.jsonName]; auto const execution = executions[0u][sfHookExecution.jsonName]; BEAST_EXPECT(execution[sfHookReturnCode.jsonName] == hookResult); } void test_state_foreign_set(FeatureBitset features) { testcase("Test state_foreign_set"); using namespace jtx; Env env{*this, features}; 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 = wasm[R"[test.hook]( #include #define sfInvoiceID ((5U << 16U) + 17U) extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t state_foreign ( uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t ); extern int64_t state_foreign_set ( uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t ); extern int64_t otxn_id(uint32_t, uint32_t, uint32_t); extern int64_t otxn_field(uint32_t, uint32_t, uint32_t); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define TOO_BIG (-3) #define TOO_SMALL (-4) #define OUT_OF_BOUNDS (-1) #define DOESNT_EXIST (-5) #define INVALID_ARGUMENT (-7) #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); // bounds tests int64_t y; ASSERT((y=state_foreign_set(1111111, 32, 1, 32, 1, 32, 1, 20)) == OUT_OF_BOUNDS); ASSERT((y=state_foreign_set(1, 1111111, 1, 32, 1, 32, 1, 20)) == OUT_OF_BOUNDS); ASSERT((y=state_foreign_set(1, 32, 1111111, 32, 1, 32, 1, 20)) == OUT_OF_BOUNDS); ASSERT((y=state_foreign_set(1, 32, 1, 1111111, 1, 32, 1, 20)) == TOO_BIG); ASSERT((y=state_foreign_set(1, 32, 1, 32, 1111111, 32, 1, 20)) == OUT_OF_BOUNDS); ASSERT((y=state_foreign_set(1, 32, 1, 32, 1, 1111111, 1, 20)) == INVALID_ARGUMENT); ASSERT((y=state_foreign_set(1, 32, 1, 32, 1, 32, 1111111, 20)) == OUT_OF_BOUNDS); ASSERT((y=state_foreign_set(1, 32, 1, 32, 1, 32, 1, 1111111)) == INVALID_ARGUMENT); // get this transaction id uint8_t txn[32]; ASSERT(otxn_id(SBUF(txn), 0) == 32); // get the invoice id, which contains the grantor account uint8_t grantor[32]; ASSERT(otxn_field(SBUF(grantor), sfInvoiceID) == 32); // set the current txn id on the grantor's state under key 1, namespace 0 uint8_t one[32]; one[31] = 1U; uint8_t zero[32]; ASSERT(state_foreign_set(SBUF(txn), SBUF(one), SBUF(zero), grantor + 12, 20) == 32); return accept(0,0,0); } )[test.hook]"]; HASH_WASM(grantee); // this is the grantor TestHook grantor_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t otxn_field (uint32_t, uint32_t, uint32_t); extern int64_t hook_account(uint32_t write_ptr, uint32_t write_len); extern int64_t state_foreign_set ( uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t ); #define DOESNT_EXIST (-5) #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define BUFFER_EQUAL_20(buf1, buf2)\ (\ *(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\ *(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\ *(((uint32_t*)(buf1)) + 4) == *(((uint32_t*)(buf2)) + 4)) #define SBUF(x) (uint32_t)(x), sizeof(x) #define sfAccount ((8U << 16U) + 1U) int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t otxnacc[20]; ASSERT(otxn_field(SBUF(otxnacc), sfAccount) == 20); uint8_t hookacc[20]; ASSERT(hook_account(SBUF(hookacc)) == 20); if (BUFFER_EQUAL_20(otxnacc, hookacc)) { // outgoin txn, delete the state uint8_t one[32]; one[31] = 1U; uint8_t zero[32]; // we can use state_foreign_set to do the deletion, a concise way to test this functionality too int64_t y = state_foreign_set(0,0, SBUF(one), SBUF(zero), SBUF(hookacc)); ASSERT(y == 0 || y == DOESNT_EXIST); } return accept(0,0,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, {{hso(grantor_wasm, 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, {{hso(grantee_wasm, 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, {{hso(grantee_wasm, 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, {{hso(grantor_wasm, 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, {{hso(grantor_wasm, 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 = wasm[R"[test.hook]( #include #define sfInvoiceID ((5U << 16U) + 17U) extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t otxn_field (uint32_t, uint32_t, uint32_t); extern int64_t otxn_id(uint32_t, uint32_t, uint32_t); extern int64_t state_foreign_set ( uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t ); extern int64_t trace_num(uint32_t, uint32_t, int64_t); extern int64_t trace(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); #define SBUF(x) (uint32_t)(x), sizeof(x) #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), y); int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t i; int64_t y; // get this transaction id uint8_t txn[32]; ASSERT(otxn_id(SBUF(txn), 0) == 32); trace(SBUF("txnid"), SBUF(txn), 1); // get the invoice id, which contains the grantor account uint8_t grantor[32]; ASSERT(otxn_field(SBUF(grantor), sfInvoiceID) == 32); uint8_t iterations = grantor[0]; trace_num(SBUF("iterations"), iterations); // set the current txn id on the grantor's state under key 1, namespace 0 uint8_t zero[32]; for (i = 0; GUARD(255), i < iterations; ++i) { txn[0] = i; trace_num(SBUF("exhaustion i:"), i); ASSERT((y=state_foreign_set(SBUF(txn), SBUF(txn), SBUF(zero), grantor + 12, 20)) == 32); } return accept(0,0,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, {{hso(grantor_wasm, 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, {{hso(exhaustion_wasm, 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}; 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 auto const gary = Account{"gary"}; auto const hank = Account{"hank"}; 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); env.fund(XRP(10000), gary); env.fund(XRP(10000), hank); // install a rollback hook on cho env(ripple::test::jtx::hook( cho, {{hso(rollback_wasm, 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); } // bounds and buffer size checks { TestHook hook = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 state_set ( uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len ); extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define TOO_SMALL (-4) #define TOO_BIG (-3) #define OUT_OF_BOUNDS (-1) int64_t hook(uint32_t reserved) { _g(1,1); // bounds and buffer size checks { // RH NOTE: readptr/len 0/0 = delete entry ASSERT(state_set(0,0,0,0) == TOO_SMALL); ASSERT(state_set(0,0,0,33) == TOO_BIG); ASSERT(state_set(0,0,0,1000000) == TOO_BIG); ASSERT(state_set(0,0,1000000,1) == OUT_OF_BOUNDS); ASSERT(state_set(0,1000000, 0, 32) == OUT_OF_BOUNDS); ASSERT(state_set(1000000, 0, 0, 32) == OUT_OF_BOUNDS); uint16_t size; ASSERT(otxn_param(&size, 2, "SIZE", 4) > 0); ASSERT(state_set(0, size, 0, 32) == TOO_BIG); } accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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) Json::Value payJv1 = pay(alice, cho, XRP(1)); { Json::Value params{Json::arrayValue}; params[0U][jss::HookParameter][jss::HookParameterName] = strHex(std::string("SIZE")); params[0U][jss::HookParameter][jss::HookParameterValue] = features[featureExtendedHookState] ? "0108" /* 2049 */ : "0101" /* 257 */; payJv1[jss::HookParameters] = params; } env(payJv1, 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 Json::Value payJv2 = pay(bob, alice, XRP(1)); { Json::Value params{Json::arrayValue}; params[0U][jss::HookParameter][jss::HookParameterName] = strHex(std::string("SIZE")); params[0U][jss::HookParameter][jss::HookParameterValue] = features[featureExtendedHookState] ? "0108" /* 2049 */ : "0101" /* 257 */; payJv2[jss::HookParameters] = params; } env(payJv2, M("test state_set 1"), fee(XRP(1))); env.close(); } // first hook will set two state objects with different keys and data on // alice { TestHook hook = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t state_set ( uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len ); 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 }; #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved) { _g(1,1); // create state 1 { 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,0 }; uint8_t data[4] = { 0xCAU,0xFEU,0xBAU,0xBEU }; ASSERT(state_set(SBUF(data), SBUF(key)) == sizeof(data)); } // create state 2 { uint8_t key[3] = { 1,2,3 }; ASSERT(state_set(SBUF(data2), SBUF(key)) == sizeof(data2)); } accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t state_set ( uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len ); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); // override state 1 { uint8_t data[16] = { 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,2 }; uint8_t zero = 0; ASSERT(state_set(SBUF(data), &zero, 1) == sizeof(data)); } // delete state 2 { 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 }; uint8_t zero[1] = {0}; ASSERT(state_set(0,0, key2, 32) == 0); } accept(0,0,0); } )[test.hook]"]; TestHook hook2 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t state ( uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len ); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); // verify updated state { uint8_t data[16] = { 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,2 }; uint8_t data_read[16]; uint8_t zero = 0; ASSERT(state(SBUF(data_read), &zero, 1) == sizeof(data)); for (uint32_t i = 0; GUARD(16), i < 16; ++i) ASSERT(data[i] == data_read[i]); } accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook( alice, {{{hso(hook, overrideFlag)}, {}, {}, {hso(hook2, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t hook_again(void); extern int64_t state_foreign ( uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len ); extern int64_t otxn_field(uint32_t, uint32_t, uint32_t); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define sfAccount ((8U << 16U) + 1U) #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); hook_again(); // we're going to check in weak execution too uint8_t alice[20]; ASSERT(otxn_field(SBUF(alice), sfAccount) == 20); // verify updated state { uint8_t data[16] = { 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,2 }; uint8_t data_read[16]; uint8_t zero[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,0 }; ASSERT(state_foreign(SBUF(data_read), SBUF(zero), SBUF(zero), SBUF(alice)) == sizeof(data)); for (uint32_t i = 0; GUARD(16), i < 16; ++i) ASSERT(data[i] == data_read[i]); } accept(0,0,0); } )[test.hook]"]; // install the hook on bob env(ripple::test::jtx::hook(bob, {{hso(hook3, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t state_set ( uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len ); extern int64_t hook_again(void); #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define SBUF(x) (uint32_t)(x), sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); hook_again(); // create state { uint8_t data[16] = { 0xFFU,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,2 }; uint8_t ff = 0xFFU; ASSERT(state_set(SBUF(data), &ff, 1) == sizeof(data)); } accept(0,0,0); } )[test.hook]"]; // install the hook on alice, deleting the other hook env(ripple::test::jtx::hook( alice, {{{hso(hook, 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, {{hso(rollback_wasm, 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 = wasm[R"[test.hook]( #include #define sfInvoiceID ((5U << 16U) + 17U) extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t otxn_field (uint32_t, uint32_t, uint32_t); extern int64_t otxn_id(uint32_t, uint32_t, uint32_t); extern int64_t state_set ( uint32_t, uint32_t, uint32_t, uint32_t ); extern int64_t hook_pos(void); #define SBUF(x) (uint32_t)(x), sizeof(x) #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reserved ) { _g(1,1); // get this transaction id uint8_t txn[32]; ASSERT(otxn_id(SBUF(txn), 0) == 32); // get the invoice id, which contains the grantor account uint8_t grantor[32]; ASSERT(otxn_field(SBUF(grantor), sfInvoiceID) == 32); uint16_t iterations = (((uint16_t)grantor[0]) << 8U) + ((uint16_t)grantor[1]); uint8_t p = hook_pos(); for (uint16_t i = 0; GUARD(1500), i < iterations; ++i) { *((uint16_t*)txn) = i; txn[2] = (uint8_t)p; ASSERT(state_set(SBUF(txn), SBUF(txn)) == 32); } return accept(0,0,0); } )[test.hook]"]; HASH_WASM(exhaustion); // install the exhaustion hook on eve { Json::Value json = ripple::test::jtx::hook( eve, {{hso(exhaustion_wasm, 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, {{hso(exhaustion_wasm), hso(exhaustion_wasm), hso(exhaustion_wasm), hso(exhaustion_wasm)}}, 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); } if (env.current()->rules().enabled(featureExtendedHookState)) { // Test hook with scaled state data TestHook scaled_state_wasm = wasm[ R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 state_set (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len); extern int64_t util_keylet(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); extern int64_t slot_set(uint32_t, uint32_t, uint32_t); extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t); extern int64_t slot(uint32_t, uint32_t, uint32_t); extern int64_t hook_account(uint32_t, uint32_t); extern int64_t util_keylet(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); #define SBUF(x) x, sizeof(x) #define TOO_BIG -3 #define DOESNT_EXIST -5 #define KEYLET_ACCOUNT 3 #define sfHookStateScale ((1U << 16U) + 21U) #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x,sizeof(#x),__LINE__) int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t hook_acc[20]; ASSERT(hook_account(hook_acc, 20) == 20); uint8_t account_keylet[34]; ASSERT(util_keylet(account_keylet, 34, KEYLET_ACCOUNT, hook_acc, 20, 0,0,0,0) == 34); ASSERT(slot_set(account_keylet, 34, 1) == 1); slot_subfield(1, sfHookStateScale, 2); int64_t scale = slot(0,0,2); if (scale == DOESNT_EXIST) { ASSERT(state_set(0, 256, SBUF("test0")) == 256); ASSERT(state_set(0, 257, SBUF("test")) == TOO_BIG); accept(0,0,scale); } if (scale == 2) { ASSERT(state_set(0, 256, SBUF("test1")) == 256); ASSERT(state_set(0, 256*2, SBUF("test2")) == 256*2); ASSERT(state_set(0, 256*2+1, SBUF("test")) == TOO_BIG); accept(0,0,scale); } if (scale == 5) { ASSERT(state_set(0, 256, SBUF("test3")) == 256); ASSERT(state_set(0, 256*5, SBUF("test4")) == 256*5); ASSERT(state_set(0, 256*5+1, SBUF("test")) == TOO_BIG); accept(0,0,scale); } rollback(0,0,scale); } )[test.hook]"]; HASH_WASM(scaled_state); BEAST_EXPECT(!env.le(gary)->isFieldPresent(sfHookStateCount)); // Install hook on carol Json::Value jv = ripple::test::jtx::hook(gary, {{hso(scaled_state_wasm)}}, 0); // jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str; jv[jss::Hooks][0U][jss::Hook][jss::HookOn] = to_string(UINT256_BIT[ttACCOUNT_SET]); env(jv, M("Create scaled state hook"), HSFEE, ter(tesSUCCESS)); env.close(); BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 1); BEAST_EXPECT(!env.le(gary)->isFieldPresent(sfHookStateCount)); { // no HookStateScale Json::Value invoke = invoke::invoke(gary); env(invoke, HSFEE); env.close(); BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 2); BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 1); } { // HookStateScale => 2 Json::Value jv = noop(gary); jv[sfHookStateScale.fieldName] = 2; env(jv, HSFEE); env.close(); BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 3); BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 1); Json::Value invoke = invoke::invoke(gary); env(invoke, HSFEE); env.close(); BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 7); BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 3); } { // HookStateScale => 5 Json::Value jv = noop(gary); jv[sfHookStateScale.fieldName] = 5; env(jv, HSFEE); env.close(); BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 16); BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 3); Json::Value invoke = invoke::invoke(gary); env(invoke, HSFEE); env.close(); BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 26); BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 5); } } { bool extHookStateEnabled = features[featureExtendedHookState]; // tests for set_state_cache if (extHookStateEnabled) { TestHook extended_state_reserve_hook = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); 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 state_set ( uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len ); extern int64_t state_foreign_set ( uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len ); extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t); #define RESERVE_INSUFFICIENT -38 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define ASSERT_EQUAL(x, y)\ if (!(x == y))\ rollback((uint32_t)#x, sizeof(#x), x); int64_t hook(uint32_t reserved) { _g(1,1); { // 1. first account for StateMap ASSERT_EQUAL(state_set(0, 1, "1", 1), RESERVE_INSUFFICIENT); // 2. first namespace for StateMap ASSERT_EQUAL(state_foreign_set(0, 1, "1", 1, "1", 32, 0, 0), RESERVE_INSUFFICIENT); // 3. first statekey for StateMap ASSERT_EQUAL(state_set(0, 1, "2", 1), RESERVE_INSUFFICIENT); // 4. existing statedata ASSERT_EQUAL(state_set(0, 1, "1", 1), RESERVE_INSUFFICIENT); } accept(0,0,0); } )[test.hook]"]; // install the hook on gary Json::Value jv = hso(extended_state_reserve_hook, overrideFlag); jv[jss::HookOn] = "fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff" "bfffff"; // only invoke high env(ripple::test::jtx::hook(hank, {{jv}}, 0), HSFEE); env.close(); Json::Value jv1 = noop(hank); jv1[sfHookStateScale.fieldName] = 8; env(jv1, HSFEE); env.close(); auto const caller = Account{"caller"}; env.fund(XRP(10000), caller); env.close(); auto const payAmount = env.balance(hank) - (env.current()->fees().accountReserve(1 + 8)) - drops(1); // 8 + Hook // reduce hank's balance env(pay(hank, Account{"master"}, payAmount), fee(XRP(1))); env.close(); // invoke the hook from alice Json::Value invokeJv5 = invoke::invoke(caller, hank, ""); env(invokeJv5, M("test state_set 15"), fee(XRP(1))); env.close(); } } // RH TODO: // check state can be set on emit callback // check namespacing provides for non-collision of same key } 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t sto_emplace ( uint32_t write_ptr, uint32_t write_len, uint32_t sread_ptr, uint32_t sread_len, uint32_t fread_ptr, uint32_t fread_len, uint32_t field_id ); extern int64_t trace_num(uint32_t, uint32_t, int64_t); #define TOO_SMALL -4 #define TOO_BIG -3 #define OUT_OF_BOUNDS -1 #define MEM_OVERLAP -43 #define PARSE_ERROR -18 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); uint8_t sto[] = { 0x11U,0x00U,0x61U,0x22U,0x00U,0x00U,0x00U,0x00U,0x24U,0x04U,0x1FU,0x94U,0xD9U,0x25U,0x04U,0x5EU, 0x84U,0xB7U,0x2DU,0x00U,0x00U,0x00U,0x00U,0x55U,0x13U,0x40U,0xB3U,0x25U,0x86U,0x31U,0x96U,0xB5U, 0x6FU,0x41U,0xF5U,0x89U,0xEBU,0x7DU,0x2FU,0xD9U,0x4CU,0x0DU,0x7DU,0xB8U,0x0EU,0x4BU,0x2CU,0x67U, 0xA7U,0x78U,0x2AU,0xD6U,0xC2U,0xB0U,0x77U,0x50U,0x62U,0x40U,0x00U,0x00U,0x00U,0x00U,0xA4U,0x79U, 0x94U,0x81U,0x14U,0x37U,0xDFU,0x44U,0x07U,0xE7U,0xAAU,0x07U,0xF1U,0xD5U,0xC9U,0x91U,0xF2U,0xD3U, 0x6FU,0x9EU,0xB8U,0xC7U,0x34U,0xAFU,0x6CU }; uint8_t ins[] = { 0x56U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U, 0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U, 0x11U,0x11U,0x11U }; uint8_t ans[] = { 0x11U,0x00U,0x61U,0x22U,0x00U,0x00U,0x00U,0x00U,0x24U,0x04U,0x1FU,0x94U,0xD9U,0x25U,0x04U, 0x5EU,0x84U,0xB7U,0x2DU,0x00U,0x00U,0x00U,0x00U,0x55U,0x13U,0x40U,0xB3U,0x25U,0x86U,0x31U, 0x96U,0xB5U,0x6FU,0x41U,0xF5U,0x89U,0xEBU,0x7DU,0x2FU,0xD9U,0x4CU,0x0DU,0x7DU,0xB8U,0x0EU, 0x4BU,0x2CU,0x67U,0xA7U,0x78U,0x2AU,0xD6U,0xC2U,0xB0U,0x77U,0x50U,0x56U,0x11U,0x11U,0x11U, 0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U, 0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x62U, 0x40U,0x00U,0x00U,0x00U,0x00U,0xA4U,0x79U,0x94U,0x81U,0x14U,0x37U,0xDFU,0x44U,0x07U,0xE7U, 0xAAU,0x07U,0xF1U,0xD5U,0xC9U,0x91U,0xF2U,0xD3U,0x6FU,0x9EU,0xB8U,0xC7U,0x34U,0xAFU,0x6CU }; uint8_t ans2[] = { 0x11U,0x00U,0x61U,0x22U,0x00U,0x00U,0x00U,0x00U,0x24U,0x04U,0x1FU,0x94U,0xD9U,0x25U,0x04U, 0x5EU,0x84U,0xB7U,0x2DU,0x00U,0x00U,0x00U,0x00U,0x54U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U, 0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U, 0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x11U,0x55U,0x13U,0x40U,0xB3U, 0x25U,0x86U,0x31U,0x96U,0xB5U,0x6FU,0x41U,0xF5U,0x89U,0xEBU,0x7DU,0x2FU,0xD9U,0x4CU,0x0DU, 0x7DU,0xB8U,0x0EU,0x4BU,0x2CU,0x67U,0xA7U,0x78U,0x2AU,0xD6U,0xC2U,0xB0U,0x77U,0x50U,0x62U, 0x40U,0x00U,0x00U,0x00U,0x00U,0xA4U,0x79U,0x94U,0x81U,0x14U,0x37U,0xDFU,0x44U,0x07U,0xE7U, 0xAAU,0x07U,0xF1U,0xD5U,0xC9U,0x91U,0xF2U,0xD3U,0x6FU,0x9EU,0xB8U,0xC7U,0x34U,0xAFU,0x6CU }; int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t hash[32]; // Test out of bounds check ASSERT(sto_emplace(1000000, 32, 0, 32, 32, 32, 1) == OUT_OF_BOUNDS); ASSERT(sto_emplace(0, 1000000, 0, 32, 32, 32, 1) == OUT_OF_BOUNDS); ASSERT(sto_emplace(0, 32, 1000000, 32, 32, 32, 1) == OUT_OF_BOUNDS); ASSERT(sto_emplace(0, 32, 64, 1000000, 32, 32, 1) == OUT_OF_BOUNDS); ASSERT(sto_emplace(0, 32, 64, 32, 1000000, 32, 1) == OUT_OF_BOUNDS); ASSERT(sto_emplace(0, 32, 64, 32, 0, 1000000, 1) == OUT_OF_BOUNDS); // Test size check { // write buffer too small ASSERT(sto_emplace(0,1, 0,2, 0,2, 1) == TOO_SMALL); // src buffer too small ASSERT(sto_emplace(0,3, 0,1, 0,2, 1) == TOO_SMALL); // field buffer too small ASSERT(sto_emplace(0,3, 0,2, 0,1, 1) == TOO_SMALL); // field buffer too big ASSERT(sto_emplace(6000, 32000, 0, 5, 5, 6000, 1) == TOO_BIG); // src buffer too big ASSERT(sto_emplace(0, 32000, 32000, 17000, 49000, 4000, 1) == TOO_BIG); } uint8_t buf[1024]; // Test overlapping memory ASSERT(sto_emplace(buf, 1024, buf+1, 512, 0, 32, 1) == MEM_OVERLAP); ASSERT(sto_emplace(buf+1, 1024, buf, 512, 0, 32, 1) == MEM_OVERLAP); ASSERT(sto_emplace(0, 700, buf, 512, buf+1, 32, 1) == MEM_OVERLAP); // insert ledger index 561111111111111111111111111111111111111111111111111111111111111111 { ASSERT(sto_emplace( buf, sizeof(buf), sto, sizeof(sto), ins, sizeof(ins), 0x50006U) == sizeof(sto) + sizeof(ins)); for (int i = 0; GUARD(200), i < sizeof(ans); ++i) ASSERT(ans[i] == buf[i]); // flip it to 54 and check it is installed before ins[0] = 0x54U; ASSERT(sto_emplace( buf, sizeof(buf), sto, sizeof(sto), ins, sizeof(ins), 0x50004U) == sizeof(sto) + sizeof(ins)); for (int i = 0; GUARD(200), i < sizeof(ans2); ++i) ASSERT(ans2[i] == buf[i]); } // test front insertion { uint8_t sto[] = {0x22U,0x00U,0x00U,0x00U,0x00U}; uint8_t ins[] = {0x11U,0x11U,0x11U}; ASSERT(sto_emplace(buf, sizeof(buf), sto, sizeof(sto), ins, sizeof(ins), 0x10001U) == sizeof(sto) + sizeof(ins)); uint8_t ans[] = {0x11U,0x11U,0x11U,0x22U,0x00U,0x00U,0x00U,0x00U}; for (int i = 0; GUARD(10), i < sizeof(ans); ++i) ASSERT(ans[i] == buf[i]); } // test back insertion { uint8_t sto[] = {0x22U,0x00U,0x00U,0x00U,0x00U}; uint8_t ins[] = {0x31U,0x11U,0x11U,0x11U,0x11U,0x12U,0x22U,0x22U,0x22U}; ASSERT(sto_emplace(buf, sizeof(buf), sto, sizeof(sto), ins, sizeof(ins), 0x30001U) == sizeof(sto) + sizeof(ins)); uint8_t ans[] = {0x22U,0x00U,0x00U,0x00U,0x00U,0x31U,0x11U,0x11U,0x11U,0x11U,0x12U,0x22U,0x22U, 0x22U}; for (int i = 0; GUARD(20), i < sizeof(ans); ++i) ASSERT(ans[i] == buf[i]); } // test replacement { uint8_t rep[] = {0x22U,0x10U,0x20U,0x30U,0x40U}; ASSERT(sto_emplace(buf, sizeof(buf), sto, sizeof(sto), rep, sizeof(rep), 0x20002U) == sizeof(sto)); // check start ASSERT(buf[0] == sto[0] && buf[1] == sto[1] && buf[2] == sto[2]); // check replaced part for (int i = 3; GUARD(sizeof(rep)+1), i < sizeof(rep)+3; ++i) ASSERT(buf[i] == rep[i-3]); // check end for (int i = sizeof(rep)+3; GUARD(sizeof(sto)), i < sizeof(sto); ++i) ASSERT(sto[i] == buf[i]); } accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t sto_erase ( uint32_t write_ptr, uint32_t write_len, uint32_t sread_ptr, uint32_t sread_len, uint32_t field_id ); #define TOO_SMALL -4 #define TOO_BIG -3 #define OUT_OF_BOUNDS -1 #define MEM_OVERLAP -43 #define PARSE_ERROR -18 #define DOESNT_EXIST -5 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); uint8_t sto[] = { 0x11U,0x00U,0x61U,0x22U,0x00U,0x00U,0x00U,0x00U,0x24U,0x04U,0x1FU,0x94U,0xD9U,0x25U,0x04U,0x5EU, 0x84U,0xB7U,0x2DU,0x00U,0x00U,0x00U,0x00U,0x55U,0x13U,0x40U,0xB3U,0x25U,0x86U,0x31U,0x96U,0xB5U, 0x6FU,0x41U,0xF5U,0x89U,0xEBU,0x7DU,0x2FU,0xD9U,0x4CU,0x0DU,0x7DU,0xB8U,0x0EU,0x4BU,0x2CU,0x67U, 0xA7U,0x78U,0x2AU,0xD6U,0xC2U,0xB0U,0x77U,0x50U,0x62U,0x40U,0x00U,0x00U,0x00U,0x00U,0xA4U,0x79U, 0x94U,0x81U,0x14U,0x37U,0xDFU,0x44U,0x07U,0xE7U,0xAAU,0x07U,0xF1U,0xD5U,0xC9U,0x91U,0xF2U,0xD3U, 0x6FU,0x9EU,0xB8U,0xC7U,0x34U,0xAFU,0x6CU }; int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t hash[32]; // Test out of bounds check ASSERT(sto_erase(1000000, 32, 0, 32, 1) == OUT_OF_BOUNDS); ASSERT(sto_erase(0, 1000000, 0, 32, 1) == OUT_OF_BOUNDS); ASSERT(sto_erase(0, 32, 1000000, 32, 1) == OUT_OF_BOUNDS); ASSERT(sto_erase(0, 32, 64, 1000000, 1) == OUT_OF_BOUNDS); // Test size check { // write buffer too small ASSERT(sto_erase(0,1, 0,2, 1) == TOO_SMALL); ASSERT(sto_erase(0, 32000, 0, 17000, 1) == TOO_BIG); } uint8_t buf[1024]; // Test overlapping memory ASSERT(sto_erase(buf, 1024, buf+1, 512, 1) == MEM_OVERLAP); ASSERT(sto_erase(buf+1, 1024, buf, 512, 1) == MEM_OVERLAP); // erase field 22 { ASSERT(sto_erase( buf, sizeof(buf), sto, sizeof(sto), 0x20002U) == sizeof(sto) - 5); ASSERT(buf[0] == sto[0] && buf[1] == sto[1] && buf[2] == sto[2]); for (int i = 3; GUARD(sizeof(sto) + 1), i < sizeof(sto) - 5; ++i) ASSERT(sto[i+5] == buf[i]); } // test front erasure { ASSERT(sto_erase( buf, sizeof(buf), sto, sizeof(sto), 0x10001U) == sizeof(sto) - 3); for (int i = 3; GUARD(sizeof(sto) + 1), i < sizeof(sto) - 3; ++i) ASSERT(sto[i] == buf[i-3]); } // test back erasure { ASSERT(sto_erase( buf, sizeof(buf), sto, sizeof(sto), 0x80001U) == sizeof(sto) - 22); for (int i = 0; GUARD(sizeof(sto) - 21), i < sizeof(sto)-22; ++i) ASSERT(sto[i] == buf[i]); } // test not found { ASSERT(sto_erase( buf, sizeof(buf), sto, sizeof(sto), 0x80002U) == DOESNT_EXIST); for (int i = 0; GUARD(sizeof(sto) +1), i < sizeof(sto); ++i) ASSERT(sto[i] == buf[i]); } // test total erasure { uint8_t rep[] = {0x22U,0x10U,0x20U,0x30U,0x40U}; ASSERT(sto_erase(buf, sizeof(buf), rep, sizeof(rep), 0x20002U) == 0); } accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t sto_subarray( uint32_t read_ptr, uint32_t read_len, uint32_t field_id); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define DOESNT_EXIST -5 #define PARSE_ERROR -18 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); uint8_t sto[] = { 0xF4U,0xEBU,0x13U,0x00U,0x01U,0x81U,0x14U,0x20U,0x42U,0x88U,0xD2U,0xE4U,0x7FU,0x8EU,0xF6U,0xC9U, 0x9BU,0xCCU,0x45U,0x79U,0x66U,0x32U,0x0DU,0x12U,0x40U,0x97U,0x11U,0xE1U,0xEBU,0x13U,0x00U,0x01U, 0x81U,0x14U,0x3EU,0x9DU,0x4AU,0x2BU,0x8AU,0xA0U,0x78U,0x0FU,0x68U,0x2DU,0x13U,0x6FU,0x7AU,0x56U, 0xD6U,0x72U,0x4EU,0xF5U,0x37U,0x54U,0xE1U,0xF1U }; int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t hash[32]; // Test out of bounds check ASSERT(sto_subarray(1000000, 32, 1) == OUT_OF_BOUNDS); ASSERT(sto_subarray(0, 1000000, 1) == OUT_OF_BOUNDS); // Test size check ASSERT(sto_subarray(0,1, 1) == TOO_SMALL); // Test index 0, should be position 1 length 27 ASSERT(sto_subarray(sto, sizeof(sto), 0) == (1ULL << 32ULL) + 27ULL); // Test index 1, should be position 28 length 27 ASSERT(sto_subarray(sto, sizeof(sto), 1) == (28ULL << 32ULL) + 27ULL); // Test index2, doesn't exist ASSERT(sto_subarray(sto, sizeof(sto), 2) == DOESNT_EXIST); accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t sto_subfield( uint32_t read_ptr, uint32_t read_len, uint32_t field_id); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define DOESNT_EXIST -5 #define PARSE_ERROR -18 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); uint8_t sto[] = { 0x11U,0x00U,0x53U,0x22U,0x00U,0x00U,0x00U,0x00U,0x25U,0x01U,0x52U,0x70U,0x1AU,0x20U,0x23U,0x00U, 0x00U,0x00U,0x02U,0x20U,0x26U,0x00U,0x00U,0x00U,0x00U,0x34U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U, 0x00U,0x00U,0x55U,0x09U,0xA9U,0xC8U,0x6BU,0xF2U,0x06U,0x95U,0x73U,0x5AU,0xB0U,0x36U,0x20U,0xEBU, 0x1CU,0x32U,0x60U,0x66U,0x35U,0xACU,0x3DU,0xA0U,0xB7U,0x02U,0x82U,0xF3U,0x7CU,0x67U,0x4FU,0xC8U, 0x89U,0xEFU,0xE7U }; int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t hash[32]; // Test out of bounds check ASSERT(sto_subfield(1000000, 32, 1) == OUT_OF_BOUNDS); ASSERT(sto_subfield(0, 1000000, 1) == OUT_OF_BOUNDS); // Test size check ASSERT(sto_subfield(0,1, 1) == TOO_SMALL); // Test subfield 0x11, should be position 0 length 3, payload pos 1, len 2 ASSERT(sto_subfield(sto, sizeof(sto), 0x10001U) == (1ULL << 32ULL) + 2ULL); // Test subfield 0x22, should be position 3 length 5, payload pos 4, len 4 ASSERT(sto_subfield(sto, sizeof(sto), 0x20002U) == (4ULL << 32ULL) + 4ULL); // Test subfield 0x34, should be at position 25, length = 9, payload pos 26, len 8 ASSERT(sto_subfield(sto, sizeof(sto), 0x30004U) == (26ULL << 32ULL) + 8ULL); // Test final subfield, position 34, length 33, payload pos 35, len 32 ASSERT(sto_subfield(sto, sizeof(sto), 0x50005U) == (35ULL << 32ULL) + 32ULL); // Test not found ASSERT(sto_subfield(sto, sizeof(sto), 0x90009U) == DOESNT_EXIST); accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t sto_validate ( uint32_t read_ptr, uint32_t read_len); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); uint8_t sto[] = { 0x11U,0x00U,0x61U,0x22U,0x00U,0x00U,0x00U,0x00U,0x24U,0x04U,0x1FU,0x94U,0xD9U,0x25U,0x04U,0x5EU, 0x84U,0xB7U,0x2DU,0x00U,0x00U,0x00U,0x00U,0x55U,0x13U,0x40U,0xB3U,0x25U,0x86U,0x31U,0x96U,0xB5U, 0x6FU,0x41U,0xF5U,0x89U,0xEBU,0x7DU,0x2FU,0xD9U,0x4CU,0x0DU,0x7DU,0xB8U,0x0EU,0x4BU,0x2CU,0x67U, 0xA7U,0x78U,0x2AU,0xD6U,0xC2U,0xB0U,0x77U,0x50U,0x62U,0x40U,0x00U,0x00U,0x00U,0x00U,0xA4U,0x79U, 0x94U,0x81U,0x14U,0x37U,0xDFU,0x44U,0x07U,0xE7U,0xAAU,0x07U,0xF1U,0xD5U,0xC9U,0x91U,0xF2U,0xD3U, 0x6FU,0x9EU,0xB8U,0xC7U,0x34U,0xAFU,0x6CU }; int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t hash[32]; // Test out of bounds check ASSERT(sto_validate(1000000, 32) == OUT_OF_BOUNDS); ASSERT(sto_validate(0, 1000000) == OUT_OF_BOUNDS); // Test size check ASSERT(sto_validate(0,1) == TOO_SMALL); // Test validation ASSERT(sto_validate(sto, sizeof(sto)) == 1); // Invalidate sto[0] = 0x22U; ASSERT(sto_validate(sto, sizeof(sto)) == 0); // Fix sto[0] = 0x11U; // Invalidate somewhere else sto[3] = 0x40U; ASSERT(sto_validate(sto, sizeof(sto)) == 0); // test small validation { uint8_t sto[] = {0x22U,0x00U,0x00U,0x00U,0x00U}; ASSERT(sto_validate(sto, sizeof(sto)) == 1); } accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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_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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t trace (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reservmaed ) { _g(1,1); // Test out of bounds check ASSERT(trace(1000000, 10, 0, 10, 0) == OUT_OF_BOUNDS); ASSERT(trace(0, 1000000, 0, 10, 0) == OUT_OF_BOUNDS); ASSERT(trace(0, 10, 1000000, 10, 0) == OUT_OF_BOUNDS); ASSERT(trace(0, 10, 0, 1000000, 0) == OUT_OF_BOUNDS); ASSERT(trace(0,0,0,0,0) == 0); ASSERT(trace(0,1,2,3,1) == 0); return accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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_trace_float(FeatureBitset features) { testcase("Test trace_float"); 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t trace_float (uint32_t, uint32_t, int64_t); #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reservmaed ) { _g(1,1); // Test out of bounds check ASSERT(trace_float(1000000, 10, 0) == OUT_OF_BOUNDS); ASSERT(trace_float(0, 1000000, 0) == OUT_OF_BOUNDS); return accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), M("set trace_float"), HSFEE); env.close(); // invoke the hook env(pay(bob, alice, XRP(1)), M("test trace_float"), fee(XRP(1))); } void test_trace_num(FeatureBitset features) { testcase("Test trace_num"); 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t trace_num (uint32_t, uint32_t, int64_t); #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t r ) { _g(1,1); // Test out of bounds check ASSERT(trace_num(1000000, 10, 0) == OUT_OF_BOUNDS); ASSERT(trace_num(0, 1000000, 0) == OUT_OF_BOUNDS); return accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), M("set trace_num"), HSFEE); env.close(); // invoke the hook env(pay(bob, alice, XRP(1)), M("test trace_num"), 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t util_accid (uint32_t, uint32_t, uint32_t, uint32_t); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t b[20]; { const char addr[] = "rMEGJtK2SttrtAfoKaqKUpCrDCi9saNuLg"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0xDEU && b[ 1] == 0x15U && b[ 2] == 0x1EU && b[ 3] == 0x2FU && b[ 4] == 0xB2U && b[ 5] == 0xAAU && b[ 6] == 0xBDU && b[ 7] == 0x1AU && b[ 8] == 0x5BU && b[ 9] == 0xD0U && b[10] == 0x2FU && b[11] == 0x63U && b[12] == 0x68U && b[13] == 0x26U && b[14] == 0xDFU && b[15] == 0x43U && b[16] == 0x50U && b[17] == 0xC0U && b[18] == 0x40U && b[19] == 0xDEU); } { const char addr[] = "rNo8xzUAauXENpvsMVJ9Q9w5LtVxCVFN4p"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x97U && b[ 1] == 0x73U && b[ 2] == 0x23U && b[ 3] == 0xAAU && b[ 4] == 0x33U && b[ 5] == 0x7CU && b[ 6] == 0xB6U && b[ 7] == 0x82U && b[ 8] == 0x37U && b[ 9] == 0x83U && b[10] == 0x58U && b[11] == 0x3AU && b[12] == 0x7AU && b[13] == 0xDFU && b[14] == 0x4EU && b[15] == 0xD8U && b[16] == 0x52U && b[17] == 0x2CU && b[18] == 0xA8U && b[19] == 0xF0U); } { const char addr[] = "rUpwuJR1xLH18aHLP5nEm4Hw215tmkq6V7"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x78U && b[ 1] == 0xE2U && b[ 2] == 0x10U && b[ 3] == 0xACU && b[ 4] == 0x98U && b[ 5] == 0x38U && b[ 6] == 0xF2U && b[ 7] == 0x5AU && b[ 8] == 0x3BU && b[ 9] == 0x7EU && b[10] == 0xDEU && b[11] == 0x51U && b[12] == 0x37U && b[13] == 0x13U && b[14] == 0x94U && b[15] == 0xEDU && b[16] == 0x80U && b[17] == 0x77U && b[18] == 0x89U && b[19] == 0x48U); } { const char addr[] = "ravUPmVUQ65qeuNSFiN6W2U88smjJYHBJm"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x40U && b[ 1] == 0xE8U && b[ 2] == 0x2FU && b[ 3] == 0x55U && b[ 4] == 0xC7U && b[ 5] == 0x3AU && b[ 6] == 0xEBU && b[ 7] == 0xCFU && b[ 8] == 0xC9U && b[ 9] == 0x1DU && b[10] == 0x3BU && b[11] == 0xF4U && b[12] == 0x77U && b[13] == 0x76U && b[14] == 0x50U && b[15] == 0x2BU && b[16] == 0x49U && b[17] == 0x7BU && b[18] == 0x12U && b[19] == 0x2CU); } { const char addr[] = "rPXQ8PW1C382oewiEyJrAWtDQBNsQhAtWA"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0xF7U && b[ 1] == 0x13U && b[ 2] == 0x19U && b[ 3] == 0x49U && b[ 4] == 0x3FU && b[ 5] == 0xA6U && b[ 6] == 0xA3U && b[ 7] == 0xDBU && b[ 8] == 0x62U && b[ 9] == 0xAEU && b[10] == 0x12U && b[11] == 0x1BU && b[12] == 0x12U && b[13] == 0x6CU && b[14] == 0xFEU && b[15] == 0x81U && b[16] == 0x49U && b[17] == 0x5AU && b[18] == 0x49U && b[19] == 0x16U); } { const char addr[] = "rnZbUT8tpm48KEdfELCxRjJJhNV1JNYcg5"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x32U && b[ 1] == 0x0AU && b[ 2] == 0x5CU && b[ 3] == 0x53U && b[ 4] == 0x61U && b[ 5] == 0x5BU && b[ 6] == 0x4BU && b[ 7] == 0x57U && b[ 8] == 0x1DU && b[ 9] == 0xC4U && b[10] == 0x6FU && b[11] == 0x13U && b[12] == 0xBDU && b[13] == 0x4FU && b[14] == 0x31U && b[15] == 0x70U && b[16] == 0x84U && b[17] == 0xD1U && b[18] == 0xB1U && b[19] == 0x68U); } { const char addr[] = "rPghxri3jhBaxBfWGAHrVC4KANoRBe6dcM"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0xF8U && b[ 1] == 0xB6U && b[ 2] == 0x49U && b[ 3] == 0x2BU && b[ 4] == 0x5BU && b[ 5] == 0x21U && b[ 6] == 0xC8U && b[ 7] == 0xDAU && b[ 8] == 0xBDU && b[ 9] == 0x0FU && b[10] == 0x1DU && b[11] == 0x2FU && b[12] == 0xD9U && b[13] == 0xF4U && b[14] == 0x5BU && b[15] == 0xDEU && b[16] == 0xCCU && b[17] == 0x6AU && b[18] == 0xEBU && b[19] == 0x91U); } { const char addr[] = "r4Tck2QJcfcwBuTgVJXYb4QbrKP6mT1acM"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0xEBU && b[ 1] == 0x63U && b[ 2] == 0x4CU && b[ 3] == 0xD6U && b[ 4] == 0xF9U && b[ 5] == 0xBFU && b[ 6] == 0x50U && b[ 7] == 0xC1U && b[ 8] == 0xD9U && b[ 9] == 0x79U && b[10] == 0x30U && b[11] == 0x84U && b[12] == 0x1BU && b[13] == 0xFCU && b[14] == 0x35U && b[15] == 0x32U && b[16] == 0xBDU && b[17] == 0x6DU && b[18] == 0xC0U && b[19] == 0x75U); } { const char addr[] = "rETHUL5T1SzM6AMotnsK5V3J5XMwJ9UhZ2"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x9EU && b[ 1] == 0x8AU && b[ 2] == 0x18U && b[ 3] == 0x66U && b[ 4] == 0x92U && b[ 5] == 0x0EU && b[ 6] == 0xE5U && b[ 7] == 0xEDU && b[ 8] == 0xFAU && b[ 9] == 0xE3U && b[10] == 0x23U && b[11] == 0x15U && b[12] == 0xCBU && b[13] == 0x83U && b[14] == 0xEFU && b[15] == 0x73U && b[16] == 0xE4U && b[17] == 0x91U && b[18] == 0x0BU && b[19] == 0xCAU); } { const char addr[] = "rh9CggaWiY6QdD55ZkbbnrFpHJkKSauLfC"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x22U && b[ 1] == 0x8BU && b[ 2] == 0xFFU && b[ 3] == 0x31U && b[ 4] == 0xB4U && b[ 5] == 0x93U && b[ 6] == 0xF6U && b[ 7] == 0xC1U && b[ 8] == 0x12U && b[ 9] == 0xEAU && b[10] == 0xD6U && b[11] == 0xDFU && b[12] == 0xC4U && b[13] == 0x05U && b[14] == 0xB3U && b[15] == 0x7DU && b[16] == 0xC0U && b[17] == 0x65U && b[18] == 0x21U && b[19] == 0x34U); } { const char addr[] = "r9sYGdPCGuJauy8QVG4CHnvp5U4eu3yY2B"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x58U && b[ 1] == 0x3BU && b[ 2] == 0xF0U && b[ 3] == 0xCBU && b[ 4] == 0x95U && b[ 5] == 0x80U && b[ 6] == 0xDEU && b[ 7] == 0xA0U && b[ 8] == 0xB3U && b[ 9] == 0x71U && b[10] == 0xD0U && b[11] == 0x18U && b[12] == 0x17U && b[13] == 0x1AU && b[14] == 0xBBU && b[15] == 0x98U && b[16] == 0x1FU && b[17] == 0xCCU && b[18] == 0x7CU && b[19] == 0x68U); } { const char addr[] = "r4yJX9eU65WHfmKz6xXmSRf9CZN6bXfpWb"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0xF1U && b[ 1] == 0x00U && b[ 2] == 0x8FU && b[ 3] == 0x64U && b[ 4] == 0x0FU && b[ 5] == 0x99U && b[ 6] == 0x19U && b[ 7] == 0xDAU && b[ 8] == 0xCFU && b[ 9] == 0x48U && b[10] == 0x18U && b[11] == 0x1CU && b[12] == 0x35U && b[13] == 0x2EU && b[14] == 0xE4U && b[15] == 0x3EU && b[16] == 0x37U && b[17] == 0x7CU && b[18] == 0x01U && b[19] == 0xF6U); } { const char addr[] = "rBkXoWoXPHuZy2nHbE7L1zJfqAvb4jHRrK"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x75U && b[ 1] == 0xECU && b[ 2] == 0xDBU && b[ 3] == 0x3BU && b[ 4] == 0x9AU && b[ 5] == 0x71U && b[ 6] == 0xD9U && b[ 7] == 0xEFU && b[ 8] == 0xD6U && b[ 9] == 0x55U && b[10] == 0x15U && b[11] == 0xDDU && b[12] == 0xEAU && b[13] == 0xD2U && b[14] == 0x36U && b[15] == 0x7AU && b[16] == 0x05U && b[17] == 0x6FU && b[18] == 0x4EU && b[19] == 0x5FU); } { const char addr[] = "rnaUBeEBNuyv57Jk127DsApEQoR8JqWpie"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x2CU && b[ 1] == 0xDBU && b[ 2] == 0xEBU && b[ 3] == 0x1FU && b[ 4] == 0x5EU && b[ 5] == 0xC5U && b[ 6] == 0xD7U && b[ 7] == 0x5FU && b[ 8] == 0xACU && b[ 9] == 0xBDU && b[10] == 0x19U && b[11] == 0xC8U && b[12] == 0x3FU && b[13] == 0x45U && b[14] == 0x3BU && b[15] == 0xA8U && b[16] == 0xA0U && b[17] == 0x1CU && b[18] == 0xDBU && b[19] == 0x0FU); } { const char addr[] = "rJHmUPMQ6qYdaqMizDZY8FKcCqCJxYYnb3"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0xBDU && b[ 1] == 0xA5U && b[ 2] == 0xAFU && b[ 3] == 0xDAU && b[ 4] == 0x5FU && b[ 5] == 0x04U && b[ 6] == 0xE7U && b[ 7] == 0xEFU && b[ 8] == 0x16U && b[ 9] == 0x7AU && b[10] == 0x35U && b[11] == 0x94U && b[12] == 0x6EU && b[13] == 0xEFU && b[14] == 0x19U && b[15] == 0xFAU && b[16] == 0x12U && b[17] == 0xF3U && b[18] == 0x1CU && b[19] == 0x64U); } { const char addr[] = "rpJtt64FNNtaEBgqbJcrrunucUWJSdKJa2"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x0EU && b[ 1] == 0x5AU && b[ 2] == 0x83U && b[ 3] == 0x89U && b[ 4] == 0xC0U && b[ 5] == 0x5EU && b[ 6] == 0x56U && b[ 7] == 0xD1U && b[ 8] == 0x50U && b[ 9] == 0xBCU && b[10] == 0x45U && b[11] == 0x7BU && b[12] == 0x86U && b[13] == 0x46U && b[14] == 0xF1U && b[15] == 0xCFU && b[16] == 0xB7U && b[17] == 0xD0U && b[18] == 0xBFU && b[19] == 0xD4U); } { const char addr[] = "rUC2XjZURBYQ8r6i5sqWnhtDmFFdJFobb9"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x7FU && b[ 1] == 0xF5U && b[ 2] == 0x2DU && b[ 3] == 0xF4U && b[ 4] == 0x98U && b[ 5] == 0x2BU && b[ 6] == 0x7CU && b[ 7] == 0x14U && b[ 8] == 0x7EU && b[ 9] == 0x9AU && b[10] == 0x8BU && b[11] == 0xEBU && b[12] == 0x1AU && b[13] == 0x53U && b[14] == 0x60U && b[15] == 0x34U && b[16] == 0x95U && b[17] == 0x42U && b[18] == 0x4AU && b[19] == 0x44U); } { const char addr[] = "rKEsw1ExpKaukXyyPCxeZdAF5V68kPSAVZ"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0xC8U && b[ 1] == 0x19U && b[ 2] == 0xE6U && b[ 3] == 0x2AU && b[ 4] == 0xDDU && b[ 5] == 0x42U && b[ 6] == 0x48U && b[ 7] == 0xD6U && b[ 8] == 0x7DU && b[ 9] == 0xA5U && b[10] == 0x56U && b[11] == 0x66U && b[12] == 0x55U && b[13] == 0xB4U && b[14] == 0xBFU && b[15] == 0xDEU && b[16] == 0x99U && b[17] == 0xCFU && b[18] == 0xEDU && b[19] == 0x96U); } { const char addr[] = "rEXhVGVWdte28r1DUzfgKLjNiHi1Tn6R7X"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x9FU && b[ 1] == 0x41U && b[ 2] == 0x26U && b[ 3] == 0xA3U && b[ 4] == 0x6DU && b[ 5] == 0x56U && b[ 6] == 0x01U && b[ 7] == 0xC8U && b[ 8] == 0x09U && b[ 9] == 0x63U && b[10] == 0x76U && b[11] == 0xEDU && b[12] == 0x4CU && b[13] == 0x45U && b[14] == 0x66U && b[15] == 0x63U && b[16] == 0x16U && b[17] == 0xC9U && b[18] == 0x5CU && b[19] == 0x80U); } { const char addr[] = "r3TcfPNEvidJ2LkNoFojffcCd7RgT53Thg"; ASSERT(20 == util_accid((uint32_t)b, 20, (uint32_t)addr, sizeof(addr))); ASSERT( b[ 0] == 0x51U && b[ 1] == 0xD1U && b[ 2] == 0x00U && b[ 3] == 0xFFU && b[ 4] == 0x0DU && b[ 5] == 0x92U && b[ 6] == 0x18U && b[ 7] == 0x73U && b[ 8] == 0x80U && b[ 9] == 0x30U && b[10] == 0xC5U && b[11] == 0x1AU && b[12] == 0xF2U && b[13] == 0x9FU && b[14] == 0x52U && b[15] == 0x8EU && b[16] == 0xB8U && b[17] == 0x63U && b[18] == 0x08U && b[19] == 0x7CU); } // Test out of bounds check ASSERT(util_accid(1000000, 20, 0, 35) == OUT_OF_BOUNDS); ASSERT(util_accid(0, 35, 10000000, 20) == OUT_OF_BOUNDS); ASSERT(util_accid(0, 19, 0, 0) == TOO_SMALL); accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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}; 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 id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t util_keylet ( uint32_t write_ptr, uint32_t write_len, uint32_t keylet_type, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f ); #define OUT_OF_BOUNDS -1 #define INVALID_ARGUMENT -7 #define TOO_SMALL -4 #define KEYLET_HOOK 1 #define KEYLET_HOOK_STATE 2 #define KEYLET_ACCOUNT 3 #define KEYLET_AMENDMENTS 4 #define KEYLET_CHILD 5 #define KEYLET_SKIP 6 #define KEYLET_FEES 7 #define KEYLET_NEGATIVE_UNL 8 #define KEYLET_LINE 9 #define KEYLET_OFFER 10 #define KEYLET_QUALITY 11 #define KEYLET_EMITTED_DIR 12 #define KEYLET_SIGNERS 14 #define KEYLET_CHECK 15 #define KEYLET_DEPOSIT_PREAUTH 16 #define KEYLET_UNCHECKED 17 #define KEYLET_OWNER_DIR 18 #define KEYLET_PAGE 19 #define KEYLET_ESCROW 20 #define KEYLET_PAYCHAN 21 #define KEYLET_EMITTED_TXN 22 #define KEYLET_NFT_OFFER 23 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #define ASSERT_KL_EQ(b)\ {\ uint64_t* n = (uint64_t*)(b);\ uint64_t* m = (uint64_t*)(buf);\ ASSERT(n[0] == m[0] && n[1] == m[1] && n[2] == m[2] && n[3] == m[3]);\ } #define SBUF(x) x,sizeof(x) //C5D0F34B0A1905BC3B29AA1BE139FE04D60C8694D3950A8D80251D10B563A822 uint8_t ns[] = { 0xC5U,0xD0U,0xF3U,0x4BU,0x0AU,0x19U,0x05U,0xBCU,0x3BU,0x29U,0xAAU,0x1BU,0xE1U, 0x39U,0xFEU,0x04U,0xD6U,0x0CU,0x86U,0x94U,0xD3U,0x95U,0x0AU,0x8DU,0x80U,0x25U, 0x1DU,0x10U,0xB5U,0x63U,0xA8U,0x22U }; //2D0CB3CD60DA33B5AA7FEA321F111663EAED32481C6B700E484550F45AD96223 uint8_t klkey[] = { 0x00U, 0x00U, 0x2DU,0x0CU,0xB3U,0xCDU,0x60U,0xDAU,0x33U,0xB5U,0xAAU,0x7FU,0xEAU,0x32U,0x1FU, 0x11U,0x16U,0x63U,0xEAU,0xEDU,0x32U,0x48U,0x1CU,0x6BU,0x70U,0x0EU,0x48U,0x45U, 0x50U,0xF4U,0x5AU,0xD9U,0x62U,0x23U }; uint8_t cur[] = { 0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U, 0x00U,0x00U,0x55U,0x53U,0x44U,0x00U,0x00U,0x00U,0x00U,0x00U }; uint8_t* key = klkey + 2; uint8_t a[] = //rB6v18pQ765Z9DH5RQsTFevoQPFmRtBqhT { 0x75U,0x6EU,0xDEU,0x88U,0xA9U,0x07U,0xD4U,0xCCU,0xF3U,0x8DU,0x6AU,0xDBU, 0x9FU,0xC7U,0x94U,0x64U,0x19U,0xF0U,0xC4U,0x1DU }; uint8_t b[] = //raKM1bZkGmASBqN5v2swrf2uAPJ32Cd8GV { 0x3AU,0x51U,0x8AU,0x22U,0x53U,0x81U,0x60U,0x84U,0x1CU,0x14U,0x32U,0xFEU, 0x6FU,0x3EU,0x6DU,0x6EU,0x76U,0x29U,0xFBU,0xBAU }; int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t buf[34]; int64_t e = 0; // Test out of bounds check ASSERT(util_keylet(1000000, 34, KEYLET_SKIP, 0,0,0,0,0,0) == OUT_OF_BOUNDS); ASSERT(util_keylet((uint32_t)buf, 1000000, KEYLET_SKIP, 0,0,0,0,0,0) == OUT_OF_BOUNDS); // Test min size ASSERT(util_keylet((uint32_t)buf, 33, KEYLET_SKIP, 0,0,0,0,0,0) == TOO_SMALL); // Test one of each type ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_HOOK, SBUF(a), 0,0,0,0 ))); { uint8_t ans[] = { 0x00U,0x48U,0x6CU,0x4BU,0x29U,0xC6U,0x0FU,0x40U,0x5DU,0xB7U,0x6EU,0x87U, 0x65U,0x4AU,0x2FU,0x15U,0x4BU,0xABU,0x99U,0xC7U,0x62U,0x29U,0x80U,0x10U, 0xA1U,0x89U,0x78U,0x52U,0x90U,0x80U,0x2FU,0x78U,0xBDU,0xCCU }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_HOOK_STATE, SBUF(b), key, 32, SBUF(ns) ))); { uint8_t ans[] = { 0x00U,0x76U,0x28U,0xAFU,0xCCU,0x25U,0x0AU,0x64U,0x41U,0x8EU,0xB7U,0x83U, 0x68U,0xEBU,0x4EU,0xC5U,0x52U,0x4AU,0xEBU,0x97U,0x54U,0xABU,0xC1U,0x0BU, 0x13U,0x06U,0x7FU,0xFBU,0x9FU,0x4BU,0xD8U,0x38U,0x62U,0xF2U }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_ACCOUNT, SBUF(b), 0,0,0,0 ))); { uint8_t ans[] = { 0x00U,0x61U,0xC6U,0x55U,0xDDU,0x8DU,0x8EU,0xD3U,0xBAU,0xB4U,0xA0U,0xF1U, 0xECU,0x2DU,0xA9U,0x99U,0xF4U,0x1BU,0xA6U,0x82U,0xC6U,0x84U,0xF9U,0x99U, 0x66U,0xB9U,0x3CU,0x9AU,0xC3U,0xE3U,0x5CU,0x9AU,0x81U,0x6DU }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_AMENDMENTS, 0,0,0,0,0,0 ))); { uint8_t ans[] = { 0x00U,0x66U,0x7DU,0xB0U,0x78U,0x8CU,0x02U,0x0FU,0x02U,0x78U,0x0AU,0x67U, 0x3DU,0xC7U,0x47U,0x57U,0xF2U,0x38U,0x23U,0xFAU,0x30U,0x14U,0xC1U,0x86U, 0x6EU,0x72U,0xCCU,0x4CU,0xD8U,0xB2U,0x26U,0xCDU,0x6EU,0xF4U }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_CHILD, key, 32, 0,0,0,0 ))); { klkey[0] = 0x1CU; klkey[1] = 0xD2U; ASSERT_KL_EQ(klkey); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_SKIP, 0,0,0,0,0,0 ))); { uint8_t ans[] = { 0x00U,0x68U,0xB4U,0x97U,0x9AU,0x36U,0xCDU,0xC7U,0xF3U,0xD3U, 0xD5U,0xC3U,0x1AU,0x4EU,0xAEU,0x2AU,0xC7U,0xD7U,0x20U,0x9DU, 0xDAU,0x87U,0x75U,0x88U,0xB9U,0xAFU,0xC6U,0x67U,0x99U,0x69U, 0x2AU,0xB0U,0xD6U,0x6BU }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_FEES, 0,0,0,0,0,0 ))); { uint8_t ans[] = { 0x00U,0x73U,0x4BU,0xC5U,0x0CU,0x9BU,0x0DU,0x85U,0x15U,0xD3U, 0xEAU,0xAEU,0x1EU,0x74U,0xB2U,0x9AU,0x95U,0x80U,0x43U,0x46U, 0xC4U,0x91U,0xEEU,0x1AU,0x95U,0xBFU,0x25U,0xE4U,0xAAU,0xB8U, 0x54U,0xA6U,0xA6U,0x51U }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_NEGATIVE_UNL, 0,0,0,0,0,0 ))); { uint8_t ans[] = { 0x00U,0x4EU,0x2EU,0x8AU,0x59U,0xAAU,0x9DU,0x3BU,0x5BU,0x18U, 0x6BU,0x0BU,0x9EU,0x0FU,0x62U,0xE6U,0xC0U,0x25U,0x87U,0xCAU, 0x74U,0xA4U,0xD7U,0x78U,0x93U,0x8EU,0x95U,0x7BU,0x63U,0x57U, 0xD3U,0x64U,0xB2U,0x44U }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_LINE, SBUF(a), SBUF(b), SBUF(cur) ))); { uint8_t ans[] = { 0x00U,0x72U,0x0EU,0xB8U,0x2AU,0xDDU,0x5EU,0x15U,0x59U,0x1BU, 0xF6U,0xE3U,0x6DU,0xBCU,0x3CU,0x12U,0xD3U,0x07U,0x6DU,0x43U, 0xA8U,0x53U,0xF8U,0xF9U,0xE8U,0xA7U,0xD8U,0x4FU,0xE1U,0xE9U, 0x7AU,0x2AU,0xC7U,0x3DU }; ASSERT_KL_EQ(ans); } // test 3 byte code ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_LINE, SBUF(a), SBUF(b), (uint32_t)"USD", 3 ))); { // same answer uint8_t ans[] = { 0x00U,0x72U,0x0EU,0xB8U,0x2AU,0xDDU,0x5EU,0x15U,0x59U,0x1BU, 0xF6U,0xE3U,0x6DU,0xBCU,0x3CU,0x12U,0xD3U,0x07U,0x6DU,0x43U, 0xA8U,0x53U,0xF8U,0xF9U,0xE8U,0xA7U,0xD8U,0x4FU,0xE1U,0xE9U, 0x7AU,0x2AU,0xC7U,0x3DU }; ASSERT_KL_EQ(ans); } // test invalid 3 byte code ASSERT(INVALID_ARGUMENT == util_keylet(buf, 34, KEYLET_LINE, SBUF(a), SBUF(b), (uint32_t)"`SD", 3)); ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_OFFER, SBUF(a), 1, 0,0,0 ))); { uint8_t ans[] = { 0x00U,0x6FU,0x60U,0x14U,0x48U,0x80U,0x97U,0x5FU,0x76U,0x6AU, 0xB2U,0x2CU,0x32U,0x2FU,0x10U,0x8EU,0x03U,0x43U,0x51U,0xDEU, 0x89U,0x6CU,0xF4U,0x9FU,0x6BU,0x4AU,0xC7U,0x2CU,0x54U,0xF7U, 0x27U,0x29U,0x9BU,0xE8U }; ASSERT_KL_EQ(ans); } // again with a uint256 ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_OFFER, SBUF(a), SBUF(ns), 0,0 ))); { uint8_t ans[] = { 0x00U,0x6FU,0x23U,0x61U,0x7FU,0x44U,0x91U,0x1CU,0xBAU,0x3BU, 0x5CU,0xBEU,0xE9U,0x42U,0x22U,0xACU,0xA4U,0x29U,0xF4U,0xD6U, 0x60U,0x01U,0xA8U,0xABU,0x9BU,0x98U,0x5EU,0xB8U,0xB8U,0x42U, 0x9FU,0x1EU,0x91U,0x4BU }; ASSERT_KL_EQ(ans); } // verify that quality returns invalid argument when passed // something that isn't a dir keylet klkey[0] = 0; klkey[1] = 0x65U; ASSERT(INVALID_ARGUMENT == (e=util_keylet(buf, 34, KEYLET_QUALITY, SBUF(klkey), 0,1, 0,0 ))); // now change it to a dir klkey[1] = 0x64U; ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_QUALITY, SBUF(klkey), 0,1, 0,0 ))); { uint8_t ans[] = { 0x00U,0x64U,0x2DU,0x0CU,0xB3U,0xCDU,0x60U,0xDAU,0x33U,0xB5U, 0xAAU,0x7FU,0xEAU,0x32U,0x1FU,0x11U,0x16U,0x63U,0xEAU,0xEDU, 0x32U,0x48U,0x1CU,0x6BU,0x70U,0x0EU,0x00U,0x00U,0x00U,0x00U, 0x00U,0x00U,0x00U,0x01U }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_EMITTED_DIR, 0,0,0,0,0,0 ))); { uint8_t ans[] = { 0x00U,0x64U,0xB4U,0xDEU,0x82U,0x30U,0x55U,0xD0U,0x0BU,0xC1U, 0x2CU,0xD7U,0x8FU,0xE1U,0xAAU,0xF7U,0x4EU,0xE6U,0x06U,0x21U, 0x95U,0xB2U,0x62U,0x9FU,0x49U,0xA2U,0x59U,0x15U,0xA3U,0x9CU, 0x64U,0xBEU,0x19U,0x00U }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_SIGNERS, SBUF(a), 0,0,0,0 ))); { uint8_t ans[] = { 0x00U,0x53U,0xDFU,0x8FU,0xF0U,0xCEU,0x41U,0x1AU,0x3BU,0x8FU, 0x1BU,0xB5U,0xBBU,0x32U,0x78U,0x17U,0x15U,0xD6U,0x77U,0x42U, 0xF5U,0xB5U,0x63U,0xB8U,0x77U,0xB3U,0x3BU,0x07U,0x76U,0xF6U, 0xF7U,0xBCU,0xDAU,0x1DU }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_CHECK, SBUF(a), 1, 0, 0,0 ))); { uint8_t ans[] = { 0x00U,0x43U,0x08U,0x1FU,0x26U,0xFFU,0x79U,0x1AU,0xF7U,0x54U, 0x26U,0xEDU,0xF9U,0xEBU,0x08U,0x44U,0x85U,0x28U,0x58U,0x2CU, 0xB1U,0xA4U,0xEFU,0x4FU,0xD0U,0xB4U,0x49U,0x9BU,0x76U,0x82U, 0xE7U,0x69U,0xA6U,0xB5U }; ASSERT_KL_EQ(ans); } // ans again with uint256 ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_CHECK, SBUF(a), SBUF(ns), 0,0 ))); { uint8_t ans[] = { 0x00U,0x43U,0x94U,0xE3U,0x6FU,0x0DU,0xD3U,0xEDU,0xC0U,0x2CU, 0x49U,0xA5U,0xAAU,0x0EU,0xCCU,0x49U,0x18U,0x39U,0x92U,0xABU, 0x57U,0xC3U,0x2DU,0x9EU,0x45U,0x51U,0x04U,0x78U,0x49U,0x49U, 0xD1U,0xE6U,0xD2U,0x01U }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_DEPOSIT_PREAUTH, SBUF(a), SBUF(b), 0,0 ))); { uint8_t ans[] = { 0x00U,0x70U,0x88U,0x90U,0x0FU,0x27U,0x66U,0x57U,0xBCU,0xC0U, 0x5DU,0xA1U,0x67U,0x40U,0xABU,0x9DU,0x33U,0x01U,0x8EU,0x45U, 0x71U,0x7BU,0x0EU,0xC4U,0x2EU,0x4DU,0x11U,0xBDU,0x6DU,0xBDU, 0x94U,0x03U,0x48U,0xE0U }; ASSERT_KL_EQ(ans); } klkey[0] = 0; klkey[1] = 0; ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_UNCHECKED, key, 32, 0,0,0,0 ))); ASSERT_KL_EQ(klkey); ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_OWNER_DIR, SBUF(a), 0,0,0,0 ))); { uint8_t ans[] = { 0x00U,0x64U,0xC8U,0x5EU,0x01U,0x29U,0x06U,0x7BU,0x75U,0xADU, 0x30U,0xB0U,0xAAU,0x1CU,0xC2U,0x5BU,0x0AU,0x82U,0xC7U,0xF9U, 0xAAU,0xBDU,0xEEU,0x05U,0xFFU,0x01U,0x66U,0x69U,0xEFU,0x9DU, 0x82U,0xDCU,0xECU,0x30U }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_PAGE, SBUF(ns), 0, 1, 0,0 ))); { uint8_t ans[] = { 0x00U,0x64U,0x61U,0xE6U,0x05U,0x1AU,0xB0U,0x49U,0x89U,0x2EU, 0x75U,0xC9U,0x3DU,0x67U,0xFBU,0x7AU,0x63U,0xF1U,0xEFU,0x56U, 0xDDU,0xAFU,0x3EU,0x6BU,0x43U,0x6FU,0x57U,0x6EU,0x8CU,0x01U, 0x81U,0x98U,0x2EU,0x48U }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_ESCROW, SBUF(a), 1, 0, 0,0 ))); { uint8_t ans[] = { 0x00U,0x75U,0x13U,0xEFU,0x04U,0xCDU,0x33U,0x6AU,0xADU,0xF6U, 0x3DU,0x0CU,0x7EU,0x05U,0x6CU,0x84U,0x9AU,0x7CU,0xF6U,0x72U, 0x5EU,0x99U,0xBCU,0x93U,0x80U,0x1EU,0xF5U,0x78U,0xA0U,0x32U, 0x72U,0x5BU,0x84U,0xFEU }; ASSERT_KL_EQ(ans); } // again with a uint256 ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_ESCROW, SBUF(a), SBUF(ns), 0,0 ))); { uint8_t ans[] = { 0x00U,0x75U,0xC1U,0xC6U,0xC5U,0x23U,0x74U,0x87U,0x12U,0x56U, 0xAAU,0x7AU,0x1FU,0xB3U,0x29U,0x7AU,0x0AU,0x55U,0x88U,0x7DU, 0x16U,0x6AU,0xCFU,0x85U,0x28U,0x59U,0x88U,0xC2U,0xDAU,0x81U, 0x7FU,0x03U,0x90U,0x43U }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_PAYCHAN, SBUF(a), SBUF(b), 1, 0 ))); { uint8_t ans[] = { 0x00U,0x78U,0xEDU,0x04U,0xCEU,0x27U,0x20U,0x21U,0x55U,0x2BU, 0xBFU,0xA1U,0xE5U,0xFFU,0xBBU,0x53U,0xB6U,0x45U,0xA2U,0xFFU, 0x8AU,0x44U,0x66U,0xD5U,0x76U,0x24U,0xB5U,0x71U,0xE6U,0x44U, 0x9EU,0xEBU,0xFCU,0x5AU }; ASSERT_KL_EQ(ans); } // again with uint256 ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_PAYCHAN, SBUF(a), SBUF(b), SBUF(ns) ))); { uint8_t ans[] = { 0x00U,0x78U,0x7DU,0xE1U,0x01U,0xF6U,0x2BU,0xB0U,0x55U,0x80U, 0xB9U,0xD6U,0xB0U,0x3FU,0x3BU,0xB0U,0x01U,0xBDU,0xE6U,0x9BU, 0x89U,0x0FU,0x8AU,0xCDU,0xBEU,0x71U,0x73U,0x5EU,0xC3U,0x63U, 0xF8U,0xC5U,0x4BU,0x9BU }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_EMITTED_TXN, ns, 32, 0,0,0,0 ))); { uint8_t ans[] = { 0x00U,0x45U,0xF3U,0x51U,0x2DU,0x1CU,0x80U,0xA3U,0xC0U,0xB1U, 0x46U,0x04U,0xE1U,0xADU,0xDBU,0x90U,0x1CU,0x66U,0x32U,0x10U, 0x08U,0xCCU,0xD0U,0xABU,0xD2U,0xDBU,0xBEU,0xC4U,0x08U,0xA6U, 0x0FU,0x6AU,0x62U,0xE9U }; ASSERT_KL_EQ(ans); } ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_NFT_OFFER, SBUF(a), 1, 0, 0,0 ))); ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_NFT_OFFER, SBUF(a), SBUF(ns), 0,0 ))); accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t util_raddr (uint32_t, uint32_t, uint32_t, uint32_t); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reserved ) { _g(1,1); { uint8_t raw[20] = { 0x6BU, 0x30U, 0xE2U, 0x94U, 0xF3U, 0x40U, 0x3FU, 0xF8U, 0x7CU, 0xEFU, 0x9EU, 0x72U, 0x21U, 0x7FU, 0xF7U, 0xEBU, 0x4AU, 0x6AU, 0x43U, 0xF4U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x77U && addr[ 2] == 0x6DU && addr[ 3] == 0x6DU && addr[ 4] == 0x31U && addr[ 5] == 0x33U && addr[ 6] == 0x70U && addr[ 7] == 0x37U && addr[ 8] == 0x56U && addr[ 9] == 0x67U && addr[10] == 0x36U && addr[11] == 0x4BU && addr[12] == 0x6DU && addr[13] == 0x6EU && addr[14] == 0x71U && addr[15] == 0x4BU && addr[16] == 0x52U && addr[17] == 0x77U && addr[18] == 0x44U && addr[19] == 0x7AU && addr[20] == 0x78U && addr[21] == 0x76U && addr[22] == 0x69U && addr[23] == 0x35U && addr[24] == 0x58U && addr[25] == 0x70U && addr[26] == 0x36U && addr[27] == 0x77U && addr[28] == 0x6EU && addr[29] == 0x48U && addr[30] == 0x4DU && addr[31] == 0x44U && addr[32] == 0x44U && addr[33] == 0x68U); } { uint8_t raw[20] = { 0xE4U, 0x0FU, 0xA3U, 0x4EU, 0x3EU, 0x66U, 0x15U, 0x36U, 0x64U, 0x89U, 0x4FU, 0xCBU, 0xFBU, 0xFCU, 0xFEU, 0x2DU, 0x2DU, 0x19U, 0x0DU, 0x69U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x4DU && addr[ 2] == 0x38U && addr[ 3] == 0x31U && addr[ 4] == 0x6FU && addr[ 5] == 0x48U && addr[ 6] == 0x77U && addr[ 7] == 0x68U && addr[ 8] == 0x37U && addr[ 9] == 0x35U && addr[10] == 0x39U && addr[11] == 0x34U && addr[12] == 0x6AU && addr[13] == 0x48U && addr[14] == 0x38U && addr[15] == 0x70U && addr[16] == 0x36U && addr[17] == 0x31U && addr[18] == 0x57U && addr[19] == 0x65U && addr[20] == 0x31U && addr[21] == 0x73U && addr[22] == 0x64U && addr[23] == 0x58U && addr[24] == 0x46U && addr[25] == 0x42U && addr[26] == 0x35U && addr[27] == 0x48U && addr[28] == 0x52U && addr[29] == 0x52U && addr[30] == 0x79U && addr[31] == 0x4BU && addr[32] == 0x76U && addr[33] == 0x4AU); } { uint8_t raw[20] = { 0x0CU, 0x90U, 0x4BU, 0x4FU, 0xA5U, 0x59U, 0xBFU, 0x10U, 0x6AU, 0xAEU, 0xB5U, 0x28U, 0x6CU, 0x94U, 0xBAU, 0x34U, 0x18U, 0xFDU, 0xF3U, 0x53U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x70U && addr[ 2] == 0x39U && addr[ 3] == 0x52U && addr[ 4] == 0x79U && addr[ 5] == 0x73U && addr[ 6] == 0x42U && addr[ 7] == 0x63U && addr[ 8] == 0x55U && addr[ 9] == 0x42U && addr[10] == 0x59U && addr[11] == 0x63U && addr[12] == 0x76U && addr[13] == 0x4AU && addr[14] == 0x4AU && addr[15] == 0x4BU && addr[16] == 0x38U && addr[17] == 0x54U && addr[18] == 0x48U && addr[19] == 0x45U && addr[20] == 0x79U && addr[21] == 0x6FU && addr[22] == 0x79U && addr[23] == 0x74U && addr[24] == 0x74U && addr[25] == 0x6BU && addr[26] == 0x57U && addr[27] == 0x58U && addr[28] == 0x39U && addr[29] == 0x4BU && addr[30] == 0x52U && addr[31] == 0x62U && addr[32] == 0x39U && addr[33] == 0x4DU); } { uint8_t raw[20] = { 0x75U, 0x82U, 0xFBU, 0x27U, 0x10U, 0x8CU, 0x0FU, 0x9AU, 0xF2U, 0x67U, 0x35U, 0xCCU, 0x7BU, 0x22U, 0x6BU, 0xD2U, 0x2FU, 0xDFU, 0x4FU, 0x92U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x42U && addr[ 2] == 0x35U && addr[ 3] == 0x4CU && addr[ 4] == 0x79U && addr[ 5] == 0x77U && addr[ 6] == 0x6BU && addr[ 7] == 0x54U && addr[ 8] == 0x4CU && addr[ 9] == 0x31U && addr[10] == 0x34U && addr[11] == 0x51U && addr[12] == 0x64U && addr[13] == 0x55U && addr[14] == 0x64U && addr[15] == 0x77U && addr[16] == 0x43U && addr[17] == 0x78U && addr[18] == 0x70U && addr[19] == 0x6EU && addr[20] == 0x65U && addr[21] == 0x46U && addr[22] == 0x32U && addr[23] == 0x7AU && addr[24] == 0x63U && addr[25] == 0x7AU && addr[26] == 0x46U && addr[27] == 0x66U && addr[28] == 0x44U && addr[29] == 0x7AU && addr[30] == 0x57U && addr[31] == 0x46U && addr[32] == 0x38U && addr[33] == 0x50U); } { uint8_t raw[20] = { 0x6CU, 0xB6U, 0x51U, 0x1FU, 0x20U, 0xECU, 0xCAU, 0x1EU, 0x98U, 0x03U, 0xFCU, 0xFAU, 0x6FU, 0x3EU, 0x56U, 0x75U, 0x72U, 0x29U, 0x51U, 0x97U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x77U && addr[ 2] == 0x75U && addr[ 3] == 0x46U && addr[ 4] == 0x50U && addr[ 5] == 0x4BU && addr[ 6] == 0x34U && addr[ 7] == 0x48U && addr[ 8] == 0x51U && addr[ 9] == 0x4EU && addr[10] == 0x73U && addr[11] == 0x59U && addr[12] == 0x42U && addr[13] == 0x47U && addr[14] == 0x74U && addr[15] == 0x46U && addr[16] == 0x52U && addr[17] == 0x4BU && addr[18] == 0x77U && addr[19] == 0x45U && addr[20] == 0x6DU && addr[21] == 0x75U && addr[22] == 0x41U && addr[23] == 0x68U && addr[24] == 0x63U && addr[25] == 0x4BU && addr[26] == 0x63U && addr[27] == 0x48U && addr[28] == 0x39U && addr[29] == 0x5AU && addr[30] == 0x32U && addr[31] == 0x59U && addr[32] == 0x7AU && addr[33] == 0x58U); } { uint8_t raw[20] = { 0xA5U, 0x31U, 0x30U, 0x28U, 0xF9U, 0x62U, 0xE4U, 0x80U, 0x48U, 0x94U, 0x3BU, 0x1AU, 0x59U, 0xBBU, 0x5EU, 0x36U, 0x96U, 0xB3U, 0x44U, 0x35U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x47U && addr[ 2] == 0x68U && addr[ 3] == 0x54U && addr[ 4] == 0x52U && addr[ 5] == 0x4AU && addr[ 6] == 0x5AU && addr[ 7] == 0x31U && addr[ 8] == 0x56U && addr[ 9] == 0x4DU && addr[10] == 0x51U && addr[11] == 0x74U && addr[12] == 0x36U && addr[13] == 0x6AU && addr[14] == 0x44U && addr[15] == 0x72U && addr[16] == 0x66U && addr[17] == 0x4EU && addr[18] == 0x63U && addr[19] == 0x6FU && addr[20] == 0x4AU && addr[21] == 0x34U && addr[22] == 0x39U && addr[23] == 0x6AU && addr[24] == 0x34U && addr[25] == 0x43U && addr[26] == 0x67U && addr[27] == 0x71U && addr[28] == 0x4BU && addr[29] == 0x6DU && addr[30] == 0x52U && addr[31] == 0x32U && addr[32] == 0x6FU && addr[33] == 0x36U); } { uint8_t raw[20] = { 0xBFU, 0x04U, 0x6CU, 0x79U, 0xA0U, 0x96U, 0xDEU, 0x80U, 0x66U, 0xD3U, 0x74U, 0xC8U, 0xDFU, 0x94U, 0x5FU, 0x89U, 0xF2U, 0x3EU, 0x9AU, 0x27U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x4AU && addr[ 2] == 0x52U && addr[ 3] == 0x72U && addr[ 4] == 0x34U && addr[ 5] == 0x72U && addr[ 6] == 0x4CU && addr[ 7] == 0x32U && addr[ 8] == 0x43U && addr[ 9] == 0x4AU && addr[10] == 0x48U && addr[11] == 0x67U && addr[12] == 0x46U && addr[13] == 0x47U && addr[14] == 0x56U && addr[15] == 0x67U && addr[16] == 0x31U && addr[17] == 0x6AU && addr[18] == 0x61U && addr[19] == 0x66U && addr[20] == 0x39U && addr[21] == 0x4AU && addr[22] == 0x48U && addr[23] == 0x51U && addr[24] == 0x70U && addr[25] == 0x56U && addr[26] == 0x6DU && addr[27] == 0x68U && addr[28] == 0x76U && addr[29] == 0x45U && addr[30] == 0x37U && addr[31] == 0x68U && addr[32] == 0x61U && addr[33] == 0x62U); } { uint8_t raw[20] = { 0xE2U, 0x07U, 0xABU, 0xD3U, 0x7DU, 0xC2U, 0xCDU, 0xD4U, 0x6DU, 0x15U, 0x7BU, 0x67U, 0x5AU, 0xC8U, 0x3EU, 0x0EU, 0x05U, 0x9BU, 0x08U, 0x62U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x4DU && addr[ 2] == 0x63U && addr[ 3] == 0x33U && addr[ 4] == 0x75U && addr[ 5] == 0x4DU && addr[ 6] == 0x6BU && addr[ 7] == 0x4BU && addr[ 8] == 0x31U && addr[ 9] == 0x62U && addr[10] == 0x62U && addr[11] == 0x32U && addr[12] == 0x64U && addr[13] == 0x4BU && addr[14] == 0x7AU && addr[15] == 0x5AU && addr[16] == 0x64U && addr[17] == 0x56U && addr[18] == 0x71U && addr[19] == 0x35U && addr[20] == 0x75U && addr[21] == 0x59U && addr[22] == 0x54U && addr[23] == 0x55U && addr[24] == 0x37U && addr[25] == 0x5AU && addr[26] == 0x76U && addr[27] == 0x4EU && addr[28] == 0x45U && addr[29] == 0x41U && addr[30] == 0x32U && addr[31] == 0x33U && addr[32] == 0x67U && addr[33] == 0x44U); } { uint8_t raw[20] = { 0x2AU, 0x56U, 0x74U, 0x25U, 0x84U, 0x8DU, 0x41U, 0x6DU, 0xF1U, 0x06U, 0x01U, 0x6CU, 0x2AU, 0xB1U, 0x13U, 0xC3U, 0x1EU, 0x65U, 0x63U, 0x80U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x68U && addr[ 2] == 0x69U && addr[ 3] == 0x69U && addr[ 4] == 0x41U && addr[ 5] == 0x78U && addr[ 6] == 0x79U && addr[ 7] == 0x59U && addr[ 8] == 0x41U && addr[ 9] == 0x43U && addr[10] == 0x67U && addr[11] == 0x45U && addr[12] == 0x52U && addr[13] == 0x4BU && addr[14] == 0x47U && addr[15] == 0x51U && addr[16] == 0x4DU && addr[17] == 0x72U && addr[18] == 0x53U && addr[19] == 0x5AU && addr[20] == 0x57U && addr[21] == 0x43U && addr[22] == 0x74U && addr[23] == 0x6BU && addr[24] == 0x4DU && addr[25] == 0x6FU && addr[26] == 0x69U && addr[27] == 0x58U && addr[28] == 0x48U && addr[29] == 0x34U && addr[30] == 0x64U && addr[31] == 0x48U && addr[32] == 0x6EU && addr[33] == 0x6FU); } { uint8_t raw[20] = { 0x24U, 0xBBU, 0xA9U, 0xC3U, 0x95U, 0x74U, 0x9AU, 0x88U, 0x04U, 0x12U, 0xC0U, 0x91U, 0xE7U, 0x13U, 0x41U, 0x7FU, 0x9AU, 0xD5U, 0x74U, 0x43U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x68U && addr[ 2] == 0x4DU && addr[ 3] == 0x4EU && addr[ 4] == 0x33U && addr[ 5] == 0x79U && addr[ 6] == 0x4EU && addr[ 7] == 0x50U && addr[ 8] == 0x4EU && addr[ 9] == 0x74U && addr[10] == 0x4BU && addr[11] == 0x70U && addr[12] == 0x78U && addr[13] == 0x6BU && addr[14] == 0x71U && addr[15] == 0x4CU && addr[16] == 0x78U && addr[17] == 0x51U && addr[18] == 0x32U && addr[19] == 0x63U && addr[20] == 0x33U && addr[21] == 0x55U && addr[22] == 0x68U && addr[23] == 0x6FU && addr[24] == 0x41U && addr[25] == 0x7AU && addr[26] == 0x66U && addr[27] == 0x75U && addr[28] == 0x59U && addr[29] == 0x35U && addr[30] == 0x75U && addr[31] == 0x35U && addr[32] == 0x4AU && addr[33] == 0x7AU); } { uint8_t raw[20] = { 0x49U, 0x53U, 0x9EU, 0x65U, 0x21U, 0x8AU, 0xCFU, 0x37U, 0x85U, 0x2BU, 0xFFU, 0x87U, 0x14U, 0x76U, 0xDAU, 0x1AU, 0x62U, 0x3AU, 0xEAU, 0x80U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x66U && addr[ 2] == 0x67U && addr[ 3] == 0x35U && addr[ 4] == 0x56U && addr[ 5] == 0x41U && addr[ 6] == 0x44U && addr[ 7] == 0x41U && addr[ 8] == 0x4DU && addr[ 9] == 0x4DU && addr[10] == 0x42U && addr[11] == 0x78U && addr[12] == 0x46U && addr[13] == 0x51U && addr[14] == 0x76U && addr[15] == 0x44U && addr[16] == 0x78U && addr[17] == 0x5AU && addr[18] == 0x54U && addr[19] == 0x32U && addr[20] == 0x52U && addr[21] == 0x6AU && addr[22] == 0x55U && addr[23] == 0x64U && addr[24] == 0x47U && addr[25] == 0x69U && addr[26] == 0x64U && addr[27] == 0x59U && addr[28] == 0x61U && addr[29] == 0x35U && addr[30] == 0x76U && addr[31] == 0x69U && addr[32] == 0x37U && addr[33] == 0x5AU); } { uint8_t raw[20] = { 0xE7U, 0xD3U, 0x03U, 0xBCU, 0xAEU, 0xBDU, 0x62U, 0x20U, 0xAEU, 0xC2U, 0xE1U, 0x7EU, 0x0BU, 0xFFU, 0xDCU, 0x21U, 0x24U, 0x34U, 0x50U, 0x82U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x34U && addr[ 2] == 0x33U && addr[ 3] == 0x6DU && addr[ 4] == 0x31U && addr[ 5] == 0x31U && addr[ 6] == 0x66U && addr[ 7] == 0x74U && addr[ 8] == 0x36U && addr[ 9] == 0x79U && addr[10] == 0x6FU && addr[11] == 0x50U && addr[12] == 0x69U && addr[13] == 0x6DU && addr[14] == 0x36U && addr[15] == 0x56U && addr[16] == 0x44U && addr[17] == 0x78U && addr[18] == 0x64U && addr[19] == 0x55U && addr[20] == 0x76U && addr[21] == 0x63U && addr[22] == 0x46U && addr[23] == 0x77U && addr[24] == 0x36U && addr[25] == 0x57U && addr[26] == 0x38U && addr[27] == 0x41U && addr[28] == 0x77U && addr[29] == 0x78U && addr[30] == 0x78U && addr[31] == 0x4BU && addr[32] == 0x35U && addr[33] == 0x58U); } { uint8_t raw[20] = { 0xC3U, 0xE1U, 0x5FU, 0xABU, 0xC0U, 0x0AU, 0x79U, 0x73U, 0x71U, 0xD0U, 0x55U, 0xC0U, 0x80U, 0x79U, 0xAEU, 0x45U, 0x71U, 0x0FU, 0xA0U, 0x97U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x4AU && addr[ 2] == 0x69U && addr[ 3] == 0x35U && addr[ 4] == 0x6BU && addr[ 5] == 0x65U && addr[ 6] == 0x58U && addr[ 7] == 0x79U && addr[ 8] == 0x33U && addr[ 9] == 0x31U && addr[10] == 0x4AU && addr[11] == 0x31U && addr[12] == 0x34U && addr[13] == 0x52U && addr[14] == 0x4BU && addr[15] == 0x73U && addr[16] == 0x4EU && addr[17] == 0x59U && addr[18] == 0x41U && addr[19] == 0x46U && addr[20] == 0x31U && addr[21] == 0x51U && addr[22] == 0x36U && addr[23] == 0x6AU && addr[24] == 0x4DU && addr[25] == 0x56U && addr[26] == 0x69U && addr[27] == 0x45U && addr[28] == 0x52U && addr[29] == 0x55U && addr[30] == 0x51U && addr[31] == 0x71U && addr[32] == 0x59U && addr[33] == 0x36U); } { uint8_t raw[20] = { 0x95U, 0x15U, 0x7FU, 0x2AU, 0xAFU, 0xE3U, 0x2FU, 0x7FU, 0x2EU, 0xF1U, 0xA0U, 0xF5U, 0xEAU, 0xC3U, 0x07U, 0x06U, 0xA1U, 0xD3U, 0xF5U, 0xD9U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x4EU && addr[ 2] == 0x62U && addr[ 3] == 0x48U && addr[ 4] == 0x53U && addr[ 5] == 0x55U && addr[ 6] == 0x6DU && addr[ 7] == 0x66U && addr[ 8] == 0x4BU && addr[ 9] == 0x61U && addr[10] == 0x34U && addr[11] == 0x71U && addr[12] == 0x31U && addr[13] == 0x51U && addr[14] == 0x78U && addr[15] == 0x44U && addr[16] == 0x45U && addr[17] == 0x5AU && addr[18] == 0x4CU && addr[19] == 0x6EU && addr[20] == 0x54U && addr[21] == 0x67U && addr[22] == 0x46U && addr[23] == 0x56U && addr[24] == 0x45U && addr[25] == 0x4CU && addr[26] == 0x78U && addr[27] == 0x39U && addr[28] == 0x6DU && addr[29] == 0x57U && addr[30] == 0x45U && addr[31] == 0x43U && addr[32] == 0x6BU && addr[33] == 0x41U); } { uint8_t raw[20] = { 0xF0U, 0xECU, 0x0FU, 0x86U, 0x31U, 0xBBU, 0x2CU, 0xBFU, 0x8FU, 0xB7U, 0xE3U, 0x1CU, 0x82U, 0xA0U, 0xA3U, 0x50U, 0xD5U, 0xE0U, 0xFEU, 0x6BU }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x34U && addr[ 2] == 0x78U && addr[ 3] == 0x31U && addr[ 4] == 0x78U && addr[ 5] == 0x46U && addr[ 6] == 0x32U && addr[ 7] == 0x42U && addr[ 8] == 0x47U && addr[ 9] == 0x73U && addr[10] == 0x42U && addr[11] == 0x41U && addr[12] == 0x7AU && addr[13] == 0x77U && addr[14] == 0x77U && addr[15] == 0x61U && addr[16] == 0x4BU && addr[17] == 0x61U && addr[18] == 0x70U && addr[19] == 0x4BU && addr[20] == 0x6FU && addr[21] == 0x6FU && addr[22] == 0x35U && addr[23] == 0x57U && addr[24] == 0x65U && addr[25] == 0x31U && addr[26] == 0x59U && addr[27] == 0x53U && addr[28] == 0x6EU && addr[29] == 0x52U && addr[30] == 0x50U && addr[31] == 0x57U && addr[32] == 0x75U && addr[33] == 0x39U); } { uint8_t raw[20] = { 0x8DU, 0xA4U, 0x7DU, 0xABU, 0xD1U, 0x19U, 0xDCU, 0xC4U, 0x45U, 0x5FU, 0xAAU, 0xE2U, 0x1CU, 0x39U, 0xCAU, 0x19U, 0x34U, 0xF1U, 0x86U, 0x16U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x44U && addr[ 2] == 0x75U && addr[ 3] == 0x41U && addr[ 4] == 0x4CU && addr[ 5] == 0x6EU && addr[ 6] == 0x52U && addr[ 7] == 0x79U && addr[ 8] == 0x76U && addr[ 9] == 0x77U && addr[10] == 0x38U && addr[11] == 0x36U && addr[12] == 0x43U && addr[13] == 0x63U && addr[14] == 0x55U && addr[15] == 0x5AU && addr[16] == 0x39U && addr[17] == 0x74U && addr[18] == 0x52U && addr[19] == 0x55U && addr[20] == 0x45U && addr[21] == 0x6DU && addr[22] == 0x35U && addr[23] == 0x43U && addr[24] == 0x61U && addr[25] == 0x65U && addr[26] == 0x50U && addr[27] == 0x46U && addr[28] == 0x66U && addr[29] == 0x33U && addr[30] == 0x74U && addr[31] == 0x36U && addr[32] == 0x61U && addr[33] == 0x31U); } { uint8_t raw[20] = { 0xA9U, 0x94U, 0x5AU, 0xE3U, 0x5AU, 0x43U, 0xADU, 0xBEU, 0xBAU, 0xA4U, 0x13U, 0x94U, 0xF5U, 0xDCU, 0x8FU, 0x3BU, 0x01U, 0x14U, 0xFFU, 0xFEU }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x47U && addr[ 2] == 0x54U && addr[ 3] == 0x65U && addr[ 4] == 0x76U && addr[ 5] == 0x4AU && addr[ 6] == 0x71U && addr[ 7] == 0x76U && addr[ 8] == 0x6BU && addr[ 9] == 0x5AU && addr[10] == 0x76U && addr[11] == 0x48U && addr[12] == 0x73U && addr[13] == 0x58U && addr[14] == 0x5AU && addr[15] == 0x71U && addr[16] == 0x55U && addr[17] == 0x78U && addr[18] == 0x43U && addr[19] == 0x48U && addr[20] == 0x4CU && addr[21] == 0x68U && addr[22] == 0x73U && addr[23] == 0x43U && addr[24] == 0x53U && addr[25] == 0x38U && addr[26] == 0x57U && addr[27] == 0x68U && addr[28] == 0x79U && addr[29] == 0x4DU && addr[30] == 0x74U && addr[31] == 0x7AU && addr[32] == 0x6EU && addr[33] == 0x5AU); } { uint8_t raw[20] = { 0xC1U, 0xE6U, 0x7FU, 0x17U, 0xD3U, 0x00U, 0x9BU, 0x80U, 0x6CU, 0x85U, 0x74U, 0x9CU, 0x80U, 0x40U, 0xAFU, 0x64U, 0xCEU, 0x09U, 0x7EU, 0x2EU }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x4AU && addr[ 2] == 0x67U && addr[ 3] == 0x45U && addr[ 4] == 0x59U && addr[ 5] == 0x55U && addr[ 6] == 0x37U && addr[ 7] == 0x36U && addr[ 8] == 0x45U && addr[ 9] == 0x55U && addr[10] == 0x34U && addr[11] == 0x59U && addr[12] == 0x41U && addr[13] == 0x5AU && addr[14] == 0x41U && addr[15] == 0x44U && addr[16] == 0x79U && addr[17] == 0x61U && addr[18] == 0x37U && addr[19] == 0x6BU && addr[20] == 0x37U && addr[21] == 0x62U && addr[22] == 0x71U && addr[23] == 0x38U && addr[24] == 0x4EU && addr[25] == 0x76U && addr[26] == 0x64U && addr[27] == 0x65U && addr[28] == 0x4BU && addr[29] == 0x41U && addr[30] == 0x48U && addr[31] == 0x69U && addr[32] == 0x32U && addr[33] == 0x50U); } { uint8_t raw[20] = { 0xD8U, 0x74U, 0xCFU, 0x61U, 0x0DU, 0x97U, 0xE4U, 0xABU, 0x76U, 0xA0U, 0x70U, 0x60U, 0xB7U, 0xC5U, 0x9CU, 0x9AU, 0x88U, 0x86U, 0x62U, 0xAAU }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x4CU && addr[ 2] == 0x6AU && addr[ 3] == 0x57U && addr[ 4] == 0x74U && addr[ 5] == 0x59U && addr[ 6] == 0x52U && addr[ 7] == 0x61U && addr[ 8] == 0x6EU && addr[ 9] == 0x61U && addr[10] == 0x6BU && addr[11] == 0x33U && addr[12] == 0x74U && addr[13] == 0x52U && addr[14] == 0x43U && addr[15] == 0x5AU && addr[16] == 0x42U && addr[17] == 0x69U && addr[18] == 0x61U && addr[19] == 0x38U && addr[20] == 0x64U && addr[21] == 0x33U && addr[22] == 0x70U && addr[23] == 0x7AU && addr[24] == 0x78U && addr[25] == 0x6BU && addr[26] == 0x6EU && addr[27] == 0x63U && addr[28] == 0x73U && addr[29] == 0x7AU && addr[30] == 0x6FU && addr[31] == 0x33U && addr[32] == 0x33U && addr[33] == 0x38U); } { uint8_t raw[20] = { 0x8EU, 0xADU, 0xB4U, 0xBBU, 0x71U, 0x2AU, 0x29U, 0x1BU, 0x53U, 0x43U, 0xE0U, 0x03U, 0x1FU, 0x97U, 0x6BU, 0x0DU, 0xA9U, 0xEDU, 0x39U, 0xC2U }; uint8_t addr[50]; ASSERT(34 == util_raddr((uint32_t)addr, sizeof(addr), raw, 20)); ASSERT( addr[ 0] == 0x72U && addr[ 1] == 0x4EU && addr[ 2] == 0x72U && addr[ 3] == 0x52U && addr[ 4] == 0x73U && addr[ 5] == 0x59U && addr[ 6] == 0x57U && addr[ 7] == 0x69U && addr[ 8] == 0x4AU && addr[ 9] == 0x53U && addr[10] == 0x64U && addr[11] == 0x39U && addr[12] == 0x47U && addr[13] == 0x4AU && addr[14] == 0x50U && addr[15] == 0x50U && addr[16] == 0x36U && addr[17] == 0x51U && addr[18] == 0x71U && addr[19] == 0x33U && addr[20] == 0x4AU && addr[21] == 0x61U && addr[22] == 0x44U && addr[23] == 0x43U && addr[24] == 0x37U && addr[25] == 0x53U && addr[26] == 0x48U && addr[27] == 0x61U && addr[28] == 0x57U && addr[29] == 0x66U && addr[30] == 0x68U && addr[31] == 0x32U && addr[32] == 0x33U && addr[33] == 0x4BU); } // Test out of bounds check ASSERT(util_raddr(1000000, 50, 0, 20) == OUT_OF_BOUNDS); ASSERT(util_raddr(0, 50, 10000000, 20) == OUT_OF_BOUNDS); uint8_t raw[20] = { 0x8EU, 0xADU, 0xB4U, 0xBBU, 0x71U, 0x2AU, 0x29U, 0x1BU, 0x53U, 0x43U, 0xE0U, 0x03U, 0x1FU, 0x97U, 0x6BU, 0x0DU, 0xA9U, 0xEDU, 0x39U, 0xC2U }; ASSERT(util_raddr(0, 30, raw, 20) == TOO_SMALL); accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t util_sha512h (uint32_t, uint32_t, uint32_t, uint32_t); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); int64_t hook(uint32_t reserved ) { _g(1,1); { uint8_t raw[20] = { 0x72U, 0x4EU, 0x36U, 0x53U, 0x59U, 0x77U, 0x72U, 0x32U, 0x64U, 0x54U, 0x56U, 0x43U, 0x7AU, 0x45U, 0x71U, 0x39U, 0x57U, 0x43U, 0x77U, 0x4AU }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x42U && hash[ 1] == 0x5CU && hash[ 2] == 0x4CU && hash[ 3] == 0x01U && hash[ 4] == 0x84U && hash[ 5] == 0xA5U && hash[ 6] == 0x76U && hash[ 7] == 0x79U && hash[ 8] == 0xDCU && hash[ 9] == 0x6DU && hash[10] == 0xFFU && hash[11] == 0x40U && hash[12] == 0x8CU && hash[13] == 0x29U && hash[14] == 0x06U && hash[15] == 0x6BU && hash[16] == 0x0FU && hash[17] == 0xB9U && hash[18] == 0xEAU && hash[19] == 0x34U); } { uint8_t raw[20] = { 0x72U, 0x4BU, 0x4BU, 0x75U, 0x52U, 0x36U, 0x36U, 0x46U, 0x62U, 0x38U, 0x33U, 0x76U, 0x35U, 0x71U, 0x79U, 0x41U, 0x34U, 0x48U, 0x67U, 0x6AU }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x36U && hash[ 1] == 0x2CU && hash[ 2] == 0x32U && hash[ 3] == 0x1DU && hash[ 4] == 0x8DU && hash[ 5] == 0xDDU && hash[ 6] == 0xAFU && hash[ 7] == 0x2DU && hash[ 8] == 0x3CU && hash[ 9] == 0xE6U && hash[10] == 0x94U && hash[11] == 0x12U && hash[12] == 0x20U && hash[13] == 0xDAU && hash[14] == 0x62U && hash[15] == 0xA6U && hash[16] == 0x98U && hash[17] == 0x41U && hash[18] == 0x04U && hash[19] == 0x5EU); } { uint8_t raw[20] = { 0x72U, 0x42U, 0x54U, 0x33U, 0x58U, 0x57U, 0x43U, 0x76U, 0x61U, 0x38U, 0x48U, 0x55U, 0x4EU, 0x4EU, 0x5AU, 0x46U, 0x6AU, 0x5AU, 0x43U, 0x55U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0xCFU && hash[ 1] == 0xFDU && hash[ 2] == 0x6FU && hash[ 3] == 0x01U && hash[ 4] == 0x95U && hash[ 5] == 0x76U && hash[ 6] == 0x7DU && hash[ 7] == 0xFBU && hash[ 8] == 0xCAU && hash[ 9] == 0x41U && hash[10] == 0xFDU && hash[11] == 0x24U && hash[12] == 0x23U && hash[13] == 0xD6U && hash[14] == 0x82U && hash[15] == 0x20U && hash[16] == 0x76U && hash[17] == 0xDDU && hash[18] == 0xC9U && hash[19] == 0xECU); } { uint8_t raw[20] = { 0x72U, 0x4CU, 0x52U, 0x4CU, 0x41U, 0x6EU, 0x61U, 0x62U, 0x56U, 0x6FU, 0x46U, 0x62U, 0x37U, 0x47U, 0x68U, 0x79U, 0x58U, 0x75U, 0x42U, 0x53U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x02U && hash[ 1] == 0xEBU && hash[ 2] == 0x2FU && hash[ 3] == 0x30U && hash[ 4] == 0xFCU && hash[ 5] == 0x73U && hash[ 6] == 0x34U && hash[ 7] == 0xE7U && hash[ 8] == 0x89U && hash[ 9] == 0xA2U && hash[10] == 0x58U && hash[11] == 0xD6U && hash[12] == 0xB0U && hash[13] == 0x55U && hash[14] == 0x32U && hash[15] == 0x96U && hash[16] == 0xB5U && hash[17] == 0x2EU && hash[18] == 0x97U && hash[19] == 0x81U); } { uint8_t raw[20] = { 0x72U, 0x4CU, 0x37U, 0x33U, 0x39U, 0x47U, 0x4BU, 0x35U, 0x75U, 0x36U, 0x79U, 0x78U, 0x76U, 0x43U, 0x73U, 0x6FU, 0x68U, 0x43U, 0x32U, 0x43U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x9FU && hash[ 1] == 0xD4U && hash[ 2] == 0x7CU && hash[ 3] == 0x25U && hash[ 4] == 0xDEU && hash[ 5] == 0x23U && hash[ 6] == 0x97U && hash[ 7] == 0x57U && hash[ 8] == 0xEDU && hash[ 9] == 0x25U && hash[10] == 0xD0U && hash[11] == 0x98U && hash[12] == 0xF7U && hash[13] == 0x83U && hash[14] == 0x70U && hash[15] == 0xF6U && hash[16] == 0x5FU && hash[17] == 0x3DU && hash[18] == 0xB5U && hash[19] == 0x43U); } { uint8_t raw[20] = { 0x72U, 0x4DU, 0x4DU, 0x45U, 0x57U, 0x74U, 0x75U, 0x4BU, 0x43U, 0x77U, 0x54U, 0x43U, 0x36U, 0x31U, 0x78U, 0x41U, 0x78U, 0x35U, 0x55U, 0x46U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x77U && hash[ 1] == 0x59U && hash[ 2] == 0x43U && hash[ 3] == 0x6BU && hash[ 4] == 0x4DU && hash[ 5] == 0x11U && hash[ 6] == 0x6BU && hash[ 7] == 0xE5U && hash[ 8] == 0xF8U && hash[ 9] == 0x90U && hash[10] == 0x07U && hash[11] == 0x00U && hash[12] == 0xB3U && hash[13] == 0xB2U && hash[14] == 0x6BU && hash[15] == 0x8AU && hash[16] == 0xC8U && hash[17] == 0xF2U && hash[18] == 0x82U && hash[19] == 0xB7U); } { uint8_t raw[20] = { 0x72U, 0x66U, 0x48U, 0x6AU, 0x66U, 0x31U, 0x6BU, 0x70U, 0x4BU, 0x6AU, 0x39U, 0x66U, 0x6AU, 0x39U, 0x35U, 0x58U, 0x6AU, 0x59U, 0x69U, 0x51U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0xBDU && hash[ 1] == 0x1BU && hash[ 2] == 0xDDU && hash[ 3] == 0x9DU && hash[ 4] == 0x10U && hash[ 5] == 0xDEU && hash[ 6] == 0x24U && hash[ 7] == 0xA1U && hash[ 8] == 0xB2U && hash[ 9] == 0x6CU && hash[10] == 0x24U && hash[11] == 0xBCU && hash[12] == 0xF9U && hash[13] == 0x97U && hash[14] == 0x50U && hash[15] == 0xDEU && hash[16] == 0x93U && hash[17] == 0x39U && hash[18] == 0x58U && hash[19] == 0x21U); } { uint8_t raw[20] = { 0x72U, 0x66U, 0x6EU, 0x75U, 0x57U, 0x38U, 0x77U, 0x6FU, 0x4BU, 0x62U, 0x6EU, 0x57U, 0x4BU, 0x6BU, 0x6BU, 0x75U, 0x39U, 0x6AU, 0x79U, 0x64U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x3BU && hash[ 1] == 0x89U && hash[ 2] == 0xEDU && hash[ 3] == 0x68U && hash[ 4] == 0x0DU && hash[ 5] == 0x13U && hash[ 6] == 0x3BU && hash[ 7] == 0x1DU && hash[ 8] == 0x43U && hash[ 9] == 0xFEU && hash[10] == 0xAEU && hash[11] == 0x3EU && hash[12] == 0xC3U && hash[13] == 0x90U && hash[14] == 0xE8U && hash[15] == 0x0EU && hash[16] == 0x17U && hash[17] == 0x14U && hash[18] == 0x23U && hash[19] == 0x71U); } { uint8_t raw[20] = { 0x72U, 0x70U, 0x79U, 0x64U, 0x52U, 0x39U, 0x55U, 0x32U, 0x67U, 0x66U, 0x75U, 0x6BU, 0x34U, 0x5AU, 0x72U, 0x53U, 0x66U, 0x48U, 0x61U, 0x71U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x2BU && hash[ 1] == 0x01U && hash[ 2] == 0x00U && hash[ 3] == 0x05U && hash[ 4] == 0xF1U && hash[ 5] == 0x60U && hash[ 6] == 0x71U && hash[ 7] == 0x62U && hash[ 8] == 0x7CU && hash[ 9] == 0x4AU && hash[10] == 0xCCU && hash[11] == 0x03U && hash[12] == 0x2AU && hash[13] == 0x89U && hash[14] == 0x40U && hash[15] == 0x5AU && hash[16] == 0x03U && hash[17] == 0xDCU && hash[18] == 0x83U && hash[19] == 0xC8U); } { uint8_t raw[20] = { 0x72U, 0x4CU, 0x4CU, 0x45U, 0x36U, 0x34U, 0x74U, 0x44U, 0x4CU, 0x78U, 0x59U, 0x37U, 0x47U, 0x6FU, 0x41U, 0x41U, 0x57U, 0x66U, 0x73U, 0x36U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0xDFU && hash[ 1] == 0xE3U && hash[ 2] == 0x14U && hash[ 3] == 0xF0U && hash[ 4] == 0x5FU && hash[ 5] == 0x95U && hash[ 6] == 0x8CU && hash[ 7] == 0x57U && hash[ 8] == 0x2FU && hash[ 9] == 0x9DU && hash[10] == 0x45U && hash[11] == 0xDCU && hash[12] == 0x12U && hash[13] == 0x77U && hash[14] == 0x39U && hash[15] == 0xACU && hash[16] == 0xEAU && hash[17] == 0x4AU && hash[18] == 0xB0U && hash[19] == 0x8FU); } { uint8_t raw[20] = { 0x72U, 0x50U, 0x64U, 0x50U, 0x58U, 0x77U, 0x76U, 0x75U, 0x39U, 0x4EU, 0x4CU, 0x59U, 0x46U, 0x50U, 0x34U, 0x69U, 0x56U, 0x64U, 0x56U, 0x70U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0xD1U && hash[ 1] == 0x9FU && hash[ 2] == 0x25U && hash[ 3] == 0x93U && hash[ 4] == 0xA3U && hash[ 5] == 0xCAU && hash[ 6] == 0xEAU && hash[ 7] == 0x10U && hash[ 8] == 0x06U && hash[ 9] == 0x78U && hash[10] == 0xFCU && hash[11] == 0x58U && hash[12] == 0xA4U && hash[13] == 0x99U && hash[14] == 0x3CU && hash[15] == 0x6EU && hash[16] == 0xC4U && hash[17] == 0x2DU && hash[18] == 0x6DU && hash[19] == 0x53U); } { uint8_t raw[20] = { 0x72U, 0x44U, 0x65U, 0x44U, 0x32U, 0x5AU, 0x71U, 0x53U, 0x48U, 0x35U, 0x44U, 0x70U, 0x51U, 0x4DU, 0x78U, 0x76U, 0x36U, 0x36U, 0x52U, 0x6BU }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x4DU && hash[ 1] == 0x5BU && hash[ 2] == 0xDDU && hash[ 3] == 0x31U && hash[ 4] == 0xDEU && hash[ 5] == 0xB9U && hash[ 6] == 0xF5U && hash[ 7] == 0xB8U && hash[ 8] == 0xBDU && hash[ 9] == 0x17U && hash[10] == 0xE1U && hash[11] == 0x51U && hash[12] == 0xAAU && hash[13] == 0x51U && hash[14] == 0x9CU && hash[15] == 0x5BU && hash[16] == 0xE0U && hash[17] == 0x15U && hash[18] == 0x61U && hash[19] == 0x2CU); } { uint8_t raw[20] = { 0x72U, 0x55U, 0x34U, 0x78U, 0x54U, 0x52U, 0x75U, 0x6FU, 0x32U, 0x34U, 0x62U, 0x52U, 0x6FU, 0x65U, 0x41U, 0x48U, 0x33U, 0x53U, 0x55U, 0x66U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0xBDU && hash[ 1] == 0xA1U && hash[ 2] == 0x62U && hash[ 3] == 0x1EU && hash[ 4] == 0x84U && hash[ 5] == 0x12U && hash[ 6] == 0xB3U && hash[ 7] == 0xCCU && hash[ 8] == 0x58U && hash[ 9] == 0x19U && hash[10] == 0x9AU && hash[11] == 0x22U && hash[12] == 0xCFU && hash[13] == 0x6AU && hash[14] == 0x0AU && hash[15] == 0x43U && hash[16] == 0xDEU && hash[17] == 0xB5U && hash[18] == 0xBAU && hash[19] == 0x50U); } { uint8_t raw[20] = { 0x72U, 0x6EU, 0x6DU, 0x6FU, 0x6AU, 0x57U, 0x46U, 0x6FU, 0x41U, 0x58U, 0x72U, 0x76U, 0x71U, 0x75U, 0x62U, 0x6FU, 0x45U, 0x77U, 0x4EU, 0x4EU }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x5FU && hash[ 1] == 0x26U && hash[ 2] == 0xF9U && hash[ 3] == 0x0AU && hash[ 4] == 0xC7U && hash[ 5] == 0xD5U && hash[ 6] == 0x40U && hash[ 7] == 0x2DU && hash[ 8] == 0x1FU && hash[ 9] == 0x9EU && hash[10] == 0x46U && hash[11] == 0xAAU && hash[12] == 0x6DU && hash[13] == 0x9CU && hash[14] == 0x64U && hash[15] == 0x88U && hash[16] == 0x87U && hash[17] == 0xF3U && hash[18] == 0x29U && hash[19] == 0x72U); } { uint8_t raw[20] = { 0x72U, 0x61U, 0x33U, 0x57U, 0x65U, 0x64U, 0x69U, 0x71U, 0x58U, 0x37U, 0x34U, 0x79U, 0x42U, 0x42U, 0x68U, 0x48U, 0x4CU, 0x44U, 0x51U, 0x4DU }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x25U && hash[ 1] == 0x70U && hash[ 2] == 0x5FU && hash[ 3] == 0x6DU && hash[ 4] == 0xA8U && hash[ 5] == 0x60U && hash[ 6] == 0x54U && hash[ 7] == 0xBAU && hash[ 8] == 0xD8U && hash[ 9] == 0x33U && hash[10] == 0x41U && hash[11] == 0x48U && hash[12] == 0x95U && hash[13] == 0x52U && hash[14] == 0xA6U && hash[15] == 0x22U && hash[16] == 0x9DU && hash[17] == 0x82U && hash[18] == 0xA0U && hash[19] == 0x87U); } { uint8_t raw[20] = { 0x72U, 0x45U, 0x47U, 0x57U, 0x33U, 0x6BU, 0x6FU, 0x34U, 0x41U, 0x31U, 0x69U, 0x50U, 0x43U, 0x5AU, 0x54U, 0x78U, 0x6DU, 0x77U, 0x6AU, 0x44U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0xD4U && hash[ 1] == 0xDAU && hash[ 2] == 0xE0U && hash[ 3] == 0xC7U && hash[ 4] == 0x40U && hash[ 5] == 0xC4U && hash[ 6] == 0x28U && hash[ 7] == 0x59U && hash[ 8] == 0xA9U && hash[ 9] == 0x6DU && hash[10] == 0x91U && hash[11] == 0xDCU && hash[12] == 0x34U && hash[13] == 0x0DU && hash[14] == 0xB9U && hash[15] == 0xE6U && hash[16] == 0xE9U && hash[17] == 0x9DU && hash[18] == 0x04U && hash[19] == 0x0BU); } { uint8_t raw[20] = { 0x72U, 0x68U, 0x52U, 0x46U, 0x71U, 0x54U, 0x35U, 0x45U, 0x39U, 0x7AU, 0x63U, 0x69U, 0x70U, 0x68U, 0x4CU, 0x54U, 0x39U, 0x78U, 0x6AU, 0x52U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x61U && hash[ 1] == 0x5BU && hash[ 2] == 0xFEU && hash[ 3] == 0x17U && hash[ 4] == 0x6EU && hash[ 5] == 0x81U && hash[ 6] == 0x42U && hash[ 7] == 0xFFU && hash[ 8] == 0xEEU && hash[ 9] == 0xD7U && hash[10] == 0x1AU && hash[11] == 0x6DU && hash[12] == 0x14U && hash[13] == 0x5DU && hash[14] == 0x64U && hash[15] == 0xA8U && hash[16] == 0x20U && hash[17] == 0x1AU && hash[18] == 0x33U && hash[19] == 0xC3U); } { uint8_t raw[20] = { 0x72U, 0x70U, 0x61U, 0x4AU, 0x69U, 0x34U, 0x4CU, 0x62U, 0x55U, 0x36U, 0x55U, 0x63U, 0x4AU, 0x45U, 0x78U, 0x62U, 0x38U, 0x39U, 0x35U, 0x5AU }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x01U && hash[ 1] == 0x61U && hash[ 2] == 0xA4U && hash[ 3] == 0x8EU && hash[ 4] == 0x6DU && hash[ 5] == 0x20U && hash[ 6] == 0xBAU && hash[ 7] == 0x20U && hash[ 8] == 0x72U && hash[ 9] == 0x72U && hash[10] == 0x8FU && hash[11] == 0x4FU && hash[12] == 0x3FU && hash[13] == 0xE1U && hash[14] == 0xE1U && hash[15] == 0xE7U && hash[16] == 0xEBU && hash[17] == 0x15U && hash[18] == 0xA8U && hash[19] == 0x4CU); } { uint8_t raw[20] = { 0x72U, 0x34U, 0x59U, 0x78U, 0x47U, 0x46U, 0x71U, 0x51U, 0x64U, 0x47U, 0x70U, 0x71U, 0x6EU, 0x4CU, 0x59U, 0x65U, 0x4DU, 0x38U, 0x56U, 0x52U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0x42U && hash[ 1] == 0xC5U && hash[ 2] == 0x2FU && hash[ 3] == 0x3BU && hash[ 4] == 0xB7U && hash[ 5] == 0xD4U && hash[ 6] == 0x54U && hash[ 7] == 0xB4U && hash[ 8] == 0x97U && hash[ 9] == 0xB4U && hash[10] == 0xFCU && hash[11] == 0xB0U && hash[12] == 0x46U && hash[13] == 0xBAU && hash[14] == 0xB6U && hash[15] == 0xADU && hash[16] == 0x93U && hash[17] == 0x8DU && hash[18] == 0xEBU && hash[19] == 0x7DU); } { uint8_t raw[20] = { 0x72U, 0x33U, 0x76U, 0x71U, 0x75U, 0x79U, 0x72U, 0x45U, 0x39U, 0x55U, 0x53U, 0x70U, 0x68U, 0x62U, 0x43U, 0x55U, 0x6DU, 0x65U, 0x4BU, 0x55U }; uint8_t hash[32]; ASSERT(32 == util_sha512h((uint32_t)hash, sizeof(hash), raw, 20)); ASSERT( hash[ 0] == 0xD5U && hash[ 1] == 0x6BU && hash[ 2] == 0x6BU && hash[ 3] == 0x45U && hash[ 4] == 0x30U && hash[ 5] == 0xF0U && hash[ 6] == 0x34U && hash[ 7] == 0x76U && hash[ 8] == 0x31U && hash[ 9] == 0x56U && hash[10] == 0x8CU && hash[11] == 0x38U && hash[12] == 0x0CU && hash[13] == 0x1AU && hash[14] == 0xAFU && hash[15] == 0xABU && hash[16] == 0x42U && hash[17] == 0x16U && hash[18] == 0x21U && hash[19] == 0x42U); } // Test out of bounds check ASSERT(util_sha512h(1000000, 50, 0, 20) == OUT_OF_BOUNDS); ASSERT(util_sha512h(0, 50, 10000000, 20) == OUT_OF_BOUNDS); uint8_t raw[20] = { 0x8EU, 0xADU, 0xB4U, 0xBBU, 0x71U, 0x2AU, 0x29U, 0x1BU, 0x53U, 0x43U, 0xE0U, 0x03U, 0x1FU, 0x97U, 0x6BU, 0x0DU, 0xA9U, 0xEDU, 0x39U, 0xC2U }; ASSERT(util_sha512h(0, 30, raw, 20) == TOO_SMALL); accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t util_verify (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); #define TOO_SMALL -4 #define OUT_OF_BOUNDS -1 #define INVALID_KEY -41 #define SBUF(x) ((uint32_t)(x)), sizeof(x) #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); // secp256k1 uint8_t pubkey_sec[] = { 0x02U,0xC7U,0x38U,0x7FU,0xFCU,0x25U,0xC1U,0x56U,0xCAU,0x7FU, 0x8AU,0x6DU,0x76U,0x0CU,0x8DU,0x01U,0xEFU,0x64U,0x2CU,0xEEU, 0x9CU,0xE4U,0x68U,0x0CU,0x33U,0xFFU,0xB3U,0xFFU,0x39U,0xAFU, 0xECU,0xFEU,0x70U }; uint8_t sig_sec[] = { 0x30U,0x45U,0x02U,0x21U,0x00U,0x95U,0x6EU,0x7DU,0x1FU,0x01U, 0x16U,0xF1U,0x65U,0x00U,0xD2U,0xCCU,0xD8U,0x8DU,0x2AU,0x2FU, 0xEFU,0xF6U,0x52U,0x16U,0x85U,0x42U,0xF4U,0x4EU,0x43U,0xDBU, 0xE6U,0xF4U,0x53U,0xE8U,0x03U,0xB8U,0x4FU,0x02U,0x20U,0x0AU, 0xB6U,0xC3U,0x4BU,0x5FU,0x0CU,0xC6U,0x6BU,0x4FU,0x1FU,0x83U, 0xE9U,0x89U,0x74U,0xB8U,0x80U,0xA2U,0x2FU,0xAEU,0x52U,0x91U, 0x6BU,0xA2U,0xCEU,0x96U,0xA3U,0x61U,0x05U,0x3FU,0xFFU,0x81U, 0xE9U }; // ed25519 uint8_t pubkey_ed[] = { 0xEDU,0xD9U,0xB3U,0x59U,0x98U,0x02U,0xB2U,0x14U,0xA9U,0x9DU, 0x75U,0x77U,0x12U,0xD6U,0xABU,0xDFU,0x72U,0xF8U,0x3CU,0x63U, 0xBBU,0xD5U,0x38U,0x61U,0x41U,0x17U,0x90U,0xB1U,0x3DU,0x04U, 0xB2U,0xC5U,0xC9U }; uint8_t sig_ed[] = { 0x56U,0x68U,0x80U,0x76U,0x70U,0xFEU,0xCEU,0x60U,0x34U,0xAFU, 0xD6U,0xCDU,0x1BU,0xB4U,0xC6U,0x60U,0xAEU,0x08U,0x39U,0x6DU, 0x6DU,0x8BU,0x7DU,0x22U,0x71U,0x3BU,0xDAU,0x26U,0x43U,0xC1U, 0xE1U,0x91U,0xC4U,0xE4U,0x4DU,0x8EU,0x02U,0xE8U,0x57U,0x8BU, 0x20U,0x45U,0xDAU,0xD4U,0x8FU,0x97U,0xFCU,0x16U,0xF8U,0x92U, 0x5BU,0x6BU,0x51U,0xFBU,0x3BU,0xE5U,0x0FU,0xB0U,0x4BU,0x3AU, 0x20U,0x4CU,0x53U,0x04U }; uint8_t msg[] = { 0xDEU,0xADU,0xBEU,0xEFU }; int64_t hook(uint32_t reserved ) { _g(1,1); // Test out of bounds check ASSERT(util_verify(1000000, 33, 0, 20, 0, 20) == OUT_OF_BOUNDS); ASSERT(util_verify(0, 33, 10000000, 20, 0, 20) == OUT_OF_BOUNDS); ASSERT(util_verify(0, 33, 0, 20, 10000000, 20) == OUT_OF_BOUNDS); ASSERT(util_verify(0, 1000000, 33, 1, 20, 30) == OUT_OF_BOUNDS); ASSERT(util_verify(0, 33, 0, 10000000, 20, 30) == OUT_OF_BOUNDS); ASSERT(util_verify(0, 33, 0, 2, 20, 10000000) == OUT_OF_BOUNDS); ASSERT(util_verify(0, 30, 0, 1, 0, 30) == INVALID_KEY); ASSERT(util_verify(0, 33, 0, 0, SBUF(pubkey_sec)) == TOO_SMALL); ASSERT(util_verify(0, 0, 0, 100, SBUF(pubkey_sec)) == TOO_SMALL); // test secp256k1 verification ASSERT(util_verify(SBUF(msg), SBUF(sig_sec), SBUF(pubkey_sec)) == 1); ASSERT(util_verify(msg + 1, sizeof(msg) - 1, SBUF(sig_sec), SBUF(pubkey_sec)) == 0); // test ed25519 verification ASSERT(util_verify(SBUF(msg), SBUF(sig_ed), SBUF(pubkey_ed)) == 1); ASSERT(util_verify(msg + 1, sizeof(msg) - 1, SBUF(sig_ed), SBUF(pubkey_ed)) == 0); accept(0,0,0); } )[test.hook]"]; // install the hook on alice env(ripple::test::jtx::hook(alice, {{hso(hook, 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 testHookCanEmit(FeatureBitset features) { testcase("test HookCanEmit"); using namespace jtx; Env env{*this, features}; auto const caller = Account{"caller"}; auto const alice = Account{"alice"}; auto const bob = Account{"bob"}; auto const charlie = Account{"charlie"}; auto const hookacc = Account{"hookacc"}; env.fund(XRP(10000), caller); env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); env.fund(XRP(10000), charlie); env.fund(XRP(10000), hookacc); env.close(); 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 hook_account(uint32_t, uint32_t); extern int64_t otxn_field(uint32_t, uint32_t, uint32_t); extern int64_t hook_pos(void); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) #define OUT_OF_BOUNDS (-1) #define ttPAYMENT 0 #define ttACCOUNT_SET 3 #define ttHOOK_SET 22 #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 sfAccount ((8U << 16U) + 1U) #define EMISSION_FAILURE -11 #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); #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 248U #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 116 */ \ int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \ _06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \ } #define PREPARE_ACCOUNT_SET_SIZE 207U #define PREPARE_ACCOUNT_SET(buf_out_master)\ {\ uint8_t* buf_out = buf_out_master;\ uint8_t acc[20];\ uint32_t cls = (uint32_t)ledger_seq();\ hook_account(SBUF(acc));\ _01_02_ENCODE_TT (buf_out, ttACCOUNT_SET ); /* uint16 | size 3 */ \ _02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \ _02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* 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 */ \ 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 */ \ int64_t edlen = etxn_details((uint32_t)buf_out, PREPARE_ACCOUNT_SET_SIZE); /* emitdet | size 116 */ \ int64_t fee = etxn_fee_base(buf_out_master, PREPARE_ACCOUNT_SET_SIZE); \ _06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \ } #define PREPARE_HOOK_SET_SIZE 211U #define PREPARE_HOOK_SET(buf_out_master)\ {\ uint8_t* buf_out = buf_out_master;\ uint8_t acc[20];\ uint32_t cls = (uint32_t)ledger_seq();\ hook_account(SBUF(acc));\ _01_02_ENCODE_TT (buf_out, ttHOOK_SET ); /* uint16 | size 3 */ \ _02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \ _02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* 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 */ \ buf_out[0] = 0xFBU;\ buf_out[1] = 0xEEU;\ buf_out[2] = 0xE1U;\ buf_out[3] = 0xF1U;\ buf_out += 4;\ 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 */ \ int64_t edlen = etxn_details((uint32_t)buf_out, PREPARE_HOOK_SET_SIZE); /* emitdet | size 116 */ \ int64_t fee = etxn_fee_base(buf_out_master, PREPARE_HOOK_SET_SIZE); \ _06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \ } int64_t hook(uint32_t reserved) { _g(1,1); etxn_reserve(3); int8_t otxn_acc[20]; ASSERT(otxn_field(SBUF(otxn_acc), sfAccount) == 20); uint8_t payment_tx[PREPARE_PAYMENT_SIMPLE_SIZE]; PREPARE_PAYMENT_SIMPLE(payment_tx, 1000, otxn_acc, 0, 0); uint8_t account_set_tx[PREPARE_ACCOUNT_SET_SIZE]; PREPARE_ACCOUNT_SET(account_set_tx); uint8_t hook_set_tx[PREPARE_HOOK_SET_SIZE]; PREPARE_HOOK_SET(hook_set_tx); uint8_t hash[32]; if (hook_pos() == 0) { // default (hookcanemit not set) ASSERT(emit(SBUF(hash), SBUF(payment_tx)) == 32); ASSERT(emit(SBUF(hash), SBUF(account_set_tx)) == 32); ASSERT(emit(SBUF(hash), SBUF(hook_set_tx)) == 32); return accept(0, 0, hook_pos()); } if (hook_pos() == 1) { // hookcanemit all low ASSERT(emit(SBUF(hash), SBUF(payment_tx)) == 32); ASSERT(emit(SBUF(hash), SBUF(account_set_tx)) == 32); ASSERT(emit(SBUF(hash), SBUF(hook_set_tx)) == EMISSION_FAILURE); return accept(0, 0, hook_pos()); } if (hook_pos() == 2) { // hookcanemit all high ASSERT(emit(SBUF(hash), SBUF(payment_tx)) == EMISSION_FAILURE); ASSERT(emit(SBUF(hash), SBUF(account_set_tx)) == EMISSION_FAILURE); ASSERT(emit(SBUF(hash), SBUF(hook_set_tx)) == 32); return accept(0, 0, hook_pos()); } } )[test.hook]"]; bool const hasFeature = env.current()->rules().enabled(featureHookCanEmit); Json::Value jv; jv[jss::CreateCode] = ""; jv[jss::Flags] = hsfOVERRIDE; auto const deleteHook = [&env](Account const& account) { Json::Value jv; jv[jss::Account] = account.human(); jv[jss::TransactionType] = jss::SetHook; jv[jss::Flags] = 0; jv[jss::Hooks] = Json::Value{Json::arrayValue}; Json::Value iv; iv[jss::CreateCode] = ""; iv[jss::Flags] = hsfOVERRIDE; jv[jss::Hooks][0U][jss::Hook] = iv; jv[jss::Hooks][1U][jss::Hook] = iv; jv[jss::Hooks][2U][jss::Hook] = iv; env(jv, M("hook DELETE"), HSFEE); env.close(); }; std::array accounts{{alice, bob, charlie}}; for (int i = 0; i < 2; i++) { auto const acc = accounts[i]; // i=0: Create // i=1: Install // i=2: Update if (i == 1) { Json::Value h = hso(hook, overrideFlag); env(ripple::test::jtx::hook(hookacc, {{h}}, 0), M("set hookcanemit"), HSFEE); env.close(); } else if (i == 2) { Json::Value h = hso(hook, overrideFlag); env(ripple::test::jtx::hook(acc, {{h}}, 0), M("set hookcanemit"), HSFEE); env.close(); } { Json::Value h = hso(hook, overrideFlag); env(ripple::test::jtx::hook(acc, {{h}}, 0), M("set hookcanemit"), HSFEE); env.close(); // invoke the hook env(pay(caller, acc, XRP(1)), M("test hookcanemit 1"), 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() == 1); // get the data in the return code of the execution BEAST_EXPECT( hookExecutions[0].getFieldU64(sfHookReturnCode) == 0); if (i == 0 || i == 1) deleteHook(acc); } { // same result with no-HookCanEmit Json::Value h = hso(hook, overrideFlag); h[jss::HookCanEmit] = "0000000000000000000000000000000000000000000000000000000000" "400000"; env(ripple::test::jtx::hook(acc, {{h}}, 0), M("set hookcanemit"), HSFEE, hasFeature ? ter(tesSUCCESS) : ter(temDISABLED)); env.close(); if (hasFeature) { // invoke the hook env(pay(caller, acc, XRP(1)), M("test hookcanemit 1"), 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() == 1); // get the data in the return code of the execution BEAST_EXPECT( hookExecutions[0].getFieldU64(sfHookReturnCode) == 0); if (i == 0 || i == 1) deleteHook(acc); } } { // install the hook on acc Json::Value hookCanEmitHook = hso(hook, overrideFlag); hookCanEmitHook[jss::HookCanEmit] = "00000000000000000000000000000000000000000000000000" "00000000000000"; env(ripple::test::jtx::hook(acc, {{jv, hookCanEmitHook}}, 0), M("test hookcanemit"), HSFEE, hasFeature ? ter(tesSUCCESS) : ter(temDISABLED)); env.close(); if (!hasFeature) continue; // invoke the hook env(pay(caller, acc, XRP(1)), M("test hookcanemit 2"), 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() == 1); // get the data in the return code of the execution BEAST_EXPECT( hookExecutions[0].getFieldU64(sfHookReturnCode) == 1); if (i == 0 || i == 1) deleteHook(acc); } { // install the hook on acc Json::Value hookCanEmitHook = hso(hook, overrideFlag); hookCanEmitHook[jss::HookCanEmit] = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" "FFFFFFFFFFFFFF"; env(ripple::test::jtx::hook( acc, {{jv, jv, hookCanEmitHook}}, 0), M("test hookcanemit 3"), HSFEE); env.close(); // invoke the hook env(pay(caller, acc, XRP(1)), M("test hookcanemit 3"), 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() == 1); // get the data in the return code of the execution BEAST_EXPECT( hookExecutions[0].getFieldU64(sfHookReturnCode) == 2); if (i == 0 || i == 1) deleteHook(acc); } } } void testWithFeatures(FeatureBitset features) { testHooksOwnerDir(features); testHooksDisabled(features); testTxStructure(features); testInvalidTxFlags(features); testInferHookSetOperation(); testParams(features); testGrants(features); testHookCanEmit(features); testDelete(features); testInstall(features); testCreate(features); testWithTickets(features); testUpdate(features); testNSDelete(features); testNSDeletePartial(features); testPageCap(features); testFillCopy(features); testWasm(features); test_accept(features); test_rollback(features); testGuards(features); 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_xpop_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); // } public: void run(std::uint32_t instance, bool last = false) { using namespace test::jtx; static FeatureBitset const all{supported_amendments()}; static std::array const feats{ all, all - fixXahauV2, all - fixXahauV1 - fixXahauV2, all - fixXahauV1 - fixXahauV2 - fixNSDelete, all - fixXahauV1 - fixXahauV2 - fixNSDelete - fixPageCap, all - fixXahauV1 - fixXahauV2 - fixNSDelete - fixPageCap - featureHookCanEmit, all - fixXahauV1 - fixXahauV2 - fixNSDelete - fixPageCap - featureExtendedHookState, }; if (BEAST_EXPECT(instance < feats.size())) { testWithFeatures(feats[instance]); } BEAST_EXPECT(!last || instance == feats.size() - 1); } void run() override { run(0); } private: TestHook accept_wasm = // WASM: 0 wasm[ R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); int64_t hook(uint32_t reserved ) { _g(1,1); return accept(0,0,0); } )[test.hook]"]; HASH_WASM(accept); TestHook rollback_wasm = // WASM: 1 wasm[ R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); #define SBUF(x) (uint32_t)(x),sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); return rollback(SBUF("Hook Rejected"),0); } )[test.hook]"]; HASH_WASM(rollback); TestHook noguard_wasm = // WASM: 2 wasm[ R"[test.hook]( (module (type (;0;) (func (param i32 i32 i64) (result i64))) (type (;1;) (func (param i32) (result i64))) (import "env" "accept" (func (;0;) (type 0))) (func (;1;) (type 1) (param i32) (result i64) i32.const 0 i32.const 0 i64.const 0 call 0) (memory (;0;) 2) (export "memory" (memory 0)) (export "hook" (func 1))) )[test.hook]"]; TestHook illegalfunc_wasm = // WASM: 3 wasm[ R"[test.hook]( (module (type (;0;) (func (param i32 i32) (result i32))) (type (;1;) (func (param i32 i32 i64) (result i64))) (type (;2;) (func)) (type (;3;) (func (param i32) (result i64))) (import "env" "_g" (func (;0;) (type 0))) (import "env" "accept" (func (;1;) (type 1))) (func (;2;) (type 3) (param i32) (result i64) i32.const 1 i32.const 1 call 0 drop i32.const 0 i32.const 0 i64.const 0 call 1) (func (;3;) (type 2) i32.const 1 i32.const 1 call 0 drop) (memory (;0;) 2) (global (;0;) (mut i32) (i32.const 66560)) (global (;1;) i32 (i32.const 1024)) (global (;2;) i32 (i32.const 1024)) (global (;3;) i32 (i32.const 66560)) (global (;4;) i32 (i32.const 1024)) (export "memory" (memory 0)) (export "hook" (func 2)) (export "bad_func" (func 3))) )[test.hook]"]; TestHook long_wasm = // WASM: 4 wasm[ R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); #define SBUF(x) (uint32_t)(x), sizeof(x) #define M_REPEAT_10(X) X X X X X X X X X X #define M_REPEAT_100(X) M_REPEAT_10(M_REPEAT_10(X)) #define M_REPEAT_1000(X) M_REPEAT_100(M_REPEAT_10(X)) int64_t hook(uint32_t reserved ) { _g(1,1); char ret[] = M_REPEAT_1000("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz01234567890123"); return accept(SBUF(ret), 0); } )[test.hook]"]; TestHook makestate_wasm = // WASM: 5 wasm[ R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t state_set (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len); #define SBUF(x) x, sizeof(x) int64_t hook(uint32_t reserved ) { _g(1,1); uint8_t test_key[] = "key"; uint8_t test_value[] = "value"; return accept(0,0, state_set(SBUF(test_value), SBUF(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 wasm[ R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); int64_t hook(uint32_t reserved ) { _g(1,1); return accept(0,0,2); } )[test.hook]"]; HASH_WASM(accept2); }; #define SETHOOK_TEST(i, last) \ class SetHook##i##_test : public SetHook0_test \ { \ void \ run() override \ { \ SetHook0_test::run(i, last); \ } \ }; SETHOOK_TEST(1, false) SETHOOK_TEST(2, false) SETHOOK_TEST(3, false) SETHOOK_TEST(4, false) SETHOOK_TEST(5, false) SETHOOK_TEST(6, true) BEAST_DEFINE_TESTSUITE_PRIO(SetHook0, app, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(SetHook1, app, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(SetHook2, app, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(SetHook3, app, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(SetHook4, app, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(SetHook5, app, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(SetHook6, app, ripple, 2); } // namespace test } // namespace ripple #undef M