diff --git a/src/test/app/SetHookTSH_test.cpp b/src/test/app/SetHookTSH_test.cpp index 1afc7226e..7594d281e 100644 --- a/src/test/app/SetHookTSH_test.cpp +++ b/src/test/app/SetHookTSH_test.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -841,6 +842,391 @@ private: } } + // clang-format off + // AMM + // | otxn | tsh | Bid | Create | Delete | Clawback | Deposit | Vote | Withdraw | + // | A | I | - | W | W | W | W | - | W | + // | A | H | - | - | - | W | - | - | - | + // clang-format on + void + testAMMBidTSH(FeatureBitset features) + { + using namespace test::jtx; + using namespace std::literals; + testcase("amm bid tsh"); + + // otxn: account + // tsh issuer + // w/s: none + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("gw"); + auto const account = Account("alice"); + auto const USD = issuer["USD"]; + + env.fund(XRP(30'000), issuer, account); + env.close(); + env.trust(USD(30'000), account); + env.close(); + env(pay(issuer, account, USD(10'000))); + env.close(); + + // create AMM + AMM ammAlice(env, account, XRP(10'000), USD(10'000)); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, issuer); + + // set tsh hook + setTSHHook(env, issuer, testStrong); + + // bid + ammAlice.bid({ + .account = account, + .bidMin = 100, + }); + + // verify tsh hook triggered + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } + + // AMMCreate + void + testAMMCreateTSH(FeatureBitset features) + { + using namespace test::jtx; + using namespace std::literals; + testcase("amm create tsh"); + + // otxn: account + // tsh issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("gw"); + auto const account = Account("alice"); + auto const USD = issuer["USD"]; + + env.fund(XRP(30'000), issuer, account); + env.close(); + env.trust(USD(30'000), account); + env.close(); + env(pay(issuer, account, USD(10'000))); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, issuer); + + // set tsh hook + setTSHHook(env, issuer, testStrong); + + // create AMM + AMM ammAlice(env, account, XRP(10'000), USD(10'000)); + + // verify tsh hook triggered + if (features[featureIOUIssuerWeakTSH]) + { + auto const expected = testStrong ? tshNONE : tshWEAK; + testTSHStrongWeak(env, expected, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } + } + + // AMMDelete + void + testAMMDeleteTSH(FeatureBitset features) + { + using namespace test::jtx; + using namespace std::literals; + testcase("amm delete tsh"); + + // otxn: account + // tsh issuer, holder + // w/s: none + for (bool const testStrong : {true, false}) + { + test::jtx::Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.reference_fee = XRPAmount(1); + return cfg; + }), + features); + + auto const issuer = Account("gw"); + auto const account = Account("alice"); + auto const bob = Account("bob"); + auto const USD = issuer["USD"]; + + env.fund(XRP(20'000), issuer, account, bob); + env.close(); + env.trust(USD(10'000), account); + env.close(); + env(pay(issuer, account, USD(10'000))); + env.close(); + + AMM amm(env, account, XRP(10'000), USD(10'000)); + for (auto i = 0; i < maxDeletableAMMTrustLines + 10; ++i) + { + Account const a{std::to_string(i)}; + env.fund(XRP(1'000), a); + env(trust(a, STAmount{amm.lptIssue(), 10'000})); + // set tsh collect + if (!testStrong) + env(fset(a, asfTshCollect)); + // set tsh hook + setTSHHook(env, a, testStrong); + } + amm.withdrawAll(account); + BEAST_EXPECT(amm.ammExists()); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, issuer); + + // set tsh hook + setTSHHook(env, issuer, testStrong); + + // delete + amm.ammDelete(bob); + + // verify tsh hook triggered + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } + + // AMMClawback + void + testAMMClawbackTSH(FeatureBitset features) + { + using namespace test::jtx; + using namespace std::literals; + testcase("amm clawback tsh"); + + // otxn: account + // tsh holder + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("gw"); + auto const account = Account("alice"); + auto const USD = issuer["USD"]; + + env.fund(XRP(30'000), issuer, account); + env.close(); + env(fset(issuer, asfAllowTrustLineClawback)); + env.close(); + env.trust(USD(30'000), account); + env.close(); + env(pay(issuer, account, USD(30'000))); + env.close(); + + // create AMM + AMM ammAlice(env, account, XRP(10'000), USD(10'000)); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, account); + + // set tsh hook + setTSHHook(env, account, testStrong); + + // clawback + env(amm::ammClawback(issuer, account, USD, XRP, USD(1000))); + env.close(); + + // verify tsh hook triggered + if (features[featureIOUIssuerWeakTSH]) + { + auto const expected = testStrong ? tshNONE : tshWEAK; + testTSHStrongWeak(env, expected, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } + } + + // AMMDeposit + void + testAMMDepositTSH(FeatureBitset features) + { + using namespace test::jtx; + using namespace std::literals; + testcase("amm deposit tsh"); + + // otxn: account + // tsh issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("gw"); + auto const account = Account("alice"); + auto const USD = issuer["USD"]; + + env.fund(XRP(30'000), issuer, account); + env.close(); + env.trust(USD(30'000), account); + env.close(); + env(pay(issuer, account, USD(30'000))); + env.close(); + + // create AMM + AMM ammAlice(env, account, XRP(10'000), USD(10'000)); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, issuer); + + // set tsh hook + setTSHHook(env, issuer, testStrong); + + // deposit + ammAlice.deposit(account, 10); + + // verify tsh hook triggered + if (features[featureIOUIssuerWeakTSH]) + { + auto const expected = testStrong ? tshNONE : tshWEAK; + testTSHStrongWeak(env, expected, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } + } + + // AMMVote + void + testAMMVoteTSH(FeatureBitset features) + { + using namespace test::jtx; + using namespace std::literals; + testcase("amm vote tsh"); + + // otxn: account + // tsh issuer + // w/s: none + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("gw"); + auto const account = Account("alice"); + auto const USD = issuer["USD"]; + + env.fund(XRP(30'000), issuer, account); + env.close(); + env.trust(USD(30'000), account); + env.close(); + env(pay(issuer, account, USD(30'000))); + env.close(); + + // create AMM + AMM ammAlice(env, account, XRP(10'000), USD(10'000)); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, issuer); + + // set tsh hook + setTSHHook(env, issuer, testStrong); + + // vote + ammAlice.vote(account, 100); + + // verify tsh hook triggered + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } + + // AMMWithdraw + void + testAMMWithdrawTSH(FeatureBitset features) + { + using namespace test::jtx; + using namespace std::literals; + testcase("amm withdraw tsh"); + + // otxn: account + // tsh issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("gw"); + auto const account = Account("alice"); + auto const USD = issuer["USD"]; + + env.fund(XRP(30'000), issuer, account); + env.close(); + env.trust(USD(30'000), account); + env.close(); + env(pay(issuer, account, USD(30'000))); + env.close(); + + // create AMM + AMM ammAlice(env, account, XRP(10'000), USD(10'000)); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, issuer); + + // set tsh hook + setTSHHook(env, issuer, testStrong); + + // withdraw + ammAlice.withdraw(account, 100); + + // verify tsh hook triggered + if (features[featureIOUIssuerWeakTSH]) + { + auto const expected = testStrong ? tshNONE : tshWEAK; + testTSHStrongWeak(env, expected, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } + } + // Check // | otxn | tsh | cancel | create | cash | // | A | A | S | S | N/A | @@ -1384,6 +1770,46 @@ private: auto const expected = testStrong ? tshNONE : tshWEAK; testTSHStrongWeak(env, expected, __LINE__); } + + // otxn: MPT issuer + // tsh holder + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("alice"); + auto const holder = Account("bob"); + + MPTTester mptIssuer(env, issuer, {.holders = {holder}}); + + // issuer creates issuance + mptIssuer.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback}); + + // holder creates a MPToken + mptIssuer.authorize({.account = holder}); + + // issuer pays holder 100 tokens + mptIssuer.pay(issuer, holder, 100); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, holder); + + // set tsh hook + setTSHHook(env, holder, testStrong); + + // clawback + mptIssuer.claw(issuer, holder, 1); + + // verify tsh hook triggered + auto const expected = testStrong ? tshNONE : tshWEAK; + testTSHStrongWeak(env, expected, __LINE__); + } } // DepositPreauth @@ -6345,6 +6771,13 @@ private: { testAccountSetTSH(features); testAccountDeleteTSH(features); + testAMMBidTSH(features); + testAMMCreateTSH(features); + testAMMDeleteTSH(features); + testAMMClawbackTSH(features); + testAMMDepositTSH(features); + testAMMVoteTSH(features); + testAMMWithdrawTSH(features); testCheckCancelTSH(features); testCheckCashTSH(features); testCheckCreateTSH(features); @@ -6391,7 +6824,8 @@ public: run(std::uint32_t instance, bool last = false) { using namespace test::jtx; - static FeatureBitset const all{supported_amendments()}; + static FeatureBitset const all{ + supported_amendments() | featureMPTokensV1}; static std::array const feats{ all, diff --git a/src/xrpld/app/hook/detail/applyHook.cpp b/src/xrpld/app/hook/detail/applyHook.cpp index ea40cc3b7..4ad7ed05e 100644 --- a/src/xrpld/app/hook/detail/applyHook.cpp +++ b/src/xrpld/app/hook/detail/applyHook.cpp @@ -497,12 +497,90 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) case ttCLAWBACK: { auto const amount = tx.getFieldAmount(sfAmount); - ADD_TSH(amount.getIssuer(), tshWEAK); + + if (amount.holds()) + { + if (!tx.isFieldPresent(sfHolder)) + return {}; + auto const holder = tx.getAccountID(sfHolder); + ADD_TSH(holder, tshWEAK); + } + else + ADD_TSH(amount.getIssuer(), tshWEAK); + break; } - - default: - return {}; + case ttAMM_CREATE: + case ttAMM_DEPOSIT: + case ttAMM_WITHDRAW: + case ttAMM_VOTE: + case ttAMM_BID: + case ttAMM_DELETE: + case ttAMM_CLAWBACK: { + // The issuer or holder of tokens related to AMM is weakTSH with + // IOUIssuerWeakTSH Amendment. + break; + } + case ttORACLE_SET: + case ttORACLE_DELETE: { + break; + } + case ttXCHAIN_CREATE_CLAIM_ID: + case ttXCHAIN_COMMIT: + case ttXCHAIN_CLAIM: + case ttXCHAIN_ACCOUNT_CREATE_COMMIT: + case ttXCHAIN_ADD_CLAIM_ATTESTATION: + case ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION: + case ttXCHAIN_MODIFY_BRIDGE: + case ttXCHAIN_CREATE_BRIDGE: { + // TODO: Implement if needed + break; + } + case ttDID_SET: + case ttDID_DELETE: { + // TODO: Implement if needed + break; + } + case ttLEDGER_STATE_FIX: { + // TODO: Implement if needed + break; + } + case ttMPTOKEN_ISSUANCE_CREATE: + case ttMPTOKEN_ISSUANCE_DESTROY: + case ttMPTOKEN_ISSUANCE_SET: + case ttMPTOKEN_AUTHORIZE: { + // TODO: Implement if needed + break; + } + case ttCREDENTIAL_CREATE: + case ttCREDENTIAL_ACCEPT: + case ttCREDENTIAL_DELETE: { + // TODO: Implement if needed + break; + } + case ttNFTOKEN_MODIFY: { + // TODO: Implement if needed + break; + } + case ttPERMISSIONED_DOMAIN_SET: + case ttPERMISSIONED_DOMAIN_DELETE: { + // TODO: Implement if needed + break; + } + case ttREMARKS_SET: { + break; + } + // pseudo transactions + case ttAMENDMENT: + case ttFEE: + case ttUNL_MODIFY: + case ttEMIT_FAILURE: + case ttUNL_REPORT: { + break; + } + default: { + UNREACHABLE("Unknown transaction type"); + } } std::vector> ret{tshEntries.size()};