Compare commits

...

8 Commits

Author SHA1 Message Date
Denis Angell
b926e2e2f6 update tsh 2023-12-18 13:29:20 +01:00
Denis Angell
2876ee146d add nftoken 2023-12-15 14:34:52 +01:00
Denis Angell
94fe1f7b29 clang-format 2023-12-15 14:27:10 +01:00
Denis Angell
2d93c50bcc refactor tsh 2023-12-15 14:24:23 +01:00
Denis Angell
a9f7bc81c3 Merge branch 'fixXahauV1' into fxv1-tsh 2023-12-15 12:30:21 +01:00
Denis Angell
10ae534300 clang-format 2023-12-15 12:28:25 +01:00
Denis Angell
3b353e146f fixup for offer sequence 2023-12-12 16:50:41 +01:00
Denis Angell
7295577cdd fxv1-tsh 2023-12-12 16:17:52 +01:00
5 changed files with 351 additions and 372 deletions

View File

@@ -45,43 +45,6 @@ public:
};
using namespace ripple;
static const std::map<uint16_t, uint8_t> TSHAllowances = {
{ttPAYMENT, tshROLLBACK},
{ttESCROW_CREATE, tshROLLBACK},
{ttESCROW_FINISH, tshROLLBACK},
{ttACCOUNT_SET, tshNONE},
{ttESCROW_CANCEL, tshCOLLECT},
{ttREGULAR_KEY_SET, tshROLLBACK},
{ttOFFER_CREATE, tshCOLLECT},
{ttOFFER_CANCEL, tshNONE},
{ttTICKET_CREATE, tshNONE},
{ttSIGNER_LIST_SET, tshROLLBACK},
{ttPAYCHAN_CREATE, tshROLLBACK},
{ttPAYCHAN_FUND, tshCOLLECT},
{ttPAYCHAN_CLAIM, tshCOLLECT},
{ttCHECK_CREATE, tshROLLBACK},
{ttCHECK_CASH, tshROLLBACK},
{ttCHECK_CANCEL, tshCOLLECT},
{ttDEPOSIT_PREAUTH, tshROLLBACK},
{ttTRUST_SET, tshCOLLECT},
{ttACCOUNT_DELETE, tshROLLBACK},
{ttHOOK_SET, tshNONE},
{ttNFTOKEN_MINT, tshROLLBACK},
{ttNFTOKEN_BURN, tshCOLLECT},
{ttNFTOKEN_CREATE_OFFER, tshROLLBACK},
{ttNFTOKEN_CANCEL_OFFER, tshCOLLECT},
{ttNFTOKEN_ACCEPT_OFFER, tshROLLBACK},
{ttCLAIM_REWARD, tshROLLBACK},
{ttINVOKE, tshROLLBACK},
{ttURITOKEN_MINT, tshNONE},
{ttURITOKEN_BURN, tshROLLBACK},
{ttURITOKEN_BUY, tshROLLBACK},
{ttURITOKEN_CREATE_SELL_OFFER, tshROLLBACK},
{ttURITOKEN_CANCEL_SELL_OFFER, tshNONE},
{ttIMPORT, tshROLLBACK},
{ttGENESIS_MINT, tshCOLLECT},
};
std::vector<std::pair<AccountID, bool>>
getTransactionalStakeHolders(STTx const& tx, ReadView const& rv);
} // namespace hook

View File

