mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
681 lines
24 KiB
C++
681 lines
24 KiB
C++
#include <test/jtx.h>
|
|
#include <test/jtx/WSClient.h>
|
|
|
|
#include <xrpl/beast/unit_test.h>
|
|
#include <xrpl/protocol/Feature.h>
|
|
#include <xrpl/protocol/SField.h>
|
|
#include <xrpl/protocol/jss.h>
|
|
|
|
namespace xrpl {
|
|
|
|
class TrustAndBalance_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
testPayNonexistent(FeatureBitset features)
|
|
{
|
|
testcase("Payment to Nonexistent Account");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this, features};
|
|
env(pay(env.master, "alice", XRP(1)), ter(tecNO_DST_INSUF_XRP));
|
|
env.close();
|
|
}
|
|
|
|
void
|
|
testTrustNonexistent()
|
|
{
|
|
testcase("Trust Nonexistent Account");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this};
|
|
Account alice{"alice"};
|
|
|
|
env(trust(env.master, alice["USD"](100)), ter(tecNO_DST));
|
|
}
|
|
|
|
void
|
|
testCreditLimit()
|
|
{
|
|
testcase("Credit Limit");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this};
|
|
Account gw{"gateway"};
|
|
Account alice{"alice"};
|
|
Account bob{"bob"};
|
|
|
|
env.fund(XRP(10000), gw, alice, bob);
|
|
env.close();
|
|
|
|
// credit limit doesn't exist yet - verify ledger_entry
|
|
// reflects this
|
|
auto jrr = ledgerEntryState(env, gw, alice, "USD");
|
|
BEAST_EXPECT(jrr[jss::error] == "entryNotFound");
|
|
|
|
// now create a credit limit
|
|
env(trust(alice, gw["USD"](800)));
|
|
|
|
jrr = ledgerEntryState(env, gw, alice, "USD");
|
|
BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
|
|
BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::value] == "800");
|
|
BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::issuer] == alice.human());
|
|
BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::currency] == "USD");
|
|
BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::value] == "0");
|
|
BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::issuer] == gw.human());
|
|
BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::currency] == "USD");
|
|
|
|
// modify the credit limit
|
|
env(trust(alice, gw["USD"](700)));
|
|
|
|
jrr = ledgerEntryState(env, gw, alice, "USD");
|
|
BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
|
|
BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::value] == "700");
|
|
BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::issuer] == alice.human());
|
|
BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::currency] == "USD");
|
|
BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::value] == "0");
|
|
BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::issuer] == gw.human());
|
|
BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::currency] == "USD");
|
|
|
|
// set negative limit - expect failure
|
|
env(trust(alice, gw["USD"](-1)), ter(temBAD_LIMIT));
|
|
|
|
// set zero limit
|
|
env(trust(alice, gw["USD"](0)));
|
|
|
|
// ensure line is deleted
|
|
jrr = ledgerEntryState(env, gw, alice, "USD");
|
|
BEAST_EXPECT(jrr[jss::error] == "entryNotFound");
|
|
|
|
// TODO Check in both owner books.
|
|
|
|
// set another credit limit
|
|
env(trust(alice, bob["USD"](600)));
|
|
|
|
// set limit on other side
|
|
env(trust(bob, alice["USD"](500)));
|
|
|
|
// check the ledger state for the trust line
|
|
jrr = ledgerEntryState(env, alice, bob, "USD");
|
|
BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
|
|
BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::value] == "500");
|
|
BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::issuer] == bob.human());
|
|
BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::currency] == "USD");
|
|
BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::value] == "600");
|
|
BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::issuer] == alice.human());
|
|
BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::currency] == "USD");
|
|
}
|
|
|
|
void
|
|
testDirectRipple(FeatureBitset features)
|
|
{
|
|
testcase("Direct Payment, Ripple");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this, features};
|
|
Account alice{"alice"};
|
|
Account bob{"bob"};
|
|
|
|
env.fund(XRP(10000), alice, bob);
|
|
env.close();
|
|
|
|
env(trust(alice, bob["USD"](600)));
|
|
env(trust(bob, alice["USD"](700)));
|
|
|
|
// alice sends bob partial with alice as issuer
|
|
env(pay(alice, bob, alice["USD"](24)));
|
|
env.require(balance(bob, alice["USD"](24)));
|
|
|
|
// alice sends bob more with bob as issuer
|
|
env(pay(alice, bob, bob["USD"](33)));
|
|
env.require(balance(bob, alice["USD"](57)));
|
|
|
|
// bob sends back more than sent
|
|
env(pay(bob, alice, bob["USD"](90)));
|
|
env.require(balance(bob, alice["USD"](-33)));
|
|
|
|
// alice sends to her limit
|
|
env(pay(alice, bob, bob["USD"](733)));
|
|
env.require(balance(bob, alice["USD"](700)));
|
|
|
|
// bob sends to his limit
|
|
env(pay(bob, alice, bob["USD"](1300)));
|
|
env.require(balance(bob, alice["USD"](-600)));
|
|
|
|
// bob sends past limit
|
|
env(pay(bob, alice, bob["USD"](1)), ter(tecPATH_DRY));
|
|
env.require(balance(bob, alice["USD"](-600)));
|
|
}
|
|
|
|
void
|
|
testWithTransferFee(bool subscribe, bool with_rate, FeatureBitset features)
|
|
{
|
|
testcase(
|
|
std::string("Direct Payment: ") + (with_rate ? "With " : "Without ") + " Xfer Fee, " +
|
|
(subscribe ? "With " : "Without ") + " Subscribe");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this, features};
|
|
auto wsc = test::makeWSClient(env.app().config());
|
|
Account gw{"gateway"};
|
|
Account alice{"alice"};
|
|
Account bob{"bob"};
|
|
|
|
env.fund(XRP(10000), gw, alice, bob);
|
|
env.close();
|
|
|
|
env(trust(alice, gw["AUD"](100)));
|
|
env(trust(bob, gw["AUD"](100)));
|
|
|
|
env(pay(gw, alice, alice["AUD"](1)));
|
|
env.close();
|
|
|
|
env.require(balance(alice, gw["AUD"](1)));
|
|
|
|
// alice sends bob 1 AUD
|
|
env(pay(alice, bob, gw["AUD"](1)));
|
|
env.close();
|
|
|
|
env.require(balance(alice, gw["AUD"](0)));
|
|
env.require(balance(bob, gw["AUD"](1)));
|
|
env.require(balance(gw, bob["AUD"](-1)));
|
|
|
|
if (with_rate)
|
|
{
|
|
// set a transfer rate
|
|
env(rate(gw, 1.1));
|
|
env.close();
|
|
// bob sends alice 0.5 AUD with a max to spend
|
|
env(pay(bob, alice, gw["AUD"](0.5)), sendmax(gw["AUD"](0.55)));
|
|
}
|
|
else
|
|
{
|
|
// bob sends alice 0.5 AUD
|
|
env(pay(bob, alice, gw["AUD"](0.5)));
|
|
}
|
|
|
|
env.require(balance(alice, gw["AUD"](0.5)));
|
|
env.require(balance(bob, gw["AUD"](with_rate ? 0.45 : 0.5)));
|
|
env.require(balance(gw, bob["AUD"](with_rate ? -0.45 : -0.5)));
|
|
|
|
if (subscribe)
|
|
{
|
|
Json::Value jvs;
|
|
jvs[jss::accounts] = Json::arrayValue;
|
|
jvs[jss::accounts].append(gw.human());
|
|
jvs[jss::streams] = Json::arrayValue;
|
|
jvs[jss::streams].append("transactions");
|
|
jvs[jss::streams].append("ledger");
|
|
auto jv = wsc->invoke("subscribe", jvs);
|
|
BEAST_EXPECT(jv[jss::status] == "success");
|
|
|
|
env.close();
|
|
|
|
using namespace std::chrono_literals;
|
|
BEAST_EXPECT(wsc->findMsg(5s, [](auto const& jval) {
|
|
auto const& t = jval[jss::transaction];
|
|
return t[jss::TransactionType] == jss::Payment;
|
|
}));
|
|
BEAST_EXPECT(wsc->findMsg(5s, [](auto const& jval) { return jval[jss::type] == "ledgerClosed"; }));
|
|
|
|
BEAST_EXPECT(wsc->invoke("unsubscribe", jv)[jss::status] == "success");
|
|
}
|
|
}
|
|
|
|
void
|
|
testWithPath(FeatureBitset features)
|
|
{
|
|
testcase("Payments With Paths and Fees");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this, features};
|
|
Account gw{"gateway"};
|
|
Account alice{"alice"};
|
|
Account bob{"bob"};
|
|
|
|
env.fund(XRP(10000), gw, alice, bob);
|
|
env.close();
|
|
|
|
// set a transfer rate
|
|
env(rate(gw, 1.1));
|
|
|
|
env(trust(alice, gw["AUD"](100)));
|
|
env(trust(bob, gw["AUD"](100)));
|
|
|
|
env(pay(gw, alice, alice["AUD"](4.4)));
|
|
env.require(balance(alice, gw["AUD"](4.4)));
|
|
|
|
// alice sends gw issues to bob with a max spend that allows for the
|
|
// xfer rate
|
|
env(pay(alice, bob, gw["AUD"](1)), sendmax(gw["AUD"](1.1)));
|
|
env.require(balance(alice, gw["AUD"](3.3)));
|
|
env.require(balance(bob, gw["AUD"](1)));
|
|
|
|
// alice sends bob issues to bob with a max spend
|
|
env(pay(alice, bob, bob["AUD"](1)), sendmax(gw["AUD"](1.1)));
|
|
env.require(balance(alice, gw["AUD"](2.2)));
|
|
env.require(balance(bob, gw["AUD"](2)));
|
|
|
|
// alice sends gw issues to bob with a max spend
|
|
env(pay(alice, bob, gw["AUD"](1)), sendmax(alice["AUD"](1.1)));
|
|
env.require(balance(alice, gw["AUD"](1.1)));
|
|
env.require(balance(bob, gw["AUD"](3)));
|
|
|
|
// alice sends bob issues to bob with a max spend in alice issues.
|
|
// expect fail since gw is not involved
|
|
env(pay(alice, bob, bob["AUD"](1)), sendmax(alice["AUD"](1.1)), ter(tecPATH_DRY));
|
|
|
|
env.require(balance(alice, gw["AUD"](1.1)));
|
|
env.require(balance(bob, gw["AUD"](3)));
|
|
}
|
|
|
|
void
|
|
testIndirect(FeatureBitset features)
|
|
{
|
|
testcase("Indirect Payment");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this, features};
|
|
Account gw{"gateway"};
|
|
Account alice{"alice"};
|
|
Account bob{"bob"};
|
|
|
|
env.fund(XRP(10000), gw, alice, bob);
|
|
env.close();
|
|
|
|
env(trust(alice, gw["USD"](600)));
|
|
env(trust(bob, gw["USD"](700)));
|
|
|
|
env(pay(gw, alice, alice["USD"](70)));
|
|
env(pay(gw, bob, bob["USD"](50)));
|
|
|
|
env.require(balance(alice, gw["USD"](70)));
|
|
env.require(balance(bob, gw["USD"](50)));
|
|
|
|
// alice sends more than has to issuer: 100 out of 70
|
|
env(pay(alice, gw, gw["USD"](100)), ter(tecPATH_PARTIAL));
|
|
|
|
// alice sends more than has to bob: 100 out of 70
|
|
env(pay(alice, bob, gw["USD"](100)), ter(tecPATH_PARTIAL));
|
|
|
|
env.close();
|
|
|
|
env.require(balance(alice, gw["USD"](70)));
|
|
env.require(balance(bob, gw["USD"](50)));
|
|
|
|
// send with an account path
|
|
env(pay(alice, bob, gw["USD"](5)), test::jtx::path(gw));
|
|
|
|
env.require(balance(alice, gw["USD"](65)));
|
|
env.require(balance(bob, gw["USD"](55)));
|
|
}
|
|
|
|
void
|
|
testIndirectMultiPath(bool with_rate, FeatureBitset features)
|
|
{
|
|
testcase(std::string("Indirect Payment, Multi Path, ") + (with_rate ? "With " : "Without ") + " Xfer Fee, ");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this, features};
|
|
Account gw{"gateway"};
|
|
Account amazon{"amazon"};
|
|
Account alice{"alice"};
|
|
Account bob{"bob"};
|
|
Account carol{"carol"};
|
|
|
|
env.fund(XRP(10000), gw, amazon, alice, bob, carol);
|
|
env.close();
|
|
|
|
env(trust(amazon, gw["USD"](2000)));
|
|
env(trust(bob, alice["USD"](600)));
|
|
env(trust(bob, gw["USD"](1000)));
|
|
env(trust(carol, alice["USD"](700)));
|
|
env(trust(carol, gw["USD"](1000)));
|
|
|
|
if (with_rate)
|
|
env(rate(gw, 1.1));
|
|
|
|
env(pay(gw, bob, bob["USD"](100)));
|
|
env(pay(gw, carol, carol["USD"](100)));
|
|
env.close();
|
|
|
|
// alice pays amazon via multiple paths
|
|
if (with_rate)
|
|
env(pay(alice, amazon, gw["USD"](150)),
|
|
sendmax(alice["USD"](200)),
|
|
test::jtx::path(bob),
|
|
test::jtx::path(carol));
|
|
else
|
|
env(pay(alice, amazon, gw["USD"](150)), test::jtx::path(bob), test::jtx::path(carol));
|
|
|
|
if (with_rate)
|
|
{
|
|
env.require(
|
|
balance(alice, STAmount(carol["USD"].issue(), 6500000000000000ull, -14, true, STAmount::unchecked{})));
|
|
env.require(balance(carol, gw["USD"](35)));
|
|
}
|
|
else
|
|
{
|
|
env.require(balance(alice, carol["USD"](-50)));
|
|
env.require(balance(carol, gw["USD"](50)));
|
|
}
|
|
env.require(balance(alice, bob["USD"](-100)));
|
|
env.require(balance(amazon, gw["USD"](150)));
|
|
env.require(balance(bob, gw["USD"](0)));
|
|
}
|
|
|
|
void
|
|
testInvoiceID(FeatureBitset features)
|
|
{
|
|
testcase("Set Invoice ID on Payment");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this, features};
|
|
Account alice{"alice"};
|
|
auto wsc = test::makeWSClient(env.app().config());
|
|
|
|
env.fund(XRP(10000), alice);
|
|
env.close();
|
|
|
|
Json::Value jvs;
|
|
jvs[jss::accounts] = Json::arrayValue;
|
|
jvs[jss::accounts].append(env.master.human());
|
|
jvs[jss::streams] = Json::arrayValue;
|
|
jvs[jss::streams].append("transactions");
|
|
BEAST_EXPECT(wsc->invoke("subscribe", jvs)[jss::status] == "success");
|
|
|
|
char const* invoiceId = "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89";
|
|
|
|
Json::Value jv;
|
|
auto tx = env.jt(pay(env.master, alice, XRP(10000)), json(sfInvoiceID.fieldName, invoiceId));
|
|
jv[jss::tx_blob] = strHex(tx.stx->getSerializer().slice());
|
|
auto jrr = wsc->invoke("submit", jv)[jss::result];
|
|
BEAST_EXPECT(jrr[jss::status] == "success");
|
|
BEAST_EXPECT(jrr[jss::tx_json][sfInvoiceID.fieldName] == invoiceId);
|
|
env.close();
|
|
|
|
using namespace std::chrono_literals;
|
|
BEAST_EXPECT(wsc->findMsg(2s, [invoiceId](auto const& jval) {
|
|
auto const& t = jval[jss::transaction];
|
|
return t[jss::TransactionType] == jss::Payment && t[sfInvoiceID.fieldName] == invoiceId;
|
|
}));
|
|
|
|
BEAST_EXPECT(wsc->invoke("unsubscribe", jv)[jss::status] == "success");
|
|
}
|
|
|
|
void
|
|
testOwnerCountOnBalanceChange(FeatureBitset features)
|
|
{
|
|
testcase("Reserve Edge Cases at Boundary");
|
|
using namespace test::jtx;
|
|
|
|
Env env{*this, features};
|
|
Account gw{"gateway"};
|
|
Account market{"market"};
|
|
|
|
auto const USD = gw["USD"];
|
|
auto const fee = env.current()->fees().base;
|
|
auto const acctReserve = env.current()->fees().accountReserve(0);
|
|
auto const incReserve = env.current()->fees().increment;
|
|
|
|
env.fund(XRP(10000), gw, market);
|
|
env.close();
|
|
|
|
auto checkAccountUSD = [&](Account const& acc,
|
|
bool const accHigh,
|
|
std::uint32_t expectedOwnerCount,
|
|
bool expectedReserveSet,
|
|
STAmount expectedUsdBalance) {
|
|
BEAST_EXPECT(env.le(acc)->getFieldU32(sfOwnerCount) == expectedOwnerCount);
|
|
auto const line = env.le(keylet::line(acc, gw, to_currency("USD")));
|
|
BEAST_EXPECT(line && line->isFlag(accHigh ? lsfHighReserve : lsfLowReserve) == expectedReserveSet);
|
|
BEAST_EXPECT(env.balance(acc, USD) == expectedUsdBalance);
|
|
};
|
|
|
|
// Setup: fund account, create trust line with balance, clear default
|
|
// ripple, set limit to 0, clear balance. Results in owner count 0 with
|
|
// reserve flag cleared.
|
|
auto setupAccount = [&](Account const& acc, bool const accHigh, STAmount const& fundAmount) {
|
|
env.fund(fundAmount, acc);
|
|
env.close();
|
|
|
|
env(fset(gw, asfDefaultRipple));
|
|
env.close();
|
|
|
|
env(trust(acc, USD(1000)));
|
|
env.close();
|
|
env(pay(gw, acc, USD(1000)));
|
|
env.close();
|
|
checkAccountUSD(acc, accHigh, 1, true, USD(1000));
|
|
|
|
env(fclear(gw, asfDefaultRipple));
|
|
env.close();
|
|
|
|
env(trust(acc, USD(0)));
|
|
env.close();
|
|
|
|
env(pay(acc, gw, USD(1000)));
|
|
env.close();
|
|
checkAccountUSD(acc, accHigh, 0, false, USD(0));
|
|
};
|
|
|
|
// Alice receives USD via offer crossing, there's enough balance to
|
|
// cover everything (reserves, offer, fees etc.)
|
|
{
|
|
Account alice{"alice"};
|
|
bool const aliceHigh = alice.id() > gw.id();
|
|
|
|
setupAccount(alice, aliceHigh, acctReserve + incReserve + fee * 4 + XRP(25));
|
|
|
|
// Making a limit order to match against later
|
|
env(offer(gw, XRP(100), USD(100)));
|
|
env.close();
|
|
|
|
// Alice creates offer to buy USD - it should cross fully
|
|
// immediately. Both cases should succeed because there is enough
|
|
// balance to cover the reserve requirements and the offer, but
|
|
// owner counter only increments in the fixed case.
|
|
if (features[fixTrustLineOwnerCount])
|
|
{
|
|
env(offer(alice, USD(25), XRP(25)));
|
|
env.close();
|
|
|
|
// Offer crosses, alice gets USD, owner count becomes 1, reserve
|
|
// flag set
|
|
env.require(offers(alice, 0));
|
|
checkAccountUSD(alice, aliceHigh, 1, true, USD(25));
|
|
}
|
|
else
|
|
{
|
|
env(offer(alice, USD(25), XRP(25)));
|
|
env.close();
|
|
|
|
// Offer crosses, alice gets USD, owner count stays 0, reserve
|
|
// flag not set
|
|
env.require(offers(alice, 0));
|
|
checkAccountUSD(alice, aliceHigh, 0, false, USD(25));
|
|
}
|
|
}
|
|
|
|
// Bob receives USD via offer crossing, there's only 10 XRP available
|
|
// for the offer to cross, so the unconsummed part of the offer would be
|
|
// staying in the book and add another entry to the owner count.
|
|
{
|
|
Account bob{"bob"};
|
|
bool const bobHigh = bob.id() > gw.id();
|
|
|
|
setupAccount(bob, bobHigh, acctReserve + incReserve + fee * 4 + XRP(10));
|
|
|
|
// Making a limit order to match against later
|
|
env(offer(gw, XRP(100), USD(100)));
|
|
env.close();
|
|
|
|
// Bob creates offer to buy USD - it should cross immediately, but
|
|
// Bob only has 10 USD available so the offer is not supposed to be
|
|
// consumed completely.
|
|
if (features[fixTrustLineOwnerCount])
|
|
{
|
|
env(offer(bob, USD(25), XRP(25)));
|
|
env.close();
|
|
|
|
// Offer crosses, bob gets 10 USD. It only consumes 10 USD, but
|
|
// there's no more remaining unreserved XRPs available, so the
|
|
// remaining quantity is unfunded. It does not land into the
|
|
// book. The owner count is incremented and the reserve flag is
|
|
// set.
|
|
env.require(offers(bob, 0));
|
|
checkAccountUSD(bob, bobHigh, 1, true, USD(10));
|
|
}
|
|
else
|
|
{
|
|
env(offer(bob, USD(25), XRP(25)));
|
|
env.close();
|
|
|
|
// Offer crosses, bob gets 25 USD. The owner count is not
|
|
// incremented and the reserve flag is not set.
|
|
env.require(offers(bob, 0));
|
|
checkAccountUSD(bob, bobHigh, 0, false, USD(25));
|
|
}
|
|
}
|
|
|
|
// Carol creates offer to buy USD - but they only have enough XRPs for
|
|
// reserves and transaction fees.
|
|
{
|
|
Account carol{"carol"};
|
|
bool const carolHigh = carol.id() > gw.id();
|
|
|
|
setupAccount(carol, carolHigh, acctReserve + incReserve + fee * 4);
|
|
|
|
// Making a limit order to match against later
|
|
env(offer(gw, XRP(100), USD(100)));
|
|
env.close();
|
|
|
|
// Carol creates offer to buy USD - but there should be no
|
|
// unreserved XRPs available to fund the offer.
|
|
if (features[fixTrustLineOwnerCount])
|
|
{
|
|
env(offer(carol, USD(25), XRP(25)));
|
|
env.close();
|
|
|
|
// The offer is unfunded, but it still ends up in the book.
|
|
// Carol does not get owner count due to the trustline state (it
|
|
// does not change), but due to the offer.
|
|
env.require(offers(carol, 1));
|
|
checkAccountUSD(carol, carolHigh, 1, false, USD(0));
|
|
}
|
|
else
|
|
{
|
|
env(offer(carol, USD(25), XRP(25)));
|
|
env.close();
|
|
|
|
// Offer crosses, bob gets 25 USD. The owner count is not
|
|
// incremented and the reserve flag is not set.
|
|
env.require(offers(carol, 0));
|
|
checkAccountUSD(carol, carolHigh, 0, false, USD(25));
|
|
}
|
|
}
|
|
|
|
// Receiving USD via CheckCash. rippleCreditIOU is reached
|
|
// with the receiver's balance going from 0 to positive.
|
|
{
|
|
Account dave{"dave"};
|
|
bool const daveHigh = dave.id() > gw.id();
|
|
|
|
setupAccount(dave, daveHigh, acctReserve + fee * 4);
|
|
|
|
uint256 const checkId{keylet::check(gw, env.seq(gw)).key};
|
|
env(check::create(gw, dave, USD(25)));
|
|
env.close();
|
|
|
|
if (features[fixTrustLineOwnerCount])
|
|
{
|
|
env(check::cash(dave, checkId, USD(25)));
|
|
env.close();
|
|
|
|
// rippleCreditIOU set the reserve flag and incremented
|
|
// owner count. No reserve requirements since it's the only
|
|
// object owned by this account
|
|
checkAccountUSD(dave, daveHigh, 1, true, USD(25));
|
|
}
|
|
else
|
|
{
|
|
env(check::cash(dave, checkId, USD(25)));
|
|
env.close();
|
|
|
|
// Without fix: owner count stays 0, reserve not set.
|
|
checkAccountUSD(dave, daveHigh, 0, false, USD(25));
|
|
}
|
|
}
|
|
|
|
// Receiving USD via CheckCash. rippleCreditIOU is reached
|
|
// with the receiver's balance going from 0 to positive.
|
|
{
|
|
Account ed{"ed"};
|
|
bool const edHigh = ed.id() > gw.id();
|
|
|
|
setupAccount(ed, edHigh, acctReserve + incReserve * 2 + fee * 4);
|
|
env(trust(ed, gw["EUR"](1000)));
|
|
env(trust(ed, gw["JPY"](1000)));
|
|
|
|
uint256 const checkId{keylet::check(gw, env.seq(gw)).key};
|
|
env(check::create(gw, ed, USD(25)));
|
|
env.close();
|
|
|
|
if (features[fixTrustLineOwnerCount])
|
|
{
|
|
// rippleCreditIOU returns tecINSUFFICIENT_RESERVE here since
|
|
// there is not enough balance for the reserve requirement on
|
|
// 3rd trustline. Unfortunately payment engine somehow does not
|
|
// propagate this error
|
|
env(check::cash(ed, checkId, USD(25)));
|
|
env.close();
|
|
|
|
// Check cash actually failed, no change to owner count or USD
|
|
// balance
|
|
checkAccountUSD(ed, edHigh, 2, false, USD(0));
|
|
}
|
|
else
|
|
{
|
|
env(check::cash(ed, checkId, USD(25)));
|
|
env.close();
|
|
|
|
// Without fix: owner count stays 2 (due to eur and jpy
|
|
// trustlines), reserve not set.
|
|
checkAccountUSD(ed, edHigh, 2, false, USD(25));
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
void
|
|
run() override
|
|
{
|
|
testTrustNonexistent();
|
|
testCreditLimit();
|
|
|
|
auto testWithFeatures = [this](FeatureBitset features) {
|
|
testPayNonexistent(features);
|
|
testDirectRipple(features);
|
|
testWithTransferFee(false, false, features);
|
|
testWithTransferFee(false, true, features);
|
|
testWithTransferFee(true, false, features);
|
|
testWithTransferFee(true, true, features);
|
|
testWithPath(features);
|
|
testIndirect(features);
|
|
testIndirectMultiPath(true, features);
|
|
testIndirectMultiPath(false, features);
|
|
testInvoiceID(features);
|
|
testOwnerCountOnBalanceChange(features);
|
|
};
|
|
|
|
using namespace test::jtx;
|
|
auto const sa = testable_amendments();
|
|
testWithFeatures(sa - featurePermissionedDEX);
|
|
testWithFeatures(sa - fixTrustLineOwnerCount);
|
|
testWithFeatures(sa);
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(TrustAndBalance, app, xrpl);
|
|
|
|
} // namespace xrpl
|