debug & testing

This commit is contained in:
Denis Angell
2023-02-02 22:11:53 -05:00
committed by Richard Holland
parent 641b8c546d
commit 66105b6c68
2 changed files with 514 additions and 22 deletions

View File

@@ -63,6 +63,16 @@ inline URIOperation inferOperation(STTx const& tx)
(hasBurnableFlag ? 0b000000010U : 0) +
(blankFlags ? 0b000000001U : 0);
std::cout << "combination" << combination << "\n";
std::cout << "mint" << (0b110000001U == combination) << "\n";
std::cout << "mint" << (0b110000010U == combination) << "\n";
std::cout << "mint" << (0b010000001U == combination) << "\n";
std::cout << "mint" << (0b010000010U == combination) << "\n";
std::cout << "burn" << (0b001100000U == combination) << "\n";
std::cout << "buy" << (0b000110001U == combination) << "\n";
std::cout << "sell" << (0b000110100U == combination) << "\n";
std::cout << "sell" << (0b000111100U == combination) << "\n";
std::cout << "clear" << (0b000100001U == combination) << "\n";
switch (combination)
{
case 0b110000001U:
@@ -281,6 +291,7 @@ URIToken::preclaim(PreclaimContext const& ctx)
default:
{
std::cout << "preclaim with URIOperation::Invalid\n " << "\n";
JLOG(ctx.j.warn())
<< "URIToken txid=" << ctx.tx.getTransactionID() << " preclaim with URIOperation::Invalid\n";
return tecINTERNAL;
@@ -321,10 +332,16 @@ URIToken::doApply()
sleU = view().peek(*kl);
if (!sleU)
{
std::cout << "no entry object" << "\n";
return tecNO_ENTRY;
}
if (sleU->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN)
{
std::cout << "wrong uri token" << "\n";
return tecNO_ENTRY;
}
owner = (*sleU)[sfOwner];
issuer = (*sleU)[sfIssuer];
@@ -338,6 +355,7 @@ URIToken::doApply()
if (!sleOwner)
{
std::cout << "tecNO_ENTRY - no owner" << "\n";
JLOG(j.warn())
<< "Malformed transaction: owner of URIToken is not in the ledger.";
return tecNO_ENTRY;
@@ -450,7 +468,7 @@ URIToken::doApply()
else
{
// IOU sale
std::cout << "IOU SALE" << "\n";
STAmount availableFunds{accountFunds(
view(),
account_,
@@ -458,6 +476,8 @@ URIToken::doApply()
fhZERO_IF_FROZEN,
j)};
// std::cout << "PURCHASE AMT: " << purchaseAmount << "\n";
// std::cout << "AVAIL FUNDS: " << availableFunds << "\n";
if (purchaseAmount > availableFunds)
return tecINSUFFICIENT_FUNDS;
@@ -486,31 +506,39 @@ URIToken::doApply()
}
// remove from buyer
initBuyerBal = buyerLow ? (*sleSrcLine)[sfBalance] : -(*sleSrcLine)[sfBalance];
initBuyerBal = buyerLow ? ((*sleSrcLine)[sfBalance]) : -((*sleSrcLine)[sfBalance]);
// std::cout << "BUYER LOW: " << buyerLow << "\n";
// std::cout << "BUYER BAL: " << *initBuyerBal << "\n";
finBuyerBal = *initBuyerBal - purchaseAmount;
// std::cout << "FIN BUYER BAL: " << *finBuyerBal << "\n";
// compute amount to deliver
static Rate const parityRate(QUALITY_ONE);
auto xferRate = transferRate(view(), saleAmount->getIssuer());
dstAmt =
xferRate == parityRate
dstAmt =
xferRate == parityRate
? purchaseAmount
: multiplyRound(purchaseAmount, xferRate, purchaseAmount.issue(), true);
if (!sellerLow)
dstAmt->negate();
// std::cout << "SELLER LOW: " << sellerLow << "\n";
// std::cout << "DEST AMT: " << *dstAmt << "\n";
// if (!sellerLow)
// dstAmt->negate();
initSellerBal = !sleDstLine
? purchaseAmount.zeroed()
: (sellerLow ? (*sleDstLine)[sfBalance] : -(*sleDstLine)[sfBalance]);
// std::cout << "TRUE DEST AMT: " << *dstAmt << "\n";
initSellerBal = !sleDstLine
? purchaseAmount.zeroed()
: sellerLow ? ((*sleDstLine)[sfBalance]) : -((*sleDstLine)[sfBalance]);
// std::cout << "INIT SELLER BAL: " << *initSellerBal << "\n";
finSellerBal = *initSellerBal + *dstAmt;
// std::cout << "FINAL SELLER BAL: " << *finSellerBal << "\n";
}
// sanity check balance mutations (xrp or iou, both are checked the same way now)
if (*finSellerBal < *initSellerBal)
{
std::cout << "finSellerBal" << *finSellerBal << "< initSellerBal" << *initSellerBal << "\n";
JLOG(j.warn())
<< "URIToken txid=" << ctx_.tx.getTransactionID() << " "
<< "finSellerBal < initSellerBal";
@@ -519,6 +547,7 @@ URIToken::doApply()
if (*finBuyerBal > *initBuyerBal)
{
std::cout << "finBuyerBal > initBuyerBal\n " << "\n";
JLOG(j.warn())
<< "URIToken txid=" << ctx_.tx.getTransactionID() << " "
<< "finBuyerBal > initBuyerBal";
@@ -527,6 +556,7 @@ URIToken::doApply()
if (*finBuyerBal < beast::zero)
{
std::cout << "finBuyerBal < 0\n " << "\n";
JLOG(j.warn())
<< "URIToken txid=" << ctx_.tx.getTransactionID() << " "
<< "finBuyerBal < 0";
@@ -535,6 +565,7 @@ URIToken::doApply()
if (*finSellerBal < beast::zero)
{
std::cout << "finSellerBal < 0\n " << "\n";
JLOG(j.warn())
<< "URIToken txid=" << ctx_.tx.getTransactionID() << " "
<< "finSellerBal < 0";
@@ -693,7 +724,7 @@ URIToken::doApply()
view().update(sleU);
view().update(sleOwner);
// std::cout << "BUY tecSUCCESS" << "\n";
return tesSUCCESS;
}
case URIOperation::Burn:
@@ -714,6 +745,7 @@ URIToken::doApply()
auto const page = (*sleU)[sfOwnerNode];
if (!view().dirRemove(keylet::ownerDir(*owner), page, kl->key, true))
{
JLOG(j.fatal())
<< "Could not remove URIToken from owner directory";
return tefBAD_LEDGER;
@@ -721,6 +753,7 @@ URIToken::doApply()
view().erase(sleU);
adjustOwnerCount(view(), sle, -1, j);
// std::cout << "BURN tecSUCCESS" << "\n";
return tesSUCCESS;
}
@@ -740,6 +773,7 @@ URIToken::doApply()
sleU->setFieldAmount(sfAmount, ctx_.tx[sfAmount]);
view().update(sleU);
// std::cout << "SELL tecSUCCESS" << "\n";
return tesSUCCESS;
}

View File

@@ -55,6 +55,23 @@ struct URIToken_test : public beast::unit_test::suite
return std::distance(ownerDir.begin(), ownerDir.end());
};
static std::pair<uint256, std::shared_ptr<SLE const>>
uriTokenKeyAndSle(
ReadView const& view,
jtx::Account const& account,
std::string const& uri)
{
auto const k = keylet::uritoken(account, Blob(uri.begin(), uri.end()));
return {k.key, view.read(k)};
}
static bool
uritExists(ReadView const& view, uint256 const& id)
{
auto const slep = view.read({ltURI_TOKEN, id});
return bool(slep);
}
static Json::Value
mint(
jtx::Account const& account,
@@ -124,6 +141,30 @@ struct URIToken_test : public beast::unit_test::suite
return jv;
}
void
debugBalance(
jtx::Env const& env,
std::string const& name,
jtx::Account const& account,
jtx::IOU const& iou)
{
std::cout << name << " BALANCE XRP: " << env.balance(account) << "\n";
std::cout << name << " BALANCE USD: " << env.balance(account, iou.issue()) << "\n";
}
// void
// debugOwnerDir(
// jtx::Env const& env,
// std::string const& name,
// jtx::Account const& account,
// std::string const& uri)
// {
// auto const [urit, uritSle] = uriTokenKeyAndSle(env.current(), account, uri);
// std::cout << "URIT: " << urit << "\n";
// std::cout << name << "IN OWNER DIR: " << inOwnerDir(env.current(), account, uritSle) << "\n";
// std::cout << name << "DIR: " << ownerDirCount(env.current(), account) << "\n";
// }
void
testEnabled(FeatureBitset features)
{
@@ -414,6 +455,7 @@ struct URIToken_test : public beast::unit_test::suite
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
auto const dave = Account("dave");
auto const gw = Account("gw");
auto const USD = gw["USD"];
auto const EUR = gw["EUR"];
@@ -454,7 +496,7 @@ struct URIToken_test : public beast::unit_test::suite
// tecNO_PERMISSION - for sale to dest, you are not dest
env(buy(carol, id, USD(10)), ter(tecNO_PERMISSION));
env.close();
// tecNFTOKEN_BUY_SELL_MISMATCH - invalid buy sell amounts
env(buy(bob, id, EUR(10)), ter(tecNFTOKEN_BUY_SELL_MISMATCH));
env.close();
@@ -483,13 +525,63 @@ struct URIToken_test : public beast::unit_test::suite
//----------------------------------------------------------------------
// doApply
// clear sell
env(clear(alice, id));
env.close();
// tecNO_PERMISSION - not listed
env(buy(bob, id, USD(10)), ter(tecNO_PERMISSION));
env.close();
// set sell
env(sell(alice, id, USD(10)), txflags(tfSell), jtx::token::destination(bob));
env.close();
// tecNO_PERMISSION - for sale to dest, you are not dest
env(buy(carol, id, USD(10)), ter(tecNO_PERMISSION));
env.close();
// tecNFTOKEN_BUY_SELL_MISMATCH - invalid buy sell amounts
// tecINSUFFICIENT_PAYMENT - insuficient xrp
env(buy(bob, id, EUR(10)), ter(tecNFTOKEN_BUY_SELL_MISMATCH));
env.close();
// clear sell and set xrp sell
env(clear(alice, id));
env(sell(alice, id, XRP(1000)), txflags(tfSell));
env.close();
// tecINSUFFICIENT_PAYMENT - insuficient xrp sent
env(buy(bob, id, XRP(900)), ter(tecINSUFFICIENT_PAYMENT));
env.close();
// tecINSUFFICIENT_FUNDS - insuficient xrp - fees
// tecINSUFFICIENT_FUNDS - insuficient amount
// tecNO_LINE_INSUF_RESERVE - insuficient amount
env(buy(bob, id, XRP(1000)), ter(tecINSUFFICIENT_FUNDS));
env.close();
// clear sell and set usd sell
env(clear(alice, id));
env(sell(alice, id, USD(1000)), txflags(tfSell));
env.close();
// tecINSUFFICIENT_PAYMENT - insuficient amount sent
env(buy(bob, id, USD(900)), ter(tecINSUFFICIENT_PAYMENT));
env.close();
// tecINSUFFICIENT_FUNDS - insuficient amount sent
env(buy(bob, id, USD(10000)), ter(tecINSUFFICIENT_FUNDS));
env.close();
// fund dave 200 xrp (not enough for reserve)
env.fund(XRP(260), dave);
env.trust(USD(10000), dave);
env(pay(gw, dave, USD(1000)));
env.close();
// auto const reserveFee = env.current()->fees().accountReserve(ownerDirCount(*env.current(), dave));
// std::cout << "XRP RESERVE: " << reserveFee << "\n";
// tecNO_LINE_INSUF_RESERVE - insuficient xrp to create line
// env(buy(dave, id, USD(1000)), ter(tecNO_LINE_INSUF_RESERVE));
// env.close();
// tecDIR_FULL - unknown how to test/handle
// tecINTERNAL - unknown how to test/handle
// tecINTERNAL - unknown how to test/handle
@@ -501,14 +593,380 @@ struct URIToken_test : public beast::unit_test::suite
// tecINTERNAL - unknown how to test/handle
}
void
testClearInvalid(FeatureBitset features)
{
testcase("clear_invalid");
using namespace jtx;
using namespace std::literals::chrono_literals;
// setup env
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const gw = Account("gw");
auto const USD = gw["USD"];
auto const EUR = gw["EUR"];
env.fund(XRP(1000), alice, bob, gw);
env.trust(USD(10000), alice);
env.trust(USD(10000), bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
// mint token
std::string const uri(2, '?');
std::string const id{strHex(tokenid(alice, uri))};
env(mint(alice, uri));
env.close();
//----------------------------------------------------------------------
// operator preflight
// temDISABLED
// temMALFORMED - invalid buy flag
env(clear(alice, id), txflags(0b000100011U), ter(temMALFORMED));
env.close();
//----------------------------------------------------------------------
// preclaim
// tecNO_PERMISSION - not your uritoken
env(clear(bob, id), ter(tecNO_PERMISSION));
env.close();
}
void
testMetaAndOwnership(FeatureBitset features)
{
testcase("Metadata & Ownership");
using namespace jtx;
using namespace std::literals::chrono_literals;
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const gw = Account("gw");
auto const USD = gw["USD"];
std::string const uri(maxTokenURILength, '?');
std::string const id{strHex(tokenid(alice, uri))};
{
// Test without adding the uritoken to the recipient's owner
// directory
Env env{*this, features};
env.fund(XRP(1000), alice, bob, gw);
env.trust(USD(10000), alice);
env.trust(USD(10000), bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
env(mint(alice, uri));
env(sell(alice, id, USD(10)), txflags(tfSell));
env.close();
auto const [urit, uritSle] = uriTokenKeyAndSle(*env.current(), alice, uri);
BEAST_EXPECT(inOwnerDir(*env.current(), alice, uritSle));
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 2);
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, uritSle));
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1);
// // alice sets the sell offer
// // bob sets the buy offer
env(buy(bob, id, USD(10)));
BEAST_EXPECT(uritExists(*env.current(), urit));
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, uritSle));
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1);
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, uritSle));
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2);
}
{
// Test with adding the uritoken to the recipient's owner
// directory
Env env{*this, features};
env.fund(XRP(1000), alice, bob, gw);
env.trust(USD(10000), alice);
env.trust(USD(10000), bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
env(mint(alice, uri));
env(sell(alice, id, USD(10)), txflags(tfSell));
env.close();
auto const [urit, uritSle] = uriTokenKeyAndSle(*env.current(), alice, uri);
BEAST_EXPECT(inOwnerDir(*env.current(), alice, uritSle));
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 2);
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, uritSle));
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1);
// // alice sets the sell offer
// // bob sets the buy offer
env(buy(bob, id, USD(10)));
env.close();
BEAST_EXPECT(uritExists(*env.current(), urit));
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, uritSle));
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1);
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, uritSle));
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2);
}
}
void
testAccountDelete(FeatureBitset features)
{
testcase("Account Delete");
using namespace test::jtx;
using namespace std::literals::chrono_literals;
auto rmAccount = [this](
Env& env,
Account const& toRm,
Account const& dst,
TER expectedTer = tesSUCCESS) {
// only allow an account to be deleted if the account's sequence
// number is at least 256 less than the current ledger sequence
for (auto minRmSeq = env.seq(toRm) + 257;
env.current()->seq() < minRmSeq;
env.close())
{
}
env(acctdelete(toRm, dst),
fee(drops(env.current()->fees().increment)),
ter(expectedTer));
env.close();
this->BEAST_EXPECT(
isTesSuccess(expectedTer) ==
!env.closed()->exists(keylet::account(toRm.id())));
};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
auto const gw = Account("gw");
auto const USD = gw["USD"];
std::string const uri(maxTokenURILength, '?');
std::string const id{strHex(tokenid(alice, uri))};
{
Env env{*this, features};
env.fund(XRP(1000), alice, bob, carol, gw);
env.trust(USD(10000), alice);
env.trust(USD(10000), bob);
env.trust(USD(10000), carol);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env(pay(gw, carol, USD(1000)));
env.close();
auto const feeDrops = env.current()->fees().base;
// debugBalance(env, "alice", alice, USD);
// debugBalance(env, "bob", bob, USD);
// mint a uritoken from alice
env(mint(alice, uri));
env.close();
BEAST_EXPECT(uritExists(*env.current(), tokenid(alice, uri)));
env(sell(alice, id, USD(10)), txflags(tfSell));
env.close();
// alice has trustline + mint + sell
rmAccount(env, alice, bob, tecHAS_OBLIGATIONS);
env(clear(alice, id));
env(burn(alice, id), txflags(tfBurn));
env.close();
// alice still has a trustline
rmAccount(env, alice, bob, tecHAS_OBLIGATIONS);
BEAST_EXPECT(uritExists(*env.current(), tokenid(alice, uri)));
// buy should fail if the uri token was removed
auto preBob = env.balance(bob, USD.issue());
env(buy(bob, id, USD(10)), ter(tecNO_ENTRY));
env.close();
BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob);
env(mint(bob, uri));
BEAST_EXPECT(uritExists(*env.current(), tokenid(bob, uri)));
}
}
void
testUsingTickets(FeatureBitset features)
{
testcase("using tickets");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const gw = Account("gw");
auto USD = gw["USD"];
env.fund(XRP(1000), alice, bob, gw);
env.trust(USD(10000), alice);
env.trust(USD(10000), bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
// alice and bob grab enough tickets for all of the following
// transactions. Note that once the tickets are acquired alice's
// and bob's account sequence numbers should not advance.
std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
env(ticket::create(alice, 10));
std::uint32_t const aliceSeq{env.seq(alice)};
std::uint32_t bobTicketSeq{env.seq(bob) + 1};
env(ticket::create(bob, 10));
std::uint32_t const bobSeq{env.seq(bob)};
std::string const uri(maxTokenURILength, '?');
std::string const id{strHex(tokenid(alice, uri))};
env(mint(alice, uri), ticket::use(aliceTicketSeq++));
env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
BEAST_EXPECT(env.seq(alice) == aliceSeq);
BEAST_EXPECT(uritExists(*env.current(), tokenid(alice, uri)));
// {
// auto const preAlice = env.balance(alice);
// env(sell(alice, id, XRP(1000)), ticket::use(aliceTicketSeq++));
// env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
// BEAST_EXPECT(env.seq(alice) == aliceSeq);
// auto const feeDrops = env.current()->fees().base;
// BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1000) - feeDrops);
// }
// BEAST_EXPECT(uritExists(*env.current(), tokenid(alice, uri)));
// {
// // No signature needed since the bob is buying
// auto const preBob = env.balance(bob);
// env(buy(bob, id, USD(10)), ticket::use(bobTicketSeq++));
// env.require(tickets(bob, env.seq(bob) - bobTicketSeq));
// BEAST_EXPECT(env.seq(bob) == bobSeq);
// BEAST_EXPECT(uritExists(*env.current(), tokenid(alice, uri)));
// BEAST_EXPECT(env.balance(bob) == preBob + USD(10));
// }
// {
// // Claim with signature
// auto preBob = env.balance(bob);
// auto const delta = XRP(500);
// auto const reqBal = chanBal + delta;
// auto const authAmt = reqBal + XRP(100);
// assert(reqBal <= chanAmt);
// auto const sig =
// signClaimAuth(alice.pk(), alice.sk(), chan, authAmt);
// env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()),
// ticket::use(bobTicketSeq++));
// env.require(tickets(bob, env.seq(bob) - bobTicketSeq));
// BEAST_EXPECT(env.seq(bob) == bobSeq);
// BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal);
// BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt);
// auto const feeDrops = env.current()->fees().base;
// BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops);
// chanBal = reqBal;
// // claim again
// preBob = env.balance(bob);
// // A transaction that generates a tec still consumes its ticket.
// env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()),
// ticket::use(bobTicketSeq++),
// ter(tecUNFUNDED_PAYMENT));
// env.require(tickets(bob, env.seq(bob) - bobTicketSeq));
// BEAST_EXPECT(env.seq(bob) == bobSeq);
// BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal);
// BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt);
// BEAST_EXPECT(env.balance(bob) == preBob - feeDrops);
// }
// {
// // Try to claim more than authorized
// auto const preBob = env.balance(bob);
// STAmount const authAmt = chanBal + XRP(500);
// STAmount const reqAmt = authAmt + drops(1);
// assert(reqAmt <= chanAmt);
// // Note that since claim() returns a tem (neither tec nor tes),
// // the ticket is not consumed. So we don't increment bobTicket.
// auto const sig =
// signClaimAuth(alice.pk(), alice.sk(), chan, authAmt);
// env(claim(bob, chan, reqAmt, authAmt, Slice(sig), alice.pk()),
// ticket::use(bobTicketSeq),
// ter(temBAD_AMOUNT));
// env.require(tickets(bob, env.seq(bob) - bobTicketSeq));
// BEAST_EXPECT(env.seq(bob) == bobSeq);
// BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal);
// BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt);
// BEAST_EXPECT(env.balance(bob) == preBob);
// }
// // Dst tries to fund the channel
// env(fund(bob, chan, XRP(1000)),
// ticket::use(bobTicketSeq++),
// ter(tecNO_PERMISSION));
// env.require(tickets(bob, env.seq(bob) - bobTicketSeq));
// BEAST_EXPECT(env.seq(bob) == bobSeq);
// BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal);
// BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt);
// {
// // Dst closes channel
// auto const preAlice = env.balance(alice);
// auto const preBob = env.balance(bob);
// env(claim(bob, chan),
// txflags(tfClose),
// ticket::use(bobTicketSeq++));
// env.require(tickets(bob, env.seq(bob) - bobTicketSeq));
// BEAST_EXPECT(env.seq(bob) == bobSeq);
// BEAST_EXPECT(!channelExists(*env.current(), chan));
// auto const feeDrops = env.current()->fees().base;
// auto const delta = chanAmt - chanBal;
// assert(delta > beast::zero);
// BEAST_EXPECT(env.balance(alice) == preAlice + delta);
// BEAST_EXPECT(env.balance(bob) == preBob - feeDrops);
// }
// env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
// BEAST_EXPECT(env.seq(alice) == aliceSeq);
// env.require(tickets(bob, env.seq(bob) - bobTicketSeq));
// BEAST_EXPECT(env.seq(bob) == bobSeq);
}
void
testWithFeats(FeatureBitset features)
{
testEnabled(features);
testMintInvalid(features);
testBurnInvalid(features);
testSellInvalid(features);
testBuyInvalid(features);
// testEnabled(features);
// testMintInvalid(features);
// testBurnInvalid(features);
// testSellInvalid(features);
// testBuyInvalid(features);
// testClearInvalid(features);
// testMetaAndOwnership(features);
// testAccountDelete(features);
testUsingTickets(features);
}
public: