mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
Per XLS-0095, we are taking steps to rename ripple(d) to xrpl(d). This change specifically removes all copyright notices referencing Ripple, XRPLF, and certain affiliated contributors upon mutual agreement, so the notice in the LICENSE.md file applies throughout. Copyright notices referencing external contributions remain as-is. Duplicate verbiage is also removed.
473 lines
15 KiB
C++
473 lines
15 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 ripple {
|
|
|
|
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");
|
|
}
|
|
|
|
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);
|
|
};
|
|
|
|
using namespace test::jtx;
|
|
auto const sa = testable_amendments();
|
|
testWithFeatures(sa - featurePermissionedDEX);
|
|
testWithFeatures(sa);
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(TrustAndBalance, app, ripple);
|
|
|
|
} // namespace ripple
|