Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 81 additions & 2 deletions src/test/app/Path_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ class Path_test : public beast::unit_test::Suite
STAmount const& saDstAmount,
std::optional<STAmount> const& saSendMax = std::nullopt,
std::optional<Currency> const& saSrcCurrency = std::nullopt,
std::optional<uint256> const& domain = std::nullopt)
std::optional<uint256> const& domain = std::nullopt,
std::optional<std::uint32_t> const& txFlags = std::nullopt,
bool allowError = false)
{
using namespace jtx;

Expand Down Expand Up @@ -184,6 +186,8 @@ class Path_test : public beast::unit_test::Suite
}
if (domain)
params[jss::domain] = to_string(*domain);
if (txFlags)
params[jss::Flags] = *txFlags;

json::Value result;
Gate g;
Expand All @@ -196,7 +200,8 @@ class Path_test : public beast::unit_test::Suite

using namespace std::chrono_literals;
BEAST_EXPECT(g.waitFor(5s));
BEAST_EXPECT(!result.isMember(jss::error));
if (!allowError)
BEAST_EXPECT(!result.isMember(jss::error));
return result;
}

Expand Down Expand Up @@ -419,6 +424,79 @@ class Path_test : public beast::unit_test::Suite
BEAST_EXPECT(std::get<0>(result).empty());
}

void
sponsoredCreateAccountPathFind()
{
testcase("sponsored create account path find");
using namespace jtx;

Env env = pathTestEnv();

auto const alice = Account("alice");
auto const bob = Account("bob");
auto const charlie = Account("charlie");
auto const dan = Account("dan");
auto const gw = Account("gw");
auto const usd = gw["USD"];

env.fund(XRP(10000), alice, bob, gw);
env.close();
env.trust(usd(100), alice, bob);
env.close();
env(pay(gw, alice, usd(50)));
env.close();

env(offer(bob, usd(50), XRP(5)));
env.close();

auto hasAlternatives = [](json::Value const& result) {
return result.isMember(jss::alternatives) && result[jss::alternatives].isArray() &&
result[jss::alternatives].size() > 0;
};

auto const fundedResult =
findPathsRequest(env, alice, bob, drops(1), std::nullopt, usd.currency);
BEAST_EXPECTS(hasAlternatives(fundedResult), fundedResult.toStyledString());

env(pay(alice, charlie, drops(1)),
Path(~XRP),
Sendmax(usd(50)),
Txflags(tfSponsorCreatedAccount));
env.close();

auto const charlieSle = env.le(keylet::account(charlie));
BEAST_EXPECT(charlieSle);
if (charlieSle)
{
BEAST_EXPECT(charlieSle->isFieldPresent(sfSponsor));
BEAST_EXPECT(charlieSle->getAccountID(sfSponsor) == alice.id());
}

auto const withoutFlag = findPathsRequest(
env,
alice,
dan,
drops(1),
std::nullopt,
usd.currency,
std::nullopt,
std::nullopt,
true);
BEAST_EXPECT(withoutFlag.isMember(jss::error));
BEAST_EXPECT(withoutFlag[jss::error].asString() == "dstAmtMalformed");

auto const withFlag = findPathsRequest(
env,
alice,
dan,
drops(1),
std::nullopt,
usd.currency,
std::nullopt,
tfSponsorCreatedAccount);
BEAST_EXPECTS(hasAlternatives(withFlag), withFlag.toStyledString());
}

