Files
rippled/src/test/app/Clawback_test.cpp
Bart 1eb0fdac65 refactor: Rename ripple namespace to xrpl (#5982)
This change renames all occurrences of `namespace ripple` and `ripple::` to `namespace xrpl` and `xrpl::`, respectively, as well as the names of test suites. It also provides a script to allow developers to replicate the changes in their local branch or fork to avoid conflicts.
2025-12-11 16:51:49 +00:00

942 lines
30 KiB
C++

#include <test/jtx.h>
#include <test/jtx/trust.h>
#include <xrpl/protocol/Feature.h>
namespace xrpl {
class Clawback_test : public beast::unit_test::suite
{
template <class T>
static std::string
to_string(T const& t)
{
return boost::lexical_cast<std::string>(t);
}
// Helper function that returns the number of tickets held by an account.
static std::uint32_t
ticketCount(test::jtx::Env const& env, test::jtx::Account const& acct)
{
std::uint32_t ret{0};
if (auto const sleAcct = env.le(acct))
ret = sleAcct->at(~sfTicketCount).value_or(0);
return ret;
}
// Helper function that returns the freeze status of a trustline
static bool
getLineFreezeFlag(
test::jtx::Env const& env,
test::jtx::Account const& src,
test::jtx::Account const& dst,
Currency const& cur)
{
if (auto sle = env.le(keylet::line(src, dst, cur)))
{
auto const useHigh = src.id() > dst.id();
return sle->isFlag(useHigh ? lsfHighFreeze : lsfLowFreeze);
}
Throw<std::runtime_error>("No line in getLineFreezeFlag");
return false; // silence warning
}
void
testAllowTrustLineClawbackFlag(FeatureBitset features)
{
testcase("Enable AllowTrustLineClawback flag");
using namespace test::jtx;
// Test that one can successfully set asfAllowTrustLineClawback flag.
// If successful, asfNoFreeze can no longer be set.
// Also, asfAllowTrustLineClawback cannot be cleared.
{
Env env(*this, features);
Account alice{"alice"};
env.fund(XRP(1000), alice);
env.close();
// set asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// clear asfAllowTrustLineClawback does nothing
env(fclear(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// asfNoFreeze cannot be set when asfAllowTrustLineClawback is set
env.require(nflags(alice, asfNoFreeze));
env(fset(alice, asfNoFreeze), ter(tecNO_PERMISSION));
env.close();
}
// Test that asfAllowTrustLineClawback cannot be set when
// asfNoFreeze has been set
{
Env env(*this, features);
Account alice{"alice"};
env.fund(XRP(1000), alice);
env.close();
env.require(nflags(alice, asfNoFreeze));
// set asfNoFreeze
env(fset(alice, asfNoFreeze));
env.close();
// NoFreeze is set
env.require(flags(alice, asfNoFreeze));
// asfAllowTrustLineClawback cannot be set if asfNoFreeze is set
env(fset(alice, asfAllowTrustLineClawback), ter(tecNO_PERMISSION));
env.close();
env.require(nflags(alice, asfAllowTrustLineClawback));
}
// Test that asfAllowTrustLineClawback is not allowed when owner dir is
// non-empty
{
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
env.fund(XRP(1000), alice, bob);
env.close();
auto const USD = alice["USD"];
env.require(nflags(alice, asfAllowTrustLineClawback));
// alice issues 10 USD to bob
env.trust(USD(1000), bob);
env(pay(alice, bob, USD(10)));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 1);
// alice fails to enable clawback because she has trustline with bob
env(fset(alice, asfAllowTrustLineClawback), ter(tecOWNERS));
env.close();
// bob sets trustline to default limit and pays alice back to delete
// the trustline
env(trust(bob, USD(0), 0));
env(pay(bob, alice, USD(10)));
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 0);
// alice now is able to set asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 0);
}
// Test that one cannot enable asfAllowTrustLineClawback when
// featureClawback amendment is disabled
{
Env env(*this, features - featureClawback);
Account alice{"alice"};
env.fund(XRP(1000), alice);
env.close();
env.require(nflags(alice, asfAllowTrustLineClawback));
// alice attempts to set asfAllowTrustLineClawback flag while
// amendment is disabled. no error is returned, but the flag remains
// to be unset.
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(nflags(alice, asfAllowTrustLineClawback));
// now enable clawback amendment
env.enableFeature(featureClawback);
env.close();
// asfAllowTrustLineClawback can be set
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
}
}
void
testValidation(FeatureBitset features)
{
testcase("Validation");
using namespace test::jtx;
// Test that Clawback tx fails for the following:
// 1. when amendment is disabled
// 2. when asfAllowTrustLineClawback flag has not been set
{
Env env(*this, features - featureClawback);
Account alice{"alice"};
Account bob{"bob"};
env.fund(XRP(1000), alice, bob);
env.close();
env.require(nflags(alice, asfAllowTrustLineClawback));
auto const USD = alice["USD"];
// alice issues 10 USD to bob
env.trust(USD(1000), bob);
env(pay(alice, bob, USD(10)));
env.close();
env.require(balance(bob, alice["USD"](10)));
env.require(balance(alice, bob["USD"](-10)));
// clawback fails because amendment is disabled
env(claw(alice, bob["USD"](5)), ter(temDISABLED));
env.close();
// now enable clawback amendment
env.enableFeature(featureClawback);
env.close();
// clawback fails because asfAllowTrustLineClawback has not been set
env(claw(alice, bob["USD"](5)), ter(tecNO_PERMISSION));
env.close();
env.require(balance(bob, alice["USD"](10)));
env.require(balance(alice, bob["USD"](-10)));
}
// Test that Clawback tx fails for the following:
// 1. invalid flag
// 2. negative STAmount
// 3. zero STAmount
// 4. XRP amount
// 5. `account` and `issuer` fields are same account
// 6. trustline has a balance of 0
// 7. trustline does not exist
{
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
env.fund(XRP(1000), alice, bob);
env.close();
// alice sets asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
auto const USD = alice["USD"];
// alice issues 10 USD to bob
env.trust(USD(1000), bob);
env(pay(alice, bob, USD(10)));
env.close();
env.require(balance(bob, alice["USD"](10)));
env.require(balance(alice, bob["USD"](-10)));
// fails due to invalid flag
env(claw(alice, bob["USD"](5)),
txflags(0x00008000),
ter(temINVALID_FLAG));
env.close();
// fails due to negative amount
env(claw(alice, bob["USD"](-5)), ter(temBAD_AMOUNT));
env.close();
// fails due to zero amount
env(claw(alice, bob["USD"](0)), ter(temBAD_AMOUNT));
env.close();
// fails because amount is in XRP
env(claw(alice, XRP(10)), ter(temBAD_AMOUNT));
env.close();
// fails when `issuer` field in `amount` is not token holder
// NOTE: we are using the `issuer` field for the token holder
env(claw(alice, alice["USD"](5)), ter(temBAD_AMOUNT));
env.close();
// bob pays alice back, trustline has a balance of 0
env(pay(bob, alice, USD(10)));
env.close();
// bob still owns the trustline that has 0 balance
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 1);
env.require(balance(bob, alice["USD"](0)));
env.require(balance(alice, bob["USD"](0)));
// clawback fails because because balance is 0
env(claw(alice, bob["USD"](5)), ter(tecINSUFFICIENT_FUNDS));
env.close();
// set the limit to default, which should delete the trustline
env(trust(bob, USD(0), 0));
env.close();
// bob no longer owns the trustline
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 0);
// clawback fails because trustline does not exist
env(claw(alice, bob["USD"](5)), ter(tecNO_LINE));
env.close();
}
}
void
testPermission(FeatureBitset features)
{
// Checks the tx submitter has the permission to clawback.
// Exercises preclaim code
testcase("Permission");
using namespace test::jtx;
// Clawing back from an non-existent account returns error
{
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
// bob's account is not funded and does not exist
env.fund(XRP(1000), alice);
env.close();
// alice sets asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// bob, the token holder, does not exist
env(claw(alice, bob["USD"](5)), ter(terNO_ACCOUNT));
env.close();
}
// Test that trustline cannot be clawed by someone who is
// not the issuer of the currency
{
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
Account cindy{"cindy"};
env.fund(XRP(1000), alice, bob, cindy);
env.close();
auto const USD = alice["USD"];
// alice sets asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// cindy sets asfAllowTrustLineClawback
env(fset(cindy, asfAllowTrustLineClawback));
env.close();
env.require(flags(cindy, asfAllowTrustLineClawback));
// alice issues 1000 USD to bob
env.trust(USD(1000), bob);
env(pay(alice, bob, USD(1000)));
env.close();
env.require(balance(bob, alice["USD"](1000)));
env.require(balance(alice, bob["USD"](-1000)));
// cindy tries to claw from bob, and fails because trustline does
// not exist
env(claw(cindy, bob["USD"](200)), ter(tecNO_LINE));
env.close();
}
// When a trustline is created between issuer and holder,
// we must make sure the holder is unable to claw back from
// the issuer by impersonating the issuer account.
//
// This must be tested bidirectionally for both accounts because the
// issuer could be either the low or high account in the trustline
// object
{
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
env.fund(XRP(1000), alice, bob);
env.close();
auto const USD = alice["USD"];
auto const CAD = bob["CAD"];
// alice sets asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// bob sets asfAllowTrustLineClawback
env(fset(bob, asfAllowTrustLineClawback));
env.close();
env.require(flags(bob, asfAllowTrustLineClawback));
// alice issues 10 USD to bob.
// bob then attempts to submit a clawback tx to claw USD from alice.
// this must FAIL, because bob is not the issuer for this
// trustline!!!
{
// bob creates a trustline with alice, and alice sends 10 USD to
// bob
env.trust(USD(1000), bob);
env(pay(alice, bob, USD(10)));
env.close();
env.require(balance(bob, alice["USD"](10)));
env.require(balance(alice, bob["USD"](-10)));
// bob cannot claw back USD from alice because he's not the
// issuer
env(claw(bob, alice["USD"](5)), ter(tecNO_PERMISSION));
env.close();
}
// bob issues 10 CAD to alice.
// alice then attempts to submit a clawback tx to claw CAD from bob.
// this must FAIL, because alice is not the issuer for this
// trustline!!!
{
// alice creates a trustline with bob, and bob sends 10 CAD to
// alice
env.trust(CAD(1000), alice);
env(pay(bob, alice, CAD(10)));
env.close();
env.require(balance(bob, alice["CAD"](-10)));
env.require(balance(alice, bob["CAD"](10)));
// alice cannot claw back CAD from bob because she's not the
// issuer
env(claw(alice, bob["CAD"](5)), ter(tecNO_PERMISSION));
env.close();
}
}
}
void
testEnabled(FeatureBitset features)
{
testcase("Enable clawback");
using namespace test::jtx;
// Test that alice is able to successfully clawback tokens from bob
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
env.fund(XRP(1000), alice, bob);
env.close();
auto const USD = alice["USD"];
// alice sets asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// alice issues 1000 USD to bob
env.trust(USD(1000), bob);
env(pay(alice, bob, USD(1000)));
env.close();
env.require(balance(bob, alice["USD"](1000)));
env.require(balance(alice, bob["USD"](-1000)));
// alice claws back 200 USD from bob
env(claw(alice, bob["USD"](200)));
env.close();
// bob should have 800 USD left
env.require(balance(bob, alice["USD"](800)));
env.require(balance(alice, bob["USD"](-800)));
// alice claws back 800 USD from bob again
env(claw(alice, bob["USD"](800)));
env.close();
// trustline has a balance of 0
env.require(balance(bob, alice["USD"](0)));
env.require(balance(alice, bob["USD"](0)));
}
void
testMultiLine(FeatureBitset features)
{
// Test scenarios where multiple trustlines are involved
testcase("Multi line");
using namespace test::jtx;
// Both alice and bob issues their own "USD" to cindy.
// When alice and bob tries to claw back, they will only
// claw back from their respective trustline.
{
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
Account cindy{"cindy"};
env.fund(XRP(1000), alice, bob, cindy);
env.close();
// alice sets asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// bob sets asfAllowTrustLineClawback
env(fset(bob, asfAllowTrustLineClawback));
env.close();
env.require(flags(bob, asfAllowTrustLineClawback));
// alice sends 1000 USD to cindy
env.trust(alice["USD"](1000), cindy);
env(pay(alice, cindy, alice["USD"](1000)));
env.close();
// bob sends 1000 USD to cindy
env.trust(bob["USD"](1000), cindy);
env(pay(bob, cindy, bob["USD"](1000)));
env.close();
// alice claws back 200 USD from cindy
env(claw(alice, cindy["USD"](200)));
env.close();
// cindy has 800 USD left in alice's trustline after clawed by alice
env.require(balance(cindy, alice["USD"](800)));
env.require(balance(alice, cindy["USD"](-800)));
// cindy still has 1000 USD in bob's trustline
env.require(balance(cindy, bob["USD"](1000)));
env.require(balance(bob, cindy["USD"](-1000)));
// bob claws back 600 USD from cindy
env(claw(bob, cindy["USD"](600)));
env.close();
// cindy has 400 USD left in bob's trustline after clawed by bob
env.require(balance(cindy, bob["USD"](400)));
env.require(balance(bob, cindy["USD"](-400)));
// cindy still has 800 USD in alice's trustline
env.require(balance(cindy, alice["USD"](800)));
env.require(balance(alice, cindy["USD"](-800)));
}
// alice issues USD to both bob and cindy.
// when alice claws back from bob, only bob's USD balance is
// affected, and cindy's balance remains unchanged, and vice versa.
{
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
Account cindy{"cindy"};
env.fund(XRP(1000), alice, bob, cindy);
env.close();
auto const USD = alice["USD"];
// alice sets asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// alice sends 600 USD to bob
env.trust(USD(1000), bob);
env(pay(alice, bob, USD(600)));
env.close();
env.require(balance(alice, bob["USD"](-600)));
env.require(balance(bob, alice["USD"](600)));
// alice sends 1000 USD to cindy
env.trust(USD(1000), cindy);
env(pay(alice, cindy, USD(1000)));
env.close();
env.require(balance(alice, cindy["USD"](-1000)));
env.require(balance(cindy, alice["USD"](1000)));
// alice claws back 500 USD from bob
env(claw(alice, bob["USD"](500)));
env.close();
// bob's balance is reduced
env.require(balance(alice, bob["USD"](-100)));
env.require(balance(bob, alice["USD"](100)));
// cindy's balance is unchanged
env.require(balance(alice, cindy["USD"](-1000)));
env.require(balance(cindy, alice["USD"](1000)));
// alice claws back 300 USD from cindy
env(claw(alice, cindy["USD"](300)));
env.close();
// bob's balance is unchanged
env.require(balance(alice, bob["USD"](-100)));
env.require(balance(bob, alice["USD"](100)));
// cindy's balance is reduced
env.require(balance(alice, cindy["USD"](-700)));
env.require(balance(cindy, alice["USD"](700)));
}
}
void
testBidirectionalLine(FeatureBitset features)
{
testcase("Bidirectional line");
using namespace test::jtx;
// Test when both alice and bob issues USD to each other.
// This scenario creates only one trustline.
// In this case, both alice and bob can be seen as the "issuer"
// and they can send however many USDs to each other.
// We test that only the person who has a negative balance from their
// perspective is allowed to clawback
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
env.fund(XRP(1000), alice, bob);
env.close();
// alice sets asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// bob sets asfAllowTrustLineClawback
env(fset(bob, asfAllowTrustLineClawback));
env.close();
env.require(flags(bob, asfAllowTrustLineClawback));
// alice issues 1000 USD to bob
env.trust(alice["USD"](1000), bob);
env(pay(alice, bob, alice["USD"](1000)));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 1);
// bob is the holder, and alice is the issuer
env.require(balance(bob, alice["USD"](1000)));
env.require(balance(alice, bob["USD"](-1000)));
// bob issues 1500 USD to alice
env.trust(bob["USD"](1500), alice);
env(pay(bob, alice, bob["USD"](1500)));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(ownerCount(env, bob) == 1);
// bob has negative 500 USD because bob issued 500 USD more than alice
// bob can now been seen as the issuer, while alice is the holder
env.require(balance(bob, alice["USD"](-500)));
env.require(balance(alice, bob["USD"](500)));
// At this point, both alice and bob are the issuers of USD
// and can send USD to each other through one trustline
// alice fails to clawback. Even though she is also an issuer,
// the trustline balance is positive from her perspective
env(claw(alice, bob["USD"](200)), ter(tecNO_PERMISSION));
env.close();
// bob is able to successfully clawback from alice because
// the trustline balance is negative from his perspective
env(claw(bob, alice["USD"](200)));
env.close();
env.require(balance(bob, alice["USD"](-300)));
env.require(balance(alice, bob["USD"](300)));
// alice pays bob 1000 USD
env(pay(alice, bob, alice["USD"](1000)));
env.close();
// bob's balance becomes positive from his perspective because
// alice issued more USD than the balance
env.require(balance(bob, alice["USD"](700)));
env.require(balance(alice, bob["USD"](-700)));
// bob is now the holder and fails to clawback
env(claw(bob, alice["USD"](200)), ter(tecNO_PERMISSION));
env.close();
// alice successfully claws back
env(claw(alice, bob["USD"](200)));
env.close();
env.require(balance(bob, alice["USD"](500)));
env.require(balance(alice, bob["USD"](-500)));
}
void
testDeleteDefaultLine(FeatureBitset features)
{
testcase("Delete default trustline");
using namespace test::jtx;
// If clawback results the trustline to be default,
// trustline should be automatically deleted
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
env.fund(XRP(1000), alice, bob);
env.close();
auto const USD = alice["USD"];
// alice sets asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// alice issues 1000 USD to bob
env.trust(USD(1000), bob);
env(pay(alice, bob, USD(1000)));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 1);
env.require(balance(bob, alice["USD"](1000)));
env.require(balance(alice, bob["USD"](-1000)));
// set limit to default,
env(trust(bob, USD(0), 0));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 1);
// alice claws back full amount from bob, and should also delete
// trustline
env(claw(alice, bob["USD"](1000)));
env.close();
// bob no longer owns the trustline because it was deleted
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 0);
}
void
testFrozenLine(FeatureBitset features)
{
testcase("Frozen trustline");
using namespace test::jtx;
// Claws back from frozen trustline
// and the trustline should remain frozen
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
env.fund(XRP(1000), alice, bob);
env.close();
auto const USD = alice["USD"];
// alice sets asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// alice issues 1000 USD to bob
env.trust(USD(1000), bob);
env(pay(alice, bob, USD(1000)));
env.close();
env.require(balance(bob, alice["USD"](1000)));
env.require(balance(alice, bob["USD"](-1000)));
// freeze trustline
env(trust(alice, bob["USD"](0), tfSetFreeze));
env.close();
// alice claws back 200 USD from bob
env(claw(alice, bob["USD"](200)));
env.close();
// bob should have 800 USD left
env.require(balance(bob, alice["USD"](800)));
env.require(balance(alice, bob["USD"](-800)));
// trustline remains frozen
BEAST_EXPECT(getLineFreezeFlag(env, alice, bob, USD.currency));
}
void
testAmountExceedsAvailable(FeatureBitset features)
{
testcase("Amount exceeds available");
using namespace test::jtx;
// When alice tries to claw back an amount that is greater
// than what bob holds, only the max available balance is clawed
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
env.fund(XRP(1000), alice, bob);
env.close();
auto const USD = alice["USD"];
// alice sets asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// alice issues 1000 USD to bob
env.trust(USD(1000), bob);
env(pay(alice, bob, USD(1000)));
env.close();
env.require(balance(bob, alice["USD"](1000)));
env.require(balance(alice, bob["USD"](-1000)));
// alice tries to claw back 2000 USD
env(claw(alice, bob["USD"](2000)));
env.close();
// check alice and bob's balance.
// alice was only able to claw back 1000 USD at maximum
env.require(balance(bob, alice["USD"](0)));
env.require(balance(alice, bob["USD"](0)));
// bob still owns the trustline because trustline is not in default
// state
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 1);
// set limit to default,
env(trust(bob, USD(0), 0));
env.close();
// verify that bob's trustline was deleted
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 0);
}
void
testTickets(FeatureBitset features)
{
testcase("Tickets");
using namespace test::jtx;
// Tests clawback with tickets
Env env(*this, features);
Account alice{"alice"};
Account bob{"bob"};
env.fund(XRP(1000), alice, bob);
env.close();
auto const USD = alice["USD"];
// alice sets asfAllowTrustLineClawback
env(fset(alice, asfAllowTrustLineClawback));
env.close();
env.require(flags(alice, asfAllowTrustLineClawback));
// alice issues 100 USD to bob
env.trust(USD(1000), bob);
env(pay(alice, bob, USD(100)));
env.close();
env.require(balance(bob, alice["USD"](100)));
env.require(balance(alice, bob["USD"](-100)));
// alice creates 10 tickets
std::uint32_t ticketCnt = 10;
std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
env(ticket::create(alice, ticketCnt));
env.close();
std::uint32_t const aliceSeq{env.seq(alice)};
BEAST_EXPECT(ticketCount(env, alice) == ticketCnt);
BEAST_EXPECT(ownerCount(env, alice) == ticketCnt);
while (ticketCnt > 0)
{
// alice claws back 5 USD using a ticket
env(claw(alice, bob["USD"](5)), ticket::use(aliceTicketSeq++));
env.close();
ticketCnt--;
BEAST_EXPECT(ticketCount(env, alice) == ticketCnt);
BEAST_EXPECT(ownerCount(env, alice) == ticketCnt);
}
// alice clawed back 50 USD total, trustline has 50 USD remaining
env.require(balance(bob, alice["USD"](50)));
env.require(balance(alice, bob["USD"](-50)));
// Verify that the account sequence numbers did not advance.
BEAST_EXPECT(env.seq(alice) == aliceSeq);
}
void
testWithFeats(FeatureBitset features)
{
testAllowTrustLineClawbackFlag(features);
testValidation(features);
testPermission(features);
testEnabled(features);
testMultiLine(features);
testBidirectionalLine(features);
testDeleteDefaultLine(features);
testFrozenLine(features);
testAmountExceedsAvailable(features);
testTickets(features);
}
public:
void
run() override
{
using namespace test::jtx;
FeatureBitset const all{testable_amendments()};
testWithFeats(all - featureMPTokensV1);
testWithFeats(all);
}
};
BEAST_DEFINE_TESTSUITE(Clawback, app, xrpl);
} // namespace xrpl