From 27f43ba9ee65fd60b159941fa30a34b71124996f Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 3 Dec 2023 10:13:05 +0100 Subject: [PATCH] TSH Tests (#222) --- Builds/CMake/RippledCore.cmake | 2 + src/test/app/Import_test.cpp | 67 - src/test/app/SetHookTSH_test.cpp | 4815 +++++++++++++++++++++++ src/test/consensus/NegativeUNL_test.cpp | 252 +- src/test/consensus/UNLReport_test.cpp | 184 +- src/test/jtx.h | 1 + src/test/jtx/impl/unl.cpp | 95 + src/test/jtx/unl.h | 86 + 8 files changed, 5099 insertions(+), 403 deletions(-) create mode 100644 src/test/app/SetHookTSH_test.cpp create mode 100644 src/test/jtx/impl/unl.cpp create mode 100644 src/test/jtx/unl.h diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 63311c0b198..8e6ed576548 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -755,6 +755,7 @@ if (tests) src/test/app/ValidatorList_test.cpp src/test/app/ValidatorSite_test.cpp src/test/app/SetHook_test.cpp + src/test/app/SetHookTSH_test.cpp src/test/app/Wildcard_test.cpp src/test/app/XahauGenesis_test.cpp src/test/app/tx/apply_test.cpp @@ -896,6 +897,7 @@ if (tests) src/test/jtx/impl/token.cpp src/test/jtx/impl/trust.cpp src/test/jtx/impl/txflags.cpp + src/test/jtx/impl/unl.cpp src/test/jtx/impl/uritoken.cpp src/test/jtx/impl/utility.cpp diff --git a/src/test/app/Import_test.cpp b/src/test/app/Import_test.cpp index 27496d1d4b9..b8020bd1e8e 100644 --- a/src/test/app/Import_test.cpp +++ b/src/test/app/Import_test.cpp @@ -5549,73 +5549,6 @@ class Import_test : public beast::unit_test::suite } } - // std::unique_ptr - // network::makeGenesisConfig( - // FeatureBitset features, - // uint32_t networkID, - // std::string fee, - // std::string a_res, - // std::string o_res, - // uint32_t ledgerID) - // { - // using namespace jtx; - - // // IMPORT VL KEY - // std::vector const keys = { - // "ED74D4036C6591A4BDF9C54CEFA39B996A" - // "5DCE5F86D11FDA1874481CE9D5A1CDC1"}; - - // Json::Value jsonValue; - // Json::Reader reader; - // reader.parse(ImportTCHalving::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; - - // for (auto const& strPk : keys) - // { - // auto pkHex = strUnHex(strPk); - // if (!pkHex) - // Throw( - // "Import VL Key '" + strPk + "' was not valid hex."); - - // auto const pkType = publicKeyType(makeSlice(*pkHex)); - // if (!pkType) - // Throw( - // "Import VL Key '" + strPk + - // "' was not a valid key type."); - - // cfg->IMPORT_VL_KEYS.emplace(strPk, makeSlice(*pkHex)); - // } - // return cfg; - // }); - // } - void testHalving(FeatureBitset features) { diff --git a/src/test/app/SetHookTSH_test.cpp b/src/test/app/SetHookTSH_test.cpp new file mode 100644 index 00000000000..2d99a81ae4a --- /dev/null +++ b/src/test/app/SetHookTSH_test.cpp @@ -0,0 +1,4815 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 XRPL-Labs + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS S + OFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +struct SetHookTSH_test : public beast::unit_test::suite +{ +private: + // helper + void static overrideFlag(Json::Value& jv) + { + jv[jss::Flags] = hsfOVERRIDE; + } + void static collectFlag(Json::Value& jv) + { + jv[jss::Flags] = hsfOVERRIDE | hsfCOLLECT; + } + + const std::vector TshHook = { + 0x00U, 0x61U, 0x73U, 0x6dU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x1cU, + 0x04U, 0x60U, 0x05U, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x7fU, 0x01U, 0x7eU, + 0x60U, 0x03U, 0x7fU, 0x7fU, 0x7eU, 0x01U, 0x7eU, 0x60U, 0x02U, 0x7fU, + 0x7fU, 0x01U, 0x7fU, 0x60U, 0x01U, 0x7fU, 0x01U, 0x7eU, 0x02U, 0x23U, + 0x03U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x05U, 0x74U, 0x72U, 0x61U, 0x63U, + 0x65U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6eU, 0x76U, 0x06U, 0x61U, 0x63U, + 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6eU, 0x76U, + 0x02U, 0x5fU, 0x67U, 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, + 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x2bU, 0x07U, 0x7fU, 0x01U, 0x41U, + 0xc0U, 0x8bU, 0x04U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, + 0x7fU, 0x00U, 0x41U, 0xb8U, 0x0bU, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x80U, + 0x08U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0xc0U, 0x8bU, 0x04U, 0x0bU, 0x7fU, + 0x00U, 0x41U, 0x00U, 0x0bU, 0x7fU, 0x00U, 0x41U, 0x01U, 0x0bU, 0x07U, + 0x08U, 0x01U, 0x04U, 0x68U, 0x6fU, 0x6fU, 0x6bU, 0x00U, 0x03U, 0x0aU, + 0xf3U, 0x81U, 0x00U, 0x01U, 0xefU, 0x81U, 0x00U, 0x01U, 0x01U, 0x7fU, + 0x23U, 0x00U, 0x41U, 0x10U, 0x6bU, 0x22U, 0x01U, 0x24U, 0x00U, 0x20U, + 0x01U, 0x20U, 0x00U, 0x36U, 0x02U, 0x0cU, 0x41U, 0x9aU, 0x0bU, 0x41U, + 0x0fU, 0x41U, 0xbdU, 0x09U, 0x41U, 0x0eU, 0x41U, 0x00U, 0x10U, 0x00U, + 0x1aU, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x20U, + 0x01U, 0x28U, 0x02U, 0x0cU, 0x0eU, 0x03U, 0x00U, 0x01U, 0x02U, 0x03U, + 0x0bU, 0x41U, 0xd9U, 0x0aU, 0x41U, 0xc0U, 0x00U, 0x41U, 0xfeU, 0x08U, + 0x41U, 0x3fU, 0x41U, 0x00U, 0x10U, 0x00U, 0x1aU, 0x0cU, 0x02U, 0x0bU, + 0x41U, 0x9bU, 0x0aU, 0x41U, 0x3dU, 0x41U, 0xc2U, 0x08U, 0x41U, 0x3cU, + 0x41U, 0x00U, 0x10U, 0x00U, 0x1aU, 0x0cU, 0x01U, 0x0bU, 0x41U, 0xd7U, + 0x09U, 0x41U, 0xc3U, 0x00U, 0x41U, 0x80U, 0x08U, 0x41U, 0xc2U, 0x00U, + 0x41U, 0x00U, 0x10U, 0x00U, 0x1aU, 0x0bU, 0x41U, 0xaaU, 0x0bU, 0x41U, + 0x0dU, 0x41U, 0xcbU, 0x09U, 0x41U, 0x0cU, 0x41U, 0x00U, 0x10U, 0x00U, + 0x1aU, 0x20U, 0x01U, 0x20U, 0x01U, 0x41U, 0x08U, 0x6aU, 0x22U, 0x00U, + 0x36U, 0x02U, 0x04U, 0x20U, 0x01U, 0x28U, 0x02U, 0x04U, 0x20U, 0x01U, + 0x35U, 0x02U, 0x0cU, 0x42U, 0x18U, 0x88U, 0x42U, 0xffU, 0x01U, 0x83U, + 0x3cU, 0x00U, 0x00U, 0x20U, 0x01U, 0x28U, 0x02U, 0x04U, 0x20U, 0x01U, + 0x35U, 0x02U, 0x0cU, 0x42U, 0x10U, 0x88U, 0x42U, 0xffU, 0x01U, 0x83U, + 0x3cU, 0x00U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U, 0x04U, 0x20U, 0x01U, + 0x35U, 0x02U, 0x0cU, 0x42U, 0x08U, 0x88U, 0x42U, 0xffU, 0x01U, 0x83U, + 0x3cU, 0x00U, 0x02U, 0x20U, 0x01U, 0x28U, 0x02U, 0x04U, 0x20U, 0x01U, + 0x35U, 0x02U, 0x0cU, 0x42U, 0xffU, 0x01U, 0x83U, 0x3cU, 0x00U, 0x03U, + 0x20U, 0x00U, 0x41U, 0x04U, 0x42U, 0x18U, 0x10U, 0x01U, 0x1aU, 0x41U, + 0x01U, 0x41U, 0x01U, 0x10U, 0x02U, 0x1aU, 0x20U, 0x01U, 0x41U, 0x10U, + 0x6aU, 0x24U, 0x00U, 0x42U, 0x00U, 0x0bU, 0x0bU, 0xbfU, 0x03U, 0x01U, + 0x00U, 0x41U, 0x80U, 0x08U, 0x0bU, 0xb7U, 0x03U, 0x74U, 0x73U, 0x68U, + 0x2eU, 0x63U, 0x3aU, 0x20U, 0x57U, 0x65U, 0x61U, 0x6bU, 0x20U, 0x41U, + 0x67U, 0x61U, 0x69U, 0x6eU, 0x2eU, 0x20U, 0x45U, 0x78U, 0x65U, 0x63U, + 0x75U, 0x74U, 0x65U, 0x20U, 0x41U, 0x46U, 0x54U, 0x45U, 0x52U, 0x20U, + 0x74U, 0x72U, 0x61U, 0x6eU, 0x73U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6fU, + 0x6eU, 0x20U, 0x69U, 0x73U, 0x20U, 0x61U, 0x70U, 0x70U, 0x6cU, 0x69U, + 0x65U, 0x64U, 0x20U, 0x74U, 0x6fU, 0x20U, 0x6cU, 0x65U, 0x64U, 0x67U, + 0x65U, 0x72U, 0x00U, 0x74U, 0x73U, 0x68U, 0x2eU, 0x63U, 0x3aU, 0x20U, + 0x57U, 0x65U, 0x61U, 0x6bU, 0x2eU, 0x20U, 0x45U, 0x78U, 0x65U, 0x63U, + 0x75U, 0x74U, 0x65U, 0x20U, 0x41U, 0x46U, 0x54U, 0x45U, 0x52U, 0x20U, + 0x74U, 0x72U, 0x61U, 0x6eU, 0x73U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6fU, + 0x6eU, 0x20U, 0x69U, 0x73U, 0x20U, 0x61U, 0x70U, 0x70U, 0x6cU, 0x69U, + 0x65U, 0x64U, 0x20U, 0x74U, 0x6fU, 0x20U, 0x6cU, 0x65U, 0x64U, 0x67U, + 0x65U, 0x72U, 0x00U, 0x74U, 0x73U, 0x68U, 0x2eU, 0x63U, 0x3aU, 0x20U, + 0x53U, 0x74U, 0x72U, 0x6fU, 0x6eU, 0x67U, 0x2eU, 0x20U, 0x45U, 0x78U, + 0x65U, 0x63U, 0x75U, 0x74U, 0x65U, 0x20U, 0x42U, 0x45U, 0x46U, 0x4fU, + 0x52U, 0x45U, 0x20U, 0x74U, 0x72U, 0x61U, 0x6eU, 0x73U, 0x61U, 0x63U, + 0x74U, 0x69U, 0x6fU, 0x6eU, 0x20U, 0x69U, 0x73U, 0x20U, 0x61U, 0x70U, + 0x70U, 0x6cU, 0x69U, 0x65U, 0x64U, 0x20U, 0x74U, 0x6fU, 0x20U, 0x6cU, + 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x00U, 0x74U, 0x73U, 0x68U, 0x2eU, + 0x63U, 0x3aU, 0x20U, 0x53U, 0x74U, 0x61U, 0x72U, 0x74U, 0x2eU, 0x00U, + 0x74U, 0x73U, 0x68U, 0x2eU, 0x63U, 0x3aU, 0x20U, 0x45U, 0x6eU, 0x64U, + 0x2eU, 0x00U, 0x22U, 0x74U, 0x73U, 0x68U, 0x2eU, 0x63U, 0x3aU, 0x20U, + 0x57U, 0x65U, 0x61U, 0x6bU, 0x20U, 0x41U, 0x67U, 0x61U, 0x69U, 0x6eU, + 0x2eU, 0x20U, 0x45U, 0x78U, 0x65U, 0x63U, 0x75U, 0x74U, 0x65U, 0x20U, + 0x41U, 0x46U, 0x54U, 0x45U, 0x52U, 0x20U, 0x74U, 0x72U, 0x61U, 0x6eU, + 0x73U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6fU, 0x6eU, 0x20U, 0x69U, 0x73U, + 0x20U, 0x61U, 0x70U, 0x70U, 0x6cU, 0x69U, 0x65U, 0x64U, 0x20U, 0x74U, + 0x6fU, 0x20U, 0x6cU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x22U, 0x00U, + 0x22U, 0x74U, 0x73U, 0x68U, 0x2eU, 0x63U, 0x3aU, 0x20U, 0x57U, 0x65U, + 0x61U, 0x6bU, 0x2eU, 0x20U, 0x45U, 0x78U, 0x65U, 0x63U, 0x75U, 0x74U, + 0x65U, 0x20U, 0x41U, 0x46U, 0x54U, 0x45U, 0x52U, 0x20U, 0x74U, 0x72U, + 0x61U, 0x6eU, 0x73U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6fU, 0x6eU, 0x20U, + 0x69U, 0x73U, 0x20U, 0x61U, 0x70U, 0x70U, 0x6cU, 0x69U, 0x65U, 0x64U, + 0x20U, 0x74U, 0x6fU, 0x20U, 0x6cU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, + 0x22U, 0x00U, 0x22U, 0x74U, 0x73U, 0x68U, 0x2eU, 0x63U, 0x3aU, 0x20U, + 0x53U, 0x74U, 0x72U, 0x6fU, 0x6eU, 0x67U, 0x2eU, 0x20U, 0x45U, 0x78U, + 0x65U, 0x63U, 0x75U, 0x74U, 0x65U, 0x20U, 0x42U, 0x45U, 0x46U, 0x4fU, + 0x52U, 0x45U, 0x20U, 0x74U, 0x72U, 0x61U, 0x6eU, 0x73U, 0x61U, 0x63U, + 0x74U, 0x69U, 0x6fU, 0x6eU, 0x20U, 0x69U, 0x73U, 0x20U, 0x61U, 0x70U, + 0x70U, 0x6cU, 0x69U, 0x65U, 0x64U, 0x20U, 0x74U, 0x6fU, 0x20U, 0x6cU, + 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x22U, 0x00U, 0x22U, 0x74U, 0x73U, + 0x68U, 0x2eU, 0x63U, 0x3aU, 0x20U, 0x53U, 0x74U, 0x61U, 0x72U, 0x74U, + 0x2eU, 0x22U, 0x00U, 0x22U, 0x74U, 0x73U, 0x68U, 0x2eU, 0x63U, 0x3aU, + 0x20U, 0x45U, 0x6eU, 0x64U, 0x2eU, 0x22U}; + + // AccountSet + // | otxn | tsh | set | + // | A | A | S | + void + testAccountSetTSH(FeatureBitset features) + { + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + env.fund(XRP(1000), account); + env.close(); + + // set tsh hook on bob + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // account set + env(fset(account, asfTshCollect), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + // AccountDelete + // | otxn | tsh | delete | + // | A | A | N | + // | A | B | S | + void + testAccountDeleteTSH(FeatureBitset features) + { + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh bene + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const bene = Account("bob"); + env.fund(XRP(1000), account, bene); + env.close(); + + // set tsh hook + env(hook(bene, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // AccountDelete + incLgrSeqForAccDel(env, account); + env(acctdelete(account, bene), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + Json::Value params; + params[jss::transaction] = + env.tx()->getJson(JsonOptions::none)[jss::hash]; + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + // Check + // | otxn | tsh | cancel | create | cash | + // | A | A | S | S | N | + // | A | D | N | S | N | + // | D | D | S | N | S | + // | D | A | S | N | S | + static uint256 + getCheckIndex(AccountID const& account, std::uint32_t uSequence) + { + return keylet::check(account, uSequence).key; + } + + void + testCheckCancelTSH(FeatureBitset features) + { + testcase("check cancel tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create check + uint256 const checkId{getCheckIndex(account, env.seq(account))}; + env(check::create(account, dest, XRP(100)), ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel check + env(check::cancel(account, checkId), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh destination + // w/s: none + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh collect + env(fset(dest, asfTshCollect)); + env.close(); + + // create check + uint256 const checkId{getCheckIndex(account, env.seq(account))}; + env(check::create(account, dest, XRP(100)), ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel check + env(check::cancel(account, checkId), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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]; + BEAST_EXPECT(executions.size() == 0); + } + + // otxn: dest + // tsh dest + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create check + uint256 const checkId{getCheckIndex(account, env.seq(account))}; + env(check::create(account, dest, XRP(100)), ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel check + env(check::cancel(dest, checkId), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: dest + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create check + uint256 const checkId{getCheckIndex(account, env.seq(account))}; + env(check::create(account, dest, XRP(100)), ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel check + env(check::cancel(dest, checkId), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + void + testCheckCashTSH(FeatureBitset features) + { + testcase("check cash tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: dest + // tsh dest + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create check + uint256 const checkId{getCheckIndex(account, env.seq(account))}; + env(check::create(account, dest, XRP(100)), ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cash check + env(check::cash(dest, checkId, XRP(100)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: dest + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh collect + env(fset(account, asfTshCollect)); + env.close(); + + // create check + uint256 const checkId{getCheckIndex(account, env.seq(account))}; + env(check::create(account, dest, XRP(100)), ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cash check + env(check::cash(dest, checkId, XRP(100)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + void + testCheckCreateTSH(FeatureBitset features) + { + testcase("check create tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // create check + env(check::create(account, dest, XRP(100)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh destination + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // create check + env(check::create(account, dest, XRP(100)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + // ClaimReward + // | otxn | tsh | claim | + // | A | A | S | + // | A | I | S | + + void + testClaimRewardTSH(FeatureBitset features) + { + testcase("claim reward tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const issuer = Account("issuer"); + env.fund(XRP(1000), account, issuer); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // claim reward + env(reward::claim(account), + reward::issuer(issuer), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh issuer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const issuer = Account("issuer"); + env.fund(XRP(1000), account, issuer); + env.close(); + + // set tsh hook + env(hook(issuer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // claim reward + env(reward::claim(account), + reward::issuer(issuer), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + // DepositPreauth + // | otxn | tsh | preauth | + // | A | A | S | + // | A | Au | S | + + void + testDepositPreauthTSH(FeatureBitset features) + { + testcase("deposit preauth tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const authed = Account("alice"); + auto const account = Account("bob"); + env.fund(XRP(1000), authed, account); + env.close(); + + // require authorization for deposits. + env(fset(account, asfDepositAuth)); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // deposit preauth + env(deposit::auth(account, authed), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh authorize + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const authed = Account("alice"); + auto const account = Account("bob"); + env.fund(XRP(1000), authed, account); + env.close(); + + // require authorization for deposits. + env(fset(account, asfDepositAuth)); + + // set tsh hook + env(hook(authed, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // deposit preauth + env(deposit::auth(account, authed), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + // Escrow + // | otxn | tsh | cancel | create | finish | + // | A | A | S | S | S | + // | A | D | W | S | W | + // | D | D | S | N | S | + // | D | A | S | N | S | + + static uint256 + getEscrowIndex(AccountID const& account, std::uint32_t uSequence) + { + return keylet::escrow(account, uSequence).key; + } + + void + testEscrowCancelTSH(FeatureBitset features) + { + testcase("escrow cancel tsh"); + + using namespace jtx; + using namespace std::chrono; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create escrow + auto const seq1 = env.seq(account); + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel escrow + env(escrow::cancel(account, account, seq1), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh dest + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh collect + env(fset(dest, asfTshCollect)); + env.close(); + + // create escrow + auto const seq1 = env.seq(account); + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel escrow + env(escrow::cancel(account, account, seq1), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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]; + BEAST_EXPECT(executions.size() == 0); + } + + // otxn: dest + // tsh dest + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create escrow + auto const seq1 = env.seq(account); + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel escrow + env(escrow::cancel(dest, account, seq1), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: dest + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh collect + env(fset(account, asfTshCollect)); + env.close(); + + // create escrow + auto const seq1 = env.seq(account); + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel escrow + env(escrow::cancel(dest, account, seq1), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + void + testEscrowIDCancelTSH(FeatureBitset features) + { + testcase("escrow id cancel tsh"); + + using namespace jtx; + using namespace std::chrono; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create escrow + uint256 const escrowId{getEscrowIndex(account, env.seq(account))}; + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel escrow + env(escrow::cancel(account, account, 0), + escrow::escrow_id(escrowId), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh dest + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh collect + env(fset(dest, asfTshCollect)); + env.close(); + + // create escrow + uint256 const escrowId{getEscrowIndex(account, env.seq(account))}; + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel escrow + env(escrow::cancel(account, account, 0), + escrow::escrow_id(escrowId), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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]; + BEAST_EXPECT(executions.size() == 0); + // auto const execution = executions[0u][sfHookExecution.jsonName]; + // BEAST_EXPECT(execution[sfHookResult.jsonName] == 3); + // BEAST_EXPECT(execution[sfHookReturnString.jsonName] == + // "00000001"); + } + + // otxn: dest + // tsh dest + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create escrow + uint256 const escrowId{getEscrowIndex(account, env.seq(account))}; + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel escrow + env(escrow::cancel(dest, account, 0), + escrow::escrow_id(escrowId), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: dest + // tsh account + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh collect + env(fset(account, asfTshCollect)); + env.close(); + + // create escrow + uint256 const escrowId{getEscrowIndex(account, env.seq(account))}; + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel escrow + env(escrow::cancel(dest, account, 0), + escrow::escrow_id(escrowId), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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]; + BEAST_EXPECT(executions.size() == 0); + } + } + + void + testEscrowCreateTSH(FeatureBitset features) + { + testcase("escrow create tsh"); + + using namespace jtx; + using namespace std::chrono; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // create escrow + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh dest + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // create escrow + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + void + testEscrowFinishTSH(FeatureBitset features) + { + testcase("escrow finish tsh"); + + using namespace jtx; + using namespace std::chrono; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create escrow + auto const seq1 = env.seq(account); + NetClock::time_point const finishTime = env.now() + 1s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // finish escrow + env(escrow::finish(account, account, seq1), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh dest + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh collect + // env(fset(dest, asfTshCollect)); + // env.close(); + + // create escrow + auto const seq1 = env.seq(account); + NetClock::time_point const finishTime = env.now() + 1s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // finish escrow + env(escrow::finish(account, account, seq1), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: dest + // tsh dest + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create escrow + auto const seq1 = env.seq(account); + NetClock::time_point const finishTime = env.now() + 1s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // finish escrow + env(escrow::finish(dest, account, seq1), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: dest + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create escrow + auto const seq1 = env.seq(account); + NetClock::time_point const finishTime = env.now() + 1s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // finish escrow + env(escrow::finish(dest, account, seq1), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + void + testEscrowIDFinishTSH(FeatureBitset features) + { + testcase("escrow id finish tsh"); + + using namespace jtx; + using namespace std::chrono; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create escrow + uint256 const escrowId{getEscrowIndex(account, env.seq(account))}; + NetClock::time_point const finishTime = env.now() + 1s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // finish escrow + env(escrow::finish(account, account, 0), + escrow::escrow_id(escrowId), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh dest + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh collect + env(fset(dest, asfTshCollect)); + env.close(); + + // create escrow + uint256 const escrowId{getEscrowIndex(account, env.seq(account))}; + NetClock::time_point const finishTime = env.now() + 1s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // finish escrow + env(escrow::finish(account, account, 0), + escrow::escrow_id(escrowId), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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]; + BEAST_EXPECT(executions.size() == 0); + } + + // otxn: dest + // tsh dest + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // create escrow + uint256 const escrowId{getEscrowIndex(account, env.seq(account))}; + NetClock::time_point const finishTime = env.now() + 1s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // finish escrow + env(escrow::finish(dest, account, 0), + escrow::escrow_id(escrowId), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: dest + // tsh account + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh collect + env(fset(account, asfTshCollect)); + env.close(); + + // create escrow + uint256 const escrowId{getEscrowIndex(account, env.seq(account))}; + NetClock::time_point const finishTime = env.now() + 1s; + auto createTx = escrow::create(account, dest, XRP(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // finish escrow + env(escrow::finish(dest, account, 0), + escrow::escrow_id(escrowId), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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]; + BEAST_EXPECT(executions.size() == 0); + } + } + + // GenesisMint + // | otxn | tsh | mint | + // | A | A | S | + // | A | D | S | + // | A | B | W | + + void + testGenesisMintTSH(FeatureBitset features) + { + testcase("genesis mint tsh"); + + // trigger the emitted txn + using namespace jtx; + using namespace std::chrono; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const issuer = env.master; + auto const bene = Account("bob"); + env.fund(XRP(1000), account, bene); + env.close(); + + // burn down the total ledger coins so that genesis mints don't mint + // above 100B tripping invariant + env(noop(issuer), fee(XRP(10'000'000ULL))); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set mint hook on master + env(hook(issuer, {{hso(genesis::MintTestHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + env(invoke::invoke( + account, + issuer, + genesis::makeBlob({ + {bene.id(), + XRP(123).value(), + std::nullopt, + std::nullopt}, + })), + fee(XRP(10)), + ter(tesSUCCESS)); + env.close(); + + // get the emitted txn id + 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 execution1 = executions[0u][sfHookExecution.jsonName]; + BEAST_EXPECT(execution1[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution1[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh issuer + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const issuer = env.master; + auto const bene = Account("bob"); + env.fund(XRP(1000), account, bene); + env.close(); + + // burn down the total ledger coins so that genesis mints don't mint + // above 100B tripping invariant + env(noop(issuer), fee(XRP(10'000'000ULL))); + env.close(); + + // set tsh hook && set mint hook on master + env(hook( + issuer, + {{hso(TshHook, overrideFlag), + hso(genesis::MintTestHook, overrideFlag)}}, + 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + env(invoke::invoke( + account, + issuer, + genesis::makeBlob({ + {bene.id(), + XRP(123).value(), + std::nullopt, + std::nullopt}, + })), + fee(XRP(10)), + ter(tesSUCCESS)); + env.close(); + + // get the emitted txn id + 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 execution1 = executions[0u][sfHookExecution.jsonName]; + BEAST_EXPECT(execution1[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution1[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh bene + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const issuer = env.master; + auto const bene = Account("bob"); + env.fund(XRP(1000), account, bene); + env.close(); + + // burn down the total ledger coins so that genesis mints don't mint + // above 100B tripping invariant + env(noop(issuer), fee(XRP(10'000'000ULL))); + env.close(); + + // set tsh collect + env(fset(bene, asfTshCollect)); + env.close(); + + // set tsh hook + env(hook(bene, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set mint hook on master + env(hook(issuer, {{hso(genesis::MintTestHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + env(invoke::invoke( + account, + issuer, + genesis::makeBlob({ + {bene.id(), + XRP(123).value(), + std::nullopt, + std::nullopt}, + })), + fee(XRP(10)), + ter(tesSUCCESS)); + env.close(); + + // get the emitted txn id + 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 emissions = meta[sfHookEmissions.jsonName]; + auto const emission = emissions[0u][sfHookEmission.jsonName]; + auto const txId = emission[sfEmittedTxnID.jsonName]; + env.close(); + + // verify tsh hook triggered + Json::Value params1; + params1[jss::transaction] = txId; + auto const jrr1 = env.rpc("json", "tx", to_string(params1)); + auto const meta1 = jrr1[jss::result][jss::meta]; + auto const executions1 = meta1[sfHookExecutions.jsonName]; + auto const execution1 = executions1[0u][sfHookExecution.jsonName]; + BEAST_EXPECT(execution1[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution1[sfHookReturnString.jsonName] == "00000001"); + } + } + + // Import + // | otxn | tsh | import | + // | A | A | S | + // | A | I | S | + // * Issuer cannot import on itself + + void + testImportTSH(FeatureBitset features) + { + testcase("import tsh"); + + using namespace test::jtx; + using namespace std::literals; + + std::vector const keys = { + "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC" + "1"}; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkVLConfig( + 21337, keys, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const issuer = Account("bob"); + env.fund(XRP(1000), account, issuer); + env.close(); + + // burn down the total ledger coins so that genesis mints don't mint + // above 100B tripping invariant + env(noop(env.master), fee(XRP(10'000'000ULL))); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // import + env(import::import( + account, import::loadXpop(ImportTCAccountSet::w_seed)), + import::issuer(issuer), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // get the emitted txn id + 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 execution1 = executions[0u][sfHookExecution.jsonName]; + BEAST_EXPECT(execution1[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution1[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh issuer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkVLConfig( + 21337, keys, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const issuer = Account("bob"); + env.fund(XRP(1000), account, issuer); + env.close(); + + // burn down the total ledger coins so that genesis mints don't mint + // above 100B tripping invariant + env(noop(env.master), fee(XRP(10'000'000ULL))); + env.close(); + + // set tsh hook + env(hook(issuer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // import + env(import::import( + account, import::loadXpop(ImportTCAccountSet::w_seed)), + import::issuer(issuer), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // get the emitted txn id + 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 execution1 = executions[0u][sfHookExecution.jsonName]; + BEAST_EXPECT(execution1[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution1[sfHookReturnString.jsonName] == "00000000"); + } + } + + // Invoke + // | otxn | tsh | invoke | + // | A | A | S | + // | A | D | W | + + void + testInvokeTSH(FeatureBitset features) + { + testcase("invoke tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // ttINVOKE + env(invoke::invoke(account), + invoke::dest(dest), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh dest + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account("bob"); + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // ttINVOKE + env(invoke::invoke(account), + invoke::dest(dest), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + // Offer + // | otxn | tsh | cancel | create | + // | A | A | S | S | + // | A | C | N | N | + + void + testOfferCancelTSH(FeatureBitset features) + { + testcase("offer cancel tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, gw); + env.close(); + + // gw create offer + env(offer(gw, USD(1000), XRP(1000))); + env.close(); + + // create offer + auto const offerSeq = env.seq(account); + env(offer(account, USD(1000), XRP(1000)), ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel offer + env(offer_cancel(account, offerSeq), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + void + testOfferCreateTSH(FeatureBitset features) + { + testcase("offer create tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, gw); + env.close(); + + // gw create offer + env(offer(gw, USD(1000), XRP(1000))); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // create offer + env(offer(account, USD(1000), XRP(1000)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh cross + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const cross = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, cross, gw); + env.close(); + + // set tsh collect + env(fset(cross, asfTshCollect)); + + // gw create offer + env(offer(gw, USD(1000), XRP(1000))); + env.close(); + + // set tsh hook + env(hook(cross, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // create offer + env(offer(account, USD(1000), XRP(1000)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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]; + BEAST_EXPECT(executions.size() == 0); + } + } + + // Payment + // | otxn | tsh | payment | + // | A | A | S | + // | A | D | S | + // | A | C | W | + + void + testPaymentTSH(FeatureBitset features) + { + testcase("payment tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // payment + env(pay(account, dest, XRP(1)), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh dest + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // payment + env(pay(account, dest, XRP(1)), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh cross + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const cross = Account("bob"); + auto const dest = Account("carol"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, cross, dest, gw); + env.close(); + + // setup rippling + auto const USDA = account["USD"]; + auto const USDB = cross["USD"]; + auto const USDC = dest["USD"]; + env.trust(USDA(10), cross); + env.trust(USDB(10), dest); + + // set tsh collect + env(fset(cross, asfTshCollect)); + + // set tsh hook + env(hook(cross, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // payment + env(pay(account, dest, USDB(10)), paths(USDA), fee(XRP(1))); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001"); + } + } + + // PaymentChannel + // | otxn | tsh | claim | create | fund | + // | A | A | S | S | S | + // | A | D | W | S | W | + // | D | D | S | N | N | + // | D | A | S | N | N | + + static uint256 + channel( + jtx::Account const& account, + jtx::Account const& dst, + std::uint32_t seqProxyValue) + { + auto const k = keylet::payChan(account, dst, seqProxyValue); + return k.key; + } + + static Buffer + signClaimAuth( + PublicKey const& pk, + SecretKey const& sk, + uint256 const& channel, + STAmount const& authAmt) + { + Serializer msg; + serializePayChanAuthorization(msg, channel, authAmt.xrp()); + return sign(pk, sk, msg.slice()); + } + + void + testPaymentChannelClaimTSH(FeatureBitset features) + { + testcase("payment channel claim tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + env.fund(XRP(1000), account, dest); + env.close(); + + // create paychannel + auto const pk = account.pk(); + auto const settleDelay = 100s; + auto const chan = channel(account, dest, env.seq(account)); + env(paychan::create(account, dest, XRP(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + auto const delta = XRP(1); + auto const reqBal = delta; + auto const authAmt = reqBal + XRP(1); + + // claim paychannel + env(paychan::claim(account, chan, reqBal, authAmt), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh dest + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh collect + env(fset(dest, asfTshCollect)); + + // create paychannel + auto const pk = account.pk(); + auto const settleDelay = 100s; + auto const chan = channel(account, dest, env.seq(account)); + env(paychan::create(account, dest, XRP(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + auto const delta = XRP(1); + auto const reqBal = delta; + auto const authAmt = reqBal + XRP(1); + + // claim paychannel + env(paychan::claim(account, chan, reqBal, authAmt), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001"); + } + + // otxn: dest + // tsh dest + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + env.fund(XRP(1000), account, dest); + env.close(); + + // create paychannel + auto const pk = account.pk(); + auto const settleDelay = 100s; + auto const chan = channel(account, dest, env.seq(account)); + env(paychan::create(account, dest, XRP(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + auto const delta = XRP(1); + auto const reqBal = delta; + auto const authAmt = reqBal + XRP(1); + + // claim paychannel + auto const sig = + signClaimAuth(account.pk(), account.sk(), chan, authAmt); + env(paychan::claim( + dest, chan, reqBal, authAmt, Slice(sig), account.pk()), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: dest + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + env.fund(XRP(1000), account, dest); + env.close(); + + // create paychannel + auto const pk = account.pk(); + auto const settleDelay = 100s; + auto const chan = channel(account, dest, env.seq(account)); + env(paychan::create(account, dest, XRP(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + auto const delta = XRP(1); + auto const reqBal = delta; + auto const authAmt = reqBal + XRP(1); + + // claim paychannel + auto const sig = + signClaimAuth(account.pk(), account.sk(), chan, authAmt); + env(paychan::claim( + dest, chan, reqBal, authAmt, Slice(sig), account.pk()), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + void + testPaymentChannelCreateTSH(FeatureBitset features) + { + testcase("payment channel create tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // create paychannel + auto const pk = account.pk(); + auto const settleDelay = 100s; + auto const chan = channel(account, dest, env.seq(account)); + env(paychan::create(account, dest, XRP(10), settleDelay, pk), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh dest + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // create paychannel + auto const pk = account.pk(); + auto const settleDelay = 100s; + auto const chan = channel(account, dest, env.seq(account)); + env(paychan::create(account, dest, XRP(10), settleDelay, pk), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + void + testPaymentChannelFundTSH(FeatureBitset features) + { + testcase("payment channel fund tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + env.fund(XRP(1000), account, dest); + env.close(); + + // create paychannel + auto const pk = account.pk(); + auto const settleDelay = 100s; + auto const chan = channel(account, dest, env.seq(account)); + env(paychan::create(account, dest, XRP(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // fund paychannel + env(paychan::fund(account, chan, XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh dest + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh collect + env(fset(dest, asfTshCollect)); + + // create paychannel + auto const pk = account.pk(); + auto const settleDelay = 100s; + auto const chan = channel(account, dest, env.seq(account)); + env(paychan::create(account, dest, XRP(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // fund paychannel + env(paychan::fund(account, chan, XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001"); + } + } + + // SetHook + // | otxn | tsh | set | + // | A | A | S | + void + testSetHookTSH(FeatureBitset features) + { + testcase("set hook tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + env.fund(XRP(1000), account); + env.close(); + + // set tsh hook + auto hook1 = hso(TshHook, overrideFlag); + hook1[jss::HookOn] = + "00000000000000000000000000000000000000000000000000000000004000" + "00"; + env(hook(account, {{hook1}}, 0), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // set hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + // SetRegularKey + // | otxn | tsh | srk | + // | A | A | S | + // | A | D | S | + void + testSetRegularKeyTSH(FeatureBitset features) + { + testcase("set regular key tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set regular key + env(regkey(account, dest), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh dest + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + env.fund(XRP(1000), account, dest); + env.close(); + + // set tsh hook + env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set regular key + env(regkey(account, dest), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + // SignersListSet + // | otxn | tsh | sls | + // | A | A | S | + // | A | S | S | + void + testSignersListSetTSH(FeatureBitset features) + { + testcase("signers list set tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const signer1 = Account{"bob"}; + auto const signer2 = Account{"carol"}; + env.fund(XRP(1000), account, signer1, signer2); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // signers list set + env(signers(account, 2, {{signer1, 1}, {signer2, 1}}), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh signer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const signer1 = Account{"bob"}; + auto const signer2 = Account{"carol"}; + env.fund(XRP(1000), account, signer1, signer2); + env.close(); + + // set tsh hook + env(hook(signer1, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(signer2, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // signers list set + env(signers(account, 2, {{signer1, 1}, {signer2, 1}}), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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 execution1 = executions[0u][sfHookExecution.jsonName]; + BEAST_EXPECT(execution1[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution1[sfHookReturnString.jsonName] == "00000000"); + auto const execution2 = executions[1u][sfHookExecution.jsonName]; + BEAST_EXPECT(execution2[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution2[sfHookReturnString.jsonName] == "00000000"); + } + } + + // Ticket + // | otxn | tsh | create | + // | A | A | S | + void + testTicketCreateTSH(FeatureBitset features) + { + testcase("ticket create tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + env.fund(XRP(1000), account); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // ticket create + env(ticket::create(account, 2), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + // TrustSet + // | otxn | tsh | trustset | + // | A | A | S | + // | A | I | W | + void + testTrustSetTSH(FeatureBitset features) + { + testcase("trust set tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const issuer = Account{"gw"}; + auto const USD = issuer["USD"]; + env.fund(XRP(1000), account, issuer); + env.close(); + + // set tsh hook + env(hook(account, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // trust set + env(trust(account, USD(1000)), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: account + // tsh issuer + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const issuer = Account{"gw"}; + auto const USD = issuer["USD"]; + env.fund(XRP(1000), account, issuer); + env.close(); + + // set tsh collect on bob + env(fset(issuer, asfTshCollect)); + + // set tsh hook + env(hook(issuer, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // trust set + env(trust(account, USD(1000)), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001"); + } + } + + // | otxn | tfBurnable | tsh | mint | burn | buy | sell | cancel + // | O | false | O | N | S | S | S | S + // | O | false | I | N | W | W | W | N + // | O | false | B | N | N | N | S | N + // | O | true | B | N | N | N | S | N + // | O | true | O | N | S | S | S | S + // | O | true | I | N | W | S | S | N + // | I | false | O | N | N | N | N | N + // | I | false | I | S | N | N | N | N + // | I | false | B | N | N | N | N | N + // | I | true | O | N | W | N | N | N + // | I | true | I | S | S | N | N | N + // | I | true | B | N | N | N | N | N + // | B | true | O | N | N | ? | N | N + // | B | true | B | N | N | ? | N | N + + void + testURITokenBurnTSH(FeatureBitset features) + { + testcase("uritoken burn tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: owner + // flag: not burnable + // tsh owner + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + env.fund(XRP(1000), issuer, owner); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(owner, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // ttURITOKEN_BURN + env(uritoken::burn(owner, hexid), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: owner + // flag: not burnable + // tsh issuer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + env.fund(XRP(1000), issuer, owner); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(issuer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // ttURITOKEN_BURN + env(uritoken::burn(owner, hexid), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: owner + // flag: burnable + // tsh owner + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + env.fund(XRP(1000), issuer, owner); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + txflags(tfBurnable), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(owner, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // ttURITOKEN_BURN + env(uritoken::burn(owner, hexid), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: owner + // flag: burnable + // tsh issuer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + env.fund(XRP(1000), issuer, owner); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + txflags(tfBurnable), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(issuer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // ttURITOKEN_BURN + env(uritoken::burn(owner, hexid), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: issuer + // flag: burnable + // tsh owner + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + env.fund(XRP(1000), issuer, owner); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + txflags(tfBurnable), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(owner, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // ttURITOKEN_BURN + env(uritoken::burn(issuer, hexid), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: issuer + // flag: burnable + // tsh issuer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + env.fund(XRP(1000), issuer, owner); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + txflags(tfBurnable), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(issuer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // ttURITOKEN_BURN + env(uritoken::burn(issuer, hexid), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + void + testURITokenBuyTSH(FeatureBitset features) + { + testcase("uritoken buy tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: owner + // flag: not burnable + // tsh owner + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + env.fund(XRP(1000), issuer, owner); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(owner, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: owner + // flag: not burnable + // tsh issuer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + env.fund(XRP(1000), issuer, owner); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(issuer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: owner + // flag: burnable + // tsh owner + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + env.fund(XRP(1000), issuer, owner); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + txflags(tfBurnable), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(owner, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: owner + // flag: burnable + // tsh issuer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + env.fund(XRP(1000), issuer, owner); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + txflags(tfBurnable), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(issuer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: buyer + // tsh owner + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, owner, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(owner, hexid), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(owner, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(buyer, hexid), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: buyer + // tsh buyer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, owner, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(owner, hexid), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(buyer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(buyer, hexid), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + void + testURITokenCancelSellOfferTSH(FeatureBitset features) + { + testcase("uritoken cancel sell offer tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: owner + // flag: not burnable + // tsh owner + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, owner, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(owner, hexid), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(owner, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel uritoken + env(uritoken::cancel(owner, hexid), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: owner + // flag: not burnable + // tsh buyer + // w/s: none + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, owner, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + env(fset(buyer, asfTshCollect)); + env.close(); + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(owner, hexid), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(buyer, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel uritoken + env(uritoken::cancel(owner, hexid), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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]; + BEAST_EXPECT(executions.size() == 0); + } + + // otxn: owner + // flag: burnable + // tsh buyer + // w/s: none + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, owner, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + env(fset(buyer, asfTshCollect)); + env.close(); + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + txflags(tfBurnable), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(owner, hexid), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(buyer, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel uritoken + env(uritoken::cancel(owner, hexid), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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]; + BEAST_EXPECT(executions.size() == 0); + } + + // otxn: owner + // flag: burnable + // tsh owner + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, owner, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + txflags(tfBurnable), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(owner, hexid), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(owner, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // cancel uritoken + env(uritoken::cancel(owner, hexid), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + void + testURITokenCreateSellOfferTSH(FeatureBitset features) + { + testcase("uritoken create sell offer tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: owner + // flag: not burnable + // tsh owner + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, owner, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(owner, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(owner, hexid), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: owner + // flag: not burnable + // tsh issuer + // w/s: weak + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, owner, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + env(fset(issuer, asfTshCollect)); + env.close(); + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(issuer, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(owner, hexid), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001"); + } + + // otxn: owner + // flag: not burnable + // tsh buyer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, owner, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(buyer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(owner, hexid), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: owner + // flag: not burnable + // tsh buyer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, owner, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(buyer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(owner, hexid), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: owner + // flag: burnable + // tsh owner + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, owner, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + txflags(tfBurnable), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(owner, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(owner, hexid), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: owner + // flag: burnable + // tsh issuer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const owner = Account("bob"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, owner, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(owner), + uritoken::amt(XRP(1)), + txflags(tfBurnable), + ter(tesSUCCESS)); + env.close(); + + // buy uritoken + env(uritoken::buy(owner, hexid), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh hook + env(hook(issuer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(owner, hexid), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + } + + void + testURITokenMintTSH(FeatureBitset features) + { + testcase("uritoken mint tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: issuer + // flag: not burnable + // tsh issuer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // set tsh hook + env(hook(issuer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: issuer + // flag: not burnable + // tsh buyer + // w/s: none + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, buyer); + env.close(); + + env(fset(buyer, asfTshCollect)); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // set tsh hook + env(hook(buyer, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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]; + BEAST_EXPECT(executions.size() == 0); + } + + // otxn: issuer + // flag: burnable + // tsh issuer + // w/s: strong + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, buyer); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // set tsh hook + env(hook(issuer, {{hso(TshHook, overrideFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + fee(XRP(1)), + txflags(tfBurnable), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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[sfHookResult.jsonName] == 3); + BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000000"); + } + + // otxn: issuer + // flag: burnable + // tsh buyer + // w/s: none + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const buyer = Account("carol"); + env.fund(XRP(1000), issuer, buyer); + env.close(); + + env(fset(buyer, asfTshCollect)); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // set tsh hook + env(hook(buyer, {{hso(TshHook, collectFlag)}}, 0), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(buyer), + uritoken::amt(XRP(1)), + fee(XRP(1)), + txflags(tfBurnable), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + 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]; + BEAST_EXPECT(executions.size() == 0); + } + } + + void + testWithFeats(FeatureBitset features) + { + testAccountSetTSH(features); + testAccountDeleteTSH(features); + testCheckCancelTSH(features); + testCheckCashTSH(features); + testCheckCreateTSH(features); + testClaimRewardTSH(features); + testDepositPreauthTSH(features); + testEscrowCancelTSH(features); + testEscrowIDCancelTSH(features); + testEscrowCreateTSH(features); + testEscrowFinishTSH(features); + testEscrowIDFinishTSH(features); + testGenesisMintTSH(features); + testImportTSH(features); + testInvokeTSH(features); + testOfferCancelTSH(features); + testOfferCreateTSH(features); + testPaymentTSH(features); + testPaymentChannelClaimTSH(features); + testPaymentChannelCreateTSH(features); + testPaymentChannelFundTSH(features); + testSetHookTSH(features); + testSetRegularKeyTSH(features); + testSignersListSetTSH(features); + testTicketCreateTSH(features); + testTrustSetTSH(features); + testURITokenBurnTSH(features); + testURITokenBuyTSH(features); + testURITokenCancelSellOfferTSH(features); + testURITokenCreateSellOfferTSH(features); + testURITokenMintTSH(features); + } + +public: + void + run() override + { + using namespace test::jtx; + auto const sa = supported_amendments(); + testWithFeats(sa); + } +}; + +BEAST_DEFINE_TESTSUITE(SetHookTSH, app, ripple); + +} // namespace test +} // namespace ripple \ No newline at end of file diff --git a/src/test/consensus/NegativeUNL_test.cpp b/src/test/consensus/NegativeUNL_test.cpp index 013f279d139..7ec04cea1a9 100644 --- a/src/test/consensus/NegativeUNL_test.cpp +++ b/src/test/consensus/NegativeUNL_test.cpp @@ -42,35 +42,6 @@ namespace test { * are put in their existing unit test files. */ -/** - * Test the size of the negative UNL in a ledger, - * also test if the ledger has ToDisalbe and/or ToReEnable - * - * @param l the ledger - * @param size the expected negative UNL size - * @param hasToDisable if expect ToDisable in ledger - * @param hasToReEnable if expect ToDisable in ledger - * @return true if meet all three expectation - */ -bool -negUnlSizeTest( - std::shared_ptr const& l, - size_t size, - bool hasToDisable, - bool hasToReEnable); - -/** - * Try to apply a ttUNL_MODIFY Tx, and test the apply result - * - * @param env the test environment - * @param view the OpenView of the ledger - * @param tx the ttUNL_MODIFY Tx - * @param pass if the Tx should be applied successfully - * @return true if meet the expectation of apply result - */ -bool -applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass); - /** * Verify the content of negative UNL entries (public key and ledger sequence) * of a ledger @@ -85,15 +56,6 @@ VerifyPubKeyAndSeq( std::shared_ptr const& l, hash_map nUnlLedgerSeq); -/** - * Count the number of Tx in a TxSet - * - * @param txSet the TxSet - * @return the number of Tx - */ -std::size_t -countTx(std::shared_ptr const& txSet); - /** * Create fake public keys * @@ -103,17 +65,6 @@ countTx(std::shared_ptr const& txSet); std::vector createPublicKeys(std::size_t n); -/** - * Create ttUNL_MODIFY Tx - * - * @param disabling disabling or re-enabling a validator - * @param seq current ledger seq - * @param txKey the public key of the validator - * @return the ttUNL_MODIFY Tx - */ -STTx -createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey); - class NegativeUNL_test : public beast::unit_test::suite { /** @@ -245,14 +196,16 @@ class NegativeUNL_test : public beast::unit_test::suite l = std::make_shared( *l, env.app().timeKeeper().closeTime()); - auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]); - auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]); + auto txDisable_0 = unl::createTx(true, l->seq(), publicKeys[0]); + auto txReEnable_1 = unl::createTx(false, l->seq(), publicKeys[1]); OpenView accum(&*l); - BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false)); - BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txDisable_0, false)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txReEnable_1, false)); accum.apply(*l); - BEAST_EXPECT(negUnlSizeTest(l, 0, false, false)); + BEAST_EXPECT(unl::negUnlSizeTest(l, 0, false, false)); } { @@ -266,18 +219,21 @@ class NegativeUNL_test : public beast::unit_test::suite BEAST_EXPECT(l->isFlagLedger()); l->updateNegativeUNL(); - auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]); - auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]); - auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]); + auto txDisable_0 = unl::createTx(true, l->seq(), publicKeys[0]); + auto txDisable_1 = unl::createTx(true, l->seq(), publicKeys[1]); + auto txReEnable_2 = unl::createTx(false, l->seq(), publicKeys[2]); // can apply 1 and only 1 ToDisable Tx, // cannot apply ToReEnable Tx, since negative UNL is empty OpenView accum(&*l); - BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true)); - BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, false)); - BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txDisable_0, true)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txDisable_1, false)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txReEnable_2, false)); accum.apply(*l); - auto good_size = negUnlSizeTest(l, 0, true, false); + auto good_size = unl::negUnlSizeTest(l, 0, true, false); BEAST_EXPECT(good_size); if (good_size) { @@ -292,7 +248,7 @@ class NegativeUNL_test : public beast::unit_test::suite //(3) ledgers before the next flag ledger for (auto i = 0; i < 256; ++i) { - auto good_size = negUnlSizeTest(l, 0, true, false); + auto good_size = unl::negUnlSizeTest(l, 0, true, false); BEAST_EXPECT(good_size); if (good_size) BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]); @@ -304,7 +260,7 @@ class NegativeUNL_test : public beast::unit_test::suite //(4) next flag ledger // test if the ledger updated correctly - auto good_size = negUnlSizeTest(l, 1, false, false); + auto good_size = unl::negUnlSizeTest(l, 1, false, false); BEAST_EXPECT(good_size); if (good_size) { @@ -312,20 +268,25 @@ class NegativeUNL_test : public beast::unit_test::suite nUnlLedgerSeq.emplace(publicKeys[0], l->seq()); } - auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]); - auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]); - auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]); - auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]); - auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]); + auto txDisable_0 = unl::createTx(true, l->seq(), publicKeys[0]); + auto txDisable_1 = unl::createTx(true, l->seq(), publicKeys[1]); + auto txReEnable_0 = unl::createTx(false, l->seq(), publicKeys[0]); + auto txReEnable_1 = unl::createTx(false, l->seq(), publicKeys[1]); + auto txReEnable_2 = unl::createTx(false, l->seq(), publicKeys[2]); OpenView accum(&*l); - BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false)); - BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, true)); - BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false)); - BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false)); - BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txDisable_0, false)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txDisable_1, true)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txReEnable_1, false)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txReEnable_2, false)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txReEnable_0, true)); accum.apply(*l); - good_size = negUnlSizeTest(l, 1, true, true); + good_size = unl::negUnlSizeTest(l, 1, true, true); BEAST_EXPECT(good_size); if (good_size) { @@ -341,7 +302,7 @@ class NegativeUNL_test : public beast::unit_test::suite //(5) ledgers before the next flag ledger for (auto i = 0; i < 256; ++i) { - auto good_size = negUnlSizeTest(l, 1, true, true); + auto good_size = unl::negUnlSizeTest(l, 1, true, true); BEAST_EXPECT(good_size); if (good_size) { @@ -357,19 +318,20 @@ class NegativeUNL_test : public beast::unit_test::suite //(6) next flag ledger // test if the ledger updated correctly - auto good_size = negUnlSizeTest(l, 1, false, false); + auto good_size = unl::negUnlSizeTest(l, 1, false, false); BEAST_EXPECT(good_size); if (good_size) { BEAST_EXPECT(l->negativeUNL().count(publicKeys[1])); } - auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]); + auto txDisable_0 = unl::createTx(true, l->seq(), publicKeys[0]); OpenView accum(&*l); - BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txDisable_0, true)); accum.apply(*l); - good_size = negUnlSizeTest(l, 1, true, false); + good_size = unl::negUnlSizeTest(l, 1, true, false); BEAST_EXPECT(good_size); if (good_size) { @@ -385,7 +347,7 @@ class NegativeUNL_test : public beast::unit_test::suite //(7) ledgers before the next flag ledger for (auto i = 0; i < 256; ++i) { - auto good_size = negUnlSizeTest(l, 1, true, false); + auto good_size = unl::negUnlSizeTest(l, 1, true, false); BEAST_EXPECT(good_size); if (good_size) { @@ -400,7 +362,7 @@ class NegativeUNL_test : public beast::unit_test::suite //(8) next flag ledger // test if the ledger updated correctly - auto good_size = negUnlSizeTest(l, 2, false, false); + auto good_size = unl::negUnlSizeTest(l, 2, false, false); BEAST_EXPECT(good_size); if (good_size) { @@ -410,16 +372,19 @@ class NegativeUNL_test : public beast::unit_test::suite BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq)); } - auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]); - auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]); - auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]); + auto txDisable_0 = unl::createTx(true, l->seq(), publicKeys[0]); + auto txReEnable_0 = unl::createTx(false, l->seq(), publicKeys[0]); + auto txReEnable_1 = unl::createTx(false, l->seq(), publicKeys[1]); OpenView accum(&*l); - BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true)); - BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false)); - BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txReEnable_0, true)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txReEnable_1, false)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txDisable_0, false)); accum.apply(*l); - good_size = negUnlSizeTest(l, 2, false, true); + good_size = unl::negUnlSizeTest(l, 2, false, true); BEAST_EXPECT(good_size); if (good_size) { @@ -434,7 +399,7 @@ class NegativeUNL_test : public beast::unit_test::suite //(9) ledgers before the next flag ledger for (auto i = 0; i < 256; ++i) { - auto good_size = negUnlSizeTest(l, 2, false, true); + auto good_size = unl::negUnlSizeTest(l, 2, false, true); BEAST_EXPECT(good_size); if (good_size) { @@ -450,7 +415,7 @@ class NegativeUNL_test : public beast::unit_test::suite //(10) next flag ledger // test if the ledger updated correctly - auto good_size = negUnlSizeTest(l, 1, false, false); + auto good_size = unl::negUnlSizeTest(l, 1, false, false); BEAST_EXPECT(good_size); if (good_size) { @@ -459,12 +424,13 @@ class NegativeUNL_test : public beast::unit_test::suite BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq)); } - auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]); + auto txReEnable_1 = unl::createTx(false, l->seq(), publicKeys[1]); OpenView accum(&*l); - BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, true)); + BEAST_EXPECT( + unl::applyAndTestResult(env, accum, txReEnable_1, true)); accum.apply(*l); - good_size = negUnlSizeTest(l, 1, false, true); + good_size = unl::negUnlSizeTest(l, 1, false, true); BEAST_EXPECT(good_size); if (good_size) { @@ -478,7 +444,7 @@ class NegativeUNL_test : public beast::unit_test::suite //(11) ledgers before the next flag ledger for (auto i = 0; i < 256; ++i) { - auto good_size = negUnlSizeTest(l, 1, false, true); + auto good_size = unl::negUnlSizeTest(l, 1, false, true); BEAST_EXPECT(good_size); if (good_size) { @@ -492,14 +458,14 @@ class NegativeUNL_test : public beast::unit_test::suite l->updateNegativeUNL(); //(12) next flag ledger - BEAST_EXPECT(negUnlSizeTest(l, 0, false, false)); + BEAST_EXPECT(unl::negUnlSizeTest(l, 0, false, false)); } { //(13) ledgers before the next flag ledger for (auto i = 0; i < 256; ++i) { - BEAST_EXPECT(negUnlSizeTest(l, 0, false, false)); + BEAST_EXPECT(unl::negUnlSizeTest(l, 0, false, false)); l = std::make_shared( *l, env.app().timeKeeper().closeTime()); } @@ -507,7 +473,7 @@ class NegativeUNL_test : public beast::unit_test::suite l->updateNegativeUNL(); //(14) next flag ledger - BEAST_EXPECT(negUnlSizeTest(l, 0, false, false)); + BEAST_EXPECT(unl::negUnlSizeTest(l, 0, false, false)); } } @@ -542,11 +508,11 @@ class NegativeUNLNoAmendment_test : public beast::unit_test::suite *l, env.app().timeKeeper().closeTime()); } BEAST_EXPECT(l->seq() == 256); - auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]); + auto txDisable_0 = unl::createTx(true, l->seq(), publicKeys[0]); OpenView accum(&*l); - BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false)); + BEAST_EXPECT(unl::applyAndTestResult(env, accum, txDisable_0, false)); accum.apply(*l); - BEAST_EXPECT(negUnlSizeTest(l, 0, false, false)); + BEAST_EXPECT(unl::negUnlSizeTest(l, 0, false, false)); } void @@ -634,8 +600,8 @@ struct NetworkHistory OpenView accum(&*l); if (l->negativeUNL().size() < param.negUNLSize) { - auto tx = createTx(true, l->seq(), UNLKeys[nidx]); - if (!applyAndTestResult(env, accum, tx, true)) + auto tx = unl::createTx(true, l->seq(), UNLKeys[nidx]); + if (!unl::applyAndTestResult(env, accum, tx, true)) break; ++nidx; } @@ -643,15 +609,15 @@ struct NetworkHistory { if (param.hasToDisable) { - auto tx = createTx(true, l->seq(), UNLKeys[nidx]); - if (!applyAndTestResult(env, accum, tx, true)) + auto tx = unl::createTx(true, l->seq(), UNLKeys[nidx]); + if (!unl::applyAndTestResult(env, accum, tx, true)) break; ++nidx; } if (param.hasToReEnable) { - auto tx = createTx(false, l->seq(), UNLKeys[0]); - if (!applyAndTestResult(env, accum, tx, true)) + auto tx = unl::createTx(false, l->seq(), UNLKeys[0]); + if (!unl::applyAndTestResult(env, accum, tx, true)) break; } } @@ -659,7 +625,7 @@ struct NetworkHistory } l->updateSkipList(); } - return negUnlSizeTest( + return unl::negUnlSizeTest( l, param.negUNLSize, param.hasToDisable, param.hasToReEnable); } @@ -759,7 +725,7 @@ voteAndCheck( vote.doVoting( history.lastLedger(), history.UNLKeySet, history.validations, txSet); - return countTx(txSet) >= expect; + return unl::countTx(txSet) >= expect; } /** @@ -782,11 +748,11 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite PublicKey toDisableKey; PublicKey toReEnableKey; LedgerIndex seq(1234); - BEAST_EXPECT(countTx(txSet) == 0); + BEAST_EXPECT(unl::countTx(txSet) == 0); vote.addTx(seq, toDisableKey, NegativeUNLVote::ToDisable, txSet); - BEAST_EXPECT(countTx(txSet) == 1); + BEAST_EXPECT(unl::countTx(txSet) == 1); vote.addTx(seq, toReEnableKey, NegativeUNLVote::ToReEnable, txSet); - BEAST_EXPECT(countTx(txSet) == 2); + BEAST_EXPECT(unl::countTx(txSet) == 2); // content of a tx is implicitly tested after applied to a ledger // in later test cases } @@ -1912,31 +1878,6 @@ BEAST_DEFINE_TESTSUITE(NegativeUNLVoteFilterValidations, consensus, ripple); /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// -bool -negUnlSizeTest( - std::shared_ptr const& l, - size_t size, - bool hasToDisable, - bool hasToReEnable) -{ - bool sameSize = l->negativeUNL().size() == size; - bool sameToDisable = - (l->validatorToDisable() != std::nullopt) == hasToDisable; - bool sameToReEnable = - (l->validatorToReEnable() != std::nullopt) == hasToReEnable; - - return sameSize && sameToDisable && sameToReEnable; -} - -bool -applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass) -{ - auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); - if (pass) - return res.first == tesSUCCESS; - else - return res.first == tefFAILURE || res.first == temDISABLED; -} bool VerifyPubKeyAndSeq( @@ -1975,34 +1916,6 @@ VerifyPubKeyAndSeq( return nUnlLedgerSeq.size() == 0; } -std::size_t -countTx(std::shared_ptr const& txSet) -{ - /*uint64_t counter = 0; - if (txSet) - for (auto const& item : *txSet) - { - - SerialIter sit(item.slice()); - auto tx = std::make_shared(SerialIter{sit.getSlice(sit.getVLDataLength())}); - - if (tx->getFieldU16(sfTransactionType) == ttUNL_MODIFY) - counter++; - } - */ - - std::size_t count = 0; - for (auto i = txSet->begin(); i != txSet->end(); ++i) - { - // RH TODO: why does the above parse?? - auto raw = i->slice(); - if (raw[0] == 0x12U && raw[1] == 0 && raw[2] == 0x66U) - count++; - } - return count; -}; - std::vector createPublicKeys(std::size_t n) { @@ -2019,16 +1932,5 @@ createPublicKeys(std::size_t n) return keys; } -STTx -createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey) -{ - auto fill = [&](auto& obj) { - obj.setFieldU8(sfUNLModifyDisabling, disabling ? 1 : 0); - obj.setFieldU32(sfLedgerSequence, seq); - obj.setFieldVL(sfUNLModifyValidator, txKey); - }; - return STTx(ttUNL_MODIFY, fill); -} - } // namespace test } // namespace ripple diff --git a/src/test/consensus/UNLReport_test.cpp b/src/test/consensus/UNLReport_test.cpp index a580f1845e8..e2a3021bd85 100644 --- a/src/test/consensus/UNLReport_test.cpp +++ b/src/test/consensus/UNLReport_test.cpp @@ -51,23 +51,6 @@ namespace test { // * are put in their existing unit test files. // */ -// /** -// * Test the size of the negative UNL in a ledger, -// * also test if the ledger has ToDisalbe and/or ToReEnable -// * -// * @param l the ledger -// * @param size the expected negative UNL size -// * @param hasToDisable if expect ToDisable in ledger -// * @param hasToReEnable if expect ToDisable in ledger -// * @return true if meet all three expectation -// */ -inline bool -negUnlSizeTest( - std::shared_ptr const& l, - size_t size, - bool hasToDisable, - bool hasToReEnable); - // /** // * Try to apply a ttUNL_MODIFY Tx, and test the apply result // * @@ -110,9 +93,6 @@ countUNLRTx(std::shared_ptr const& txSet); std::vector const keys = { "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC1"}; -std::unique_ptr -makeNetworkVLConfig(uint32_t networkID, std::vector keys); - /** * Verify if the UNL report exists * @@ -165,38 +145,6 @@ createUNLRTx( PublicKey const& importKey, PublicKey const& valKey); -/** - * Count the number of Tx in a TxSet - * - * @param txSet the TxSet - * @return the number of Tx - */ -inline std::size_t -countTx(std::shared_ptr const& txSet); - -/** - * Create ttUNL_MODIFY Tx - * - * @param disabling disabling or re-enabling a validator - * @param seq current ledger seq - * @param txKey the public key of the validator - * @return the ttUNL_MODIFY Tx - */ -inline STTx -createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey); - -/** - * Try to apply a ttUNL_MODIFY Tx, and test the apply result - * - * @param env the test environment - * @param view the OpenView of the ledger - * @param tx the ttUNL_MODIFY Tx - * @param pass if the Tx should be applied successfully - * @return true if meet the expectation of apply result - */ -inline bool -applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass); - class UNLReport_test : public beast::unit_test::suite { // Import VL Keys @@ -345,7 +293,10 @@ class UNLReport_test : public beast::unit_test::suite // telIMPORT_VL_KEY_NOT_RECOGNISED { test::jtx::Env env{ - *this, makeNetworkVLConfig(21337, keys), features, nullptr}; + *this, + jtx::network::makeNetworkVLConfig(21337, keys), + features, + nullptr}; auto l = std::make_shared( create_genesis, @@ -374,7 +325,10 @@ class UNLReport_test : public beast::unit_test::suite // SUCCESS { test::jtx::Env env{ - *this, makeNetworkVLConfig(21337, keys), features, nullptr}; + *this, + jtx::network::makeNetworkVLConfig(21337, keys), + features, + nullptr}; auto l = std::make_shared( create_genesis, @@ -413,7 +367,10 @@ class UNLReport_test : public beast::unit_test::suite using namespace jtx; test::jtx::Env env{ - *this, makeNetworkVLConfig(21337, keys), features, nullptr}; + *this, + jtx::network::makeNetworkVLConfig(21337, keys), + features, + nullptr}; std::vector ivlKeys; for (auto const& strPk : _ivlKeys) @@ -674,7 +631,8 @@ struct URNetworkHistory URNetworkHistory(beast::unit_test::suite& suite, Parameter const& p) : env(suite, - p.withVL ? makeNetworkVLConfig(21337, keys) : jtx::envconfig(), + p.withVL ? jtx::network::makeNetworkVLConfig(21337, keys) + : jtx::envconfig(), jtx::supported_amendments() | featureNegativeUNL) , param(p) , validations(env.app().getValidations()) @@ -728,8 +686,8 @@ struct URNetworkHistory OpenView accum(&*l); if (l->negativeUNL().size() < param.negUNLSize) { - auto tx = createTx(true, l->seq(), UNLKeys[nidx]); - if (!applyAndTestResult(env, accum, tx, true)) + auto tx = unl::createTx(true, l->seq(), UNLKeys[nidx]); + if (!unl::applyAndTestResult(env, accum, tx, true)) break; ++nidx; } @@ -737,15 +695,15 @@ struct URNetworkHistory { if (param.hasToDisable) { - auto tx = createTx(true, l->seq(), UNLKeys[nidx]); - if (!applyAndTestResult(env, accum, tx, true)) + auto tx = unl::createTx(true, l->seq(), UNLKeys[nidx]); + if (!unl::applyAndTestResult(env, accum, tx, true)) break; ++nidx; } if (param.hasToReEnable) { - auto tx = createTx(false, l->seq(), UNLKeys[0]); - if (!applyAndTestResult(env, accum, tx, true)) + auto tx = unl::createTx(false, l->seq(), UNLKeys[0]); + if (!unl::applyAndTestResult(env, accum, tx, true)) break; } } @@ -753,7 +711,7 @@ struct URNetworkHistory } l->updateSkipList(); } - return negUnlSizeTest( + return unl::negUnlSizeTest( l, param.negUNLSize, param.hasToDisable, param.hasToReEnable); } @@ -854,7 +812,8 @@ voteAndCheckUNLR( vote.doVoting( history.lastLedger(), history.UNLKeySet, history.validations, txSet); - return countUNLRTx(txSet) == expectReport && countTx(txSet) >= expectModify; + return countUNLRTx(txSet) == expectReport && + unl::countTx(txSet) >= expectModify; } /* @@ -1235,22 +1194,6 @@ BEAST_DEFINE_TESTSUITE(UNLReportVoteNewValidator, consensus, ripple); /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// -inline bool -negUnlSizeTest( - std::shared_ptr const& l, - size_t size, - bool hasToDisable, - bool hasToReEnable) -{ - bool sameSize = l->negativeUNL().size() == size; - bool sameToDisable = - (l->validatorToDisable() != std::nullopt) == hasToDisable; - bool sameToReEnable = - (l->validatorToReEnable() != std::nullopt) == hasToReEnable; - - return sameSize && sameToDisable && sameToReEnable; -} - bool applyAndTestUNLRResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass) { @@ -1313,38 +1256,6 @@ countUNLRTx(std::shared_ptr const& txSet) return count; }; -std::unique_ptr -makeNetworkVLConfig(uint32_t networkID, std::vector keys) -{ - using namespace jtx; - return envconfig([&](std::unique_ptr cfg) { - cfg->NETWORK_ID = networkID; - Section config; - config.append( - {"reference_fee = 10", - "account_reserve = 1000000", - "owner_reserve = 200000"}); - auto setup = setup_FeeVote(config); - cfg->FEES = setup; - - for (auto const& strPk : keys) - { - auto pkHex = strUnHex(strPk); - if (!pkHex) - Throw( - "Import VL Key '" + strPk + "' was not valid hex."); - - auto const pkType = publicKeyType(makeSlice(*pkHex)); - if (!pkType) - Throw( - "Import VL Key '" + strPk + "' was not a valid key type."); - - cfg->IMPORT_VL_KEYS.emplace(strPk, makeSlice(*pkHex)); - } - return cfg; - }); -} - bool hasUNLReport(jtx::Env const& env) { @@ -1412,54 +1323,5 @@ createUNLRTx( return STTx(ttUNL_REPORT, fill); } -inline STTx -createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey) -{ - auto fill = [&](auto& obj) { - obj.setFieldU8(sfUNLModifyDisabling, disabling ? 1 : 0); - obj.setFieldU32(sfLedgerSequence, seq); - obj.setFieldVL(sfUNLModifyValidator, txKey); - }; - return STTx(ttUNL_MODIFY, fill); -} - -inline std::size_t -countTx(std::shared_ptr const& txSet) -{ - /*uint64_t counter = 0; - if (txSet) - for (auto const& item : *txSet) - { - - SerialIter sit(item.slice()); - auto tx = std::make_shared(SerialIter{sit.getSlice(sit.getVLDataLength())}); - - if (tx->getFieldU16(sfTransactionType) == ttUNL_MODIFY) - counter++; - } - */ - - std::size_t count = 0; - for (auto i = txSet->begin(); i != txSet->end(); ++i) - { - // RH TODO: why does the above parse?? - auto raw = i->slice(); - if (raw[0] == 0x12U && raw[1] == 0 && raw[2] == 0x66U) - count++; - } - return count; -}; - -inline bool -applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass) -{ - auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); - if (pass) - return res.first == tesSUCCESS; - else - return res.first == tefFAILURE || res.first == temDISABLED; -} - } // namespace test } // namespace ripple \ No newline at end of file diff --git a/src/test/jtx.h b/src/test/jtx.h index 03aa39aac48..39d4a966207 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -70,6 +70,7 @@ #include #include #include +#include #include #include diff --git a/src/test/jtx/impl/unl.cpp b/src/test/jtx/impl/unl.cpp new file mode 100644 index 00000000000..41cbd50b0af --- /dev/null +++ b/src/test/jtx/impl/unl.cpp @@ -0,0 +1,95 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 XRPL Labs + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS 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 + +namespace ripple { +namespace test { +namespace unl { + +bool +negUnlSizeTest( + std::shared_ptr const& l, + size_t size, + bool hasToDisable, + bool hasToReEnable) +{ + bool sameSize = l->negativeUNL().size() == size; + bool sameToDisable = + (l->validatorToDisable() != std::nullopt) == hasToDisable; + bool sameToReEnable = + (l->validatorToReEnable() != std::nullopt) == hasToReEnable; + + return sameSize && sameToDisable && sameToReEnable; +} + +bool +applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass) +{ + auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); + if (pass) + return res.first == tesSUCCESS; + else + return res.first == tefFAILURE || res.first == temDISABLED; +} + +std::size_t +countTx(std::shared_ptr const& txSet) +{ + /*uint64_t counter = 0; + if (txSet) + for (auto const& item : *txSet) + { + + SerialIter sit(item.slice()); + auto tx = std::make_shared(SerialIter{sit.getSlice(sit.getVLDataLength())}); + + if (tx->getFieldU16(sfTransactionType) == ttUNL_MODIFY) + counter++; + } + */ + + std::size_t count = 0; + for (auto i = txSet->begin(); i != txSet->end(); ++i) + { + // RH TODO: why does the above parse?? + auto raw = i->slice(); + if (raw[0] == 0x12U && raw[1] == 0 && raw[2] == 0x66U) + count++; + } + return count; +}; + +STTx +createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey) +{ + auto fill = [&](auto& obj) { + obj.setFieldU8(sfUNLModifyDisabling, disabling ? 1 : 0); + obj.setFieldU32(sfLedgerSequence, seq); + obj.setFieldVL(sfUNLModifyValidator, txKey); + }; + return STTx(ttUNL_MODIFY, fill); +} + +} // namespace unl +} // namespace test +} // namespace ripple diff --git a/src/test/jtx/unl.h b/src/test/jtx/unl.h new file mode 100644 index 00000000000..68a30cc6b0d --- /dev/null +++ b/src/test/jtx/unl.h @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 XRPL Labs + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TEST_JTX_UNL_H_INCLUDED +#define RIPPLE_TEST_JTX_UNL_H_INCLUDED + +#include +#include +#include + +namespace ripple { +namespace test { + +namespace unl { + +/** + * Test the size of the negative UNL in a ledger, + * also test if the ledger has ToDisalbe and/or ToReEnable + * + * @param l the ledger + * @param size the expected negative UNL size + * @param hasToDisable if expect ToDisable in ledger + * @param hasToReEnable if expect ToDisable in ledger + * @return true if meet all three expectation + */ +bool +negUnlSizeTest( + std::shared_ptr const& l, + size_t size, + bool hasToDisable, + bool hasToReEnable); + +/** + * Try to apply a ttUNL_MODIFY Tx, and test the apply result + * + * @param env the test environment + * @param view the OpenView of the ledger + * @param tx the ttUNL_MODIFY Tx + * @param pass if the Tx should be applied successfully + * @return true if meet the expectation of apply result + */ +bool +applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass); + +/** + * Count the number of Tx in a TxSet + * + * @param txSet the TxSet + * @return the number of Tx + */ +std::size_t +countTx(std::shared_ptr const& txSet); + +/** + * Create ttUNL_MODIFY Tx + * + * @param disabling disabling or re-enabling a validator + * @param seq current ledger seq + * @param txKey the public key of the validator + * @return the ttUNL_MODIFY Tx + */ +STTx +createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey); + +} // namespace unl + +} // namespace test +} // namespace ripple + +#endif // RIPPLE_TEST_JTX_UNL_H_INCLUDED