@@ -21,6 +21,8 @@
using namespace ripple;
namespace hook {
using namespace ripple;
std::vector<std::pair<AccountID, bool>>
getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
{
@@ -38,19 +40,10 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
uint16_t tt = tx.getFieldU16(sfTransactionType);
uint8_t tsh = tshNONE;
if (auto const& found = hook::TSHAllowances.find(tt);
found != hook::TSHAllowances.end())
tsh = found->second;
else
return {};
std::map<AccountID, std::pair<int, bool>> tshEntries;
int upto = 0;
bool canRollback = tsh & tshROLLBACK;
auto const ADD_TSH = [&otxnAcc, &tshEntries, &upto](
const AccountID& acc_r, bool rb) {
if (acc_r != *otxnAcc)
@@ -71,11 +64,14 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
return rv.read(keylet::nftoffer(*id));
};
bool const tshSTRONG = true; // tshROLLBACK
bool const tshWEAK = false; // tshCOLLECT
switch (tt)
{
case ttIMPORT: {
if (tx.isFieldPresent(sfIssuer))
ADD_TSH(tx.getAccountID(sfIssuer), canRollback);
ADD_TSH(tx.getAccountID(sfIssuer), tshSTRONG);
break;
}
@@ -103,13 +99,13 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
else if (*otxnAcc == owner)
{
// the owner burns their token, and the issuer is a weak TSH
ADD_TSH(issuer, canRollback);
ADD_TSH(issuer, tshWEAK);
}
else
{
// the issuer burns the owner's token, and the owner is a weak
// TSH
ADD_TSH(owner, canRollback);
ADD_TSH(owner, tshWEAK);
}
break;
@@ -126,16 +122,16 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
auto const owner = ut->getAccountID(sfOwner);
if (owner != tx.getAccountID(sfAccount))
if (owner != *otxnAcc)
{
// current owner is a strong TSH
ADD_TSH(owner, canRollback);
// current owner is a TSH
ADD_TSH(owner, tshSTRONG);
}
// issuer is also a strong TSH if the burnable flag is set
// issuer is also a TSH if the burnable flag is set
auto const issuer = ut->getAccountID(sfIssuer);
if (issuer != owner)
ADD_TSH(issuer, ut->getFlags() & lsfBurnable);
ADD_TSH(issuer, (ut->getFlags() & lsfBurnable) ? tshSTRONG : tshWEAK);
break;
}
@@ -154,22 +150,30 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
// issuer is a strong TSH if the burnable flag is set
if (issuer != owner)
ADD_TSH(issuer, ut->getFlags() & lsfBurnable);
ADD_TSH(issuer, (ut->getFlags() & lsfBurnable) ? tshSTRONG : tshWEAK);
// destination is a strong tsh
if (tx.isFieldPresent(sfDestination))
ADD_TSH(tx.getAccountID(sfDestination), canRollback);
ADD_TSH(tx.getAccountID(sfDestination), tshSTRONG);
break;
}
// NFT
case ttNFTOKEN_MINT:
case ttCLAIM_REWARD: {
if (tx.isFieldPresent(sfIssuer))
ADD_TSH(tx.getAccountID(sfIssuer), canRollback);
case ttURITOKEN_CANCEL_SELL_OFFER: {
Keylet const id{ltURI_TOKEN, tx.getFieldH256(sfURITokenID)};
if (!rv.exists(id))
return {};
auto const ut = rv.read(id);
if (!ut || ut->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN)
return {};
// destination is weak tsh
if (ut->isFieldPresent(sfDestination))
ADD_TSH(ut->getAccountID(sfDestination), tshWEAK);
break;
};
}
case ttNFTOKEN_BURN:
case ttNFTOKEN_CREATE_OFFER: {
@@ -190,7 +194,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
ADD_TSH(issuer, issuerCanRollback);
if (hasOwner)
ADD_TSH(owner, canRollback);
ADD_TSH(owner, tshWEAK);
break;
}
@@ -209,16 +213,16 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
if (bo)
{
ADD_TSH(bo->getAccountID(sfOwner), canRollback);
ADD_TSH(bo->getAccountID(sfOwner), tshSTRONG);
if (bo->isFieldPresent(sfDestination))
ADD_TSH(bo->getAccountID(sfDestination), canRollback);
ADD_TSH(bo->getAccountID(sfDestination), tshSTRONG);
}
if (so)
{
ADD_TSH(so->getAccountID(sfOwner), canRollback);
ADD_TSH(so->getAccountID(sfOwner), tshSTRONG);
if (so->isFieldPresent(sfDestination))
ADD_TSH(so->getAccountID(sfDestination), canRollback);
ADD_TSH(so->getAccountID(sfDestination), tshSTRONG);
}
break;
@@ -234,10 +238,10 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
auto const offer = getNFTOffer(offerID, rv);
if (offer)
{
ADD_TSH(offer->getAccountID(sfOwner), canRollback);
ADD_TSH(offer->getAccountID(sfOwner), tshWEAK);
if (offer->isFieldPresent(sfDestination))
ADD_TSH(
offer->getAccountID(sfDestination), canRollback);
offer->getAccountID(sfDestination), tshWEAK);
// issuer can't stop people canceling their offers, but can
// get weak executions
@@ -249,9 +253,14 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
break;
}
case ttNFTOKEN_MINT:
case ttCLAIM_REWARD: {
if (tx.isFieldPresent(sfIssuer))
ADD_TSH(tx.getAccountID(sfIssuer), tshSTRONG);
break;
};
// self transactions
case ttURITOKEN_MINT:
case ttURITOKEN_CANCEL_SELL_OFFER:
case ttACCOUNT_SET:
case ttOFFER_CANCEL:
case ttTICKET_CREATE:
@@ -264,18 +273,19 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
case ttREGULAR_KEY_SET: {
if (!tx.isFieldPresent(sfRegularKey))
return {};
ADD_TSH(tx.getAccountID(sfRegularKey), canRollback);
ADD_TSH(tx.getAccountID(sfRegularKey), tshSTRONG);
break;
}
case ttDEPOSIT_PREAUTH: {
if (!tx.isFieldPresent(sfAuthorize))
return {};
ADD_TSH(tx.getAccountID(sfAuthorize), canRollback);
ADD_TSH(tx.getAccountID(sfAuthorize), tshSTRONG);
break;
}
// simple two party transactions
case ttURITOKEN_MINT:
case ttPAYMENT:
case ttESCROW_CREATE:
case ttCHECK_CREATE:
@@ -283,7 +293,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
case ttPAYCHAN_CREATE:
case ttINVOKE: {
if (destAcc)
ADD_TSH(*destAcc, canRollback);
ADD_TSH(*destAcc, tshSTRONG);
break;
}
@@ -294,24 +304,32 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
auto const& lim = tx.getFieldAmount(sfLimitAmount);
AccountID const& issuer = lim.getIssuer();
ADD_TSH(issuer, canRollback);
ADD_TSH(issuer, tshWEAK);
break;
}
case ttESCROW_CANCEL:
case ttESCROW_FINISH: {
if (!tx.isFieldPresent(sfOwner) ||
!tx.isFieldPresent(sfOfferSequence))
if (!tx.isFieldPresent(sfOwner))
return {};
auto escrow = rv.read(keylet::escrow(
tx.getAccountID(sfOwner), tx.getFieldU32(sfOfferSequence)));
if (!tx.isFieldPresent(sfOfferSequence) &&
!tx.isFieldPresent(sfEscrowID))
return {};
std::optional<uint256> escrowID = tx[~sfEscrowID];
std::optional<std::uint32_t> offerSeq = tx[~sfOfferSequence];
Keylet k = escrowID
? Keylet(ltESCROW, *escrowID)
: keylet::escrow(tx.getAccountID(sfOwner), *offerSeq);
auto escrow = rv.read(k);
if (!escrow)
return {};
ADD_TSH(escrow->getAccountID(sfAccount), true);
ADD_TSH(escrow->getAccountID(sfDestination), canRollback);
ADD_TSH(escrow->getAccountID(sfAccount), tshWEAK);
ADD_TSH(escrow->getAccountID(sfDestination), tshWEAK);
break;
}
@@ -324,8 +342,8 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
if (!chan)
return {};
ADD_TSH(chan->getAccountID(sfAccount), true);
ADD_TSH(chan->getAccountID(sfDestination), canRollback);
ADD_TSH(chan->getAccountID(sfAccount), tshWEAK);
ADD_TSH(chan->getAccountID(sfDestination), tshWEAK);
break;
}
@@ -338,8 +356,8 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
if (!check)
return {};
ADD_TSH(check->getAccountID(sfAccount), true);
ADD_TSH(check->getAccountID(sfDestination), canRollback);
ADD_TSH(check->getAccountID(sfAccount), tshWEAK);
ADD_TSH(check->getAccountID(sfDestination), tshWEAK);
break;
}
@@ -349,7 +367,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
STArray const& signerEntries = tx.getFieldArray(sfSignerEntries);
for (auto const& entryObj : signerEntries)
if (entryObj.isFieldPresent(sfAccount))
ADD_TSH(entryObj.getAccountID(sfAccount), canRollback);
ADD_TSH(entryObj.getAccountID(sfAccount), tshSTRONG);
break;
}
@@ -361,7 +379,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
{
if (mint.isFieldPresent(sfDestination))
{
ADD_TSH(mint.getAccountID(sfDestination), canRollback);
ADD_TSH(mint.getAccountID(sfDestination), tshWEAK);
}
}
}

