#include <test/jtx.h>

#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/jss.h>

namespace xrpl {
namespace test {
namespace jtx {

/** Set Expiration on a JTx. */
class expiration
{
private:
    std::uint32_t const expiry_;

public:
    explicit expiration(NetClock::time_point const& expiry)
        : expiry_{expiry.time_since_epoch().count()}
    {
    }

    void
    operator()(Env&, JTx& jt) const
    {
        jt[sfExpiration.jsonName] = expiry_;
    }
};

/** Set SourceTag on a JTx. */
class source_tag
{
private:
    std::uint32_t const tag_;

public:
    explicit source_tag(std::uint32_t tag) : tag_{tag}
    {
    }

    void
    operator()(Env&, JTx& jt) const
    {
        jt[sfSourceTag.jsonName] = tag_;
    }
};

/** Set DestinationTag on a JTx. */
class dest_tag
{
private:
    std::uint32_t const tag_;

public:
    explicit dest_tag(std::uint32_t tag) : tag_{tag}
    {
    }

    void
    operator()(Env&, JTx& jt) const
    {
        jt[sfDestinationTag.jsonName] = tag_;
    }
};

}  // namespace jtx
}  // namespace test

class Check_test : public beast::unit_test::suite
{
    static uint256
    getCheckIndex(AccountID const& account, std::uint32_t uSequence)
    {
        return keylet::check(account, uSequence).key;
    }

    // Helper function that returns the Checks on an account.
    static std::vector<std::shared_ptr<SLE const>>
    checksOnAccount(test::jtx::Env& env, test::jtx::Account account)
    {
        std::vector<std::shared_ptr<SLE const>> result;
        forEachItem(*env.current(), account, [&result](std::shared_ptr<SLE const> const& sle) {
            if (sle && sle->getType() == ltCHECK)
                result.push_back(sle);
        });
        return result;
    }

    // Helper function that verifies the expected DeliveredAmount is present.
    //
    // NOTE: the function _infers_ the transaction to operate on by calling
    // env.tx(), which returns the result from the most recent transaction.
    void
    verifyDeliveredAmount(test::jtx::Env& env, STAmount const& amount)
    {
        // Get the hash for the most recent transaction.
        std::string const txHash{env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};

        // Verify DeliveredAmount and delivered_amount metadata are correct.
        env.close();
        Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];

        // Expect there to be a DeliveredAmount field.
        if (!BEAST_EXPECT(meta.isMember(sfDeliveredAmount.jsonName)))
            return;

        // DeliveredAmount and delivered_amount should both be present and
        // equal amount.
        BEAST_EXPECT(meta[sfDeliveredAmount.jsonName] == amount.getJson(JsonOptions::none));
        BEAST_EXPECT(meta[jss::delivered_amount] == amount.getJson(JsonOptions::none));
    }

    void
    testEnabled(FeatureBitset features)
    {
        testcase("Enabled");

        using namespace test::jtx;
        Account const alice{"alice"};
        {
            // If the Checks amendment is enabled all check-related
            // facilities should be available.
            Env env{*this, features};

            env.fund(XRP(1000), alice);
            env.close();

            uint256 const checkId1{getCheckIndex(env.master, env.seq(env.master))};
            env(check::create(env.master, alice, XRP(100)));
            env.close();

            env(check::cash(alice, checkId1, XRP(100)));
            env.close();

            uint256 const checkId2{getCheckIndex(env.master, env.seq(env.master))};
            env(check::create(env.master, alice, XRP(100)));
            env.close();

            env(check::cancel(alice, checkId2));
            env.close();
        }
    }

    void
    testCreateValid(FeatureBitset features)
    {
        // Explore many of the valid ways to create a check.
        testcase("Create valid");

        using namespace test::jtx;

        Account const gw{"gateway"};
        Account const alice{"alice"};
        Account const bob{"bob"};
        IOU const USD{gw["USD"]};

        Env env{*this, features};

        STAmount const startBalance{XRP(1000).value()};
        env.fund(startBalance, gw, alice, bob);
        env.close();

        // Note that no trust line has been set up for alice, but alice can
        // still write a check for USD.  You don't have to have the funds
        // necessary to cover a check in order to write a check.
        auto writeTwoChecks = [&env, &USD, this](Account const& from, Account const& to) {
            std::uint32_t const fromOwnerCount{ownerCount(env, from)};
            std::uint32_t const toOwnerCount{ownerCount(env, to)};

            std::size_t const fromCkCount{checksOnAccount(env, from).size()};
            std::size_t const toCkCount{checksOnAccount(env, to).size()};

            env(check::create(from, to, XRP(2000)));
            env.close();

            env(check::create(from, to, USD(50)));
            env.close();

            BEAST_EXPECT(checksOnAccount(env, from).size() == fromCkCount + 2);
            BEAST_EXPECT(checksOnAccount(env, to).size() == toCkCount + 2);

            env.require(owners(from, fromOwnerCount + 2));
            env.require(owners(to, to == from ? fromOwnerCount + 2 : toOwnerCount));
        };
        //  from     to
        writeTwoChecks(alice, bob);
        writeTwoChecks(gw, alice);
        writeTwoChecks(alice, gw);

        // Now try adding the various optional fields.  There's no
        // expected interaction between these optional fields; other than
        // the expiration, they are just plopped into the ledger.  So I'm
        // not looking at interactions.
        using namespace std::chrono_literals;
        std::size_t const aliceCount{checksOnAccount(env, alice).size()};
        std::size_t const bobCount{checksOnAccount(env, bob).size()};
        env(check::create(alice, bob, USD(50)), expiration(env.now() + 1s));
        env.close();

        env(check::create(alice, bob, USD(50)), source_tag(2));
        env.close();
        env(check::create(alice, bob, USD(50)), dest_tag(3));
        env.close();
        env(check::create(alice, bob, USD(50)), invoice_id(uint256{4}));
        env.close();
        env(check::create(alice, bob, USD(50)),
            expiration(env.now() + 1s),
            source_tag(12),
            dest_tag(13),
            invoice_id(uint256{4}));
        env.close();

        BEAST_EXPECT(checksOnAccount(env, alice).size() == aliceCount + 5);
        BEAST_EXPECT(checksOnAccount(env, bob).size() == bobCount + 5);

        // Use a regular key and also multisign to create a check.
        Account const alie{"alie", KeyType::ed25519};
        env(regkey(alice, alie));
        env.close();

        Account const bogie{"bogie", KeyType::secp256k1};
        Account const demon{"demon", KeyType::ed25519};
        env(signers(alice, 2, {{bogie, 1}, {demon, 1}}), sig(alie));
        env.close();

        // alice uses her regular key to create a check.
        env(check::create(alice, bob, USD(50)), sig(alie));
        env.close();
        BEAST_EXPECT(checksOnAccount(env, alice).size() == aliceCount + 6);
        BEAST_EXPECT(checksOnAccount(env, bob).size() == bobCount + 6);

        // alice uses multisigning to create a check.
        XRPAmount const baseFeeDrops{env.current()->fees().base};
        env(check::create(alice, bob, USD(50)), msig(bogie, demon), fee(3 * baseFeeDrops));
        env.close();
        BEAST_EXPECT(checksOnAccount(env, alice).size() == aliceCount + 7);
        BEAST_EXPECT(checksOnAccount(env, bob).size() == bobCount + 7);
    }

    void
    testCreateDisallowIncoming(FeatureBitset features)
    {
        testcase("Create valid with disallow incoming");

        using namespace test::jtx;

        Account const gw{"gateway"};
        Account const alice{"alice"};
        Account const bob{"bob"};
        IOU const USD{gw["USD"]};

        Env env{*this, features};

        STAmount const startBalance{XRP(1000).value()};
        env.fund(startBalance, gw, alice, bob);
        env.close();

        /*
         * Attempt to create two checks from `from` to `to` and
         * require they both result in error/success code `expected`
         */
        auto writeTwoChecksDI = [&env, &USD, this](
                                    Account const& from, Account const& to, TER expected) {
            std::uint32_t const fromOwnerCount{ownerCount(env, from)};
            std::uint32_t const toOwnerCount{ownerCount(env, to)};

            std::size_t const fromCkCount{checksOnAccount(env, from).size()};
            std::size_t const toCkCount{checksOnAccount(env, to).size()};

            env(check::create(from, to, XRP(2000)), ter(expected));
            env.close();

            env(check::create(from, to, USD(50)), ter(expected));
            env.close();

            if (isTesSuccess(expected))
            {
                BEAST_EXPECT(checksOnAccount(env, from).size() == fromCkCount + 2);
                BEAST_EXPECT(checksOnAccount(env, to).size() == toCkCount + 2);

                env.require(owners(from, fromOwnerCount + 2));
                env.require(owners(to, to == from ? fromOwnerCount + 2 : toOwnerCount));
                return;
            }

            BEAST_EXPECT(checksOnAccount(env, from).size() == fromCkCount);
            BEAST_EXPECT(checksOnAccount(env, to).size() == toCkCount);

            env.require(owners(from, fromOwnerCount));
            env.require(owners(to, to == from ? fromOwnerCount : toOwnerCount));
        };

        // enable the DisallowIncoming flag on both bob and alice
        env(fset(bob, asfDisallowIncomingCheck));
        env(fset(alice, asfDisallowIncomingCheck));
        env.close();

        // both alice and bob can't receive checks
        writeTwoChecksDI(alice, bob, tecNO_PERMISSION);
        writeTwoChecksDI(gw, alice, tecNO_PERMISSION);

        // remove the flag from alice but not from bob
        env(fclear(alice, asfDisallowIncomingCheck));
        env.close();

        // now bob can send alice a cheque but not visa-versa
        writeTwoChecksDI(bob, alice, tesSUCCESS);
        writeTwoChecksDI(alice, bob, tecNO_PERMISSION);

        // remove bob's flag too
        env(fclear(bob, asfDisallowIncomingCheck));
        env.close();

        // now they can send checks freely
        writeTwoChecksDI(bob, alice, tesSUCCESS);
        writeTwoChecksDI(alice, bob, tesSUCCESS);
    }

