Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
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
22 changes: 17 additions & 5 deletions src/xrpld/rpc/handlers/account/AccountObjects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,16 +204,28 @@ getAccountObjects(
!typeMatchesFilter(typeFilter.value(), sleNode->getType()))
canAppend = false;

auto const getSponsor = [&sleNode]() -> std::optional<AccountID> {
if (sleNode->isFieldPresent(sfSponsor))
return sleNode->getAccountID(sfSponsor);
auto const getSponsor = [&account, &sleNode]() -> std::optional<AccountID> {
if (sleNode->getType() == ltRIPPLE_STATE)
{
if (sleNode->isFieldPresent(sfHighSponsor))
if (sleNode->isFlag(lsfHighReserve) &&
sleNode->getFieldAmount(sfHighLimit).getIssuer() == account &&
sleNode->isFieldPresent(sfHighSponsor))
return sleNode->getAccountID(sfHighSponsor);
if (sleNode->isFieldPresent(sfLowSponsor))
if (sleNode->isFlag(lsfLowReserve) &&
sleNode->getFieldAmount(sfLowLimit).getIssuer() == account &&
sleNode->isFieldPresent(sfLowSponsor))
return sleNode->getAccountID(sfLowSponsor);

return std::nullopt;
}

if (sleNode->getType() == ltSPONSORSHIP &&
sleNode->getAccountID(sfOwner) != account)
return std::nullopt;

if (sleNode->isFieldPresent(sfSponsor))
return sleNode->getAccountID(sfSponsor);

return std::nullopt;
};
std::optional<AccountID> const sponsor = getSponsor();
Expand Down
34 changes: 17 additions & 17 deletions src/xrpld/rpc/handlers/transaction/Simulate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,23 +131,6 @@ autofillSignature(json::Value& sigObject)
static std::optional<json::Value>
autofillTx(json::Value& txJson, RPC::JsonContext& context)
{
if (!txJson.isMember(jss::Fee))
{
// autofill Fee
// Must happen after all the other autofills happen
// Error handling/messaging works better that way
auto feeOrError = RPC::getCurrentNetworkFee(
context.role,
context.app.config(),
context.app.getFeeTrack(),
context.app.getTxQ(),
context.app,
txJson);
if (feeOrError.isMember(jss::error))
return feeOrError;
txJson[jss::Fee] = feeOrError;
}

if (auto error = autofillSignature(txJson))
return error;

Expand All @@ -172,6 +155,23 @@ autofillTx(json::Value& txJson, RPC::JsonContext& context)
txJson[jss::NetworkID] = to_string(networkId);
}

if (!txJson.isMember(jss::Fee))
{
// autofill Fee
// Must happen after all the other autofills happen
Comment thread
Kassaking7 marked this conversation as resolved.
Outdated
// Error handling/messaging works better that way
auto feeOrError = RPC::getCurrentNetworkFee(
context.role,
context.app.config(),
context.app.getFeeTrack(),
context.app.getTxQ(),
context.app,
txJson);
if (feeOrError.isMember(jss::error))
return feeOrError;
txJson[jss::Fee] = feeOrError;
}

return std::nullopt;
}

Expand Down