Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
142 changes: 128 additions & 14 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,14 +1421,15 @@ 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);
}

// Now sponsor bob's trust line
auto const trustId = keylet::line(bob, gw, usd.currency);
BEAST_EXPECT(env.le(trustId));
if (!BEAST_EXPECT(env.le(trustId)))
return;

env(sponsor::transfer(bob, tfSponsorshipCreate, trustId.key),
sponsor::As(sponsor1, spfSponsorReserve),
Expand All @@ -1438,17 +1439,20 @@ class AccountObjects_test : public beast::unit_test::Suite
// Verify trust line has sponsor field
{
auto const sle = env.le(trustId);
if (!BEAST_EXPECT(sle))
return;
BEAST_EXPECT(sle->isFieldPresent(sfHighSponsor) || sle->isFieldPresent(sfLowSponsor));
}

// 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);
for (auto const& obj : objs)
if (BEAST_EXPECT(objs.size() == 1))
{
auto const& obj = objs[0u];
BEAST_EXPECT(obj[sfLedgerEntryType.jsonName] == jss::RippleState);
if (obj[sfLedgerEntryType.jsonName] == jss::RippleState)
{
BEAST_EXPECT(
Expand All @@ -1462,7 +1466,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,14 +1480,121 @@ 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 env(*this, testableAmendments());
Account const issuer("issuer");
Account const user("user");
Account const sponsor("sponsor");
auto const usd = issuer["USD"];

env.fund(XRP(10000), issuer, user, sponsor);
env.close();

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

env(trust(user, usd(100)));
env.close();

auto const trustId = keylet::line(user, issuer, usd.currency);
if (!BEAST_EXPECT(env.le(trustId)))
return;

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

auto const line = env.le(trustId);
if (!BEAST_EXPECT(line))
return;

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(env, user.id(), true, jss::state);
auto const& objs = resp[jss::result][jss::account_objects];
if (BEAST_EXPECT(objs.size() == 1))
BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::RippleState);
}
{
auto const resp = acctObjsSponsored(env, user.id(), false, jss::state);
auto const& objs = resp[jss::result][jss::account_objects];
BEAST_EXPECT(objs.size() == 0);
}
{
auto const resp = acctObjsSponsored(env, issuer.id(), true, jss::state);
auto const& objs = resp[jss::result][jss::account_objects];
BEAST_EXPECT(objs.size() == 0);
}
{
auto const resp = acctObjsSponsored(env, issuer.id(), false, jss::state);
auto const& objs = resp[jss::result][jss::account_objects];
if (BEAST_EXPECT(objs.size() == 1))
BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::RippleState);
}
}

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

env.fund(XRP(10000), owner, sponsee, sponsor);
env.close();

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

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

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

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

// NFT page sponsored filter
{
// Mint an NFT for bob (creates NFT page)
env(token::mint(bob, 0));
env.close();

auto const nftPageKeylet = keylet::nftpageMax(bob);
BEAST_EXPECT(env.le(nftPageKeylet));
if (!BEAST_EXPECT(env.le(nftPageKeylet)))
return;

// Sponsor the NFT page
env(sponsor::transfer(bob, tfSponsorshipCreate, nftPageKeylet.key),
Expand All @@ -1492,13 +1603,16 @@ class AccountObjects_test : public beast::unit_test::Suite
env.close();

// Verify NFT page has sponsor field
BEAST_EXPECT(env.le(nftPageKeylet)->isFieldPresent(sfSponsor));
auto const nftPage = env.le(nftPageKeylet);
if (!BEAST_EXPECT(nftPage))
return;
BEAST_EXPECT(nftPage->isFieldPresent(sfSponsor));

// sponsored=true should include the sponsored NFT page
// 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
67 changes: 67 additions & 0 deletions src/test/rpc/Simulate_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,20 @@ class Simulate_test : public beast::unit_test::Suite
auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx.Signers[0]'.");
}
{
// Non-object SponsorSignature field
json::Value params;
json::Value txJson = json::ValueType::Object;
txJson[jss::TransactionType] = jss::AccountSet;
txJson[jss::Account] = env.master.human();
txJson[sfSponsorSignature] = "";
params[jss::tx_json] = txJson;

auto const resp = env.rpc("json", "simulate", to_string(params));
BEAST_EXPECT(
resp[jss::result][jss::error_message] ==
"Invalid field 'SponsorSignature', not object.");
}
{
// Invalid transaction
json::Value params;
Expand Down Expand Up @@ -817,6 +831,58 @@ class Simulate_test : public beast::unit_test::Suite
}
}

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

using namespace jtx;
Env env(*this);
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];
// Verifies Fee autofill counts nested sponsor-signature signers.
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] = "123ABC";
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);

// Leave Fee unset so simulate must autofill it after sponsor signer normalization.
BEAST_EXPECT(!tx.isMember(jss::Fee));
testTx(env, tx, validateOutput, false);
}

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