void
pathFindConsumeAll(bool const domainEnabled)
{
Expand Down Expand Up @@ -1879,6 +1957,7 @@ class Path_test : public beast::unit_test::Suite
trustAutoClearTrustNormalClear();
trustAutoClearTrustAutoClear();
norippleCombinations();
sponsoredCreateAccountPathFind();

for (bool const domainEnabled : {false, true})
{
Expand Down
1 change: 1 addition & 0 deletions src/test/jtx/impl/paths.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Paths::operator()(Env& env, JTx& jt) const
amount,
std::nullopt,
domain,
false,
env.app());
if (!pf.findPaths(depth_))
return;
Expand Down
119 changes: 110 additions & 9 deletions src/test/rpc/AccountObjects_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1398,17 +1398,17 @@ class AccountObjects_test : public beast::unit_test::Suite
env.close();

// Helper to call account_objects with sponsored filter
auto acctObjsSponsored = [&env](
AccountID const& acct,
bool sponsored,
std::optional<json::StaticString> const& type = std::nullopt) {
auto acctObjsSponsored = [](Env& testEnv,
AccountID const& acct,
bool sponsored,
std::optional<json::StaticString> const& type = std::nullopt) {
json::Value params;
params[jss::account] = to_string(acct);
params[jss::sponsored] = sponsored;
if (type)
params[jss::type] = *type;
params[jss::ledger_index] = "validated";
return env.rpc("json", "account_objects", to_string(params));
return testEnv.rpc("json", "account_objects", to_string(params));
};

// Create a sponsorship (alice sponsors bob)
Expand All @@ -1421,7 +1421,7 @@ class AccountObjects_test : public beast::unit_test::Suite

// sponsored=true should not find any objects for bob (doesn't have any sponsored objects)
{
auto const resp = acctObjsSponsored(bob.id(), true);
auto const resp = acctObjsSponsored(env, bob.id(), true);
auto const& objs = resp[jss::result][jss::account_objects];
BEAST_EXPECT(objs.size() == 0);
}
Expand All @@ -1443,7 +1443,7 @@ class AccountObjects_test : public beast::unit_test::Suite

// sponsored=true on bob should include the sponsored trust line
{
auto const resp = acctObjsSponsored(bob.id(), true);
auto const resp = acctObjsSponsored(env, bob.id(), true);
auto const& objs = resp[jss::result][jss::account_objects];
bool foundTrustLine = false;
BEAST_EXPECT(objs.size() == 1);
Expand All @@ -1462,7 +1462,7 @@ class AccountObjects_test : public beast::unit_test::Suite

// sponsored=false on bob should NOT include the sponsored trust line
{
auto const resp = acctObjsSponsored(bob.id(), false);
auto const resp = acctObjsSponsored(env, bob.id(), false);
auto const& objs = resp[jss::result][jss::account_objects];
bool foundSponsoredTrustLine = false;
for (auto const& obj : objs)
Expand All @@ -1476,6 +1476,107 @@ class AccountObjects_test : public beast::unit_test::Suite
BEAST_EXPECT(!foundSponsoredTrustLine);
}

// Only the queried side of a shared trust line should determine
// sponsorship classification.
{
Env env2(*this, testableAmendments());
Comment thread
Kassaking7 marked this conversation as resolved.
Outdated
Account const issuer("issuer");
Account const user("user");
Account const sponsor2("sponsor2");
Comment thread
Kassaking7 marked this conversation as resolved.
Outdated
auto const usd2 = issuer["USD"];

env2.fund(XRP(10000), issuer, user, sponsor2);
env2.close();

env2(trust(issuer, user["USD"](100)));
env2.close();

env2(trust(user, usd2(100)));
env2.close();

auto const trustId = keylet::line(user, issuer, usd2.currency);
BEAST_EXPECT(env2.le(trustId));

env2(
sponsor::transfer(user, tfSponsorshipCreate, trustId.key),
sponsor::As(sponsor2, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor2));
env2.close();

auto const line = env2.le(trustId);
BEAST_EXPECT(line);
Comment thread
Kassaking7 marked this conversation as resolved.
Outdated

auto const userIsHigh = line->getFieldAmount(sfHighLimit).getIssuer() == user.id();
auto const& userSponsorField = userIsHigh ? sfHighSponsor : sfLowSponsor;
auto const& issuerSponsorField = userIsHigh ? sfLowSponsor : sfHighSponsor;

BEAST_EXPECT(line->isFieldPresent(userSponsorField));
BEAST_EXPECT(!line->isFieldPresent(issuerSponsorField));

{
auto const resp = acctObjsSponsored(env2, user.id(), true, jss::state);
auto const& objs = resp[jss::result][jss::account_objects];
BEAST_EXPECT(objs.size() == 1);
Comment thread
Kassaking7 marked this conversation as resolved.
Outdated
}
{
auto const resp = acctObjsSponsored(env2, user.id(), false, jss::state);
auto const& objs = resp[jss::result][jss::account_objects];
BEAST_EXPECT(objs.size() == 0);
}
{
auto const resp = acctObjsSponsored(env2, issuer.id(), true, jss::state);
auto const& objs = resp[jss::result][jss::account_objects];
BEAST_EXPECT(objs.size() == 0);
}
{
auto const resp = acctObjsSponsored(env2, issuer.id(), false, jss::state);
auto const& objs = resp[jss::result][jss::account_objects];
BEAST_EXPECT(objs.size() == 1);
}
}

// A Sponsorship object is visible to both sides, but its reserve side
// belongs only to sfOwner.
{
Env env2(*this, testableAmendments());
Account const owner("owner");
Account const sponsee("sponsee");
Account const sponsor2("sponsor2");

env2.fund(XRP(10000), owner, sponsee, sponsor2);
env2.close();

env2(sponsor::set_reserve(sponsor2, 0, 100), sponsor::SponseeAcc(owner));
env2.close();

env2(
sponsor::set(owner, 0, 100, XRP(100)),
sponsor::SponseeAcc(sponsee),
sponsor::As(sponsor2, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor2));
env2.close();

auto const sponsorship = env2.le(keylet::sponsor(owner, sponsee));
BEAST_EXPECT(sponsorship);
BEAST_EXPECT(sponsorship->isFieldPresent(sfSponsor));

{
auto const resp = acctObjsSponsored(env2, owner.id(), true, jss::sponsorship);
auto const& objs = resp[jss::result][jss::account_objects];
BEAST_EXPECT(objs.size() == 1);
}
{
auto const resp = acctObjsSponsored(env2, sponsee.id(), true, jss::sponsorship);
auto const& objs = resp[jss::result][jss::account_objects];
BEAST_EXPECT(objs.size() == 0);
}
{
auto const resp = acctObjsSponsored(env2, sponsee.id(), false, jss::sponsorship);
auto const& objs = resp[jss::result][jss::account_objects];
BEAST_EXPECT(objs.size() == 1);
}
}

// NFT page sponsored filter
{
// Mint an NFT for bob (creates NFT page)
Expand All @@ -1498,7 +1599,7 @@ class AccountObjects_test : public beast::unit_test::Suite
// sponsored=false should NOT include the sponsored NFT page
for (auto const sponsored : {true, false})
{
auto const resp = acctObjsSponsored(bob.id(), sponsored);
auto const resp = acctObjsSponsored(env, bob.id(), sponsored);
auto const& objs = resp[jss::result][jss::account_objects];
bool foundNFTPage = false;
for (auto const& obj : objs)
Expand Down
51 changes: 51 additions & 0 deletions src/test/rpc/Simulate_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,56 @@ class Simulate_test : public beast::unit_test::Suite
}
}

void
testSuccessfulSponsoredTransactionMultisigned()
{
testcase("Successful sponsored multi-signed transaction");

using namespace jtx;
Env env(*this);
static auto const kNewDomain = "123ABC";
Comment thread
Kassaking7 marked this conversation as resolved.
Outdated
Account const sponsor("sponsor");
Account const signer("signer");
env.fund(XRP(10000), sponsor, signer);
env.close();

env(signers(sponsor, 1, {{signer, 1}}));
env.close();

auto validateOutput = [&](json::Value const& resp, json::Value const& tx) {
auto const result = resp[jss::result];
auto const expectedFee = env.current()->fees().base * 2;
Comment thread
Kassaking7 marked this conversation as resolved.
checkBasicReturnValidity(result, tx, env.seq(env.master), expectedFee);

BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
BEAST_EXPECT(result[jss::engine_result_code] == 0);
BEAST_EXPECT(
result[jss::engine_result_message] ==
"The simulated transaction would have been applied.");

if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
{
json::Value const metadata = getJsonMetadata(result);
BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
}
};

json::Value tx;
tx[jss::Account] = env.master.human();
tx[jss::TransactionType] = jss::AccountSet;
tx[sfDomain] = kNewDomain;
tx[sfSponsor.jsonName] = sponsor.human();
tx[sfSponsorFlags.jsonName] = spfSponsorFee;
tx[sfSponsorSignature.jsonName] = json::ValueType::Object;
tx[sfSponsorSignature.jsonName][sfSigners.jsonName] = json::ValueType::Array;
Comment thread
Kassaking7 marked this conversation as resolved.

json::Value signerObj;
signerObj[sfSigner][jss::Account] = signer.human();
tx[sfSponsorSignature.jsonName][sfSigners.jsonName].append(signerObj);

testTx(env, tx, validateOutput, false);
}

void
testTransactionSigningFailure()
{
Expand Down Expand Up @@ -1249,6 +1299,7 @@ class Simulate_test : public beast::unit_test::Suite
testTransactionNonTecFailure();
testTransactionTecFailure();
testSuccessfulTransactionMultisigned();
testSuccessfulSponsoredTransactionMultisigned();
testTransactionSigningFailure();
testInvalidSingleAndMultiSigningTransaction();
testMultisignedBadPubKey();
Expand Down
Loading