mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-23 15:20:54 +00:00
Co-authored-by: tequ <git@tequ.dev> Co-authored-by: yinyiqian1 <yqian@ripple.com> Co-authored-by: Mayukha Vadari <mvadari@ripple.com> Co-authored-by: Mayukha Vadari <mvadari@gmail.com> Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com> Co-authored-by: Peter Chen <34582813+PeterChen13579@users.noreply.github.com> Co-authored-by: Zhiyuan Wang <96991820+Kassaking7@users.noreply.github.com> Co-authored-by: Ayaz Salikhov <asalikhov@ripple.com> Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com> Co-authored-by: Zhiyuan Wang <1830604455@qq.com>
This commit is contained in:
@@ -9,8 +9,11 @@
|
||||
#include <test/jtx/owners.h> // IWYU pragma: keep
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/permissioned_domains.h>
|
||||
#include <test/jtx/sig.h>
|
||||
#include <test/jtx/sponsor.h>
|
||||
#include <test/jtx/ticket.h>
|
||||
#include <test/jtx/token.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/txflags.h>
|
||||
#include <test/jtx/xchain_bridge.h>
|
||||
|
||||
@@ -22,6 +25,7 @@
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
@@ -200,6 +204,15 @@ public:
|
||||
resp[jss::result][jss::error_message] ==
|
||||
"Invalid field 'limit', not unsigned integer.");
|
||||
}
|
||||
// test error on sponsored param not a boolean
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::account] = bob.human();
|
||||
params[jss::sponsored] = "true";
|
||||
auto resp = env.rpc("json", "account_objects", to_string(params));
|
||||
BEAST_EXPECT(
|
||||
resp[jss::result][jss::error_message] == "Invalid field 'sponsored', not boolean.");
|
||||
}
|
||||
// test errors on marker
|
||||
{
|
||||
Account const gw{"G"};
|
||||
@@ -605,6 +618,7 @@ public:
|
||||
BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::amm), 0));
|
||||
BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::did), 0));
|
||||
BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::permissioned_domain), 0));
|
||||
BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::sponsorship), 0));
|
||||
|
||||
// we expect invalid field type reported for the following types
|
||||
BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::amendments)));
|
||||
@@ -926,6 +940,30 @@ public:
|
||||
BEAST_EXPECT(ticket[sfTicketSequence.jsonName].asUInt() == seq + 1);
|
||||
}
|
||||
|
||||
{
|
||||
// Create a sponsorship
|
||||
env(sponsor::set(alice, tfSponsorshipSetRequireSignForFee, 200, XRP(100), drops(10)),
|
||||
sponsor::SponseeAcc(gw));
|
||||
env.close();
|
||||
|
||||
// Find the sponsorship.
|
||||
for (auto const& acct : {alice, gw})
|
||||
{
|
||||
json::Value const resp = acctObjs(acct, jss::sponsorship);
|
||||
BEAST_EXPECT(acctObjsIsSize(resp, 1));
|
||||
|
||||
auto const& sponsorship = resp[jss::result][jss::account_objects][0u];
|
||||
|
||||
BEAST_EXPECT(sponsorship[sfOwner.jsonName] == alice.human());
|
||||
BEAST_EXPECT(sponsorship[sfSponsee.jsonName] == gw.human());
|
||||
BEAST_EXPECT(
|
||||
sponsorship[sfFlags.jsonName].asUInt() == tfSponsorshipSetRequireSignForFee);
|
||||
BEAST_EXPECT(sponsorship[sfRemainingOwnerCount.jsonName].asUInt() == 200);
|
||||
BEAST_EXPECT(sponsorship[sfFeeAmount.jsonName].asUInt() == 100000000);
|
||||
BEAST_EXPECT(sponsorship[sfMaxFee.jsonName].asUInt() == 10);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// See how "deletion_blockers_only" handles gw's directory.
|
||||
json::Value params;
|
||||
@@ -940,7 +978,8 @@ public:
|
||||
jss::NFTokenPage.cStr(),
|
||||
jss::RippleState.cStr(),
|
||||
jss::PayChannel.cStr(),
|
||||
jss::PermissionedDomain.cStr()};
|
||||
jss::PermissionedDomain.cStr(),
|
||||
jss::Sponsorship.cStr()};
|
||||
std::ranges::sort(v);
|
||||
return v;
|
||||
}();
|
||||
@@ -1350,6 +1389,233 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSponsoredFilter()
|
||||
{
|
||||
testcase("SponsoredFilter");
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, testableAmendments());
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
Account const sponsor1("sponsor1");
|
||||
Account const gw("gw");
|
||||
auto const usd = gw["USD"];
|
||||
|
||||
env.fund(XRP(10000), alice, bob, sponsor1, gw);
|
||||
env.close();
|
||||
|
||||
// Helper to call account_objects with sponsored filter
|
||||
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 testEnv.rpc("json", "account_objects", to_string(params));
|
||||
};
|
||||
|
||||
// Create a trust line for bob (not sponsored)
|
||||
env(trust(bob, usd(1000)));
|
||||
env.close();
|
||||
|
||||
// sponsored=true should not find any objects for bob (doesn't have any sponsored objects)
|
||||
{
|
||||
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::trustLine(bob, gw, usd.currency);
|
||||
if (!BEAST_EXPECT(env.le(trustId)))
|
||||
return;
|
||||
|
||||
env(sponsor::transfer(bob, tfSponsorshipCreate, trustId.key),
|
||||
sponsor::As(sponsor1, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor1));
|
||||
env.close();
|
||||
|
||||
// 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(env, bob.id(), true);
|
||||
auto const& objs = resp[jss::result][jss::account_objects];
|
||||
if (!BEAST_EXPECT(objs.size() == 1))
|
||||
return;
|
||||
|
||||
auto const& obj = objs[0u];
|
||||
BEAST_EXPECT(obj[sfLedgerEntryType.jsonName] == jss::RippleState);
|
||||
BEAST_EXPECT(
|
||||
obj.isMember(sfHighSponsor.jsonName) || obj.isMember(sfLowSponsor.jsonName));
|
||||
}
|
||||
|
||||
// sponsored=false on bob should NOT include the sponsored trust line
|
||||
{
|
||||
auto const resp = acctObjsSponsored(env, bob.id(), false);
|
||||
BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
|
||||
}
|
||||
|
||||
// A trust line sponsored on either side is classified as sponsored
|
||||
// for both parties.
|
||||
{
|
||||
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::trustLine(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];
|
||||
if (BEAST_EXPECT(objs.size() == 1))
|
||||
BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::RippleState);
|
||||
}
|
||||
{
|
||||
auto const resp = acctObjsSponsored(env, issuer.id(), false, jss::state);
|
||||
auto const& objs = resp[jss::result][jss::account_objects];
|
||||
BEAST_EXPECT(objs.size() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// A sponsored Check is classified as sponsored in both the writer's
|
||||
// and the destination's results.
|
||||
{
|
||||
Env env(*this, testableAmendments());
|
||||
Account const owner("owner");
|
||||
Account const dest("dest");
|
||||
Account const sponsor("sponsor");
|
||||
|
||||
env.fund(XRP(10000), owner, dest, sponsor);
|
||||
env.close();
|
||||
|
||||
auto const checkSeq = env.seq(owner);
|
||||
env(check::create(owner, dest, XRP(1)));
|
||||
env.close();
|
||||
|
||||
auto const checkId = keylet::check(owner, checkSeq);
|
||||
if (!BEAST_EXPECT(env.le(checkId)))
|
||||
return;
|
||||
|
||||
env(sponsor::transfer(owner, tfSponsorshipCreate, checkId.key),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor));
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const sle = env.le(checkId);
|
||||
if (!BEAST_EXPECT(sle))
|
||||
return;
|
||||
BEAST_EXPECT(sle->isFieldPresent(sfSponsor));
|
||||
}
|
||||
|
||||
for (auto const& acct : {owner.id(), dest.id()})
|
||||
{
|
||||
{
|
||||
auto const resp = acctObjsSponsored(env, acct, true, jss::check);
|
||||
auto const& objs = resp[jss::result][jss::account_objects];
|
||||
if (BEAST_EXPECT(objs.size() == 1))
|
||||
BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Check);
|
||||
}
|
||||
{
|
||||
auto const resp = acctObjsSponsored(env, acct, false, jss::check);
|
||||
BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A Sponsorship object is visible to both sides.
|
||||
{
|
||||
Env env(*this, testableAmendments());
|
||||
Account const owner("owner");
|
||||
Account const sponsee("sponsee");
|
||||
|
||||
env.fund(XRP(10000), owner, sponsee);
|
||||
env.close();
|
||||
|
||||
env(sponsor::set(owner, 0, 100, XRP(100)), sponsor::SponseeAcc(sponsee));
|
||||
env.close();
|
||||
|
||||
auto const sponsorshipKeylet = keylet::sponsorship(owner, sponsee);
|
||||
if (!BEAST_EXPECT(env.le(sponsorshipKeylet)))
|
||||
return;
|
||||
|
||||
{
|
||||
auto const resp = acctObjsSponsored(env, owner.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);
|
||||
}
|
||||
{
|
||||
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);
|
||||
}
|
||||
{
|
||||
auto const resp = acctObjsSponsored(env, owner.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(), true, jss::sponsorship);
|
||||
auto const& objs = resp[jss::result][jss::account_objects];
|
||||
BEAST_EXPECT(objs.size() == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -1360,6 +1626,7 @@ public:
|
||||
testNFTsMarker();
|
||||
testAccountNFTs();
|
||||
testAccountObjectMarker();
|
||||
testSponsoredFilter();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/regkey.h>
|
||||
#include <test/jtx/sig.h>
|
||||
#include <test/jtx/sponsor.h>
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/ticket.h>
|
||||
#include <test/jtx/trust.h>
|
||||
@@ -773,7 +774,7 @@ class AccountTx_test : public beast::unit_test::Suite
|
||||
|
||||
// All it takes is a large enough XRP payment to resurrect
|
||||
// becky's account. Try too small a payment.
|
||||
env(pay(alice, becky, drops(env.current()->fees().accountReserve(0)) - drops(1)),
|
||||
env(pay(alice, becky, drops(env.current()->fees().accountReserve(0, 1)) - drops(1)),
|
||||
Ter(tecNO_DST_INSUF_XRP));
|
||||
env.close();
|
||||
|
||||
@@ -888,6 +889,82 @@ class AccountTx_test : public beast::unit_test::Suite
|
||||
checkAliceAcctTx(9, jss::Payment);
|
||||
}
|
||||
|
||||
void
|
||||
testSponsorship()
|
||||
{
|
||||
// test all sponsored transactions are in sponsor and sponsee's account
|
||||
// tx list
|
||||
testcase("Sponsorship");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env(*this);
|
||||
Account const alice("alice");
|
||||
Account const sponsor("sponsor");
|
||||
Account const sponsor2("sponsor2");
|
||||
env.fund(XRP(10000), alice, sponsor, sponsor2);
|
||||
env.close();
|
||||
|
||||
// check the latest sponsorship-related txn is in account tx list
|
||||
auto const checkTx = [&](Account const& account, json::StaticString txType) {
|
||||
json::Value params;
|
||||
params[jss::account] = account.human();
|
||||
params[jss::limit] = 100;
|
||||
auto const jv = env.rpc("json", "account_tx", to_string(params))[jss::result];
|
||||
|
||||
auto const& tx0(jv[jss::transactions][0u][jss::tx]);
|
||||
BEAST_EXPECT(tx0[jss::TransactionType] == txType);
|
||||
|
||||
std::string const txHash{
|
||||
env.tx()->getJson(JsonOptions::Values::None)[jss::hash].asString()};
|
||||
BEAST_EXPECT(tx0[jss::hash] == txHash);
|
||||
};
|
||||
|
||||
// fee sponsorship
|
||||
env(noop(alice), sponsor::As(sponsor, spfSponsorFee), Sig(sfSponsorSignature, sponsor));
|
||||
env.close();
|
||||
checkTx(alice, jss::AccountSet);
|
||||
checkTx(sponsor, jss::AccountSet);
|
||||
|
||||
// set sponsor
|
||||
env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::SponseeAcc(alice), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
checkTx(alice, jss::SponsorshipSet);
|
||||
checkTx(sponsor, jss::SponsorshipSet);
|
||||
|
||||
// create an object with sponsor
|
||||
auto const checkId = keylet::check(alice, env.seq(alice)).key;
|
||||
env(check::create(alice, sponsor, XRP(1)), sponsor::As(sponsor, spfSponsorReserve));
|
||||
env.close();
|
||||
checkTx(alice, jss::CheckCreate);
|
||||
checkTx(sponsor, jss::CheckCreate);
|
||||
|
||||
// transfer object sponsorship
|
||||
env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
|
||||
sponsor::As(sponsor2, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor2));
|
||||
env.close();
|
||||
checkTx(alice, jss::SponsorshipTransfer);
|
||||
checkTx(sponsor, jss::SponsorshipTransfer);
|
||||
checkTx(sponsor2, jss::SponsorshipTransfer);
|
||||
|
||||
// delete the sponsored object
|
||||
env(check::cancel(alice, checkId),
|
||||
sponsor::As(sponsor, spfSponsorFee),
|
||||
Sig(sfSponsorSignature, sponsor));
|
||||
env.close();
|
||||
checkTx(alice, jss::CheckCancel);
|
||||
checkTx(sponsor, jss::CheckCancel);
|
||||
checkTx(sponsor2, jss::CheckCancel);
|
||||
|
||||
// account sponsorship
|
||||
env(sponsor::transfer(alice, tfSponsorshipCreate),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor));
|
||||
env.close();
|
||||
checkTx(alice, jss::SponsorshipTransfer);
|
||||
checkTx(sponsor, jss::SponsorshipTransfer);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -896,6 +973,7 @@ public:
|
||||
testContents();
|
||||
testAccountDelete();
|
||||
testMPT();
|
||||
testSponsorship();
|
||||
}
|
||||
};
|
||||
BEAST_DEFINE_TESTSUITE(AccountTx, rpc, xrpl);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <test/jtx/offer.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/permissioned_domains.h>
|
||||
#include <test/jtx/sponsor.h>
|
||||
#include <test/jtx/ticket.h>
|
||||
#include <test/jtx/token.h>
|
||||
#include <test/jtx/txflags.h>
|
||||
@@ -93,6 +94,8 @@ std::vector<std::pair<json::StaticString, FieldType>> gMappings{
|
||||
{jss::oracle_document_id, FieldType::UInt32Field},
|
||||
{jss::owner, FieldType::AccountField},
|
||||
{jss::seq, FieldType::UInt32Field},
|
||||
{jss::sponsor, FieldType::AccountField},
|
||||
{jss::sponsee, FieldType::AccountField},
|
||||
{jss::subject, FieldType::AccountField},
|
||||
{jss::ticket_seq, FieldType::UInt32Field},
|
||||
};
|
||||
@@ -107,7 +110,7 @@ getFieldType(json::StaticString fieldName)
|
||||
return it->second;
|
||||
}
|
||||
|
||||
Throw<std::runtime_error>("`mappings` is missing field " + std::string(fieldName.cStr()));
|
||||
Throw<std::runtime_error>("`gMappings` is missing field " + std::string(fieldName.cStr()));
|
||||
}
|
||||
|
||||
std::string
|
||||
@@ -1884,6 +1887,59 @@ class LedgerEntry_test : public beast::unit_test::Suite
|
||||
runLedgerEntryTest(env, jss::signer_list);
|
||||
}
|
||||
|
||||
void
|
||||
testSponsorship()
|
||||
{
|
||||
testcase("Sponsorship");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
env(sponsor::set(alice, 0, 100), sponsor::SponseeAcc(bob));
|
||||
env.close();
|
||||
std::string const ledgerHash{to_string(env.closed()->header().hash)};
|
||||
auto const sponsorshipIndex = to_string(keylet::sponsorship(alice.id(), bob.id()).key);
|
||||
|
||||
{
|
||||
// Request by sponsor and sponsee.
|
||||
json::Value jvParams;
|
||||
jvParams[jss::sponsorship][jss::sponsor] = alice.human();
|
||||
jvParams[jss::sponsorship][jss::sponsee] = bob.human();
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Sponsorship);
|
||||
BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human());
|
||||
BEAST_EXPECT(jrr[jss::node][sfSponsee.jsonName] == bob.human());
|
||||
BEAST_EXPECT(sponsorshipIndex == jrr[jss::node][jss::index].asString());
|
||||
}
|
||||
{
|
||||
// Request by index.
|
||||
json::Value jvParams;
|
||||
jvParams[jss::sponsorship] = sponsorshipIndex;
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
json::Value const jrr =
|
||||
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Sponsorship);
|
||||
BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human());
|
||||
BEAST_EXPECT(jrr[jss::node][sfSponsee.jsonName] == bob.human());
|
||||
BEAST_EXPECT(sponsorshipIndex == jrr[jss::node][jss::index].asString());
|
||||
}
|
||||
{
|
||||
// Check all malformed cases.
|
||||
runLedgerEntryTest(
|
||||
env,
|
||||
jss::sponsorship,
|
||||
{
|
||||
{.fieldName = jss::sponsor, .malformedErrorMsg = "malformedSponsor"},
|
||||
{.fieldName = jss::sponsee, .malformedErrorMsg = "malformedSponsee"},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testTicket()
|
||||
{
|
||||
@@ -2676,6 +2732,7 @@ public:
|
||||
testPayChan();
|
||||
testRippleState();
|
||||
testSignerList();
|
||||
testSponsorship();
|
||||
testTicket();
|
||||
testDID();
|
||||
testInvalidOracleLedgerEntry();
|
||||
|
||||
@@ -364,6 +364,37 @@ 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 SponsorSignature.Signers field
|
||||
json::Value params;
|
||||
json::Value txJson = json::ValueType::Object;
|
||||
txJson[jss::TransactionType] = jss::AccountSet;
|
||||
txJson[jss::Account] = env.master.human();
|
||||
json::Value sponsorSignature = json::ValueType::Object;
|
||||
sponsorSignature[sfSigners] = "1";
|
||||
txJson[sfSponsorSignature] = sponsorSignature;
|
||||
params[jss::tx_json] = txJson;
|
||||
|
||||
auto const resp = env.rpc("json", "simulate", to_string(params));
|
||||
BEAST_EXPECTS(
|
||||
resp[jss::result][jss::error_message] ==
|
||||
"Invalid field 'tx.SponsorSignature.Signers'.",
|
||||
resp.toStyledString());
|
||||
}
|
||||
{
|
||||
// Invalid transaction
|
||||
json::Value params;
|
||||
@@ -561,6 +592,75 @@ class Simulate_test : public beast::unit_test::Suite
|
||||
// test without autofill
|
||||
testTx(env, tx, validateOutput);
|
||||
}
|
||||
|
||||
{
|
||||
// autofill sponsor signature
|
||||
|
||||
auto validateOutput = [&](json::Value const& resp, json::Value const& tx) {
|
||||
auto result = resp[jss::result];
|
||||
checkBasicReturnValidity(
|
||||
result, tx, env.seq(env.master), env.current()->fees().base);
|
||||
|
||||
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);
|
||||
|
||||
if (BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName)))
|
||||
{
|
||||
BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].size() == 2);
|
||||
|
||||
auto node = metadata[sfAffectedNodes.jsonName][0u];
|
||||
if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName)))
|
||||
{
|
||||
auto modifiedNode = node[sfModifiedNode];
|
||||
BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot");
|
||||
auto previousFields = modifiedNode[sfPreviousFields];
|
||||
BEAST_EXPECT(!previousFields.isMember(sfBalance.jsonName));
|
||||
}
|
||||
|
||||
auto node2 = metadata[sfAffectedNodes.jsonName][1u];
|
||||
if (BEAST_EXPECT(node2.isMember(sfModifiedNode.jsonName)))
|
||||
{
|
||||
auto modifiedNode = node2[sfModifiedNode];
|
||||
BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot");
|
||||
|
||||
auto previousFields = modifiedNode[sfPreviousFields];
|
||||
BEAST_EXPECT(previousFields.isMember(sfBalance.jsonName));
|
||||
}
|
||||
}
|
||||
BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
|
||||
BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
|
||||
}
|
||||
};
|
||||
|
||||
Account const sponsor("sponsor");
|
||||
env.fund(XRP(10000), sponsor);
|
||||
env.close();
|
||||
|
||||
json::Value tx;
|
||||
|
||||
tx[jss::Account] = env.master.human();
|
||||
tx[jss::TransactionType] = jss::AccountSet;
|
||||
tx[sfDomain.jsonName] = kNewDomain;
|
||||
tx[sfSponsor.jsonName] = sponsor.human();
|
||||
tx[sfSponsorFlags.jsonName] = spfSponsorFee;
|
||||
tx[sfSponsorSignature.jsonName] = json::ValueType::Object;
|
||||
|
||||
// test with autofill
|
||||
testTx(env, tx, validateOutput);
|
||||
|
||||
tx[sfSponsorSignature.jsonName][sfTxnSignature.jsonName] = "";
|
||||
tx[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = "";
|
||||
|
||||
// test without autofill
|
||||
testTx(env, tx, validateOutput);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -763,6 +863,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;
|
||||
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;
|
||||
|
||||
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()
|
||||
{
|
||||
@@ -1195,6 +1347,7 @@ public:
|
||||
testTransactionNonTecFailure();
|
||||
testTransactionTecFailure();
|
||||
testSuccessfulTransactionMultisigned();
|
||||
testSuccessfulSponsoredTransactionMultisigned();
|
||||
testTransactionSigningFailure();
|
||||
testInvalidSingleAndMultiSigningTransaction();
|
||||
testMultisignedBadPubKey();
|
||||
|
||||
Reference in New Issue
Block a user