Add TSH processing for AMM, AMMClawback, Clawback, Oracle (#532)

* Add TSH processing for `AMM`, `AMMClawback`, `Oracle`, `Clawback`

* Add empty TSH processing for other transaction types

* Add AMMTsh tests
This commit is contained in:
tequ
2025-07-10 23:26:32 +09:00
committed by GitHub
parent d2e21da7a3
commit c5fa112e16
2 changed files with 517 additions and 5 deletions

View File

@@ -20,6 +20,7 @@
#include <test/app/Import_json.h>
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <test/jtx/TestHelpers.h>
#include <xrpld/app/misc/HashRouter.h>
#include <xrpld/app/misc/TxQ.h>
@@ -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<Config> 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<FeatureBitset, 4> const feats{
all,

View File

@@ -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<MPTIssue>())
{
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<std::pair<AccountID, bool>> ret{tshEntries.size()};