    void
    testCreateInvalid(FeatureBitset features)
    {
        // Explore many of the invalid ways to create a check.
        testcase("Create invalid");

        using namespace test::jtx;

        Account const gw1{"gateway1"};
        Account const gwF{"gatewayFrozen"};
        Account const alice{"alice"};
        Account const bob{"bob"};
        IOU const USD{gw1["USD"]};

        Env env{*this, features};

        STAmount const startBalance{XRP(1000).value()};
        env.fund(startBalance, gw1, gwF, alice, bob);
        env.close();

        // Bad fee.
        env(check::create(alice, bob, USD(50)), fee(drops(-10)), ter(temBAD_FEE));
        env.close();

        // Bad flags.
        env(check::create(alice, bob, USD(50)), txflags(tfImmediateOrCancel), ter(temINVALID_FLAG));
        env.close();

        // Check to self.
        env(check::create(alice, alice, XRP(10)), ter(temREDUNDANT));
        env.close();

        // Bad amount.
        env(check::create(alice, bob, drops(-1)), ter(temBAD_AMOUNT));
        env.close();

        env(check::create(alice, bob, drops(0)), ter(temBAD_AMOUNT));
        env.close();

        env(check::create(alice, bob, drops(1)));
        env.close();

        env(check::create(alice, bob, USD(-1)), ter(temBAD_AMOUNT));
        env.close();

        env(check::create(alice, bob, USD(0)), ter(temBAD_AMOUNT));
        env.close();

        env(check::create(alice, bob, USD(1)));
        env.close();
        {
            IOU const BAD{gw1, badCurrency()};
            env(check::create(alice, bob, BAD(2)), ter(temBAD_CURRENCY));
            env.close();
        }

        // Bad expiration.
        env(check::create(alice, bob, USD(50)),
            expiration(NetClock::time_point{}),
            ter(temBAD_EXPIRATION));
        env.close();

        // Destination does not exist.
        Account const bogie{"bogie"};
        env(check::create(alice, bogie, USD(50)), ter(tecNO_DST));
        env.close();

        // Require destination tag.
        env(fset(bob, asfRequireDest));
        env.close();

        env(check::create(alice, bob, USD(50)), ter(tecDST_TAG_NEEDED));
        env.close();

        env(check::create(alice, bob, USD(50)), dest_tag(11));
        env.close();

        env(fclear(bob, asfRequireDest));
        env.close();
        {
            // Globally frozen asset.
            IOU const USF{gwF["USF"]};
            env(fset(gwF, asfGlobalFreeze));
            env.close();

            env(check::create(alice, bob, USF(50)), ter(tecFROZEN));
            env.close();

            env(fclear(gwF, asfGlobalFreeze));
            env.close();

            env(check::create(alice, bob, USF(50)));
            env.close();
        }
        {
            // Frozen trust line.  Check creation should be similar to payment
            // behavior in the face of frozen trust lines.
            env.trust(USD(1000), alice);
            env.trust(USD(1000), bob);
            env.close();
            env(pay(gw1, alice, USD(25)));
            env(pay(gw1, bob, USD(25)));
            env.close();

            // Setting trustline freeze in one direction prevents alice from
            // creating a check for USD.  But bob and gw1 should still be able
            // to create a check for USD to alice.
            env(trust(gw1, alice["USD"](0), tfSetFreeze));
            env.close();
            env(check::create(alice, bob, USD(50)), ter(tecFROZEN));
            env.close();
            env(pay(alice, bob, USD(1)), ter(tecPATH_DRY));
            env.close();
            env(check::create(bob, alice, USD(50)));
            env.close();
            env(pay(bob, alice, USD(1)));
            env.close();
            env(check::create(gw1, alice, USD(50)));
            env.close();
            env(pay(gw1, alice, USD(1)));
            env.close();

            // Clear that freeze.  Now check creation works.
            env(trust(gw1, alice["USD"](0), tfClearFreeze));
            env.close();
            env(check::create(alice, bob, USD(50)));
            env.close();
            env(check::create(bob, alice, USD(50)));
            env.close();
            env(check::create(gw1, alice, USD(50)));
            env.close();

            // Freezing in the other direction does not effect alice's USD
            // check creation, but prevents bob and gw1 from writing a check
            // for USD to alice.
            env(trust(alice, USD(0), tfSetFreeze));
            env.close();
            env(check::create(alice, bob, USD(50)));
            env.close();
            env(pay(alice, bob, USD(1)));
            env.close();
            env(check::create(bob, alice, USD(50)), ter(tecFROZEN));
            env.close();
            env(pay(bob, alice, USD(1)), ter(tecPATH_DRY));
            env.close();
            env(check::create(gw1, alice, USD(50)), ter(tecFROZEN));
            env.close();
            env(pay(gw1, alice, USD(1)), ter(tecPATH_DRY));
            env.close();

            // Clear that freeze.
            env(trust(alice, USD(0), tfClearFreeze));
            env.close();
        }

        // Expired expiration.
        env(check::create(alice, bob, USD(50)), expiration(env.now()), ter(tecEXPIRED));
        env.close();

        using namespace std::chrono_literals;
        env(check::create(alice, bob, USD(50)), expiration(env.now() + 1s));
        env.close();

        // Insufficient reserve.
        Account const cheri{"cheri"};
        env.fund(env.current()->fees().accountReserve(1) - drops(1), cheri);
        env.close();

        env(check::create(cheri, bob, USD(50)),
            fee(drops(env.current()->fees().base)),
            ter(tecINSUFFICIENT_RESERVE));
        env.close();

        env(pay(bob, cheri, drops(env.current()->fees().base + 1)));
        env.close();

        env(check::create(cheri, bob, USD(50)));
        env.close();
    }