View File

@@ -1437,15 +1437,13 @@ Transactor::addWeakTSHFromSandbox(detail::ApplyViewBase const& pv)
TER
Transactor::doTSH(
bool strong, // only strong iff true, only weak iff false
std::vector<std::pair<AccountID, bool>> tsh,
hook::HookStateMap& stateMap,
std::vector<hook::HookResult>& results,
std::shared_ptr<STObject const> const& provisionalMeta)
{
auto& view = ctx_.view();
std::vector<std::pair<AccountID, bool>> tsh =
hook::getTransactionalStakeHolders(ctx_.tx, view);
// add the extra TSH marked out by the specific transactor (if applicable)
if (!strong)
for (auto& weakTsh : additionalWeakTSH_)
@@ -1463,22 +1461,37 @@ Transactor::doTSH(
// obviously we will never execute OTXN account
// as a TSH because they already had first execution
if (tshAccountID == account_)
{
JLOG(j_.trace()) << "doTSH: tshAccountID == account_";
continue;
}
if (alreadyProcessed.find(tshAccountID) != alreadyProcessed.end())
{
JLOG(j_.trace()) << "doTSH: alreadyProcessed.find(tshAccountID) != "
"alreadyProcessed.end()";
continue;
}
alreadyProcessed.emplace(tshAccountID);
// only process the relevant ones
if ((!canRollback && strong) || (canRollback && !strong))
{
JLOG(j_.trace()) << "doTSH: (!canRollback && strong) || "
"(canRollback && !strong)";
continue;
}
auto klTshHook = keylet::hook(tshAccountID);
auto tshHook = view.read(klTshHook);
if (!(tshHook && tshHook->isFieldPresent(sfHooks)))
{
JLOG(j_.trace())
<< "doTSH: !(tshHook && tshHook->isFieldPresent(sfHooks))";
continue;
}
// scoping here allows tshAcc to leave scope before
// hook execution, which is probably safer
@@ -1486,7 +1499,10 @@ Transactor::doTSH(
// check if the TSH exists and/or has any hooks
auto tshAcc = view.peek(keylet::account(tshAccountID));
if (!tshAcc)
{
JLOG(j_.trace()) << "doTSH: !tshAcc";
continue;
}
// compute and deduct fees for the TSH if applicable
XRPAmount tshFeeDrops =
@@ -1494,7 +1510,10 @@ Transactor::doTSH(
// no hooks to execute, skip tsh
if (tshFeeDrops == 0)
{
JLOG(j_.trace()) << "doTSH: tshFeeDrops == 0";
continue;
}
assert(tshFeeDrops >= beast::zero);
@@ -1698,6 +1717,9 @@ Transactor::operator()()
// application to the ledger
std::map<AccountID, std::set<uint256>> aawMap;
std::vector<std::pair<AccountID, bool>> tsh =
hook::getTransactionalStakeHolders(ctx_.tx, ctx_.view());
// Pre-application (Strong TSH) Hooks are executed here
// These TSH have the right to rollback.
// Weak TSH and callback are executed post-application.
@@ -1726,7 +1748,7 @@ Transactor::operator()()
// (who have the right to rollback the txn), any weak TSH will be
// executed after doApply has been successful (callback as well)
result = doTSH(true, stateMap, hookResults, {});
result = doTSH(true, tsh, stateMap, hookResults, {});
}
// write state if all chains executed successfully
@@ -1969,7 +1991,7 @@ Transactor::operator()()
hook::HookStateMap stateMap;
std::vector<hook::HookResult> weakResults;
doTSH(false, stateMap, weakResults, proMeta);
doTSH(false, tsh, stateMap, weakResults, proMeta);
// execute any hooks that nominated for 'again as weak'
for (auto const& [accID, hookHashes] : aawMap)

View File

@@ -188,6 +188,7 @@ protected:
TER
doTSH(
bool strong, // only do strong TSH iff true, otheriwse only weak
std::vector<std::pair<AccountID, bool>> tsh,
hook::HookStateMap& stateMap,
std::vector<hook::HookResult>& result,
std::shared_ptr<STObject const> const& provisionalMeta);

View File

@@ -223,11 +223,11 @@ private:
}
// 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 |
// | otxn | tsh | cancel | cash | create |
// | A | A | S | N | S |
// | A | D | W | N | S |
// | D | D | S | S | N |
// | D | A | W | W | N |
static uint256
getCheckIndex(AccountID const& account, std::uint32_t uSequence)
{
@@ -285,7 +285,7 @@ private:
// otxn: account
// tsh destination
// w/s: none
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -323,7 +323,9 @@ private:
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
@@ -369,7 +371,7 @@ private:
// otxn: dest
// tsh account
// w/s: strong
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -381,13 +383,17 @@ private:
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, overrideFlag)}}, 0),
env(hook(account, {{hso(TshHook, collectFlag)}}, 0),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -405,7 +411,7 @@ private:
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");
BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001");
}
}
@@ -462,7 +468,7 @@ private:
// otxn: dest
// tsh account
// w/s: strong
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -504,7 +510,7 @@ private:
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");
BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001");
}
}
@@ -782,7 +788,7 @@ private:
// | A | A | S | S | S |
// | A | D | W | S | W |
// | D | D | S | N | S |
// | D | A | S | N | S |
// | D | A | W | N | S |
static uint256
getEscrowIndex(AccountID const& account, std::uint32_t uSequence)
@@ -898,7 +904,9 @@ private:
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
@@ -953,7 +961,7 @@ private:
// otxn: dest
// tsh account
// w/s: strong
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -1002,7 +1010,7 @@ private:
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");
BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001");
}
}
@@ -1129,11 +1137,9 @@ private:
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");
auto const execution = executions[0u][sfHookExecution.jsonName];
BEAST_EXPECT(execution[sfHookResult.jsonName] == 3);
BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001");
}
// otxn: dest
@@ -1249,7 +1255,9 @@ private:
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");
}
}
@@ -1407,7 +1415,7 @@ private:
// otxn: account
// tsh dest
// w/s: strong
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -1420,8 +1428,8 @@ private:
env.close();
// set tsh collect
// env(fset(dest, asfTshCollect));
// env.close();
env(fset(dest, asfTshCollect));
env.close();
// create escrow
auto const seq1 = env.seq(account);
@@ -1433,7 +1441,7 @@ private:
env.close();
// set tsh hook
env(hook(dest, {{hso(TshHook, overrideFlag)}}, 0),
env(hook(dest, {{hso(TshHook, collectFlag)}}, 0),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -1453,7 +1461,7 @@ private:
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");
BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001");
}
// otxn: dest
@@ -1505,7 +1513,7 @@ private:
// otxn: dest
// tsh account
// w/s: strong
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -1517,6 +1525,10 @@ private:
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;
@@ -1527,7 +1539,7 @@ private:
env.close();
// set tsh hook
env(hook(account, {{hso(TshHook, overrideFlag)}}, 0),
env(hook(account, {{hso(TshHook, collectFlag)}}, 0),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -1547,7 +1559,7 @@ private:
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");
BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001");
}
}
@@ -1569,6 +1581,11 @@ private:
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
features};
// Env env{*this, envconfig(), features, nullptr,
// // beast::severities::kWarning
// beast::severities::kTrace
// };
auto const account = Account("alice");
auto const dest = Account("bob");
env.fund(XRP(1000), account, dest);
@@ -1667,7 +1684,9 @@ private:
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
@@ -1777,7 +1796,9 @@ private:
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");
}
}
@@ -2187,7 +2208,7 @@ private:
// Offer
// | otxn | tsh | cancel | create |
// | A | A | S | S |
// | A | C | N | N |
// | A | C | N | W |
void
testOfferCancelTSH(FeatureBitset features)
@@ -2309,13 +2330,15 @@ private:
auto const gw = Account{"gateway"};
auto const USD = gw["USD"];
env.fund(XRP(1000), account, cross, gw);
env.trust(USD(10000), cross);
env(pay(gw, cross, USD(1000)));
env.close();
// set tsh collect
env(fset(cross, asfTshCollect));
// gw create offer
env(offer(gw, USD(1000), XRP(1000)));
// cross create offer
env(offer(cross, XRP(1000), USD(1000)));
env.close();
// set tsh hook
@@ -2337,7 +2360,9 @@ private:
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");
}
}
@@ -2482,7 +2507,7 @@ private:
// | A | A | S | S | S |
// | A | D | W | S | W |
// | D | D | S | N | N |
// | D | A | S | N | N |
// | D | A | W | N | N |
static uint256
channel(
@@ -2548,6 +2573,7 @@ private:
// claim paychannel
env(paychan::claim(account, chan, reqBal, authAmt),
txflags(tfClose),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -2601,6 +2627,7 @@ private:
// claim paychannel
env(paychan::claim(account, chan, reqBal, authAmt),
txflags(tfClose),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -2654,6 +2681,7 @@ private:
signClaimAuth(account.pk(), account.sk(), chan, authAmt);
env(paychan::claim(
dest, chan, reqBal, authAmt, Slice(sig), account.pk()),
txflags(tfClose),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -2672,7 +2700,7 @@ private:
// otxn: dest
// tsh account
// w/s: strong
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -2684,6 +2712,9 @@ private:
env.fund(XRP(1000), account, dest);
env.close();
// set tsh collect
env(fset(account, asfTshCollect));
// create paychannel
auto const pk = account.pk();
auto const settleDelay = 100s;
@@ -2693,7 +2724,7 @@ private:
env.close();
// set tsh hook
env(hook(account, {{hso(TshHook, overrideFlag)}}, 0),
env(hook(account, {{hso(TshHook, collectFlag)}}, 0),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -2707,6 +2738,7 @@ private:
signClaimAuth(account.pk(), account.sk(), chan, authAmt);
env(paychan::claim(
dest, chan, reqBal, authAmt, Slice(sig), account.pk()),
txflags(tfClose),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -2720,7 +2752,7 @@ private:
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");
BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001");
}
}
@@ -3020,7 +3052,7 @@ private:
// otxn: account
// tsh dest
// w/s: weak
// w/s: strong
{
test::jtx::Env env{
*this,
@@ -3295,19 +3327,18 @@ private:
// | 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 | false | B | N | N | N | S | W
// | O | true | B | N | N | N | S | W
// | 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 | false | B | S | 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
// | I | true | B | S | N | N | N | N
// | B | true | O | N | N | ? | N | N
// | B | true | B | N | N | ? | N | N
void
testURITokenBurnTSH(FeatureBitset features)
{
@@ -3373,7 +3404,7 @@ private:
// otxn: owner
// flag: not burnable
// tsh issuer
// w/s: strong
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -3389,6 +3420,9 @@ private:
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),
@@ -3403,7 +3437,7 @@ private:
env.close();
// set tsh hook
env(hook(issuer, {{hso(TshHook, overrideFlag)}}, 0),
env(hook(issuer, {{hso(TshHook, collectFlag)}}, 0),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -3421,7 +3455,7 @@ private:
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");
BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001");
}
// otxn: owner
@@ -3482,7 +3516,7 @@ private:
// otxn: owner
// flag: burnable
// tsh issuer
// w/s: strong
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -3498,6 +3532,9 @@ private:
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),
@@ -3513,7 +3550,7 @@ private:
env.close();
// set tsh hook
env(hook(issuer, {{hso(TshHook, overrideFlag)}}, 0),
env(hook(issuer, {{hso(TshHook, collectFlag)}}, 0),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -3531,13 +3568,13 @@ private:
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");
BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001");
}
// otxn: issuer
// flag: burnable
// tsh owner
// w/s: strong
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -3553,6 +3590,9 @@ private:
auto const tid = uritoken::tokenid(issuer, uri);
std::string const hexid{strHex(tid)};
env(fset(owner, asfTshCollect));
env.close();
// mint uritoken
env(uritoken::mint(issuer, uri),
uritoken::dest(owner),
@@ -3568,7 +3608,7 @@ private:
env.close();
// set tsh hook
env(hook(owner, {{hso(TshHook, overrideFlag)}}, 0),
env(hook(owner, {{hso(TshHook, collectFlag)}}, 0),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -3586,7 +3626,7 @@ private:
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");
BEAST_EXPECT(execution[sfHookReturnString.jsonName] == "00000001");
}
// otxn: issuer
@@ -3653,213 +3693,8 @@ private:
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
// flag: not burnable
// tsh owner
// w/s: strong
{
@@ -3925,8 +3760,9 @@ private:
}
// otxn: buyer
// tsh buyer
// w/s: strong
// flag: not burnable
// tsh issuer
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -3943,6 +3779,9 @@ private:
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),
@@ -3965,7 +3804,141 @@ private:
env.close();
// set tsh hook
env(hook(buyer, {{hso(TshHook, overrideFlag)}}, 0),
env(hook(issuer, {{hso(TshHook, collectFlag)}}, 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] == "00000001");
}
// otxn: buyer
// 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)),
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
// 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)),
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(issuer, {{hso(TshHook, overrideFlag)}}, 0),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -4063,7 +4036,7 @@ private:
// otxn: owner
// flag: not burnable
// tsh buyer
// w/s: none
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -4120,13 +4093,15 @@ private:
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: owner
// flag: burnable
// tsh buyer
// w/s: none
// w/s: weak
{
test::jtx::Env env{
*this,
@@ -4184,7 +4159,9 @@ private:
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: owner
@@ -4675,7 +4652,7 @@ private:
// otxn: issuer
// flag: not burnable
// tsh buyer
// w/s: none
// w/s: strong
{
test::jtx::Env env{
*this,
@@ -4687,15 +4664,12 @@ private:
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),
env(hook(buyer, {{hso(TshHook, overrideFlag)}}, 0),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -4715,7 +4689,9 @@ private:
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] == "00000000");
}
// otxn: issuer
@@ -4767,7 +4743,7 @@ private:
// otxn: issuer
// flag: burnable
// tsh buyer
// w/s: none
// w/s: strong
{
test::jtx::Env env{
*this,
@@ -4779,15 +4755,12 @@ private:
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),
env(hook(buyer, {{hso(TshHook, overrideFlag)}}, 0),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
@@ -4808,7 +4781,9 @@ private:
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] == "00000000");
}
}