//------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled Copyright (c) 2025 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 #include #include namespace ripple { namespace test { struct Touch_test : public beast::unit_test::suite { private: struct TestLedgerData { std::string txType; std::string result; }; void validateTouch( jtx::Env& env, jtx::Account const& account, TestLedgerData const& testCase) { Json::Value params; params[jss::account] = account.human(); params[jss::limit] = 1; params[jss::ledger_index_min] = -1; params[jss::ledger_index_max] = -1; auto const jrr = env.rpc("json", "account_tx", to_string(params)); auto const transactions = jrr[jss::result][jss::transactions]; BEAST_EXPECT(transactions.size() == 1); BEAST_EXPECT( transactions[0u][jss::tx][jss::TransactionType] == testCase.txType); BEAST_EXPECT( transactions[0u][jss::meta][sfTransactionResult.jsonName] == testCase.result); } void testAccountSet(FeatureBitset features) { using namespace test::jtx; using namespace std::literals; testcase("account set"); test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); env.fund(XRP(1000), alice); env.close(); // alice set env(fset(alice, asfDefaultRipple), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"AccountSet", "tesSUCCESS"}); } void testAccountDelete(FeatureBitset features) { using namespace test::jtx; using namespace std::literals; testcase("account delete"); test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // AccountDelete incLgrSeqForAccDel(env, alice); env(acctdelete(alice, bob), fee(env.current()->fees().reserve), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"AccountDelete", "tesSUCCESS"}); validateTouch(env, bob, {"AccountDelete", "tesSUCCESS"}); } static uint256 getCheckIndex(AccountID const& alice, std::uint32_t uSequence) { return keylet::check(alice, uSequence).key; } void testCheckCancel(FeatureBitset features) { testcase("check cancel"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // create check uint256 const checkId{getCheckIndex(alice, env.seq(alice))}; env(check::create(alice, bob, XRP(100)), ter(tesSUCCESS)); env.close(); // cancel check env(check::cancel(alice, checkId), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"CheckCancel", "tesSUCCESS"}); validateTouch(env, bob, {"CheckCancel", "tesSUCCESS"}); } void testCheckCash(FeatureBitset features) { testcase("check cash"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // create check uint256 const checkId{getCheckIndex(alice, env.seq(alice))}; env(check::create(alice, bob, XRP(100)), ter(tesSUCCESS)); env.close(); // cash check env(check::cash(bob, checkId, XRP(100)), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"CheckCash", "tesSUCCESS"}); validateTouch(env, bob, {"CheckCash", "tesSUCCESS"}); } void testCheckCreate(FeatureBitset features) { testcase("check create"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // create check env(check::create(alice, bob, XRP(100)), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"CheckCreate", "tesSUCCESS"}); validateTouch(env, bob, {"CheckCreate", "tesSUCCESS"}); } void testClaimReward(FeatureBitset features) { testcase("claim reward"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const issuer = Account("issuer"); env.fund(XRP(1000), alice, issuer); env.close(); // claim reward env(reward::claim(alice), reward::issuer(issuer), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"ClaimReward", "tesSUCCESS"}); auto const tt = env.current()->rules().enabled(featureTouch) ? "ClaimReward" : "AccountSet"; validateTouch(env, issuer, {tt, "tesSUCCESS"}); } void testDepositPreauth(FeatureBitset features) { testcase("deposit preauth"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // require authorization for deposits. env(fset(alice, asfDepositAuth)); // deposit preauth env(deposit::auth(alice, bob), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"DepositPreauth", "tesSUCCESS"}); validateTouch(env, bob, {"DepositPreauth", "tesSUCCESS"}); } void testEscrowCancel(FeatureBitset features) { testcase("escrow cancel"); using namespace jtx; using namespace std::chrono; using namespace std::literals; { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // create escrow auto const seq1 = env.seq(alice); NetClock::time_point const finishTime = env.now() + 1s; NetClock::time_point const cancelTime = env.now() + 2s; auto createTx = escrow::create(alice, bob, XRP(10)); createTx[sfFinishAfter.jsonName] = finishTime.time_since_epoch().count(); createTx[sfCancelAfter.jsonName] = cancelTime.time_since_epoch().count(); env(createTx, ter(tesSUCCESS)); env.close(); // cancel escrow env(escrow::cancel(alice, alice, seq1), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"EscrowCancel", "tesSUCCESS"}); validateTouch(env, bob, {"EscrowCancel", "tesSUCCESS"}); } { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // create escrow auto const seq1 = env.seq(alice); NetClock::time_point const finishTime = env.now() + 1s; NetClock::time_point const cancelTime = env.now() + 2s; auto createTx = escrow::create(alice, bob, XRP(10)); createTx[sfFinishAfter.jsonName] = finishTime.time_since_epoch().count(); createTx[sfCancelAfter.jsonName] = cancelTime.time_since_epoch().count(); env(createTx, ter(tesSUCCESS)); env.close(); // cancel escrow env(escrow::cancel(bob, alice, seq1), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"EscrowCancel", "tesSUCCESS"}); validateTouch(env, bob, {"EscrowCancel", "tesSUCCESS"}); } } void testEscrowCreate(FeatureBitset features) { testcase("escrow create"); using namespace jtx; using namespace std::chrono; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // create escrow NetClock::time_point const finishTime = env.now() + 1s; NetClock::time_point const cancelTime = env.now() + 2s; auto createTx = escrow::create(alice, bob, XRP(10)); createTx[sfFinishAfter.jsonName] = finishTime.time_since_epoch().count(); createTx[sfCancelAfter.jsonName] = cancelTime.time_since_epoch().count(); env(createTx, ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"EscrowCreate", "tesSUCCESS"}); validateTouch(env, bob, {"EscrowCreate", "tesSUCCESS"}); } void testEscrowFinish(FeatureBitset features) { testcase("escrow finish"); using namespace jtx; using namespace std::chrono; using namespace std::literals; { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // create escrow auto const seq1 = env.seq(alice); NetClock::time_point const finishTime = env.now() + 1s; auto createTx = escrow::create(alice, bob, XRP(10)); createTx[sfFinishAfter.jsonName] = finishTime.time_since_epoch().count(); env(createTx, ter(tesSUCCESS)); env.close(); // finish escrow env(escrow::finish(alice, alice, seq1), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"EscrowFinish", "tesSUCCESS"}); validateTouch(env, bob, {"EscrowFinish", "tesSUCCESS"}); } { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // create escrow auto const seq1 = env.seq(alice); NetClock::time_point const finishTime = env.now() + 1s; auto createTx = escrow::create(alice, bob, XRP(10)); createTx[sfFinishAfter.jsonName] = finishTime.time_since_epoch().count(); env(createTx, ter(tesSUCCESS)); env.close(); // finish escrow env(escrow::finish(bob, alice, seq1), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"EscrowFinish", "tesSUCCESS"}); validateTouch(env, bob, {"EscrowFinish", "tesSUCCESS"}); } } void testGenesisMint(FeatureBitset features) { testcase("genesis mint"); using namespace jtx; using namespace std::chrono; using namespace std::literals; test::jtx::Env env{ *this, network::makeNetworkConfig(21337, "10", "1000000", "200000"), features}; auto const alice = Account("alice"); auto const issuer = env.master; auto const bene = Account("bob"); env.fund(XRP(1000), alice, 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 mint hook on master env(hook(issuer, {{hso(genesis::MintTestHook, overrideFlag)}}, 0), fee(XRP(1)), ter(tesSUCCESS)); env.close(); env(invoke::invoke( alice, issuer, genesis::makeBlob({ {bene.id(), XRP(123).value(), std::nullopt, std::nullopt}, })), fee(XRP(10)), ter(tesSUCCESS)); env.close(); env.close(); // verify touch validateTouch(env, alice, {"Invoke", "tesSUCCESS"}); validateTouch(env, issuer, {"GenesisMint", "tesSUCCESS"}); validateTouch(env, bene, {"GenesisMint", "tesSUCCESS"}); } void testImport(FeatureBitset features) { testcase("import"); using namespace test::jtx; using namespace std::literals; std::vector const keys = { "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC" "1"}; test::jtx::Env env{ *this, network::makeNetworkVLConfig( 21337, keys, "10", "1000000", "200000"), features}; auto const alice = Account("alice"); auto const issuer = Account("bob"); env.fund(XRP(1000), alice, 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(); // import env(import::import(alice, import::loadXpop(ImportTCAccountSet::w_seed)), import::issuer(issuer), fee(XRP(1)), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"Import", "tesSUCCESS"}); auto const tt = env.current()->rules().enabled(featureTouch) ? "Import" : "AccountSet"; validateTouch(env, issuer, {tt, "tesSUCCESS"}); } void testInvoke(FeatureBitset features) { testcase("invoke"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // ttINVOKE env(invoke::invoke(alice), invoke::dest(bob), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"Invoke", "tesSUCCESS"}); auto const tt = env.current()->rules().enabled(featureTouch) ? "Invoke" : "AccountSet"; validateTouch(env, bob, {tt, "tesSUCCESS"}); } void testOfferCancel(FeatureBitset features) { testcase("offer cancel"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; env.fund(XRP(1000), alice, gw); env.close(); // gw create offer env(offer(gw, USD(1000), XRP(1000))); env.close(); // create offer auto const offerSeq = env.seq(alice); env(offer(alice, USD(1000), XRP(1000)), ter(tesSUCCESS)); env.close(); // cancel offer env(offer_cancel(alice, offerSeq), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"OfferCancel", "tesSUCCESS"}); } void testOfferCreate(FeatureBitset features) { testcase("offer create"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; env.fund(XRP(1000), alice, gw); env.close(); // gw create offer env(offer(gw, USD(1000), XRP(1000))); env.close(); // create offer env(offer(alice, USD(1000), XRP(1000)), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, gw, {"OfferCreate", "tesSUCCESS"}); validateTouch(env, alice, {"OfferCreate", "tesSUCCESS"}); } void testPayment(FeatureBitset features) { testcase("payment"); using namespace test::jtx; using namespace std::literals; { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account{"bob"}; env.fund(XRP(1000), alice, bob); env.close(); // payment env(pay(alice, bob, XRP(1)), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"Payment", "tesSUCCESS"}); validateTouch(env, bob, {"Payment", "tesSUCCESS"}); } { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account{"bob"}; auto const gw = Account{"gw"}; auto const USD = gw["USD"]; env.fund(XRP(1000), alice, bob, gw); env.close(); env(trust(alice, USD(100)), ter(tesSUCCESS)); env(trust(bob, USD(100)), ter(tesSUCCESS)); env.close(); env(pay(gw, alice, USD(100)), ter(tesSUCCESS)); env.close(); // payment env(pay(alice, bob, USD(1)), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"Payment", "tesSUCCESS"}); validateTouch(env, bob, {"Payment", "tesSUCCESS"}); validateTouch(env, gw, {"Payment", "tesSUCCESS"}); } { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); env.fund(XRP(1000), alice, bob, carol); env.close(); // setup rippling auto const USDA = alice["USD"]; auto const USDB = bob["USD"]; auto const USDC = carol["USD"]; env.trust(USDA(10), bob); env.trust(USDB(10), carol); // payment env(pay(alice, carol, USDB(10)), paths(USDA)); env.close(); // verify touch validateTouch(env, alice, {"Payment", "tesSUCCESS"}); validateTouch(env, bob, {"Payment", "tesSUCCESS"}); validateTouch(env, carol, {"Payment", "tesSUCCESS"}); } } static uint256 channel( jtx::Account const& alice, jtx::Account const& dst, std::uint32_t seqProxyValue) { auto const k = keylet::payChan(alice, 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 testPaymentChannelClaim(FeatureBitset features) { testcase("payment channel claim"); using namespace test::jtx; using namespace std::literals; { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account{"bob"}; env.fund(XRP(1000), alice, bob); env.close(); // create paychannel auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = channel(alice, bob, env.seq(alice)); env(paychan::create(alice, bob, XRP(10), settleDelay, pk), ter(tesSUCCESS)); env.close(); auto const delta = XRP(1); auto const reqBal = delta; auto const authAmt = reqBal + XRP(1); // claim paychannel env(paychan::claim(alice, chan, reqBal, authAmt), txflags(tfClose), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"PaymentChannelClaim", "tesSUCCESS"}); validateTouch(env, bob, {"PaymentChannelClaim", "tesSUCCESS"}); } { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account{"bob"}; env.fund(XRP(1000), alice, bob); env.close(); // create paychannel auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = channel(alice, bob, env.seq(alice)); env(paychan::create(alice, bob, XRP(10), settleDelay, pk), 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(alice.pk(), alice.sk(), chan, authAmt); env(paychan::claim( bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), txflags(tfClose), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"PaymentChannelClaim", "tesSUCCESS"}); validateTouch(env, bob, {"PaymentChannelClaim", "tesSUCCESS"}); } } void testPaymentChannelCreate(FeatureBitset features) { testcase("payment channel create"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account{"bob"}; env.fund(XRP(1000), alice, bob); env.close(); // create paychannel auto const pk = alice.pk(); auto const settleDelay = 100s; env(paychan::create(alice, bob, XRP(10), settleDelay, pk), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"PaymentChannelCreate", "tesSUCCESS"}); validateTouch(env, bob, {"PaymentChannelCreate", "tesSUCCESS"}); } void testPaymentChannelFund(FeatureBitset features) { testcase("payment channel fund"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account{"bob"}; env.fund(XRP(1000), alice, bob); env.close(); // create paychannel auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = channel(alice, bob, env.seq(alice)); env(paychan::create(alice, bob, XRP(10), settleDelay, pk), ter(tesSUCCESS)); env.close(); // fund paychannel env(paychan::fund(alice, chan, XRP(1)), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"PaymentChannelFund", "tesSUCCESS"}); } // helper void static overrideFlag(Json::Value& jv) { jv[jss::Flags] = hsfOVERRIDE; } void testSetHook(FeatureBitset features) { testcase("set hook"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); env.fund(XRP(1000), alice); env.close(); // set tsh hook auto hook1 = hso(jtx::genesis::AcceptHook, overrideFlag); hook1[jss::HookOn] = "00000000000000000000000000000000000000000000000000000000004000" "00"; env(hook(alice, {{hook1}}, 0), fee(XRP(1)), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"SetHook", "tesSUCCESS"}); } void testSetRegularKey(FeatureBitset features) { testcase("set regular key"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account{"bob"}; env.fund(XRP(1000), alice, bob); env.close(); // set regular key env(regkey(alice, bob), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"SetRegularKey", "tesSUCCESS"}); validateTouch(env, bob, {"SetRegularKey", "tesSUCCESS"}); } void testSignersListSet(FeatureBitset features) { testcase("signers list set"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const signer1 = Account{"bob"}; auto const signer2 = Account{"carol"}; env.fund(XRP(1000), alice, signer1, signer2); env.close(); // signers list set env(signers(alice, 2, {{signer1, 1}, {signer2, 1}}), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"SignerListSet", "tesSUCCESS"}); auto const tt = env.current()->rules().enabled(featureTouch) ? "SignerListSet" : "AccountSet"; validateTouch(env, signer1, {tt, "tesSUCCESS"}); validateTouch(env, signer2, {tt, "tesSUCCESS"}); } void testTicketCreate(FeatureBitset features) { testcase("ticket create"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); env.fund(XRP(1000), alice); env.close(); // ticket create env(ticket::create(alice, 2), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"TicketCreate", "tesSUCCESS"}); } void testTrustSet(FeatureBitset features) { testcase("trust set"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const issuer = Account{"gw"}; auto const USD = issuer["USD"]; env.fund(XRP(1000), alice, issuer); env.close(); // trust set env(trust(alice, USD(1000)), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"TrustSet", "tesSUCCESS"}); validateTouch(env, issuer, {"TrustSet", "tesSUCCESS"}); } void testURITokenMint(FeatureBitset features) { testcase("uritoken mint"); using namespace test::jtx; using namespace std::literals; { test::jtx::Env env{*this, envconfig(), 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)}; // mint uritoken env(uritoken::mint(issuer, uri), uritoken::amt(XRP(1)), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, issuer, {"URITokenMint", "tesSUCCESS"}); } { test::jtx::Env env{*this, envconfig(), 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)}; // mint uritoken env(uritoken::mint(issuer, uri), uritoken::dest(buyer), uritoken::amt(XRP(1)), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, buyer, {"URITokenMint", "tesSUCCESS"}); validateTouch(env, issuer, {"URITokenMint", "tesSUCCESS"}); } } void testURITokenBurn(FeatureBitset features) { testcase("uritoken burn"); using namespace test::jtx; using namespace std::literals; { test::jtx::Env env{*this, envconfig(), 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(); // ttURITOKEN_BURN env(uritoken::burn(owner, hexid), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, owner, {"URITokenBurn", "tesSUCCESS"}); validateTouch(env, issuer, {"URITokenBurn", "tesSUCCESS"}); } // Issuer { test::jtx::Env env{*this, envconfig(), 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(); // ttURITOKEN_BURN env(uritoken::burn(issuer, hexid), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, owner, {"URITokenBurn", "tesSUCCESS"}); validateTouch(env, issuer, {"URITokenBurn", "tesSUCCESS"}); } } void testURITokenBuy(FeatureBitset features) { testcase("uritoken buy"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), 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(); // buy uritoken env(uritoken::buy(buyer, hexid), uritoken::amt(XRP(1)), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, buyer, {"URITokenBuy", "tesSUCCESS"}); validateTouch(env, issuer, {"URITokenBuy", "tesSUCCESS"}); } void testURITokenCancelSellOffer(FeatureBitset features) { testcase("uritoken cancel sell offer"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), 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(); // cancel uritoken env(uritoken::cancel(owner, hexid), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, owner, {"URITokenCancelSellOffer", "tesSUCCESS"}); validateTouch(env, issuer, {"URITokenCancelSellOffer", "tesSUCCESS"}); } void testURITokenCreateSellOffer(FeatureBitset features) { testcase("uritoken create sell offer"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig(), 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(); // verify touch validateTouch(env, owner, {"URITokenCreateSellOffer", "tesSUCCESS"}); validateTouch(env, buyer, {"URITokenCreateSellOffer", "tesSUCCESS"}); validateTouch(env, issuer, {"URITokenCreateSellOffer", "tesSUCCESS"}); } void testRemit(FeatureBitset features) { testcase("remit"); using namespace test::jtx; using namespace std::literals; // No Amount { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account{"bob"}; env.fund(XRP(1000), alice, bob); env.close(); // remit env(remit::remit(alice, bob), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"Remit", "tesSUCCESS"}); auto const tt = env.current()->rules().enabled(featureTouch) ? "Remit" : "AccountSet"; validateTouch(env, bob, {tt, "tesSUCCESS"}); } // IOU { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account{"bob"}; auto const gw = Account{"gw"}; auto const USD = gw["USD"]; env.fund(XRP(1000), alice, bob, gw); env.close(); env(trust(alice, USD(100)), ter(tesSUCCESS)); env(trust(bob, USD(100)), ter(tesSUCCESS)); env.close(); env(pay(gw, alice, USD(100)), ter(tesSUCCESS)); env.close(); // remit env(remit::remit(alice, bob), remit::amts({USD(1)}), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"Remit", "tesSUCCESS"}); validateTouch(env, bob, {"Remit", "tesSUCCESS"}); validateTouch(env, gw, {"Remit", "tesSUCCESS"}); } // Inform { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account{"bob"}; auto const inform = Account{"inform"}; env.fund(XRP(1000), alice, bob, inform); env.close(); // remit env(remit::remit(alice, bob), remit::inform(inform), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"Remit", "tesSUCCESS"}); auto const tt = env.current()->rules().enabled(featureTouch) ? "Remit" : "AccountSet"; validateTouch(env, bob, {tt, "tesSUCCESS"}); validateTouch(env, inform, {tt, "tesSUCCESS"}); } // URITokenIDs { test::jtx::Env env{*this, envconfig(), features}; auto const alice = Account("alice"); auto const bob = Account{"bob"}; auto const issuer = Account{"issuer"}; env.fund(XRP(1000), alice, bob, issuer); env.close(); // mint uritoken std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(issuer, uri); env(uritoken::mint(issuer, uri), txflags(tfBurnable), ter(tesSUCCESS)); // sell uritoken env(uritoken::sell(issuer, strHex(tid)), uritoken::amt(XRP(1)), uritoken::dest(alice), ter(tesSUCCESS)); env.close(); // buy uritoken env(uritoken::buy(alice, strHex(tid)), uritoken::amt(XRP(1)), ter(tesSUCCESS)); env.close(); // remit env(remit::remit(alice, bob), remit::token_ids({strHex(tid)}), ter(tesSUCCESS)); env.close(); // verify touch validateTouch(env, alice, {"Remit", "tesSUCCESS"}); validateTouch(env, bob, {"Remit", "tesSUCCESS"}); validateTouch(env, issuer, {"Remit", "tesSUCCESS"}); } } void testAllTxns(FeatureBitset features) { testAccountSet(features); testAccountDelete(features); testCheckCancel(features); testCheckCash(features); testCheckCreate(features); testClaimReward(features); testDepositPreauth(features); testEscrowCancel(features); testEscrowCreate(features); testEscrowFinish(features); testGenesisMint(features); testImport(features); testInvoke(features); testOfferCancel(features); testOfferCreate(features); testPayment(features); testPaymentChannelClaim(features); testPaymentChannelCreate(features); testPaymentChannelFund(features); testSetHook(features); testSetRegularKey(features); testSignersListSet(features); testTicketCreate(features); testTrustSet(features); testURITokenMint(features); testURITokenBurn(features); testURITokenBuy(features); testURITokenCancelSellOffer(features); testURITokenCreateSellOffer(features); testRemit(features); } public: void run() override { using namespace test::jtx; auto const sa = supported_amendments(); testAllTxns(sa); testAllTxns(sa - fixHookAPI20251128); testAllTxns(sa - featureTouch - fixHookAPI20251128); } }; BEAST_DEFINE_TESTSUITE(Touch, app, ripple); } // namespace test } // namespace ripple