    void
    testCashXRP(FeatureBitset features)
    {
        // Explore many of the valid ways to cash a check for XRP.
        testcase("Cash XRP");

        using namespace test::jtx;

        Account const alice{"alice"};
        Account const bob{"bob"};

        Env env{*this, features};

        XRPAmount const baseFeeDrops{env.current()->fees().base};
        STAmount const startBalance{XRP(300).value()};
        env.fund(startBalance, alice, bob);
        env.close();
        {
            // Basic XRP check.
            uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, XRP(10)));
            env.close();
            env.require(balance(alice, startBalance - drops(baseFeeDrops)));
            env.require(balance(bob, startBalance));
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 1);
            BEAST_EXPECT(checksOnAccount(env, bob).size() == 1);
            BEAST_EXPECT(ownerCount(env, alice) == 1);
            BEAST_EXPECT(ownerCount(env, bob) == 0);

            env(check::cash(bob, chkId, XRP(10)));
            env.close();
            env.require(balance(alice, startBalance - XRP(10) - drops(baseFeeDrops)));
            env.require(balance(bob, startBalance + XRP(10) - drops(baseFeeDrops)));
            BEAST_EXPECT(checksOnAccount(env, alice).empty());
            BEAST_EXPECT(checksOnAccount(env, bob).empty());
            BEAST_EXPECT(ownerCount(env, alice) == 0);
            BEAST_EXPECT(ownerCount(env, bob) == 0);

            // Make alice's and bob's balances easy to think about.
            env(pay(env.master, alice, XRP(10) + drops(baseFeeDrops)));
            env(pay(bob, env.master, XRP(10) - drops(baseFeeDrops * 2)));
            env.close();
            env.require(balance(alice, startBalance));
            env.require(balance(bob, startBalance));
        }
        {
            // Write a check that chews into alice's reserve.
            STAmount const reserve{env.current()->fees().reserve};
            STAmount const checkAmount{startBalance - reserve - drops(baseFeeDrops)};
            uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, checkAmount));
            env.close();

            // bob tries to cash for more than the check amount.
            env(check::cash(bob, chkId, checkAmount + drops(1)), ter(tecPATH_PARTIAL));
            env.close();
            env(check::cash(bob, chkId, check::DeliverMin(checkAmount + drops(1))),
                ter(tecPATH_PARTIAL));
            env.close();

            // bob cashes exactly the check amount.  This is successful
            // because one unit of alice's reserve is released when the
            // check is consumed.
            env(check::cash(bob, chkId, check::DeliverMin(checkAmount)));
            verifyDeliveredAmount(env, drops(checkAmount.mantissa()));
            env.require(balance(alice, reserve));
            env.require(balance(bob, startBalance + checkAmount - drops(baseFeeDrops * 3)));
            BEAST_EXPECT(checksOnAccount(env, alice).empty());
            BEAST_EXPECT(checksOnAccount(env, bob).empty());
            BEAST_EXPECT(ownerCount(env, alice) == 0);
            BEAST_EXPECT(ownerCount(env, bob) == 0);

            // Make alice's and bob's balances easy to think about.
            env(pay(env.master, alice, checkAmount + drops(baseFeeDrops)));
            env(pay(bob, env.master, checkAmount - drops(baseFeeDrops * 4)));
            env.close();
            env.require(balance(alice, startBalance));
            env.require(balance(bob, startBalance));
        }
        {
            // Write a check that goes one drop past what alice can pay.
            STAmount const reserve{env.current()->fees().reserve};
            STAmount const checkAmount{startBalance - reserve - drops(baseFeeDrops - 1)};
            uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, checkAmount));
            env.close();

            // bob tries to cash for exactly the check amount.  Fails because
            // alice is one drop shy of funding the check.
            env(check::cash(bob, chkId, checkAmount), ter(tecPATH_PARTIAL));
            env.close();

            // bob decides to get what he can from the bounced check.
            env(check::cash(bob, chkId, check::DeliverMin(drops(1))));
            verifyDeliveredAmount(env, drops(checkAmount.mantissa() - 1));
            env.require(balance(alice, reserve));
            env.require(balance(bob, startBalance + checkAmount - drops(baseFeeDrops * 2 + 1)));
            BEAST_EXPECT(checksOnAccount(env, alice).empty());
            BEAST_EXPECT(checksOnAccount(env, bob).empty());
            BEAST_EXPECT(ownerCount(env, alice) == 0);
            BEAST_EXPECT(ownerCount(env, bob) == 0);

            // Make alice's and bob's balances easy to think about.
            env(pay(env.master, alice, checkAmount + drops(baseFeeDrops - 1)));
            env(pay(bob, env.master, checkAmount - drops(baseFeeDrops * 3 + 1)));
            env.close();
            env.require(balance(alice, startBalance));
            env.require(balance(bob, startBalance));
        }
    }

    void
    testCashIOU(FeatureBitset features)
    {
        // Explore many of the valid ways to cash a check for an IOU.
        testcase("Cash IOU");

        using namespace test::jtx;

        Account const gw{"gateway"};
        Account const alice{"alice"};
        Account const bob{"bob"};
        IOU const USD{gw["USD"]};
        {
            // Simple IOU check cashed with Amount (with failures).
            Env env{*this, features};

            env.fund(XRP(1000), gw, alice, bob);
            env.close();

            // alice writes the check before she gets the funds.
            uint256 const chkId1{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(10)));
            env.close();

            // bob attempts to cash the check.  Should fail.
            env(check::cash(bob, chkId1, USD(10)), ter(tecPATH_PARTIAL));
            env.close();

            // alice gets almost enough funds.  bob tries and fails again.
            env(trust(alice, USD(20)));
            env.close();
            env(pay(gw, alice, USD(9.5)));
            env.close();
            env(check::cash(bob, chkId1, USD(10)), ter(tecPATH_PARTIAL));
            env.close();

            // alice gets the last of the necessary funds.  bob tries again
            // and fails because he hasn't got a trust line for USD.
            env(pay(gw, alice, USD(0.5)));
            env.close();

            // bob sets up the trust line, but not at a high enough limit.
            env(trust(bob, USD(9.5)));
            env.close();

            // bob sets the trust line limit high enough but asks for more
            // than the check's SendMax.
            env(trust(bob, USD(10.5)));
            env.close();
            env(check::cash(bob, chkId1, USD(10.5)), ter(tecPATH_PARTIAL));
            env.close();

            // bob asks for exactly the check amount and the check clears.
            env(check::cash(bob, chkId1, USD(10)));
            env.close();
            env.require(balance(alice, USD(0)));
            env.require(balance(bob, USD(10)));
            BEAST_EXPECT(checksOnAccount(env, alice).empty());
            BEAST_EXPECT(checksOnAccount(env, bob).empty());
            BEAST_EXPECT(ownerCount(env, alice) == 1);
            BEAST_EXPECT(ownerCount(env, bob) == 1);

            // bob tries to cash the same check again, which fails.
            env(check::cash(bob, chkId1, USD(10)), ter(tecNO_ENTRY));
            env.close();

            // bob pays alice USD(7) so he can try another case.
            env(pay(bob, alice, USD(7)));
            env.close();

            uint256 const chkId2{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(7)));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 1);
            BEAST_EXPECT(checksOnAccount(env, bob).size() == 1);

            // bob cashes the check for less than the face amount.  That works,
            // consumes the check, and bob receives as much as he asked for.
            env(check::cash(bob, chkId2, USD(5)));
            env.close();
            env.require(balance(alice, USD(2)));
            env.require(balance(bob, USD(8)));
            BEAST_EXPECT(checksOnAccount(env, alice).empty());
            BEAST_EXPECT(checksOnAccount(env, bob).empty());
            BEAST_EXPECT(ownerCount(env, alice) == 1);
            BEAST_EXPECT(ownerCount(env, bob) == 1);

            // alice writes two checks for USD(2), although she only has USD(2).
            uint256 const chkId3{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(2)));
            env.close();
            uint256 const chkId4{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(2)));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 2);
            BEAST_EXPECT(checksOnAccount(env, bob).size() == 2);

            // bob cashes the second check for the face amount.
            env(check::cash(bob, chkId4, USD(2)));
            env.close();
            env.require(balance(alice, USD(0)));
            env.require(balance(bob, USD(10)));
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 1);
            BEAST_EXPECT(checksOnAccount(env, bob).size() == 1);
            BEAST_EXPECT(ownerCount(env, alice) == 2);
            BEAST_EXPECT(ownerCount(env, bob) == 1);

            // bob is not allowed to cash the last check for USD(0), he must
            // use check::cancel instead.
            env(check::cash(bob, chkId3, USD(0)), ter(temBAD_AMOUNT));
            env.close();
            env.require(balance(alice, USD(0)));
            env.require(balance(bob, USD(10)));
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 1);
            BEAST_EXPECT(checksOnAccount(env, bob).size() == 1);
            BEAST_EXPECT(ownerCount(env, alice) == 2);
            BEAST_EXPECT(ownerCount(env, bob) == 1);

            // Automatic trust lines are enabled.  But one aspect of
            // automatic trust lines is that they allow the account
            // cashing a check to exceed their trust line limit.  Show
            // that at work.
            //
            // bob's trust line limit is currently USD(10.5).  Show that
            // a payment to bob cannot exceed that trust line, but cashing
            // a check can.

            // Payment of 20 USD fails.
            env(pay(gw, bob, USD(20)), ter(tecPATH_PARTIAL));
            env.close();

            uint256 const chkId20{getCheckIndex(gw, env.seq(gw))};
            env(check::create(gw, bob, USD(20)));
            env.close();

            // However cashing a check for 20 USD succeeds.
            env(check::cash(bob, chkId20, USD(20)));
            env.close();
            env.require(balance(bob, USD(30)));

            // Clean up this most recent experiment so the rest of the
            // tests work.
            env(pay(bob, gw, USD(20)));

            // ... so bob cancels alice's remaining check.
            env(check::cancel(bob, chkId3));
            env.close();
            env.require(balance(alice, USD(0)));
            env.require(balance(bob, USD(10)));
            BEAST_EXPECT(checksOnAccount(env, alice).empty());
            BEAST_EXPECT(checksOnAccount(env, bob).empty());
            BEAST_EXPECT(ownerCount(env, alice) == 1);
            BEAST_EXPECT(ownerCount(env, bob) == 1);
        }
        {
            // Simple IOU check cashed with DeliverMin (with failures).
            Env env{*this, features};

            env.fund(XRP(1000), gw, alice, bob);
            env.close();

            env(trust(alice, USD(20)));
            env(trust(bob, USD(20)));
            env.close();
            env(pay(gw, alice, USD(8)));
            env.close();

            // alice creates several checks ahead of time.
            uint256 const chkId9{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(9)));
            env.close();
            uint256 const chkId8{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(8)));
            env.close();
            uint256 const chkId7{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(7)));
            env.close();
            uint256 const chkId6{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(6)));
            env.close();

            // bob attempts to cash a check for the amount on the check.
            // Should fail, since alice doesn't have the funds.
            env(check::cash(bob, chkId9, check::DeliverMin(USD(9))), ter(tecPATH_PARTIAL));
            env.close();

            // bob sets a DeliverMin of 7 and gets all that alice has.
            env(check::cash(bob, chkId9, check::DeliverMin(USD(7))));
            verifyDeliveredAmount(env, USD(8));
            env.require(balance(alice, USD(0)));
            env.require(balance(bob, USD(8)));
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 3);
            BEAST_EXPECT(checksOnAccount(env, bob).size() == 3);
            BEAST_EXPECT(ownerCount(env, alice) == 4);
            BEAST_EXPECT(ownerCount(env, bob) == 1);

            // bob pays alice USD(7) so he can use another check.
            env(pay(bob, alice, USD(7)));
            env.close();

            // Using DeliverMin for the SendMax value of the check (and no
            // transfer fees) should work just like setting Amount.
            env(check::cash(bob, chkId7, check::DeliverMin(USD(7))));
            verifyDeliveredAmount(env, USD(7));
            env.require(balance(alice, USD(0)));
            env.require(balance(bob, USD(8)));
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 2);
            BEAST_EXPECT(checksOnAccount(env, bob).size() == 2);
            BEAST_EXPECT(ownerCount(env, alice) == 3);
            BEAST_EXPECT(ownerCount(env, bob) == 1);

            // bob pays alice USD(8) so he can use the last two checks.
            env(pay(bob, alice, USD(8)));
            env.close();

            // alice has USD(8). If bob uses the check for USD(6) and uses a
            // DeliverMin of 4, he should get the SendMax value of the check.
            env(check::cash(bob, chkId6, check::DeliverMin(USD(4))));
            verifyDeliveredAmount(env, USD(6));
            env.require(balance(alice, USD(2)));
            env.require(balance(bob, USD(6)));
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 1);
            BEAST_EXPECT(checksOnAccount(env, bob).size() == 1);
            BEAST_EXPECT(ownerCount(env, alice) == 2);
            BEAST_EXPECT(ownerCount(env, bob) == 1);

            // bob cashes the last remaining check setting a DeliverMin.
            // of exactly alice's remaining USD.
            env(check::cash(bob, chkId8, check::DeliverMin(USD(2))));
            verifyDeliveredAmount(env, USD(2));
            env.require(balance(alice, USD(0)));
            env.require(balance(bob, USD(8)));
            BEAST_EXPECT(checksOnAccount(env, alice).empty());
            BEAST_EXPECT(checksOnAccount(env, bob).empty());
            BEAST_EXPECT(ownerCount(env, alice) == 1);
            BEAST_EXPECT(ownerCount(env, bob) == 1);
        }
        {
            // Examine the effects of the asfRequireAuth flag.
            Env env(*this, features);

            env.fund(XRP(1000), gw, alice, bob);
            env(fset(gw, asfRequireAuth));
            env.close();
            env(trust(gw, alice["USD"](100)), txflags(tfSetfAuth));
            env(trust(alice, USD(20)));
            env.close();
            env(pay(gw, alice, USD(8)));
            env.close();

            // alice writes a check to bob for USD.  bob can't cash it
            // because he is not authorized to hold gw["USD"].
            uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(7)));
            env.close();

            env(check::cash(bob, chkId, USD(7)), ter(tecNO_AUTH));
            env.close();

            // Now give bob a trustline for USD.  bob still can't cash the
            // check because he is not authorized.
            env(trust(bob, USD(5)));
            env.close();

            env(check::cash(bob, chkId, USD(7)), ter(tecNO_AUTH));
            env.close();

            // bob gets authorization to hold gw["USD"].
            env(trust(gw, bob["USD"](1)), txflags(tfSetfAuth));
            env.close();

            // Two possible outcomes here depending on whether cashing a
            // check can build a trust line:
            //   o If it can't build a trust line, then since bob set his
            //     limit low, he cashes the check with a DeliverMin and hits
            //     his trust limit.
            //  o If it can build a trust line, then the check is allowed to
            //    exceed the trust limit and bob gets the full transfer.
            env(check::cash(bob, chkId, check::DeliverMin(USD(4))));
            STAmount const bobGot = USD(7);
            verifyDeliveredAmount(env, bobGot);
            env.require(balance(alice, USD(8) - bobGot));
            env.require(balance(bob, bobGot));

            BEAST_EXPECT(checksOnAccount(env, alice).empty());
            BEAST_EXPECT(checksOnAccount(env, bob).empty());
            BEAST_EXPECT(ownerCount(env, alice) == 1);
            BEAST_EXPECT(ownerCount(env, bob) == 1);
        }

        // Use a regular key and also multisign to cash a check.
        {
            Env env{*this, features};
            env.fund(XRP(1000), gw, alice, bob);
            env.close();

            // alice creates her checks ahead of time.
            uint256 const chkId1{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(1)));
            env.close();

            uint256 const chkId2{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(2)));
            env.close();

            env(trust(alice, USD(20)));
            env(trust(bob, USD(20)));
            env.close();
            env(pay(gw, alice, USD(8)));
            env.close();

            // Give bob a regular key and signers
            Account const bobby{"bobby", KeyType::secp256k1};
            env(regkey(bob, bobby));
            env.close();

            Account const bogie{"bogie", KeyType::secp256k1};
            Account const demon{"demon", KeyType::ed25519};
            env(signers(bob, 2, {{bogie, 1}, {demon, 1}}), sig(bobby));
            env.close();

            BEAST_EXPECT(ownerCount(env, bob) == 2);

            // bob uses his regular key to cash a check.
            env(check::cash(bob, chkId1, (USD(1))), sig(bobby));
            env.close();
            env.require(balance(alice, USD(7)));
            env.require(balance(bob, USD(1)));
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 1);
            BEAST_EXPECT(checksOnAccount(env, bob).size() == 1);
            BEAST_EXPECT(ownerCount(env, alice) == 2);
            BEAST_EXPECT(ownerCount(env, bob) == 2);

            // bob uses multisigning to cash a check.
            XRPAmount const baseFeeDrops{env.current()->fees().base};
            env(check::cash(bob, chkId2, (USD(2))), msig(bogie, demon), fee(3 * baseFeeDrops));
            env.close();
            env.require(balance(alice, USD(5)));
            env.require(balance(bob, USD(3)));
            BEAST_EXPECT(checksOnAccount(env, alice).empty());
            BEAST_EXPECT(checksOnAccount(env, bob).empty());
            BEAST_EXPECT(ownerCount(env, alice) == 1);
            BEAST_EXPECT(ownerCount(env, bob) == 2);
        }
    }

    void
    testCashXferFee(FeatureBitset features)
    {
        // Look at behavior when the issuer charges a transfer fee.
        testcase("Cash with transfer fee");

        using namespace test::jtx;

        Account const gw{"gateway"};
        Account const alice{"alice"};
        Account const bob{"bob"};
        IOU const USD{gw["USD"]};

        Env env{*this, features};

        env.fund(XRP(1000), gw, alice, bob);
        env.close();

        env(trust(alice, USD(1000)));
        env(trust(bob, USD(1000)));
        env.close();
        env(pay(gw, alice, USD(1000)));
        env.close();

        // Set gw's transfer rate and see the consequences when cashing a check.
        env(rate(gw, 1.25));
        env.close();

        // alice writes a check with a SendMax of USD(125).  The most bob
        // can get is USD(100) because of the transfer rate.
        uint256 const chkId125{getCheckIndex(alice, env.seq(alice))};
        env(check::create(alice, bob, USD(125)));
        env.close();

        // alice writes another check that won't get cashed until the transfer
        // rate changes so we can see the rate applies when the check is
        // cashed, not when it is created.
        uint256 const chkId120{getCheckIndex(alice, env.seq(alice))};
        env(check::create(alice, bob, USD(120)));
        env.close();

        // bob attempts to cash the check for face value.  Should fail.
        env(check::cash(bob, chkId125, USD(125)), ter(tecPATH_PARTIAL));
        env.close();
        env(check::cash(bob, chkId125, check::DeliverMin(USD(101))), ter(tecPATH_PARTIAL));
        env.close();

        // bob decides that he'll accept anything USD(75) or up.
        // He gets USD(100).
        env(check::cash(bob, chkId125, check::DeliverMin(USD(75))));
        verifyDeliveredAmount(env, USD(100));
        env.require(balance(alice, USD(1000 - 125)));
        env.require(balance(bob, USD(0 + 100)));
        BEAST_EXPECT(checksOnAccount(env, alice).size() == 1);
        BEAST_EXPECT(checksOnAccount(env, bob).size() == 1);

        // Adjust gw's rate...
        env(rate(gw, 1.2));
        env.close();

        // bob cashes the second check for less than the face value.  The new
        // rate applies to the actual value transferred.
        env(check::cash(bob, chkId120, USD(50)));
        env.close();
        env.require(balance(alice, USD(1000 - 125 - 60)));
        env.require(balance(bob, USD(0 + 100 + 50)));
        BEAST_EXPECT(checksOnAccount(env, alice).empty());
        BEAST_EXPECT(checksOnAccount(env, bob).empty());
    }

    void
    testCashQuality(FeatureBitset features)
    {
        // Look at the eight possible cases for Quality In/Out.
        testcase("Cash quality");

        using namespace test::jtx;

        Account const gw{"gateway"};
        Account const alice{"alice"};
        Account const bob{"bob"};
        IOU const USD{gw["USD"]};

        Env env{*this, features};

        env.fund(XRP(1000), gw, alice, bob);
        env.close();

        env(trust(alice, USD(1000)));
        env(trust(bob, USD(1000)));
        env.close();
        env(pay(gw, alice, USD(1000)));
        env.close();

        //
        // Quality effects on transfers between two non-issuers.
        //

        // Provide lambdas that return a qualityInPercent and qualityOutPercent.
        auto qIn = [](double percent) { return qualityInPercent(percent); };
        auto qOut = [](double percent) { return qualityOutPercent(percent); };

        // There are two test lambdas: one for a Payment and one for a Check.
        // This shows whether a Payment and a Check behave the same.
        auto testNonIssuerQPay = [&env, &alice, &bob, &USD](
                                     Account const& truster,
                                     IOU const& iou,
                                     auto const& inOrOut,
                                     double pct,
                                     double amount) {
            // Capture bob's and alice's balances so we can test at the end.
            STAmount const aliceStart{env.balance(alice, USD.issue()).value()};
            STAmount const bobStart{env.balance(bob, USD.issue()).value()};

            // Set the modified quality.
            env(trust(truster, iou(1000)), inOrOut(pct));
            env.close();

            env(pay(alice, bob, USD(amount)), sendmax(USD(10)));
            env.close();
            env.require(balance(alice, aliceStart - USD(10)));
            env.require(balance(bob, bobStart + USD(10)));

            // Return the quality to the unmodified state so it doesn't
            // interfere with upcoming tests.
            env(trust(truster, iou(1000)), inOrOut(0));
            env.close();
        };

        auto testNonIssuerQCheck = [&env, &alice, &bob, &USD](
                                       Account const& truster,
                                       IOU const& iou,
                                       auto const& inOrOut,
                                       double pct,
                                       double amount) {
            // Capture bob's and alice's balances so we can test at the end.
            STAmount const aliceStart{env.balance(alice, USD.issue()).value()};
            STAmount const bobStart{env.balance(bob, USD.issue()).value()};

            // Set the modified quality.
            env(trust(truster, iou(1000)), inOrOut(pct));
            env.close();

            uint256 const chkId = getCheckIndex(alice, env.seq(alice));
            env(check::create(alice, bob, USD(10)));
            env.close();

            env(check::cash(bob, chkId, USD(amount)));
            env.close();
            env.require(balance(alice, aliceStart - USD(10)));
            env.require(balance(bob, bobStart + USD(10)));

            // Return the quality to the unmodified state so it doesn't
            // interfere with upcoming tests.
            env(trust(truster, iou(1000)), inOrOut(0));
            env.close();
        };

        //                                           pct  amount
        testNonIssuerQPay(alice, gw["USD"], qIn, 50, 10);
        testNonIssuerQCheck(alice, gw["USD"], qIn, 50, 10);

        // This is the only case where the Quality affects the outcome.
        testNonIssuerQPay(bob, gw["USD"], qIn, 50, 5);
        testNonIssuerQCheck(bob, gw["USD"], qIn, 50, 5);

        testNonIssuerQPay(gw, alice["USD"], qIn, 50, 10);
        testNonIssuerQCheck(gw, alice["USD"], qIn, 50, 10);

        testNonIssuerQPay(gw, bob["USD"], qIn, 50, 10);
        testNonIssuerQCheck(gw, bob["USD"], qIn, 50, 10);

        testNonIssuerQPay(alice, gw["USD"], qOut, 200, 10);
        testNonIssuerQCheck(alice, gw["USD"], qOut, 200, 10);

        testNonIssuerQPay(bob, gw["USD"], qOut, 200, 10);
        testNonIssuerQCheck(bob, gw["USD"], qOut, 200, 10);

        testNonIssuerQPay(gw, alice["USD"], qOut, 200, 10);
        testNonIssuerQCheck(gw, alice["USD"], qOut, 200, 10);

        testNonIssuerQPay(gw, bob["USD"], qOut, 200, 10);
        testNonIssuerQCheck(gw, bob["USD"], qOut, 200, 10);

        //
        // Quality effects on transfers between an issuer and a non-issuer.
        //

        // There are two test lambdas for the same reason as before.
        auto testIssuerQPay = [&env, &gw, &alice, &USD](
                                  Account const& truster,
                                  IOU const& iou,
                                  auto const& inOrOut,
                                  double pct,
                                  double amt1,
                                  double max1,
                                  double amt2,
                                  double max2) {
            // Capture alice's balance so we can test at the end.  It doesn't
            // make any sense to look at the balance of a gateway.
            STAmount const aliceStart{env.balance(alice, USD.issue()).value()};

            // Set the modified quality.
            env(trust(truster, iou(1000)), inOrOut(pct));
            env.close();

            // alice pays gw.
            env(pay(alice, gw, USD(amt1)), sendmax(USD(max1)));
            env.close();
            env.require(balance(alice, aliceStart - USD(10)));

            // gw pays alice.
            env(pay(gw, alice, USD(amt2)), sendmax(USD(max2)));
            env.close();
            env.require(balance(alice, aliceStart));

            // Return the quality to the unmodified state so it doesn't
            // interfere with upcoming tests.
            env(trust(truster, iou(1000)), inOrOut(0));
            env.close();
        };

        auto testIssuerQCheck = [&env, &gw, &alice, &USD](
                                    Account const& truster,
                                    IOU const& iou,
                                    auto const& inOrOut,
                                    double pct,
                                    double amt1,
                                    double max1,
                                    double amt2,
                                    double max2) {
            // Capture alice's balance so we can test at the end.  It doesn't
            // make any sense to look at the balance of the issuer.
            STAmount const aliceStart{env.balance(alice, USD.issue()).value()};

            // Set the modified quality.
            env(trust(truster, iou(1000)), inOrOut(pct));
            env.close();

            // alice writes check to gw.  gw cashes.
            uint256 const chkAliceId{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, gw, USD(max1)));
            env.close();

            env(check::cash(gw, chkAliceId, USD(amt1)));
            env.close();
            env.require(balance(alice, aliceStart - USD(10)));

            // gw writes check to alice.  alice cashes.
            uint256 const chkGwId{getCheckIndex(gw, env.seq(gw))};
            env(check::create(gw, alice, USD(max2)));
            env.close();

            env(check::cash(alice, chkGwId, USD(amt2)));
            env.close();
            env.require(balance(alice, aliceStart));

            // Return the quality to the unmodified state so it doesn't
            // interfere with upcoming tests.
            env(trust(truster, iou(1000)), inOrOut(0));
            env.close();
        };

        // The first case is the only one where the quality affects the outcome.
        //                                        pct  amt1 max1 amt2 max2
        testIssuerQPay(alice, gw["USD"], qIn, 50, 10, 10, 5, 10);
        testIssuerQCheck(alice, gw["USD"], qIn, 50, 10, 10, 5, 10);

        testIssuerQPay(gw, alice["USD"], qIn, 50, 10, 10, 10, 10);
        testIssuerQCheck(gw, alice["USD"], qIn, 50, 10, 10, 10, 10);

        testIssuerQPay(alice, gw["USD"], qOut, 200, 10, 10, 10, 10);
        testIssuerQCheck(alice, gw["USD"], qOut, 200, 10, 10, 10, 10);

        testIssuerQPay(gw, alice["USD"], qOut, 200, 10, 10, 10, 10);
        testIssuerQCheck(gw, alice["USD"], qOut, 200, 10, 10, 10, 10);
    }

    void
    testCashInvalid(FeatureBitset features)
    {
        // Explore many of the ways to fail at cashing a check.
        testcase("Cash invalid");

        using namespace test::jtx;

        Account const gw{"gateway"};
        Account const alice{"alice"};
        Account const bob{"bob"};
        Account const zoe{"zoe"};
        IOU const USD{gw["USD"]};

        Env env(*this, features);

        env.fund(XRP(1000), gw, alice, bob, zoe);
        env.close();

        // Now set up alice's trustline.
        env(trust(alice, USD(20)));
        env.close();
        env(pay(gw, alice, USD(20)));
        env.close();

        // Now set up bob's trustline.
        env(trust(bob, USD(20)));
        env.close();

        // bob tries to cash a non-existent check from alice.
        {
            uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
            env(check::cash(bob, chkId, USD(20)), ter(tecNO_ENTRY));
            env.close();
        }

        // alice creates her checks ahead of time.
        uint256 const chkIdU{getCheckIndex(alice, env.seq(alice))};
        env(check::create(alice, bob, USD(20)));
        env.close();

        uint256 const chkIdX{getCheckIndex(alice, env.seq(alice))};
        env(check::create(alice, bob, XRP(10)));
        env.close();

        using namespace std::chrono_literals;
        uint256 const chkIdExp{getCheckIndex(alice, env.seq(alice))};
        env(check::create(alice, bob, XRP(10)), expiration(env.now() + 1s));
        env.close();

        uint256 const chkIdFroz1{getCheckIndex(alice, env.seq(alice))};
        env(check::create(alice, bob, USD(1)));
        env.close();

        uint256 const chkIdFroz2{getCheckIndex(alice, env.seq(alice))};
        env(check::create(alice, bob, USD(2)));
        env.close();

        uint256 const chkIdFroz3{getCheckIndex(alice, env.seq(alice))};
        env(check::create(alice, bob, USD(3)));
        env.close();

        uint256 const chkIdFroz4{getCheckIndex(alice, env.seq(alice))};
        env(check::create(alice, bob, USD(4)));
        env.close();

        uint256 const chkIdNoDest1{getCheckIndex(alice, env.seq(alice))};
        env(check::create(alice, bob, USD(1)));
        env.close();

        uint256 const chkIdHasDest2{getCheckIndex(alice, env.seq(alice))};
        env(check::create(alice, bob, USD(2)), dest_tag(7));
        env.close();

        // Same set of failing cases for both IOU and XRP check cashing.
        auto failingCases = [&env, &gw, &alice, &bob](
                                uint256 const& chkId, STAmount const& amount) {
            // Bad fee.
            env(check::cash(bob, chkId, amount), fee(drops(-10)), ter(temBAD_FEE));
            env.close();

            // Bad flags.
            env(check::cash(bob, chkId, amount),
                txflags(tfImmediateOrCancel),
                ter(temINVALID_FLAG));
            env.close();

            // Missing both Amount and DeliverMin.
            {
                Json::Value tx{check::cash(bob, chkId, amount)};
                tx.removeMember(sfAmount.jsonName);
                env(tx, ter(temMALFORMED));
                env.close();
            }
            // Both Amount and DeliverMin present.
            {
                Json::Value tx{check::cash(bob, chkId, amount)};
                tx[sfDeliverMin.jsonName] = amount.getJson(JsonOptions::none);
                env(tx, ter(temMALFORMED));
                env.close();
            }

            // Negative or zero amount.
            {
                STAmount neg{amount};
                neg.negate();
                env(check::cash(bob, chkId, neg), ter(temBAD_AMOUNT));
                env.close();
                env(check::cash(bob, chkId, amount.zeroed()), ter(temBAD_AMOUNT));
                env.close();
            }

            // Bad currency.
            if (!amount.native())
            {
                Issue const badIssue{badCurrency(), amount.getIssuer()};
                STAmount badAmount{amount};
                badAmount.setIssue(Issue{badCurrency(), amount.getIssuer()});
                env(check::cash(bob, chkId, badAmount), ter(temBAD_CURRENCY));
                env.close();
            }

            // Not destination cashing check.
            env(check::cash(alice, chkId, amount), ter(tecNO_PERMISSION));
            env.close();
            env(check::cash(gw, chkId, amount), ter(tecNO_PERMISSION));
            env.close();

            // Currency mismatch.
            {
                IOU const wrongCurrency{gw["EUR"]};
                STAmount badAmount{amount};
                badAmount.setIssue(wrongCurrency.issue());
                env(check::cash(bob, chkId, badAmount), ter(temMALFORMED));
                env.close();
            }

            // Issuer mismatch.
            {
                IOU const wrongIssuer{alice["USD"]};
                STAmount badAmount{amount};
                badAmount.setIssue(wrongIssuer.issue());
                env(check::cash(bob, chkId, badAmount), ter(temMALFORMED));
                env.close();
            }

            // Amount bigger than SendMax.
            env(check::cash(bob, chkId, amount + amount), ter(tecPATH_PARTIAL));
            env.close();

            // DeliverMin bigger than SendMax.
            env(check::cash(bob, chkId, check::DeliverMin(amount + amount)), ter(tecPATH_PARTIAL));
            env.close();
        };

        failingCases(chkIdX, XRP(10));
        failingCases(chkIdU, USD(20));

        // Verify that those two checks really were cashable.
        env(check::cash(bob, chkIdU, USD(20)));
        env.close();
        env(check::cash(bob, chkIdX, check::DeliverMin(XRP(10))));
        verifyDeliveredAmount(env, XRP(10));

        // Try to cash an expired check.
        env(check::cash(bob, chkIdExp, XRP(10)), ter(tecEXPIRED));
        env.close();

        // Cancel the expired check.  Anyone can cancel an expired check.
        env(check::cancel(zoe, chkIdExp));
        env.close();

        // Can we cash a check with frozen currency?
        {
            env(pay(bob, alice, USD(20)));
            env.close();
            env.require(balance(alice, USD(20)));
            env.require(balance(bob, USD(0)));

            // Global freeze
            env(fset(gw, asfGlobalFreeze));
            env.close();

            env(check::cash(bob, chkIdFroz1, USD(1)), ter(tecPATH_PARTIAL));
            env.close();
            env(check::cash(bob, chkIdFroz1, check::DeliverMin(USD(0.5))), ter(tecPATH_PARTIAL));
            env.close();

            env(fclear(gw, asfGlobalFreeze));
            env.close();

            // No longer frozen.  Success.
            env(check::cash(bob, chkIdFroz1, USD(1)));
            env.close();
            env.require(balance(alice, USD(19)));
            env.require(balance(bob, USD(1)));

            // Freeze individual trustlines.
            env(trust(gw, alice["USD"](0), tfSetFreeze));
            env.close();
            env(check::cash(bob, chkIdFroz2, USD(2)), ter(tecPATH_PARTIAL));
            env.close();
            env(check::cash(bob, chkIdFroz2, check::DeliverMin(USD(1))), ter(tecPATH_PARTIAL));
            env.close();

            // Clear that freeze.  Now check cashing works.
            env(trust(gw, alice["USD"](0), tfClearFreeze));
            env.close();
            env(check::cash(bob, chkIdFroz2, USD(2)));
            env.close();
            env.require(balance(alice, USD(17)));
            env.require(balance(bob, USD(3)));

            // Freeze bob's trustline.  bob can't cash the check.
            env(trust(gw, bob["USD"](0), tfSetFreeze));
            env.close();
            env(check::cash(bob, chkIdFroz3, USD(3)), ter(tecFROZEN));
            env.close();
            env(check::cash(bob, chkIdFroz3, check::DeliverMin(USD(1))), ter(tecFROZEN));
            env.close();

            // Clear that freeze.  Now check cashing works again.
            env(trust(gw, bob["USD"](0), tfClearFreeze));
            env.close();
            env(check::cash(bob, chkIdFroz3, check::DeliverMin(USD(1))));
            verifyDeliveredAmount(env, USD(3));
            env.require(balance(alice, USD(14)));
            env.require(balance(bob, USD(6)));

            // Set bob's freeze bit in the other direction.  Check
            // cashing fails.
            env(trust(bob, USD(20), tfSetFreeze));
            env.close();
            env(check::cash(bob, chkIdFroz4, USD(4)), ter(terNO_LINE));
            env.close();
            env(check::cash(bob, chkIdFroz4, check::DeliverMin(USD(1))), ter(terNO_LINE));
            env.close();

            // Clear bob's freeze bit and the check should be cashable.
            env(trust(bob, USD(20), tfClearFreeze));
            env.close();
            env(check::cash(bob, chkIdFroz4, USD(4)));
            env.close();
            env.require(balance(alice, USD(10)));
            env.require(balance(bob, USD(10)));
        }
        {
            // Set the RequireDest flag on bob's account (after the check
            // was created) then cash a check without a destination tag.
            env(fset(bob, asfRequireDest));
            env.close();
            env(check::cash(bob, chkIdNoDest1, USD(1)), ter(tecDST_TAG_NEEDED));
            env.close();
            env(check::cash(bob, chkIdNoDest1, check::DeliverMin(USD(0.5))),
                ter(tecDST_TAG_NEEDED));
            env.close();

            // bob can cash a check with a destination tag.
            env(check::cash(bob, chkIdHasDest2, USD(2)));
            env.close();
            env.require(balance(alice, USD(8)));
            env.require(balance(bob, USD(12)));

            // Clear the RequireDest flag on bob's account so he can
            // cash the check with no DestinationTag.
            env(fclear(bob, asfRequireDest));
            env.close();
            env(check::cash(bob, chkIdNoDest1, USD(1)));
            env.close();
            env.require(balance(alice, USD(7)));
            env.require(balance(bob, USD(13)));
        }
    }

    void
    testCancelValid(FeatureBitset features)
    {
        // Explore many of the ways to cancel a check.
        testcase("Cancel valid");

        using namespace test::jtx;

        Account const gw{"gateway"};
        Account const alice{"alice"};
        Account const bob{"bob"};
        Account const zoe{"zoe"};
        IOU const USD{gw["USD"]};

        {
            Env env{*this, features};

            env.fund(XRP(1000), gw, alice, bob, zoe);
            env.close();

            // alice creates her checks ahead of time.
            // Three ordinary checks with no expiration.
            uint256 const chkId1{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(10)));
            env.close();

            uint256 const chkId2{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, XRP(10)));
            env.close();

            uint256 const chkId3{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(10)));
            env.close();

            // Three checks that expire in 10 minutes.
            using namespace std::chrono_literals;
            uint256 const chkIdNotExp1{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, XRP(10)), expiration(env.now() + 600s));
            env.close();

            uint256 const chkIdNotExp2{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(10)), expiration(env.now() + 600s));
            env.close();

            uint256 const chkIdNotExp3{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, XRP(10)), expiration(env.now() + 600s));
            env.close();

            // Three checks that expire in one second.
            uint256 const chkIdExp1{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(10)), expiration(env.now() + 1s));
            env.close();

            uint256 const chkIdExp2{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, XRP(10)), expiration(env.now() + 1s));
            env.close();

            uint256 const chkIdExp3{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(10)), expiration(env.now() + 1s));
            env.close();

            // Two checks to cancel using a regular key and using multisigning.
            uint256 const chkIdReg{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, USD(10)));
            env.close();

            uint256 const chkIdMSig{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, XRP(10)));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 11);
            BEAST_EXPECT(ownerCount(env, alice) == 11);

            // Creator, destination, and an outsider cancel the checks.
            env(check::cancel(alice, chkId1));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 10);
            BEAST_EXPECT(ownerCount(env, alice) == 10);

            env(check::cancel(bob, chkId2));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 9);
            BEAST_EXPECT(ownerCount(env, alice) == 9);

            env(check::cancel(zoe, chkId3), ter(tecNO_PERMISSION));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 9);
            BEAST_EXPECT(ownerCount(env, alice) == 9);

            // Creator, destination, and an outsider cancel unexpired checks.
            env(check::cancel(alice, chkIdNotExp1));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 8);
            BEAST_EXPECT(ownerCount(env, alice) == 8);

            env(check::cancel(bob, chkIdNotExp2));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 7);
            BEAST_EXPECT(ownerCount(env, alice) == 7);

            env(check::cancel(zoe, chkIdNotExp3), ter(tecNO_PERMISSION));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 7);
            BEAST_EXPECT(ownerCount(env, alice) == 7);

            // Creator, destination, and an outsider cancel expired checks.
            env(check::cancel(alice, chkIdExp1));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 6);
            BEAST_EXPECT(ownerCount(env, alice) == 6);

            env(check::cancel(bob, chkIdExp2));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 5);
            BEAST_EXPECT(ownerCount(env, alice) == 5);

            env(check::cancel(zoe, chkIdExp3));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 4);
            BEAST_EXPECT(ownerCount(env, alice) == 4);

            // Use a regular key and also multisign to cancel checks.
            Account const alie{"alie", KeyType::ed25519};
            env(regkey(alice, alie));
            env.close();

            Account const bogie{"bogie", KeyType::secp256k1};
            Account const demon{"demon", KeyType::ed25519};
            env(signers(alice, 2, {{bogie, 1}, {demon, 1}}), sig(alie));
            env.close();

            // alice uses her regular key to cancel a check.
            env(check::cancel(alice, chkIdReg), sig(alie));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 3);
            BEAST_EXPECT(ownerCount(env, alice) == 4);

            // alice uses multisigning to cancel a check.
            XRPAmount const baseFeeDrops{env.current()->fees().base};
            env(check::cancel(alice, chkIdMSig), msig(bogie, demon), fee(3 * baseFeeDrops));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 2);
            BEAST_EXPECT(ownerCount(env, alice) == 3);

            // Creator and destination cancel the remaining unexpired checks.
            env(check::cancel(alice, chkId3), sig(alice));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).size() == 1);
            BEAST_EXPECT(ownerCount(env, alice) == 2);

            env(check::cancel(bob, chkIdNotExp3));
            env.close();
            BEAST_EXPECT(checksOnAccount(env, alice).empty());
            BEAST_EXPECT(ownerCount(env, alice) == 1);
        }
    }

    void
    testCancelInvalid(FeatureBitset features)
    {
        // Explore many of the ways to fail at canceling a check.
        testcase("Cancel invalid");

        using namespace test::jtx;

        Account const alice{"alice"};
        Account const bob{"bob"};

        Env env{*this, features};

        env.fund(XRP(1000), alice, bob);
        env.close();

        // Bad fee.
        env(check::cancel(bob, getCheckIndex(alice, env.seq(alice))),
            fee(drops(-10)),
            ter(temBAD_FEE));
        env.close();

        // Bad flags.
        env(check::cancel(bob, getCheckIndex(alice, env.seq(alice))),
            txflags(tfImmediateOrCancel),
            ter(temINVALID_FLAG));
        env.close();

        // Non-existent check.
        env(check::cancel(bob, getCheckIndex(alice, env.seq(alice))), ter(tecNO_ENTRY));
        env.close();
    }

    void
    testDeliveredAmountForCheckCashTxn(FeatureBitset features)
    {
        testcase("DeliveredAmount For CheckCash Txn");

        using namespace test::jtx;
        Account const alice{"alice"};
        Account const bob{"bob"};

        Env env{*this, features};

        env.fund(XRP(1000), alice, bob);
        env.close();

        uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
        env(check::create(alice, bob, XRP(200)));
        env.close();

        env(check::cash(bob, chkId, check::DeliverMin(XRP(100))));

        // Get the hash for the most recent transaction.
        std::string const txHash{env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};

        env.close();
        Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];

        // DeliveredAmount and delivered_amount are present.
        BEAST_EXPECT(meta.isMember(sfDeliveredAmount.jsonName));
        BEAST_EXPECT(meta.isMember(jss::delivered_amount));
    }

    void
    testWithTickets(FeatureBitset features)
    {
        testcase("With Tickets");

        using namespace test::jtx;

        Account const gw{"gw"};
        Account const alice{"alice"};
        Account const bob{"bob"};
        IOU const USD{gw["USD"]};

        Env env{*this, features};
        env.fund(XRP(1000), gw, alice, bob);
        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)};

        env.close();
        env.require(owners(alice, 10));
        env.require(owners(bob, 10));

        // alice gets enough USD to write a few checks.
        env(trust(alice, USD(1000)), ticket::use(aliceTicketSeq++));
        env(trust(bob, USD(1000)), ticket::use(bobTicketSeq++));
        env.close();
        env.require(owners(alice, 10));
        env.require(owners(bob, 10));

        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);

        env(pay(gw, alice, USD(900)));
        env.close();

        // alice creates four checks; two XRP, two IOU.  Bob will cash
        // one of each and cancel one of each.
        uint256 const chkIdXrp1{getCheckIndex(alice, aliceTicketSeq)};
        env(check::create(alice, bob, XRP(200)), ticket::use(aliceTicketSeq++));

        uint256 const chkIdXrp2{getCheckIndex(alice, aliceTicketSeq)};
        env(check::create(alice, bob, XRP(300)), ticket::use(aliceTicketSeq++));

        uint256 const chkIdUsd1{getCheckIndex(alice, aliceTicketSeq)};
        env(check::create(alice, bob, USD(200)), ticket::use(aliceTicketSeq++));

        uint256 const chkIdUsd2{getCheckIndex(alice, aliceTicketSeq)};
        env(check::create(alice, bob, USD(300)), ticket::use(aliceTicketSeq++));

        env.close();
        // Alice used four tickets but created four checks.
        env.require(owners(alice, 10));
        env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
        BEAST_EXPECT(checksOnAccount(env, alice).size() == 4);
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        env.require(owners(bob, 10));
        BEAST_EXPECT(env.seq(bob) == bobSeq);

        // Bob cancels two of alice's checks.
        env(check::cancel(bob, chkIdXrp1), ticket::use(bobTicketSeq++));
        env(check::cancel(bob, chkIdUsd2), ticket::use(bobTicketSeq++));
        env.close();

        env.require(owners(alice, 8));
        env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
        BEAST_EXPECT(checksOnAccount(env, alice).size() == 2);
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        env.require(owners(bob, 8));
        BEAST_EXPECT(env.seq(bob) == bobSeq);

        // Bob cashes alice's two remaining checks.
        env(check::cash(bob, chkIdXrp2, XRP(300)), ticket::use(bobTicketSeq++));
        env(check::cash(bob, chkIdUsd1, USD(200)), ticket::use(bobTicketSeq++));
        env.close();

        env.require(owners(alice, 6));
        env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
        BEAST_EXPECT(checksOnAccount(env, alice).empty());
        BEAST_EXPECT(env.seq(alice) == aliceSeq);
        env.require(balance(alice, USD(700)));

        env.require(owners(bob, 6));
        BEAST_EXPECT(env.seq(bob) == bobSeq);
        env.require(balance(bob, USD(200)));
    }

    void
    testTrustLineCreation(FeatureBitset features)
    {
        // Explore automatic trust line creation when a check is cashed.
        //

        testcase("Trust Line Creation");

        using namespace test::jtx;

        Env env{*this, features};

        // An account that independently tracks its owner count.
        struct AccountOwns
        {
            beast::unit_test::suite& suite;
            Env const& env;
            Account const acct;
            std::size_t owners;

            void
            verifyOwners(std::uint32_t line) const
            {
                suite.expect(
                    ownerCount(env, acct) == owners, "Owner count mismatch", __FILE__, line);
            }

            // Operators to make using the class more convenient.
            operator Account() const
            {
                return acct;
            }

            operator xrpl::AccountID() const
            {
                return acct.id();
            }

            IOU
            operator[](std::string const& s) const
            {
                return acct[s];
            }
        };

        AccountOwns alice{*this, env, "alice", 0};
        AccountOwns bob{*this, env, "bob", 0};

        // Fund with noripple so the accounts do not have any flags set.
        env.fund(XRP(5000), noripple(alice, bob));
        env.close();

        // Automatic trust line creation should fail if the check destination
        // can't afford the reserve for the trust line.
        {
            AccountOwns gw1{*this, env, "gw1", 0};

            // Fund gw1 with noripple (even though that's atypical for a
            // gateway) so it does not have any flags set.  We'll set flags
            // on gw1 later.
            env.fund(XRP(5000), noripple(gw1));
            env.close();

            IOU const CK8 = gw1["CK8"];
            gw1.verifyOwners(__LINE__);

            Account const yui{"yui"};

            // Note the reserve in unit tests is 200 XRP, not 20.  So here
            // we're just barely giving yui enough XRP to meet the
            // account reserve.
            env.fund(XRP(200), yui);
            env.close();

            uint256 const chkId{getCheckIndex(gw1, env.seq(gw1))};
            env(check::create(gw1, yui, CK8(99)));
            env.close();

            env(check::cash(yui, chkId, CK8(99)), ter(tecNO_LINE_INSUF_RESERVE));
            env.close();
            alice.verifyOwners(__LINE__);

            // Give yui enough XRP to meet the trust line's reserve.  Cashing
            // the check succeeds and creates the trust line.
            env(pay(env.master, yui, XRP(51)));
            env.close();
            env(check::cash(yui, chkId, CK8(99)));
            verifyDeliveredAmount(env, CK8(99));
            env.close();
            BEAST_EXPECT(ownerCount(env, yui) == 1);

            // The automatic trust line does not take a reserve from gw1.
            // Since gw1's check was consumed it has no owners.
            gw1.verifyOwners(__LINE__);
        }

        // We'll be looking at the effects of various account root flags.

        // Automatically create trust lines using
        //   o Offers and
        //   o Check cashing
        // Compare the resulting trust lines and expect them to be very similar.

        // Lambda that compares two trust lines created by
        //  o Offer crossing and
        //  o Check cashing
        // between the same two accounts but with two different currencies.
        // The lambda expects the two trust lines to be largely similar.
        auto cmpTrustLines = [this, &env](
                                 Account const& acct1,
                                 Account const& acct2,
                                 IOU const& offerIou,
                                 IOU const& checkIou) {
            auto const offerLine = env.le(keylet::line(acct1, acct2, offerIou.currency));
            auto const checkLine = env.le(keylet::line(acct1, acct2, checkIou.currency));
            if (offerLine == nullptr || checkLine == nullptr)
            {
                BEAST_EXPECT(offerLine == nullptr && checkLine == nullptr);
                return;
            }

            {
                // Compare the contents of required fields.
                BEAST_EXPECT(offerLine->at(sfFlags) == checkLine->at(sfFlags));

                // Lambda that compares the contents of required STAmounts
                // without comparing the currency.
                auto cmpReqAmount = [this, offerLine, checkLine](SF_AMOUNT const& sfield) {
                    STAmount const offerAmount = offerLine->at(sfield);
                    STAmount const checkAmount = checkLine->at(sfield);

                    // Neither STAmount should be native.
                    if (!BEAST_EXPECT(!offerAmount.native() && !checkAmount.native()))
                        return;

                    BEAST_EXPECT(offerAmount.issue().account == checkAmount.issue().account);
                    BEAST_EXPECT(offerAmount.negative() == checkAmount.negative());
                    BEAST_EXPECT(offerAmount.mantissa() == checkAmount.mantissa());
                    BEAST_EXPECT(offerAmount.exponent() == checkAmount.exponent());
                };
                cmpReqAmount(sfBalance);
                cmpReqAmount(sfLowLimit);
                cmpReqAmount(sfHighLimit);
            }
            {
                // Lambda that compares the contents of optional fields.
                auto cmpOptField = [this, offerLine, checkLine](auto const& sfield) {
                    // Expect both fields to either be present or absent.
                    if (!BEAST_EXPECT(
                            offerLine->isFieldPresent(sfield) == checkLine->isFieldPresent(sfield)))
                        return;

                    // If both fields are absent then there's nothing
                    // further to check.
                    if (!offerLine->isFieldPresent(sfield))
                        return;

                    // Both optional fields are present so we can compare
                    // them.
                    BEAST_EXPECT(offerLine->at(sfield) == checkLine->at(sfield));
                };
                cmpOptField(sfLowNode);
                cmpOptField(sfLowQualityIn);
                cmpOptField(sfLowQualityOut);

                cmpOptField(sfHighNode);
                cmpOptField(sfHighQualityIn);
                cmpOptField(sfHighQualityOut);
            }
        };

        //----------- No account root flags, check written by issuer -----------
        {
            // No account root flags on any participant.
            // Automatic trust line from issuer to destination.
            AccountOwns gw1{*this, env, "gw1", 0};

            BEAST_EXPECT((*env.le(gw1))[sfFlags] == 0);
            BEAST_EXPECT((*env.le(alice))[sfFlags] == 0);
            BEAST_EXPECT((*env.le(bob))[sfFlags] == 0);

            // Use offers to automatically create the trust line.
            IOU const OF1 = gw1["OF1"];
            env(offer(gw1, XRP(98), OF1(98)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(gw1, alice, OF1.currency)) == nullptr);
            env(offer(alice, OF1(98), XRP(98)));
            ++alice.owners;
            env.close();

            // Both offers should be consumed.
            // Since gw1's offer was consumed and the trust line was not
            // created by gw1, gw1's owner count should be 0.
            gw1.verifyOwners(__LINE__);

            // alice's automatically created trust line bumps her owner count.
            alice.verifyOwners(__LINE__);

            // Use check cashing to automatically create the trust line.
            IOU const CK1 = gw1["CK1"];
            uint256 const chkId{getCheckIndex(gw1, env.seq(gw1))};
            env(check::create(gw1, alice, CK1(98)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(gw1, alice, CK1.currency)) == nullptr);
            env(check::cash(alice, chkId, CK1(98)));
            ++alice.owners;
            verifyDeliveredAmount(env, CK1(98));
            env.close();

            // gw1's check should be consumed.
            // Since gw1's check was consumed and the trust line was not
            // created by gw1, gw1's owner count should be 0.
            gw1.verifyOwners(__LINE__);

            // alice's automatically created trust line bumps her owner count.
            alice.verifyOwners(__LINE__);

            cmpTrustLines(gw1, alice, OF1, CK1);
        }
        //--------- No account root flags, check written by non-issuer ---------
        {
            // No account root flags on any participant.
            // Automatic trust line from non-issuer to non-issuer.

            // Use offers to automatically create the trust line.
            // Transfer of assets using offers does not require rippling.
            // So bob's offer is successfully crossed which creates the
            // trust line.
            AccountOwns gw1{*this, env, "gw1", 0};
            IOU const OF1 = gw1["OF1"];
            env(offer(alice, XRP(97), OF1(97)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(alice, bob, OF1.currency)) == nullptr);
            env(offer(bob, OF1(97), XRP(97)));
            ++bob.owners;
            env.close();

            // Both offers should be consumed.
            env.require(balance(alice, OF1(1)));
            env.require(balance(bob, OF1(97)));

            // bob now has an owner count of 1 due to the new trust line.
            gw1.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            // Use check cashing to automatically create the trust line.
            //
            // However cashing a check (unlike crossing offers) requires
            // rippling through the currency's issuer.  Since gw1 does not
            // have rippling enabled the check cash fails and bob does not
            // have a trust line created.
            IOU const CK1 = gw1["CK1"];
            uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, CK1(97)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(alice, bob, CK1.currency)) == nullptr);
            env(check::cash(bob, chkId, CK1(97)), ter(terNO_RIPPLE));
            env.close();

            BEAST_EXPECT(env.le(keylet::line(gw1, bob, OF1.currency)) != nullptr);
            BEAST_EXPECT(env.le(keylet::line(gw1, bob, CK1.currency)) == nullptr);

            // Delete alice's check since it is no longer needed.
            env(check::cancel(alice, chkId));
            env.close();

            // No one's owner count should have changed.
            gw1.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);
        }

        //------------- lsfDefaultRipple, check written by issuer --------------
        {
            // gw1 enables rippling.
            // Automatic trust line from issuer to non-issuer should still work.
            AccountOwns gw1{*this, env, "gw1", 0};
            env(fset(gw1, asfDefaultRipple));
            env.close();

            // Use offers to automatically create the trust line.
            IOU const OF2 = gw1["OF2"];
            env(offer(gw1, XRP(96), OF2(96)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(gw1, alice, OF2.currency)) == nullptr);
            env(offer(alice, OF2(96), XRP(96)));
            ++alice.owners;
            env.close();

            // Both offers should be consumed.
            // Since gw1's offer was consumed and the trust line was not
            // created by gw1, gw1's owner count should still be 0.
            gw1.verifyOwners(__LINE__);

            // alice's automatically created trust line bumps her owner count.
            alice.verifyOwners(__LINE__);

            // Use check cashing to automatically create the trust line.
            IOU const CK2 = gw1["CK2"];
            uint256 const chkId{getCheckIndex(gw1, env.seq(gw1))};
            env(check::create(gw1, alice, CK2(96)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(gw1, alice, CK2.currency)) == nullptr);
            env(check::cash(alice, chkId, CK2(96)));
            ++alice.owners;
            verifyDeliveredAmount(env, CK2(96));
            env.close();

            // gw1's check should be consumed.
            // Since gw1's check was consumed and the trust line was not
            // created by gw1, gw1's owner count should still be 0.
            gw1.verifyOwners(__LINE__);

            // alice's automatically created trust line bumps her owner count.
            alice.verifyOwners(__LINE__);

            cmpTrustLines(gw1, alice, OF2, CK2);
        }
        //----------- lsfDefaultRipple, check written by non-issuer ------------
        {
            // gw1 enabled rippling, so automatic trust line from non-issuer
            // to non-issuer should work.

            // Use offers to automatically create the trust line.
            AccountOwns gw1{*this, env, "gw1", 0};
            IOU const OF2 = gw1["OF2"];
            env(offer(alice, XRP(95), OF2(95)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(alice, bob, OF2.currency)) == nullptr);
            env(offer(bob, OF2(95), XRP(95)));
            ++bob.owners;
            env.close();

            // bob's owner count should increase due to the new trust line.
            gw1.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            // Use check cashing to automatically create the trust line.
            IOU const CK2 = gw1["CK2"];
            uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, CK2(95)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(alice, bob, CK2.currency)) == nullptr);
            env(check::cash(bob, chkId, CK2(95)));
            ++bob.owners;
            verifyDeliveredAmount(env, CK2(95));
            env.close();

            // bob's owner count should increase due to the new trust line.
            gw1.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            cmpTrustLines(alice, bob, OF2, CK2);
        }

        //-------------- lsfDepositAuth, check written by issuer ---------------
        {
            // Both offers and checks ignore the lsfDepositAuth flag, since
            // the destination signs the transaction that delivers their funds.
            // So setting lsfDepositAuth on all the participants should not
            // change any outcomes.
            //
            // Automatic trust line from issuer to non-issuer should still work.
            AccountOwns gw1{*this, env, "gw1", 0};
            env(fset(gw1, asfDepositAuth));
            env(fset(alice, asfDepositAuth));
            env(fset(bob, asfDepositAuth));
            env.close();

            // Use offers to automatically create the trust line.
            IOU const OF3 = gw1["OF3"];
            env(offer(gw1, XRP(94), OF3(94)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(gw1, alice, OF3.currency)) == nullptr);
            env(offer(alice, OF3(94), XRP(94)));
            ++alice.owners;
            env.close();

            // Both offers should be consumed.
            // Since gw1's offer was consumed and the trust line was not
            // created by gw1, gw1's owner count should still be 0.
            gw1.verifyOwners(__LINE__);

            // alice's automatically created trust line bumps her owner count.
            alice.verifyOwners(__LINE__);

            // Use check cashing to automatically create the trust line.
            IOU const CK3 = gw1["CK3"];
            uint256 const chkId{getCheckIndex(gw1, env.seq(gw1))};
            env(check::create(gw1, alice, CK3(94)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(gw1, alice, CK3.currency)) == nullptr);
            env(check::cash(alice, chkId, CK3(94)));
            ++alice.owners;
            verifyDeliveredAmount(env, CK3(94));
            env.close();

            // gw1's check should be consumed.
            // Since gw1's check was consumed and the trust line was not
            // created by gw1, gw1's owner count should still be 0.
            gw1.verifyOwners(__LINE__);

            // alice's automatically created trust line bumps her owner count.
            alice.verifyOwners(__LINE__);

            cmpTrustLines(gw1, alice, OF3, CK3);
        }
        //------------ lsfDepositAuth, check written by non-issuer -------------
        {
            // The presence of the lsfDepositAuth flag should not affect
            // automatic trust line creation.

            // Use offers to automatically create the trust line.
            AccountOwns gw1{*this, env, "gw1", 0};
            IOU const OF3 = gw1["OF3"];
            env(offer(alice, XRP(93), OF3(93)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(alice, bob, OF3.currency)) == nullptr);
            env(offer(bob, OF3(93), XRP(93)));
            ++bob.owners;
            env.close();

            // bob's owner count should increase due to the new trust line.
            gw1.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            // Use check cashing to automatically create the trust line.
            IOU const CK3 = gw1["CK3"];
            uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, CK3(93)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(alice, bob, CK3.currency)) == nullptr);
            env(check::cash(bob, chkId, CK3(93)));
            ++bob.owners;
            verifyDeliveredAmount(env, CK3(93));
            env.close();

            // bob's owner count should increase due to the new trust line.
            gw1.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            cmpTrustLines(alice, bob, OF3, CK3);
        }

        //-------------- lsfGlobalFreeze, check written by issuer --------------
        {
            // Set lsfGlobalFreeze on gw1.  That should stop any automatic
            // trust lines from being created.
            AccountOwns gw1{*this, env, "gw1", 0};
            env(fset(gw1, asfGlobalFreeze));
            env.close();

            // Use offers to automatically create the trust line.
            IOU const OF4 = gw1["OF4"];
            env(offer(gw1, XRP(92), OF4(92)), ter(tecFROZEN));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(gw1, alice, OF4.currency)) == nullptr);
            env(offer(alice, OF4(92), XRP(92)), ter(tecFROZEN));
            env.close();

            // No one's owner count should have changed.
            gw1.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            // Use check cashing to automatically create the trust line.
            IOU const CK4 = gw1["CK4"];
            uint256 const chkId{getCheckIndex(gw1, env.seq(gw1))};
            env(check::create(gw1, alice, CK4(92)), ter(tecFROZEN));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(gw1, alice, CK4.currency)) == nullptr);
            env(check::cash(alice, chkId, CK4(92)), ter(tecNO_ENTRY));
            env.close();

            // No one's owner count should have changed.
            gw1.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            // Because gw1 has set lsfGlobalFreeze, neither trust line
            // is created.
            BEAST_EXPECT(env.le(keylet::line(gw1, alice, OF4.currency)) == nullptr);
            BEAST_EXPECT(env.le(keylet::line(gw1, alice, CK4.currency)) == nullptr);
        }
        //------------ lsfGlobalFreeze, check written by non-issuer ------------
        {
            // Since gw1 has the lsfGlobalFreeze flag set, there should be
            // no automatic trust line creation between non-issuers.

            // Use offers to automatically create the trust line.
            AccountOwns gw1{*this, env, "gw1", 0};
            IOU const OF4 = gw1["OF4"];
            env(offer(alice, XRP(91), OF4(91)), ter(tecFROZEN));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(alice, bob, OF4.currency)) == nullptr);
            env(offer(bob, OF4(91), XRP(91)), ter(tecFROZEN));
            env.close();

            // No one's owner count should have changed.
            gw1.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            // Use check cashing to automatically create the trust line.
            IOU const CK4 = gw1["CK4"];
            uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, CK4(91)), ter(tecFROZEN));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(alice, bob, CK4.currency)) == nullptr);
            env(check::cash(bob, chkId, CK4(91)), ter(tecNO_ENTRY));
            env.close();

            // No one's owner count should have changed.
            gw1.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            // Because gw1 has set lsfGlobalFreeze, neither trust line
            // is created.
            BEAST_EXPECT(env.le(keylet::line(gw1, bob, OF4.currency)) == nullptr);
            BEAST_EXPECT(env.le(keylet::line(gw1, bob, CK4.currency)) == nullptr);
        }

        //-------------- lsfRequireAuth, check written by issuer ---------------

        // We want to test the lsfRequireAuth flag, but we can't set that
        // flag on an account that already has trust lines.  So we'll fund
        // a new gateway and use that.
        {
            AccountOwns gw2{*this, env, "gw2", 0};
            env.fund(XRP(5000), gw2);
            env.close();

            // Set lsfRequireAuth on gw2.  That should stop any automatic
            // trust lines from being created.
            env(fset(gw2, asfRequireAuth));
            env.close();

            // Use offers to automatically create the trust line.
            IOU const OF5 = gw2["OF5"];
            std::uint32_t gw2OfferSeq = {env.seq(gw2)};
            env(offer(gw2, XRP(92), OF5(92)));
            ++gw2.owners;
            env.close();
            BEAST_EXPECT(env.le(keylet::line(gw2, alice, OF5.currency)) == nullptr);
            env(offer(alice, OF5(92), XRP(92)), ter(tecNO_LINE));
            env.close();

            // gw2 should still own the offer, but no one else's owner
            // count should have changed.
            gw2.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            // Since we don't need it any more, remove gw2's offer.
            env(offer_cancel(gw2, gw2OfferSeq));
            --gw2.owners;
            env.close();
            gw2.verifyOwners(__LINE__);

            // Use check cashing to automatically create the trust line.
            IOU const CK5 = gw2["CK5"];
            uint256 const chkId{getCheckIndex(gw2, env.seq(gw2))};
            env(check::create(gw2, alice, CK5(92)));
            ++gw2.owners;
            env.close();
            BEAST_EXPECT(env.le(keylet::line(gw2, alice, CK5.currency)) == nullptr);
            env(check::cash(alice, chkId, CK5(92)), ter(tecNO_AUTH));
            env.close();

            // gw2 should still own the check, but no one else's owner
            // count should have changed.
            gw2.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            // Because gw2 has set lsfRequireAuth, neither trust line
            // is created.
            BEAST_EXPECT(env.le(keylet::line(gw2, alice, OF5.currency)) == nullptr);
            BEAST_EXPECT(env.le(keylet::line(gw2, alice, CK5.currency)) == nullptr);

            // Since we don't need it any more, remove gw2's check.
            env(check::cancel(gw2, chkId));
            --gw2.owners;
            env.close();
            gw2.verifyOwners(__LINE__);
        }
        //------------ lsfRequireAuth, check written by non-issuer -------------
        {
            // Since gw2 has the lsfRequireAuth flag set, there should be
            // no automatic trust line creation between non-issuers.

            // Use offers to automatically create the trust line.
            AccountOwns gw2{*this, env, "gw2", 0};
            IOU const OF5 = gw2["OF5"];
            env(offer(alice, XRP(91), OF5(91)), ter(tecUNFUNDED_OFFER));
            env.close();
            env(offer(bob, OF5(91), XRP(91)), ter(tecNO_LINE));
            BEAST_EXPECT(env.le(keylet::line(gw2, bob, OF5.currency)) == nullptr);
            env.close();

            gw2.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            // Use check cashing to automatically create the trust line.
            IOU const CK5 = gw2["CK5"];
            uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
            env(check::create(alice, bob, CK5(91)));
            env.close();
            BEAST_EXPECT(env.le(keylet::line(alice, bob, CK5.currency)) == nullptr);
            env(check::cash(bob, chkId, CK5(91)), ter(tecPATH_PARTIAL));
            env.close();

            // Delete alice's check since it is no longer needed.
            env(check::cancel(alice, chkId));
            env.close();

            // No one's owner count should have changed.
            gw2.verifyOwners(__LINE__);
            alice.verifyOwners(__LINE__);
            bob.verifyOwners(__LINE__);

            // Because gw2 has set lsfRequireAuth, neither trust line
            // is created.
            BEAST_EXPECT(env.le(keylet::line(gw2, bob, OF5.currency)) == nullptr);
            BEAST_EXPECT(env.le(keylet::line(gw2, bob, CK5.currency)) == nullptr);
        }
    }

    void
    testWithFeats(FeatureBitset features)
    {
        testEnabled(features);
        testCreateValid(features);
        testCreateDisallowIncoming(features);
        testCreateInvalid(features);
        testCashXRP(features);
        testCashIOU(features);
        testCashXferFee(features);
        testCashQuality(features);
        testCashInvalid(features);
        testCancelValid(features);
        testCancelInvalid(features);
        testDeliveredAmountForCheckCashTxn(features);
        testWithTickets(features);
    }

public:
    void
    run() override
    {
        using namespace test::jtx;
        auto const sa = testable_amendments();
        testWithFeats(sa);
        testTrustLineCreation(sa);
    }
};

BEAST_DEFINE_TESTSUITE(Check, app, xrpl);

}  // namespace xrpl
