diff --git a/src/ripple/app/tx/impl/URIToken.cpp b/src/ripple/app/tx/impl/URIToken.cpp index 4f8a04ba4..1f357896c 100644 --- a/src/ripple/app/tx/impl/URIToken.cpp +++ b/src/ripple/app/tx/impl/URIToken.cpp @@ -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; } diff --git a/src/test/app/URIToken_test.cpp b/src/test/app/URIToken_test.cpp index 0a7a5d3dd..803bc8506 100644 --- a/src/test/app/URIToken_test.cpp +++ b/src/test/app/URIToken_test.cpp @@ -55,6 +55,23 @@ struct URIToken_test : public beast::unit_test::suite return std::distance(ownerDir.begin(), ownerDir.end()); }; + static std::pair> + 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: