mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-06 18:26:51 +00:00
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> Co-authored-by: Bart <bthomee@users.noreply.github.com>
464 lines
16 KiB
C++
464 lines
16 KiB
C++
#include <test/jtx/Account.h>
|
|
#include <test/jtx/Env.h>
|
|
#include <test/jtx/TestHelpers.h>
|
|
#include <test/jtx/amount.h>
|
|
#include <test/jtx/balance.h>
|
|
#include <test/jtx/domain.h>
|
|
#include <test/jtx/envconfig.h>
|
|
#include <test/jtx/mpt.h>
|
|
#include <test/jtx/offer.h>
|
|
#include <test/jtx/pay.h>
|
|
#include <test/jtx/permissioned_dex.h>
|
|
|
|
#include <xrpld/core/Config.h>
|
|
#include <xrpld/rpc/Context.h>
|
|
#include <xrpld/rpc/RPCHandler.h>
|
|
#include <xrpld/rpc/Role.h>
|
|
#include <xrpld/rpc/detail/RPCHelpers.h>
|
|
#include <xrpld/rpc/detail/Tuning.h>
|
|
|
|
#include <xrpl/basics/base_uint.h>
|
|
#include <xrpl/beast/unit_test/suite.h>
|
|
#include <xrpl/core/Job.h>
|
|
#include <xrpl/core/JobQueue.h>
|
|
#include <xrpl/json/json_value.h>
|
|
#include <xrpl/protocol/AccountID.h>
|
|
#include <xrpl/protocol/ApiVersion.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/STAmount.h>
|
|
#include <xrpl/protocol/STPathSet.h>
|
|
#include <xrpl/protocol/UintTypes.h>
|
|
#include <xrpl/protocol/jss.h>
|
|
#include <xrpl/resource/Charge.h>
|
|
#include <xrpl/resource/Consumer.h>
|
|
#include <xrpl/resource/Fees.h>
|
|
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
namespace xrpl::test {
|
|
namespace detail {
|
|
|
|
static Json::Value
|
|
rpf(jtx::Account const& src,
|
|
jtx::Account const& dst,
|
|
xrpl::test::jtx::MPT const& USD,
|
|
std::vector<MPTID> const& num_src)
|
|
{
|
|
Json::Value jv = Json::objectValue;
|
|
jv[jss::command] = "ripple_path_find";
|
|
jv[jss::source_account] = toBase58(src);
|
|
|
|
if (!num_src.empty())
|
|
{
|
|
auto& sc = (jv[jss::source_currencies] = Json::arrayValue);
|
|
Json::Value j = Json::objectValue;
|
|
for (auto const& id : num_src)
|
|
{
|
|
j[jss::mpt_issuance_id] = to_string(id);
|
|
sc.append(j);
|
|
}
|
|
}
|
|
|
|
auto const d = toBase58(dst);
|
|
jv[jss::destination_account] = d;
|
|
|
|
Json::Value& j = (jv[jss::destination_amount] = Json::objectValue);
|
|
j[jss::mpt_issuance_id] = to_string(USD.mpt());
|
|
j[jss::value] = "1";
|
|
|
|
return jv;
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
class PathMPT_test : public beast::unit_test::suite
|
|
{
|
|
jtx::Env
|
|
pathTestEnv()
|
|
{
|
|
// These tests were originally written with search parameters that are
|
|
// different from the current defaults. This function creates an env
|
|
// with the search parameters that the tests were written for.
|
|
using namespace jtx;
|
|
return Env(*this, envconfig([](std::unique_ptr<Config> cfg) {
|
|
cfg->PATH_SEARCH_OLD = 7;
|
|
cfg->PATH_SEARCH = 7;
|
|
cfg->PATH_SEARCH_MAX = 10;
|
|
return cfg;
|
|
}));
|
|
}
|
|
|
|
public:
|
|
void
|
|
source_currencies_limit()
|
|
{
|
|
testcase("source currency limits");
|
|
using namespace std::chrono_literals;
|
|
using namespace jtx;
|
|
Env env = pathTestEnv();
|
|
auto const gw = Account("gateway");
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
|
|
env.fund(XRP(10'000), "alice", "bob", gw);
|
|
|
|
MPT const USD =
|
|
MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 100});
|
|
|
|
auto& app = env.app();
|
|
Resource::Charge loadType = Resource::feeReferenceRPC;
|
|
Resource::Consumer c;
|
|
|
|
RPC::JsonContext context{
|
|
{.j = env.journal,
|
|
.app = app,
|
|
.loadType = loadType,
|
|
.netOps = app.getOPs(),
|
|
.ledgerMaster = app.getLedgerMaster(),
|
|
.consumer = c,
|
|
.role = Role::USER,
|
|
.coro = {},
|
|
.infoSub = {},
|
|
.apiVersion = RPC::apiVersionIfUnspecified},
|
|
{},
|
|
{}};
|
|
Json::Value result;
|
|
gate g;
|
|
// Test RPC::Tuning::max_src_cur source currencies.
|
|
std::vector<MPTID> num_src;
|
|
num_src.reserve(RPC::Tuning::max_src_cur);
|
|
for (std::uint8_t i = 0; i < RPC::Tuning::max_src_cur; ++i)
|
|
num_src.push_back(makeMptID(i, bob));
|
|
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client", [&](auto const& coro) {
|
|
context.params = xrpl::test::detail::rpf(alice, bob, USD, num_src);
|
|
context.coro = coro;
|
|
RPC::doCommand(context, result);
|
|
g.signal();
|
|
});
|
|
BEAST_EXPECT(g.wait_for(5s));
|
|
BEAST_EXPECT(!result.isMember(jss::error));
|
|
|
|
// Test more than RPC::Tuning::max_src_cur source currencies.
|
|
num_src.push_back(makeMptID(RPC::Tuning::max_src_cur, bob));
|
|
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client", [&](auto const& coro) {
|
|
context.params = xrpl::test::detail::rpf(alice, bob, USD, num_src);
|
|
context.coro = coro;
|
|
RPC::doCommand(context, result);
|
|
g.signal();
|
|
});
|
|
BEAST_EXPECT(g.wait_for(5s));
|
|
BEAST_EXPECT(result.isMember(jss::error));
|
|
|
|
// Test RPC::Tuning::max_auto_src_cur source currencies.
|
|
num_src.clear();
|
|
for (auto i = 0; i < (RPC::Tuning::max_auto_src_cur - 1); ++i)
|
|
{
|
|
auto CURM = MPTTester({.env = env, .issuer = alice, .holders = {bob}});
|
|
num_src.push_back(CURM.issuanceID());
|
|
}
|
|
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client", [&](auto const& coro) {
|
|
context.params = xrpl::test::detail::rpf(alice, bob, USD, {});
|
|
context.coro = coro;
|
|
RPC::doCommand(context, result);
|
|
g.signal();
|
|
});
|
|
BEAST_EXPECT(g.wait_for(5s));
|
|
BEAST_EXPECT(!result.isMember(jss::error));
|
|
|
|
// Test more than RPC::Tuning::max_auto_src_cur source currencies.
|
|
auto CURM = MPTTester({.env = env, .issuer = alice, .holders = {bob}});
|
|
app.getJobQueue().postCoro(jtCLIENT, "RPC-Client", [&](auto const& coro) {
|
|
context.params = xrpl::test::detail::rpf(alice, bob, USD, {});
|
|
context.coro = coro;
|
|
RPC::doCommand(context, result);
|
|
g.signal();
|
|
});
|
|
BEAST_EXPECT(g.wait_for(5s));
|
|
BEAST_EXPECT(result.isMember(jss::error));
|
|
}
|
|
|
|
void
|
|
no_direct_path_no_intermediary_no_alternatives()
|
|
{
|
|
testcase("no direct path no intermediary no alternatives");
|
|
using namespace jtx;
|
|
|
|
Env env = pathTestEnv();
|
|
|
|
env.fund(XRP(10'000), "alice", "bob");
|
|
|
|
auto USDM = MPTTester({.env = env, .issuer = "bob"});
|
|
|
|
auto const result = find_paths(env, "alice", "bob", USDM(5));
|
|
BEAST_EXPECT(std::get<0>(result).empty());
|
|
}
|
|
|
|
void
|
|
direct_path_no_intermediary()
|
|
{
|
|
testcase("direct path no intermediary");
|
|
using namespace jtx;
|
|
Env env = pathTestEnv();
|
|
env.fund(XRP(10'000), "alice", "bob");
|
|
|
|
MPT const USD = MPTTester({.env = env, .issuer = "alice", .holders = {"bob"}});
|
|
|
|
STPathSet st;
|
|
STAmount sa;
|
|
std::tie(st, sa, std::ignore) = find_paths(env, "alice", "bob", USD(5));
|
|
BEAST_EXPECT(st.empty());
|
|
BEAST_EXPECT(equal(sa, USD(5)));
|
|
}
|
|
|
|
void
|
|
payment_auto_path_find()
|
|
{
|
|
testcase("payment auto path find");
|
|
using namespace jtx;
|
|
Env env = pathTestEnv();
|
|
auto const gw = Account("gateway");
|
|
env.fund(XRP(10'000), "alice", "bob", gw);
|
|
MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {"alice", "bob"}});
|
|
env(pay(gw, "alice", USD(70)));
|
|
env(pay("alice", "bob", USD(24)));
|
|
env.require(balance("alice", USD(46)));
|
|
env.require(balance("bob", USD(24)));
|
|
}
|
|
|
|
void
|
|
path_find(bool const domainEnabled)
|
|
{
|
|
testcase(std::string("path find") + (domainEnabled ? " w/ " : " w/o ") + "domain");
|
|
using namespace jtx;
|
|
Env env = pathTestEnv();
|
|
auto const gw = Account("gateway");
|
|
env.fund(XRP(10'000), "alice", "bob", gw);
|
|
MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {"alice", "bob"}});
|
|
env(pay(gw, "alice", USD(70)));
|
|
env(pay(gw, "bob", USD(50)));
|
|
|
|
std::optional<uint256> domainID;
|
|
if (domainEnabled)
|
|
domainID = setupDomain(env, {"alice", "bob", gw});
|
|
|
|
STPathSet st;
|
|
STAmount sa;
|
|
STAmount da;
|
|
std::tie(st, sa, da) = find_paths(
|
|
env, "alice", "bob", USD(5), std::nullopt, std::nullopt, std::nullopt, domainID);
|
|
// Note, a direct IOU payment will have "gateway" as alternative path
|
|
// since IOU supports rippling
|
|
BEAST_EXPECT(st.empty());
|
|
BEAST_EXPECT(equal(sa, USD(5)));
|
|
BEAST_EXPECT(equal(da, USD(5)));
|
|
}
|
|
|
|
void
|
|
path_find_consume_all(bool const domainEnabled)
|
|
{
|
|
testcase(
|
|
std::string("path find consume all") + (domainEnabled ? " w/ " : " w/o ") + "domain");
|
|
using namespace jtx;
|
|
|
|
{
|
|
Env env = pathTestEnv();
|
|
auto const gw = Account("gateway");
|
|
env.fund(XRP(10'000), "alice", "bob", "carol", gw);
|
|
MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {"bob", "carol"}});
|
|
MPT const AUD(makeMptID(0, gw));
|
|
env(pay(gw, "carol", USD(100)));
|
|
std::optional<uint256> domainID;
|
|
if (domainEnabled)
|
|
{
|
|
domainID = setupDomain(env, {"alice", "bob", "carol", "gateway"});
|
|
env(offer("carol", XRP(100), USD(100)), domain(*domainID));
|
|
}
|
|
else
|
|
{
|
|
env(offer("carol", XRP(100), USD(100)));
|
|
}
|
|
env.close();
|
|
|
|
STPathSet st;
|
|
STAmount sa;
|
|
STAmount da;
|
|
std::tie(st, sa, da) = find_paths(
|
|
env,
|
|
"alice",
|
|
"bob",
|
|
AUD(-1),
|
|
std::optional<STAmount>(XRP(100'000'000)),
|
|
std::nullopt,
|
|
std::nullopt,
|
|
domainID);
|
|
BEAST_EXPECT(st.empty());
|
|
std::tie(st, sa, da) = find_paths(
|
|
env,
|
|
"alice",
|
|
"bob",
|
|
USD(-1),
|
|
std::optional<STAmount>(XRP(100'000'000)),
|
|
std::nullopt,
|
|
std::nullopt,
|
|
domainID);
|
|
if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
|
|
{
|
|
auto const& pathElem = st[0][0];
|
|
BEAST_EXPECT(
|
|
pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
|
|
pathElem.getMPTID() == USD.issuanceID);
|
|
}
|
|
BEAST_EXPECT(sa == XRP(100));
|
|
BEAST_EXPECT(equal(da, USD(100)));
|
|
|
|
// if domain is used, finding path in the open offerbook will return
|
|
// empty result
|
|
if (domainEnabled)
|
|
{
|
|
std::tie(st, sa, da) = find_paths(
|
|
env,
|
|
"alice",
|
|
"bob",
|
|
Account("bob")["USD"](-1),
|
|
std::optional<STAmount>(XRP(1000000)),
|
|
std::nullopt,
|
|
std::nullopt); // not specifying a domain
|
|
BEAST_EXPECT(st.empty());
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
alternative_paths_consume_best_transfer(bool const domainEnabled)
|
|
{
|
|
testcase(
|
|
std::string("alternative path consume best transfer") +
|
|
(domainEnabled ? " w/ " : " w/o ") + "domain");
|
|
using namespace jtx;
|
|
Env env = pathTestEnv();
|
|
auto const gw = Account("gateway");
|
|
auto const gw2 = Account("gateway2");
|
|
env.fund(XRP(10'000), "alice", "bob", gw, gw2);
|
|
MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {"alice", "bob"}});
|
|
MPT const gw2_USD = MPTTester(
|
|
{.env = env, .issuer = gw2, .holders = {"alice", "bob"}, .transferFee = 1'000});
|
|
std::optional<uint256> domainID;
|
|
if (domainEnabled)
|
|
{
|
|
domainID = setupDomain(env, {"alice", "bob", "gateway", "gateway2"});
|
|
env(pay(gw, "alice", USD(70)), domain(*domainID));
|
|
env(pay(gw2, "alice", gw2_USD(70)), domain(*domainID));
|
|
env(pay("alice", "bob", USD(70)), domain(*domainID));
|
|
}
|
|
else
|
|
{
|
|
env(pay(gw, "alice", USD(70)));
|
|
env(pay(gw2, "alice", gw2_USD(70)));
|
|
env(pay("alice", "bob", USD(70)));
|
|
}
|
|
env.require(balance("alice", USD(0)));
|
|
env.require(balance("alice", gw2_USD(70)));
|
|
env.require(balance("bob", USD(70)));
|
|
env.require(balance("bob", gw2_USD(0)));
|
|
}
|
|
|
|
void
|
|
receive_max(bool const domainEnabled)
|
|
{
|
|
testcase(std::string("Receive max") + (domainEnabled ? " w/ " : " w/o ") + "domain");
|
|
using namespace jtx;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const charlie = Account("charlie");
|
|
auto const gw = Account("gw");
|
|
{
|
|
// XRP -> MPT receive max
|
|
Env env = pathTestEnv();
|
|
env.fund(XRP(10'000), alice, bob, charlie, gw);
|
|
env.close();
|
|
MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, charlie}});
|
|
env(pay(gw, charlie, USD(10)));
|
|
env.close();
|
|
std::optional<uint256> domainID;
|
|
if (domainEnabled)
|
|
{
|
|
domainID = setupDomain(env, {alice, bob, charlie, gw});
|
|
env(offer(charlie, XRP(10), USD(10)), domain(*domainID));
|
|
}
|
|
else
|
|
{
|
|
env(offer(charlie, XRP(10), USD(10)));
|
|
}
|
|
env.close();
|
|
auto [st, sa, da] = find_paths(
|
|
env, alice, bob, USD(-1), XRP(100).value(), std::nullopt, std::nullopt, domainID);
|
|
BEAST_EXPECT(sa == XRP(10));
|
|
BEAST_EXPECT(equal(da, USD(10)));
|
|
if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
|
|
{
|
|
auto const& pathElem = st[0][0];
|
|
BEAST_EXPECT(
|
|
pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
|
|
pathElem.getMPTID() == USD.mpt());
|
|
}
|
|
}
|
|
{
|
|
// MPT -> XRP receive max
|
|
Env env = pathTestEnv();
|
|
env.fund(XRP(10'000), alice, bob, charlie, gw);
|
|
env.close();
|
|
MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, charlie}});
|
|
env(pay(gw, alice, USD(10)));
|
|
env.close();
|
|
std::optional<uint256> domainID;
|
|
if (domainEnabled)
|
|
{
|
|
domainID = setupDomain(env, {alice, bob, charlie, gw});
|
|
env(offer(charlie, USD(10), XRP(10)), domain(*domainID));
|
|
}
|
|
else
|
|
{
|
|
env(offer(charlie, USD(10), XRP(10)));
|
|
}
|
|
env.close();
|
|
auto [st, sa, da] = find_paths(
|
|
env, alice, bob, drops(-1), USD(100).value(), std::nullopt, std::nullopt, domainID);
|
|
BEAST_EXPECT(sa == USD(10));
|
|
BEAST_EXPECT(equal(da, XRP(10)));
|
|
if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
|
|
{
|
|
auto const& pathElem = st[0][0];
|
|
BEAST_EXPECT(
|
|
pathElem.isOffer() && pathElem.getIssuerID() == xrpAccount() &&
|
|
pathElem.getCurrency() == xrpCurrency());
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
source_currencies_limit();
|
|
no_direct_path_no_intermediary_no_alternatives();
|
|
direct_path_no_intermediary();
|
|
payment_auto_path_find();
|
|
for (auto const domainEnabled : {false, true})
|
|
{
|
|
path_find(domainEnabled);
|
|
path_find_consume_all(domainEnabled);
|
|
alternative_paths_consume_best_transfer(domainEnabled);
|
|
receive_max(domainEnabled);
|
|
}
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(PathMPT, app, xrpl);
|
|
|
|
} // namespace xrpl::test
|