Files
rippled/src/test/app/Sponsor_test.cpp
2026-01-30 17:20:15 +09:00

4329 lines
163 KiB
C++

#include <test/jtx.h>
#include <test/jtx/Env.h>
#include <test/jtx/multisign.h>
#include <test/jtx/ticket.h>
#include <test/jtx/xchain_bridge.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/Feature.h>
#include "test/jtx/envconfig.h"
#include "test/jtx/sponsor.h"
namespace xrpl {
namespace test {
static STAmount
accountReserve(jtx::Env& env, std::uint32_t count = 1)
{
return env.current()->fees().reserve * count;
}
static STAmount
reserve(jtx::Env& env, std::uint32_t count)
{
return env.current()->fees().accountReserve(count);
}
static void
adjustAccountXRPBalance(jtx::Env& env, jtx::Account const& account, STAmount const& balanceTo)
{
using namespace test::jtx;
XRPL_ASSERT(isXRP(balanceTo), "adjustAccountXRPBalance: balanceTo must be XRP");
auto const currentBalance = env.balance(account);
if (currentBalance == balanceTo)
return;
auto const baseFee = env.current()->fees().base;
if (currentBalance > balanceTo)
env(pay(account, env.master, currentBalance - (balanceTo)),
fee(XRP(1)),
sponsor::as(env.master, tfSponsorFee),
sig(sfSponsorSignature, env.master));
else
env(pay(env.master, account, balanceTo - currentBalance), fee(baseFee));
env.close();
}
class Sponsor_test : public beast::unit_test::suite
{
public:
void
testDisabled()
{
testcase("Disabled");
using namespace test::jtx;
Env env{*this, testable_amendments() - featureSponsor};
Account const alice("alice");
Account const sponsor("sponsor");
env.fund(XRP(10000), alice, sponsor);
// check Sponsor fields
auto const jt = noop(alice);
auto jt1 = jt;
jt1[sfSponsor.jsonName] = sponsor.human();
env(jt1, ter(temDISABLED));
env(jt, sig(sfSponsorSignature, sponsor), ter(temDISABLED));
auto jt2 = jt;
jt2[sfSponsorFlags.jsonName] = tfSponsorFee | tfSponsorReserve;
env(jt2, ter(temDISABLED));
// check Sponsor transactions
env(sponsor::transfer(alice), ter(temDISABLED));
env(sponsor::set(sponsor, 0), ter(temDISABLED));
}
void
testInvalidSponsorshipSet()
{
testcase("Invalid SponsorshipSet");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const noFunded("noFunded");
Account const gw("gw");
auto const USD = gw["USD"];
env.fund(XRP(10000), alice, sponsor, gw);
env.close();
//
// preflight
//
// Invalid flags
{
env(sponsor::set(sponsor, ~tfSponsorshipSetMask - tfInnerBatchTxn),
sponsor::sponseeAcc(alice),
ter(temINVALID_FLAG));
env(sponsor::set(sponsor, tfSponsorshipSetRequireSignForFee | tfSponsorshipClearRequireSignForFee),
sponsor::sponseeAcc(alice),
ter(temINVALID_FLAG));
env(sponsor::set(sponsor, tfSponsorshipSetRequireSignForReserve | tfSponsorshipClearRequireSignForReserve),
sponsor::sponseeAcc(alice),
ter(temINVALID_FLAG));
for (auto flag :
{tfSponsorshipSetRequireSignForFee,
tfSponsorshipClearRequireSignForFee,
tfSponsorshipSetRequireSignForReserve,
tfSponsorshipClearRequireSignForReserve})
{
env(sponsor::set(sponsor, tfDeleteObject | flag), sponsor::sponseeAcc(alice), ter(temINVALID_FLAG));
}
}
// invalid SponsorAccount / Sponsee
// Account = Sponsor
env(sponsor::set(alice, tfDeleteObject), sponsor::sponsorAcc(alice), ter(temMALFORMED));
// Account = Sponsee
env(sponsor::set(alice, tfDeleteObject), sponsor::sponseeAcc(alice), ter(temMALFORMED));
// Both Sponsor and Sponsee are specified
env(sponsor::set(alice, 0), sponsor::sponsorAcc(sponsor), sponsor::sponseeAcc(alice), ter(temMALFORMED));
// Invalid feeAmount
for (auto amt : {XRP(-1), XRP(0), USD(1)})
{
env(sponsor::set_fee(sponsor, 0, amt), sponsor::sponseeAcc(alice), ter(temBAD_AMOUNT));
}
// Invalid MaxFee
for (auto amt : {XRP(-1), XRP(0), USD(1)})
{
env(sponsor::set_fee(sponsor, 0, XRP(1), amt), sponsor::sponseeAcc(alice), ter(temBAD_AMOUNT));
}
// Invalid reserveCount
env(sponsor::set_reserve(sponsor, 0, 0), sponsor::sponseeAcc(alice), ter(temMALFORMED));
// Invalid Delete operation
env(sponsor::set_reserve(sponsor, tfDeleteObject, 1), sponsor::sponseeAcc(alice), ter(temMALFORMED));
env(sponsor::set_fee(sponsor, tfDeleteObject, XRP(1)), sponsor::sponseeAcc(alice), ter(temMALFORMED));
// TODO: test MaxFee with tfDeleteObject
// Invalid SponsorAccount with non-Delete operation
env(sponsor::set_reserve(sponsor, 0, 100), sponsor::sponsorAcc(alice), ter(temMALFORMED));
env(sponsor::set_fee(sponsor, 0, XRP(1), XRP(1)), sponsor::sponsorAcc(alice), ter(temMALFORMED));
//
// preclaim
//
// Invalid Sponsee
env(sponsor::set(sponsor, 0), sponsor::sponseeAcc(noFunded), ter(tecNO_DST));
// Invalid Sponsor
env(sponsor::set(sponsor, tfDeleteObject), sponsor::sponsorAcc(noFunded), ter(tecNO_DST));
// Invalid Delete operation (sponsorship not found)
env(sponsor::set(sponsor, tfDeleteObject), sponsor::sponseeAcc(alice), ter(tecNO_ENTRY));
// DisallowIncomingSponsor: tested in other testcase
// insufficent reserve to create sponsorship
adjustAccountXRPBalance(env, sponsor, reserve(env, 1) - drops(1));
env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::sponseeAcc(alice), ter(tecUNFUNDED));
// create sponsor to use above tests
adjustAccountXRPBalance(env, sponsor, reserve(env, 1));
env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
}
void
testSingleSigning()
{
testcase("Single signing");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
Account const invalid("invalid");
env.fund(XRP(10000), alice, sponsor);
env.close();
// Signature doesn't exist
auto tx = noop(alice);
tx[sfSponsor.jsonName] = sponsor.human();
tx[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = strHex(sponsor.pk().slice());
env(tx, fee(XRP(1)), sponsor::as(sponsor, tfSponsorReserve), ter(telENV_RPC_FAILED));
// Invalid signature
tx[sfSponsorSignature.jsonName][sfTxnSignature.jsonName] = "DEADBEEF";
env(tx, fee(XRP(1)), sponsor::as(sponsor, tfSponsorReserve), ter(telENV_RPC_FAILED));
// Signer account doesn't exist
env(noop(alice),
fee(XRP(1)),
sponsor::as(invalid, tfSponsorReserve),
sig(sfSponsorSignature, invalid),
ter(terNO_ACCOUNT));
// Success
env(noop(alice),
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor),
ter(tesSUCCESS));
}
void
testMultiSigning()
{
testcase("Multi signing");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
Account const invalid("invalid");
Account const signer1("signer1");
Account const signer2("signer2");
env.fund(XRP(10000), alice, sponsor);
env.close();
env(signers(sponsor, 1, {{signer1, 1}, {signer2, 1}}));
env.close();
// Invalid signature
auto tx = noop(alice);
auto& signers1 = tx[sfSponsorSignature.jsonName][sfSigners.jsonName][0U][sfSigner.jsonName];
signers1[sfAccount.jsonName] = signer1.human();
signers1[sfSigningPubKey.jsonName] = strHex(signer1.pk().slice());
signers1[sfTxnSignature.jsonName] = "DEADBEEF";
env(tx, fee(XRP(1)), sponsor::as(sponsor, tfSponsorReserve), ter(telENV_RPC_FAILED));
// Signer account doesn't exist
env(noop(alice),
fee(XRP(1)),
sponsor::as(invalid, tfSponsorReserve),
msig(sfSponsorSignature, {signer1}),
ter(tefNOT_MULTI_SIGNING));
env(noop(alice),
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
msig(sfSponsorSignature, {signer1}),
ter(tesSUCCESS));
env.close();
env(signers(sponsor, 2, {{signer1, 1}, {signer2, 1}}));
env.close();
// test calculateBaseFee for multisigned sponsor
auto const baseFee = env.current()->fees().base;
env(noop(alice),
fee(baseFee + 2 * baseFee - 1),
sponsor::as(sponsor, tfSponsorReserve),
msig(sfSponsorSignature, {signer1, signer2}),
ter(telINSUF_FEE_P));
env(noop(alice),
fee(baseFee + 2 * baseFee),
sponsor::as(sponsor, tfSponsorReserve),
msig(sfSponsorSignature, {signer1, signer2}),
ter(tesSUCCESS));
}
void
testInvalidSponsorField()
{
testcase("Invalid Sponsor Field");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
Account const noFunded("noFunded");
env.fund(XRP(10000), alice, sponsor);
env.close();
// Invalid Sponsor Account (Account = Sponsor.Account)
env(noop(alice), sponsor::as(alice), ter(temMALFORMED));
// Invalid Sponsor Account
// (SponsorSignature is specified but Sponsor.Account is not specified)
env(noop(alice), sig(sfSponsorSignature, sponsor), ter(temMALFORMED));
// Invalid Sponsor Account (Sponsor.Account doesn't exist)
env(noop(alice), sponsor::as(noFunded, tfSponsorReserve), ter(terNO_SPONSORSHIP));
env(noop(alice),
sponsor::as(noFunded, tfSponsorReserve),
sig(sfSponsorSignature, noFunded),
ter(terNO_ACCOUNT));
// Invalid Flags
env(noop(alice), sponsor::as(sponsor, (tfSponsorFee | tfSponsorReserve) + 1), ter(temINVALID_FLAG));
}
void
testSimpleSponsorshipSet()
{
testcase("Simple SponsorshipSet");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
env.fund(XRP(10000), alice, sponsor);
env.close();
{
env(sponsor::set(
sponsor,
tfSponsorshipSetRequireSignForFee | tfSponsorshipSetRequireSignForReserve,
100,
XRP(100),
XRP(1)),
fee(XRP(1)),
sponsor::sponseeAcc(alice),
ter(tesSUCCESS));
env.close();
auto sle = env.le(keylet::sponsor(sponsor, alice));
BEAST_EXPECT(sle);
BEAST_EXPECT(sle->at(sfReserveCount) == 100);
BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100));
BEAST_EXPECT(sle->at(sfMaxFee) == XRP(1));
BEAST_EXPECT(sle->isFlag(lsfSponsorshipRequireSignForFee));
BEAST_EXPECT(sle->isFlag(lsfSponsorshipRequireSignForReserve));
BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - sle->at(sfFeeAmount) - XRP(1));
// update sponsorship (decrement)
env(sponsor::set(sponsor, 0, 50, XRP(50), XRP(0.5)),
sponsor::sponseeAcc(alice),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
sle = env.le(keylet::sponsor(sponsor, alice));
BEAST_EXPECT(sle);
BEAST_EXPECT(sle->at(sfReserveCount) == 50);
BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(50));
BEAST_EXPECT(sle->at(sfMaxFee) == XRP(0.5));
BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - sle->at(sfFeeAmount) - XRP(2));
// update sponsorship (increment)
env(sponsor::set(sponsor, 0, 200, XRP(200), XRP(2)),
sponsor::sponseeAcc(alice),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
sle = env.le(keylet::sponsor(sponsor, alice));
BEAST_EXPECT(sle);
BEAST_EXPECT(sle->at(sfReserveCount) == 200);
BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(200));
BEAST_EXPECT(sle->at(sfMaxFee) == XRP(2));
BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - sle->at(sfFeeAmount) - XRP(3));
// delete from sponsor
env(sponsor::del(sponsor), sponsor::sponseeAcc(alice), fee(XRP(1)), ter(tesSUCCESS));
env.close();
BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - XRP(4));
env(sponsor::set(
sponsor,
tfSponsorshipSetRequireSignForFee | tfSponsorshipSetRequireSignForReserve,
100,
XRP(100),
XRP(1)),
sponsor::sponseeAcc(alice),
ter(tesSUCCESS));
env.close();
// delete from sponsee
env(sponsor::del(alice), sponsor::sponsorAcc(sponsor), ter(tesSUCCESS));
env.close();
}
{
// Update Sponsorship (FeeAmount)
// set empty FeeAmount
env(sponsor::set_reserve(sponsor, 0, 100), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
// add FeeAmount
env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
env(sponsor::del(alice), sponsor::sponsorAcc(sponsor), ter(tesSUCCESS));
env.close();
}
{
// Update Sponsorship (ReserveCount)
// set empty ReserveCount
env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
// add ReserveCount
env(sponsor::set_reserve(sponsor, 0, 100), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
env(sponsor::del(alice), sponsor::sponsorAcc(sponsor), ter(tesSUCCESS));
env.close();
}
{
// delete Sponsorship (only with FeeAmount)
env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
env(sponsor::del(alice), sponsor::sponsorAcc(sponsor), ter(tesSUCCESS));
env.close();
}
{
// delete Sponsorship (only with ReserveCount)
env(sponsor::set_reserve(sponsor, 0, 100), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
env(sponsor::del(alice), sponsor::sponsorAcc(sponsor), ter(tesSUCCESS));
env.close();
}
}
void
testPreFundAndCosign()
{
testcase("PreFund and Cosign");
using namespace test::jtx;
Account const alice("alice");
Account const sponsor("sponsor");
{
// both pre-funded and co-signed,pre-funded value is used
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, sponsor);
env.close();
env(sponsor::set(sponsor, 0, 100, XRP(100), XRP(1)), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
env(did::set(alice),
did::uri("uri"),
sponsor::as(sponsor, tfSponsorReserve | tfSponsorFee),
sig(sfSponsorSignature, sponsor),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
auto sle = env.le(keylet::sponsor(sponsor, alice));
BEAST_EXPECT(sle);
BEAST_EXPECT(sle->at(sfReserveCount) == 99);
BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(99));
env(did::del(alice), ter(tesSUCCESS));
env.close();
sle = env.le(keylet::sponsor(sponsor, alice));
BEAST_EXPECT(sle);
BEAST_EXPECT(sle->at(sfReserveCount) == 100); // paybacked
BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(99));
}
{
// if pre-funded value is not enough, error
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, sponsor);
env.close();
env(sponsor::set(sponsor, 0, 10, XRP(10), XRP(100)), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
// fee insufficient
env(ticket::create(alice, 1),
sponsor::as(sponsor, tfSponsorReserve | tfSponsorFee),
sig(sfSponsorSignature, sponsor),
fee(XRP(11)),
ter(terINSUF_FEE_B));
env.close();
// reserve insufficient
env(ticket::create(alice, 11),
sponsor::as(sponsor, tfSponsorReserve | tfSponsorFee),
sig(sfSponsorSignature, sponsor),
fee(XRP(1)),
ter(tecINSUFFICIENT_RESERVE));
env.close();
}
}
void
testTransferSponsor()
{
testcase("Transfer Sponsor");
using namespace test::jtx;
{
// sponsor account
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor1("sponsor1");
Account const sponsor2("sponsor2");
env.fund(XRP(10000), alice, bob, sponsor1, sponsor2);
// sfSponsor provided but sfSponsorSignature not provided
env(sponsor::transfer(alice), sponsor::as(sponsor1, tfSponsorReserve), ter(temMALFORMED));
env.close();
adjustAccountXRPBalance(env, sponsor1, accountReserve(env, 2) - drops(1));
env(sponsor::transfer(alice),
sponsor::as(sponsor1, tfSponsorReserve),
sig(sfSponsorSignature, sponsor1),
ter(tecINSUFFICIENT_RESERVE));
env.close();
adjustAccountXRPBalance(env, sponsor1, accountReserve(env, 2));
env(sponsor::transfer(alice), sponsor::as(sponsor1, tfSponsorReserve), sig(sfSponsorSignature, sponsor1));
env.close();
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 1);
auto const sle1 = env.le(keylet::account(alice));
BEAST_EXPECT(sle1->isFieldPresent(sfSponsorAccount));
BEAST_EXPECT(sle1->getAccountID(sfSponsorAccount) == sponsor1.id());
// transfer sponsor
adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 2) - drops(1));
env(sponsor::transfer(alice),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2),
ter(tecINSUFFICIENT_RESERVE));
env.close();
adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 2));
env(sponsor::transfer(alice), sponsor::as(sponsor2, tfSponsorReserve), sig(sfSponsorSignature, sponsor2));
env.close();
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1);
BEAST_EXPECT(!env.le(keylet::account(sponsor1))->isFieldPresent(sfSponsoringAccountCount));
auto const sle2 = env.le(keylet::account(alice));
BEAST_EXPECT(sle2->isFieldPresent(sfSponsorAccount));
BEAST_EXPECT(sle2->getAccountID(sfSponsorAccount) == sponsor2.id());
// sponsor 2 accounts
adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 3));
env(sponsor::transfer(bob), sponsor::as(sponsor2, tfSponsorReserve), sig(sfSponsorSignature, sponsor2));
env.close();
// dissolve sponsors
adjustAccountXRPBalance(env, alice, accountReserve(env, 1) - drops(1));
env(sponsor::transfer(alice), ter(tecINSUFFICIENT_RESERVE));
env.close();
adjustAccountXRPBalance(env, alice, accountReserve(env, 1));
env(sponsor::transfer(alice));
env.close();
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1);
auto const sle3 = env.le(keylet::account(alice));
BEAST_EXPECT(!sle3->isFieldPresent(sfSponsorAccount));
env(sponsor::transfer(bob));
env.close();
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, bob) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
BEAST_EXPECT(!env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringAccountCount));
auto const sle4 = env.le(keylet::account(bob));
BEAST_EXPECT(!sle4->isFieldPresent(sfSponsorAccount));
// not sponsored
env(sponsor::transfer(bob), ter(tecNO_PERMISSION));
env.close();
}
{
// sponsor object (co-signing)
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor1("sponsor1");
Account const sponsor2("sponsor2");
env.fund(XRP(10000), alice, bob, sponsor1, sponsor2);
env.close();
adjustAccountXRPBalance(env, sponsor1, reserve(env, 1) - drops(1));
adjustAccountXRPBalance(env, sponsor2, reserve(env, 1) - drops(1));
auto const seq = env.seq(alice);
env(check::create(alice, bob, XRP(1)));
env.close();
auto const checkId = keylet::check(alice, seq).key;
BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
env(sponsor::transfer(alice, checkId),
sponsor::as(sponsor1, tfSponsorReserve),
sig(sfSponsorSignature, sponsor1),
ter(tecINSUFFICIENT_RESERVE));
env.close();
env(pay(alice, sponsor1, drops(1)));
env.close();
// Invalid ObjectID (not found)
env(sponsor::transfer(alice, keylet::check(alice, 0).key),
sponsor::as(sponsor1, tfSponsorReserve),
sig(sfSponsorSignature, sponsor1),
ter(tecNO_ENTRY));
env.close();
// Invalid Owner
env(sponsor::transfer(bob, checkId),
sponsor::as(sponsor1, tfSponsorReserve),
sig(sfSponsorSignature, sponsor1),
ter(tecNO_PERMISSION));
env.close();
// Valid Owner
env(sponsor::transfer(alice, checkId),
sponsor::as(sponsor1, tfSponsorReserve),
sig(sfSponsorSignature, sponsor1));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
auto const sle1 = env.le(keylet::unchecked(checkId));
BEAST_EXPECT(sle1->isFieldPresent(sfSponsorAccount));
BEAST_EXPECT(sle1->getAccountID(sfSponsorAccount) == sponsor1.id());
// transfer sponsor
env(sponsor::transfer(alice, checkId),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2),
ter(tecINSUFFICIENT_RESERVE));
env(pay(alice, sponsor2, drops(1)));
env.close();
env(sponsor::transfer(alice, checkId),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
auto const sle2 = env.le(keylet::unchecked(checkId));
BEAST_EXPECT(sle2->isFieldPresent(sfSponsorAccount));
BEAST_EXPECT(sle2->getAccountID(sfSponsorAccount) == sponsor2.id());
// dissolve sponsor
adjustAccountXRPBalance(env, alice, reserve(env, 1) - drops(1));
env(sponsor::transfer(alice, checkId), ter(tecINSUFFICIENT_RESERVE));
env.close();
adjustAccountXRPBalance(env, alice, reserve(env, 1));
// object doesn't sponsored
auto const ticketSeq = env.seq(alice);
env(ticket::create(alice, 1));
env.close();
auto ticketId = keylet::ticket(alice, ticketSeq + 1).key;
BEAST_EXPECT(env.le(keylet::unchecked(ticketId)));
env(sponsor::transfer(alice, ticketId), ter(tecNO_PERMISSION));
env.close();
env(noop(alice), ticket::use(ticketSeq + 1));
env.close();
adjustAccountXRPBalance(env, alice, reserve(env, 1));
env(sponsor::transfer(alice, checkId));
env.close();
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
BEAST_EXPECT(!env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringOwnerCount));
auto const sle3 = env.le(keylet::unchecked(checkId));
BEAST_EXPECT(!sle3->isFieldPresent(sfSponsorAccount));
}
{
// sponsor object (pre-funded + no ltSponsorship entry)
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor1("sponsor1");
Account const sponsor2("sponsor2");
env.fund(XRP(10000), alice, bob, sponsor1, sponsor2);
env.close();
auto const seq = env.seq(alice);
env(check::create(alice, bob, XRP(1)));
env.close();
auto const checkId = keylet::check(alice, seq).key;
BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
env(sponsor::transfer(alice, checkId), sponsor::as(sponsor1, tfSponsorReserve), ter(terNO_SPONSORSHIP));
env.close();
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, checkId), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
env(sponsor::transfer(alice, checkId), sponsor::as(sponsor1, tfSponsorReserve), ter(terNO_SPONSORSHIP));
env.close();
}
{
// sponsor object (pre-funded)
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor1("sponsor1");
Account const sponsor2("sponsor2");
env.fund(XRP(10000), alice, bob, sponsor1, sponsor2);
env.close();
auto const seq = env.seq(alice);
env(check::create(alice, bob, XRP(1)));
env.close();
auto const checkId = keylet::check(alice, seq).key;
BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
// insufficient reserve count
env(sponsor::set_fee(sponsor1, 0, XRP(100)), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, checkId),
sponsor::as(sponsor1, tfSponsorReserve),
ter(tecINSUFFICIENT_RESERVE));
env.close();
env(sponsor::set_reserve(sponsor1, 0, 100), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, checkId), sponsor::as(sponsor1, tfSponsorReserve));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
auto checkSle = env.le(keylet::unchecked(checkId));
BEAST_EXPECT(checkSle->isFieldPresent(sfSponsorAccount));
BEAST_EXPECT(checkSle->getAccountID(sfSponsorAccount) == sponsor1.id());
auto sponsor1Sle = env.le(keylet::sponsor(sponsor1, alice));
BEAST_EXPECT(sponsor1Sle->getFieldU32(sfReserveCount) == 99);
// transfer sponsor
env(sponsor::set_reserve(sponsor2, 0, 100), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, checkId), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
checkSle = env.le(keylet::unchecked(checkId));
BEAST_EXPECT(checkSle->isFieldPresent(sfSponsorAccount));
BEAST_EXPECT(checkSle->getAccountID(sfSponsorAccount) == sponsor2.id());
sponsor1Sle = env.le(keylet::sponsor(sponsor1, alice));
BEAST_EXPECT(sponsor1Sle->getFieldU32(sfReserveCount) == 100); // paybacked
auto sponsor2Sle = env.le(keylet::sponsor(sponsor2, alice));
BEAST_EXPECT(sponsor2Sle->getFieldU32(sfReserveCount) == 99);
// dissolve sponsor
adjustAccountXRPBalance(env, alice, reserve(env, 1));
env(sponsor::transfer(alice, checkId));
env.close();
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
BEAST_EXPECT(!env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringOwnerCount));
checkSle = env.le(keylet::unchecked(checkId));
BEAST_EXPECT(!checkSle->isFieldPresent(sfSponsorAccount));
sponsor2Sle = env.le(keylet::sponsor(sponsor2, alice));
BEAST_EXPECT(sponsor2Sle->getFieldU32(sfReserveCount) == 100); // paybacked
}
{
// invalid transfer
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
env.fund(XRP(10000), alice, bob, sponsor);
env.close();
// create owner dir
env(ticket::create(alice, 1));
env.close();
// AccountRoot
// Amendments
// LedgerHashes
// FeeSettings
// NegativeUNL
// DirNode
auto const keylets = {
keylet::account(alice),
// keylet::amendments(),
keylet::skip(),
keylet::fees(),
// keylet::negativeUNL(),
keylet::ownerDir(alice),
};
for (auto const& keylet : keylets)
{
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor),
ter(tecNO_PERMISSION));
}
}
}
void
testSponsorFee()
{
using namespace test::jtx;
testcase("Sponsor Fee");
{
// co-signing
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
env.fund(XRP(10000), alice, bob);
env.close();
{
// Fee should be checked before permission check,
// otherwise tecNO_SPONSOR_PERMISSION returned when permission
// check fails could cause context reset to pay fee because it
// is tec error
auto aliceBalance = env.balance(alice);
auto bobBalance = env.balance(bob);
auto sponsorBalance = env.balance(sponsor);
env(pay(alice, bob, XRP(100)),
fee(XRP(2000)),
sponsor::as(sponsor, tfSponsorFee),
sig(sfSponsorSignature, sponsor),
ter(terNO_ACCOUNT));
env.close();
BEAST_EXPECT(env.balance(alice) == aliceBalance);
BEAST_EXPECT(env.balance(bob) == bobBalance);
BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
}
env.fund(XRP(1000), sponsor);
env.close();
{
// Sponsor pays the fee
auto aliceBalance = env.balance(alice);
auto bobBalance = env.balance(bob);
auto sponsorBalance = env.balance(sponsor);
auto const sendAmt = XRP(100);
auto const feeAmt = XRP(10);
env(pay(alice, bob, sendAmt),
fee(feeAmt),
sponsor::as(sponsor, tfSponsorFee),
sig(sfSponsorSignature, sponsor));
env.close();
BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt);
BEAST_EXPECT(env.balance(bob) == bobBalance + sendAmt);
BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt);
}
{
// insufficient balance to pay fee
auto aliceBalance = env.balance(alice);
auto bobBalance = env.balance(bob);
auto sponsorBalance = env.balance(sponsor);
env(pay(alice, bob, XRP(100)),
fee(XRP(2000)),
sponsor::as(sponsor, tfSponsorFee),
sig(sfSponsorSignature, sponsor),
ter(terINSUF_FEE_B));
env.close();
BEAST_EXPECT(env.balance(alice) == aliceBalance);
BEAST_EXPECT(env.balance(bob) == bobBalance);
BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
}
{
// fee is paid by Sponsor
// on context reset (tec error)
auto aliceBalance = env.balance(alice);
auto bobBalance = env.balance(bob);
auto sponsorBalance = env.balance(sponsor);
auto const feeAmt = XRP(10);
env(pay(alice, bob, XRP(20000)),
fee(feeAmt),
sponsor::as(sponsor, tfSponsorFee),
sig(sfSponsorSignature, sponsor),
ter(tecUNFUNDED_PAYMENT));
env.close();
BEAST_EXPECT(env.balance(alice) == aliceBalance);
BEAST_EXPECT(env.balance(bob) == bobBalance);
BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt);
}
}
{
// pre funded
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
env.fund(XRP(10000), alice, bob, sponsor);
env.close();
auto const sponsorFeeBalance = [&](Account const& sponsor, Account const& alice) {
return env.le(keylet::sponsor(sponsor, alice))->getFieldAmount(sfFeeAmount).xrp();
};
{
// Fee should be checked before permission check,
// otherwise tecNO_SPONSOR_PERMISSION returned when permission
// check fails could cause context reset to pay fee because it
// is tec error
auto aliceBalance = env.balance(alice);
auto bobBalance = env.balance(bob);
auto sponsorBalance = env.balance(sponsor);
env(pay(alice, bob, XRP(100)),
fee(XRP(2000)),
sponsor::as(sponsor, tfSponsorFee),
ter(terNO_SPONSORSHIP));
env.close();
BEAST_EXPECT(env.balance(alice) == aliceBalance);
BEAST_EXPECT(env.balance(bob) == bobBalance);
BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
}
env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::sponseeAcc(alice));
env.close();
{
// Sponsor pays the fee
auto aliceBalance = env.balance(alice);
auto bobBalance = env.balance(bob);
auto sponsorBalance = env.balance(sponsor);
auto sponsorFee = sponsorFeeBalance(sponsor, alice);
auto const sendAmt = XRP(100);
auto const feeAmt = XRP(10);
env(pay(alice, bob, sendAmt), fee(feeAmt), sponsor::as(sponsor, tfSponsorFee));
env.close();
BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt);
BEAST_EXPECT(env.balance(bob) == bobBalance + sendAmt);
BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
BEAST_EXPECT(sponsorFeeBalance(sponsor, alice) == sponsorFee - feeAmt);
}
{
// insufficient balance to pay fee
{
// > FeeAmount
auto aliceBalance = env.balance(alice);
auto bobBalance = env.balance(bob);
auto sponsorBalance = env.balance(sponsor);
auto sponsorFee = sponsorFeeBalance(sponsor, alice);
env(pay(alice, bob, XRP(100)),
fee(XRP(90) + drops(1)),
sponsor::as(sponsor, tfSponsorFee),
ter(terINSUF_FEE_B));
env.close();
BEAST_EXPECT(env.balance(alice) == aliceBalance);
BEAST_EXPECT(env.balance(bob) == bobBalance);
BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
BEAST_EXPECT(sponsorFeeBalance(sponsor, alice) == sponsorFee);
}
// use all FeeAmount
{
// = FeeAmount
auto aliceBalance = env.balance(alice);
auto bobBalance = env.balance(bob);
auto sponsorBalance = env.balance(sponsor);
env(pay(alice, bob, XRP(100)), fee(XRP(90)), sponsor::as(sponsor, tfSponsorFee), ter(tesSUCCESS));
env.close();
BEAST_EXPECT(env.balance(alice) == aliceBalance - XRP(100));
BEAST_EXPECT(env.balance(bob) == bobBalance + XRP(100));
BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
BEAST_EXPECT(!env.le(keylet::sponsor(sponsor, alice))->isFieldPresent(sfFeeAmount));
}
// reset FeeAmount and MaxFee
env(sponsor::del(sponsor), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::set_fee(sponsor, 0, XRP(10), XRP(1)), sponsor::sponseeAcc(alice));
env.close();
{
// > MaxFee
auto aliceBalance = env.balance(alice);
auto bobBalance = env.balance(bob);
auto sponsorBalance = env.balance(sponsor);
auto sponsorFee = sponsorFeeBalance(sponsor, alice);
env(pay(alice, bob, XRP(100)),
fee(XRP(1) + drops(1)),
sponsor::as(sponsor, tfSponsorFee),
ter(terINSUF_FEE_B));
env.close();
BEAST_EXPECT(env.balance(alice) == aliceBalance);
BEAST_EXPECT(env.balance(bob) == bobBalance);
BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
BEAST_EXPECT(sponsorFeeBalance(sponsor, alice) == sponsorFee);
}
}
{
// fee is paid by Sponsor
// on context reset (tec error)
auto aliceBalance = env.balance(alice);
auto bobBalance = env.balance(bob);
auto sponsorBalance = env.balance(sponsor);
auto sponsorFee = sponsorFeeBalance(sponsor, alice);
auto const feeAmt = XRP(1);
env(pay(alice, bob, XRP(20000)),
fee(feeAmt),
sponsor::as(sponsor, tfSponsorFee),
ter(tecUNFUNDED_PAYMENT));
env.close();
BEAST_EXPECT(env.balance(alice) == aliceBalance);
BEAST_EXPECT(env.balance(bob) == bobBalance);
BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
BEAST_EXPECT(sponsorFeeBalance(sponsor, alice) == sponsorFee - feeAmt);
}
}
// test lsfSponsorshipRequireSignForFee
{
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
env.fund(XRP(10000), alice, bob, sponsor);
env.close();
// set flag
env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)), sponsor::sponseeAcc(alice));
env.close();
env(pay(alice, bob, XRP(100)), fee(XRP(10)), sponsor::as(sponsor, tfSponsorFee), ter(terNO_SPONSORSHIP));
env.close();
BEAST_EXPECT(env.le(keylet::sponsor(sponsor, alice))->getFieldAmount(sfFeeAmount) == XRP(10));
// clear flag
env(sponsor::set_fee(sponsor, tfSponsorshipClearRequireSignForFee, XRP(10)), sponsor::sponseeAcc(alice));
env.close();
// Payment is re-applied
BEAST_EXPECT(!env.le(keylet::sponsor(sponsor, alice))->isFieldPresent(sfFeeAmount));
}
}
void
testSponsorAccount()
{
testcase("Sponsor Account");
using namespace test::jtx;
Env env{*this, testable_amendments() - featureSponsor};
Account const alice("alice");
Account const sponsor("sponsor");
Account const bob("bob");
Account const charlie("charlie");
Account const gw("gw");
auto const USD = gw["USD"];
env.fund(XRP(10000), alice, sponsor);
// Disabled
env(pay(alice, bob, XRP(100)), txflags(tfSponsorCreatedAccount), ter(temDISABLED));
env.close();
env.enableFeature(featureSponsor);
env.close();
// Invalid flags
for (auto flag : {
tfNoRippleDirect,
tfPartialPayment,
tfLimitQuality,
})
{
env(pay(alice, bob, XRP(100)), txflags(tfSponsorCreatedAccount | flag), ter(temINVALID_FLAG));
env.close();
}
// Invalid amount(iou)
env(pay(alice, bob, USD(100)), txflags(tfSponsorCreatedAccount), ter(temBAD_AMOUNT));
env.close();
// Insufficient reserve
env(pay(alice, bob, (env.current()->fees().accountReserve(0) - drops(1))),
txflags(tfSponsorCreatedAccount),
ter(tecNO_DST_INSUF_XRP));
env.close();
// Account is not sponsored by normal Sponsor specification
{
env(pay(alice, bob, drops(env.current()->fees().accountReserve(0))),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor));
env.close();
auto const bobSle = env.le(keylet::account(bob));
BEAST_EXPECT(!bobSle->isFieldPresent(sfSponsorAccount));
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0);
}
// Use tfSponsorCreatedAccount to sponsor an account
{
// to funded account
env(pay(sponsor, bob, drops(env.current()->fees().accountReserve(0))),
txflags(tfSponsorCreatedAccount),
ter(tecNO_SPONSOR_PERMISSION));
// to non-funded account
env(pay(sponsor, charlie, drops(env.current()->fees().accountReserve(0))),
txflags(tfSponsorCreatedAccount));
env.close();
auto const charlieSle = env.le(keylet::account(charlie));
BEAST_EXPECT(charlieSle->isFieldPresent(sfSponsorAccount));
BEAST_EXPECT(charlieSle->getAccountID(sfSponsorAccount) == sponsor.id());
BEAST_EXPECT(sponsoredOwnerCount(env, charlie) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 1);
}
}
void
testRequireFlag()
{
using namespace test::jtx;
{
testcase("SponsorshipRequireSignForReserve");
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
env.fund(XRP(10000), alice, bob, sponsor);
env.close();
// set flag
env(sponsor::set_reserve(sponsor, tfSponsorshipSetRequireSignForReserve, 10), sponsor::sponseeAcc(alice));
env.close();
env(check::create(alice, bob, XRP(100)),
fee(XRP(10)),
sponsor::as(sponsor, tfSponsorReserve),
ter(terNO_SPONSORSHIP));
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
// clear flag
env(sponsor::set_reserve(sponsor, tfSponsorshipClearRequireSignForReserve, 1), sponsor::sponseeAcc(alice));
env.close();
// CheckCreate is re-applied
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
}
{
testcase("SponsorshipRequireSignForFee");
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
env.fund(XRP(10000), alice, bob, sponsor);
env.close();
// set flag
env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)), sponsor::sponseeAcc(alice));
env.close();
env(check::create(alice, bob, XRP(100)),
fee(XRP(10)),
sponsor::as(sponsor, tfSponsorFee),
ter(terNO_SPONSORSHIP));
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(env.le(keylet::sponsor(sponsor, alice))->getFieldAmount(sfFeeAmount) == XRP(10));
// clear flag
env(sponsor::set_fee(sponsor, tfSponsorshipClearRequireSignForFee, XRP(10)), sponsor::sponseeAcc(alice));
env.close();
// CheckCreate is re-applied
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(!env.le(keylet::sponsor(sponsor, alice))->isFieldPresent(sfFeeAmount));
}
}
// test helper for both cosigning and pre-funded sponsorship
template <typename SubmitCallback>
void
testEachSponsorship(
test::jtx::Env& env,
bool cosigning,
jtx::Account const& sponsor,
jtx::Account const& sponsee,
uint32_t reserveCount,
uint32_t sponsorReserveCount,
TER insufficientReserveResult,
SubmitCallback callback,
std::optional<std::function<void()>> expected = std::nullopt)
{
using namespace test::jtx;
// auto const sponsorOwnerCountBefore = ownerCount(env, sponsor);
auto const sponseeOwnerCountBefore = ownerCount(env, sponsee);
auto const sponseeSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsee);
auto const sponseeSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsee);
auto const sponsorSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsor);
std::optional<sig> sponsorSig = cosigning ? std::optional<sig>(sig(sfSponsorSignature, sponsor)) : std::nullopt;
auto const sponsorCurrentOwnerCount =
ownerCount(env, sponsor) - sponsoredOwnerCount(env, sponsor) + sponsoringOwnerCount(env, sponsor);
auto submit = [&](TER _ter) {
return [&, _ter](Json::Value const& jv, auto const&... fN) {
if (sponsorSig)
env(jv, fN..., sponsor::as(sponsor, tfSponsorReserve), *sponsorSig, ter(_ter));
else
env(jv, fN..., sponsor::as(sponsor, tfSponsorReserve), ter(_ter));
};
};
// Insufficient Reserve
{
if (cosigning)
{
adjustAccountXRPBalance(
env, sponsor, reserve(env, sponsorCurrentOwnerCount + sponsorReserveCount) - drops(1));
}
else
{
// cleanup previous sponsorship
if (env.le(keylet::sponsor(sponsor, sponsee)))
{
env(sponsor::del(sponsor), sponsor::sponseeAcc(sponsee));
env.close();
}
if (sponsorReserveCount - 1 > 0)
env(sponsor::set(sponsor, 0, sponsorReserveCount - 1, XRP(1)), sponsor::sponseeAcc(sponsee));
else
// just create sponsor object
env(sponsor::set(sponsor, 0, std::nullopt, XRP(1)), sponsor::sponseeAcc(sponsee));
env.close();
}
callback(env, submit(insufficientReserveResult));
env.close();
}
// Success
{
if (cosigning)
{
adjustAccountXRPBalance(env, sponsor, reserve(env, sponsorCurrentOwnerCount + sponsorReserveCount));
}
else
{
// reset sponsorship
env(sponsor::del(sponsor), sponsor::sponseeAcc(sponsee));
env(sponsor::set(sponsor, 0, sponsorReserveCount, XRP(1)), sponsor::sponseeAcc(sponsee));
env.close();
}
callback(env, submit(tesSUCCESS));
env.close();
if (!cosigning)
{
// cleanup sponsorship
env(sponsor::del(sponsor), sponsor::sponseeAcc(sponsee));
env.close();
}
}
if (expected)
(*expected)();
else
{
BEAST_EXPECT(ownerCount(env, sponsee) - sponseeOwnerCountBefore == reserveCount);
BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) - sponseeSponsoredOwnerCountBefore == sponsorReserveCount);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsee) - sponseeSponsoringOwnerCountBefore == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) - sponsorSponsoringOwnerCountBefore == sponsorReserveCount);
}
};
void
testAMM(bool cosigning)
{
testcase("AMM");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const gw("gw");
Account const sponsor("sponsor");
auto const USD = gw["USD"];
auto const EUR = gw["EUR"];
auto const ammCreate = [&](Env& env, Account const& account, STAmount const& amount1, STAmount const& amount2) {
Json::Value jv;
jv[jss::TransactionType] = jss::AMMCreate;
jv[jss::Account] = account.human();
jv[jss::Amount] = amount1.getJson(JsonOptions::none);
jv[jss::Amount2] = amount2.getJson(JsonOptions::none);
jv[jss::TradingFee] = 0;
jv[jss::Fee] = std::to_string(env.current()->fees().increment.drops());
return jv;
};
auto const ammDeposit =
[&](Env& env, Account const& account, STAmount const& amount1, STAmount const& amount2) {
Json::Value jv;
jv[jss::TransactionType] = jss::AMMDeposit;
jv[jss::Account] = account.human();
jv[jss::Asset] = STIssue(sfAsset, amount1.issue()).getJson(JsonOptions::none);
jv[jss::Asset2] = STIssue(sfAsset, amount2.issue()).getJson(JsonOptions::none);
jv[jss::Amount] = amount1.value().getJson(JsonOptions::none);
jv[jss::Amount2] = amount2.value().getJson(JsonOptions::none);
jv[jss::Flags] = tfTwoAsset;
return jv;
};
{
// AMMCreate
// - sponsor LPToken
// - doesn't sponsor AMM object
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, gw, sponsor);
env.close();
env(trust(alice, USD(10000)));
env(trust(alice, EUR(10000)));
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, alice, EUR(1000)));
env.close();
testEachSponsorship(
env,
cosigning,
sponsor,
alice,
1,
1,
tecINSUF_RESERVE_LINE,
[&](Env& env, auto const& submit) { submit(ammCreate(env, alice, USD(100), EUR(100))); },
[&]() {
auto const amm = env.current()->read(keylet::amm(USD.issue(), EUR.issue()));
auto const ammAccount = Account("amm", amm->getAccountID(sfAccount));
BEAST_EXPECT(ownerCount(env, alice) == 3); // RippleState (USD,EUR/LP Token)
BEAST_EXPECT(ownerCount(env, ammAccount) == 2); // USD, EUR
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); // LPToken
BEAST_EXPECT(sponsoredOwnerCount(env, ammAccount) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); // LPToken
BEAST_EXPECT(!env.le(keylet::amm(USD.issue(), EUR.issue()))->isFieldPresent(sfSponsorAccount));
});
auto const ammKeylet = keylet::amm(USD.issue(), EUR.issue());
if (cosigning)
{
env(sponsor::transfer(alice, ammKeylet.key),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor),
ter(tecNO_PERMISSION));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor, 0, 1), sponsor::sponseeAcc(alice));
env(sponsor::set_reserve(sponsor, 0, 1), sponsor::sponseeAcc(alice));
env(sponsor::transfer(alice, ammKeylet.key),
sponsor::as(sponsor, tfSponsorReserve),
ter(tecNO_PERMISSION));
env.close();
}
}
{
// AMMDeposit
// - sponsor new LPToken
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, bob, gw, sponsor);
env.close();
env(trust(alice, USD(10000)));
env(trust(alice, EUR(10000)));
env(trust(bob, USD(10000)));
env(trust(bob, EUR(10000)));
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, alice, EUR(1000)));
env(pay(gw, bob, USD(1000)));
env(pay(gw, bob, EUR(1000)));
env.close();
env(ammCreate(env, alice, USD(100), EUR(100)));
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 2); // RippleState (USD,EUR)
testEachSponsorship(
env, cosigning, sponsor, bob, 1, 1, tecINSUF_RESERVE_LINE, [&](Env& env, auto const& submit) {
submit(ammDeposit(env, bob, USD(100), EUR(100)));
});
}
{
// AMMWithdraw
{
// Single Asset Withdraw
// - sponsor new RippleState
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, bob, gw, sponsor);
env.close();
env(trust(alice, USD(10000)));
env(trust(alice, EUR(10000)));
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, alice, EUR(1000)));
env.close();
env(ammCreate(env, alice, USD(1000), EUR(1000)),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor));
env.close();
env(trust(alice, USD(0)));
env(trust(alice, EUR(0)));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1); // LPToken
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); // LPToken
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); // LPToken
Json::Value jv;
jv[jss::TransactionType] = jss::AMMWithdraw;
jv[jss::Account] = alice.human();
jv[jss::Asset] = STIssue(sfAsset, USD.issue()).getJson(JsonOptions::none);
jv[jss::Asset2] = STIssue(sfAsset, EUR.issue()).getJson(JsonOptions::none);
jv[jss::Amount] = USD(100).value().getJson(JsonOptions::none);
jv[jss::Flags] = tfSingleAsset;
env(ticket::create(sponsor, 1)); // adjust for free
env.close();
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(jv);
});
}
{
// Double Asset Withdraw
// - sponsor new RippleState * 2
// - remove sponsored LPToken
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, bob, gw, sponsor);
env.close();
env(trust(alice, USD(10000)));
env(trust(alice, EUR(10000)));
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, alice, EUR(1000)));
env.close();
env(ammCreate(env, alice, USD(1000), EUR(1000)),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor));
env.close();
env(trust(alice, USD(0)));
env(trust(alice, EUR(0)));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1); // LPToken
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
Json::Value jv;
jv[jss::TransactionType] = jss::AMMWithdraw;
jv[jss::Account] = alice.human();
jv[jss::Asset] = STIssue(sfAsset, USD.issue()).getJson(JsonOptions::none);
jv[jss::Asset2] = STIssue(sfAsset, EUR.issue()).getJson(JsonOptions::none);
jv[jss::Flags] = tfWithdrawAll;
env(ticket::create(sponsor, 1)); // adjust for free trustline
env.close();
testEachSponsorship(
env,
cosigning,
sponsor,
alice,
2,
2,
tecINSUFFICIENT_RESERVE,
[&](Env& env, auto const& submit) { submit(jv); },
[&]() {
// LPToken deleted, USD, EUR created
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
});
}
}
{
// AMMClawback
// - doesn't sponsor holder's new RippleState
// - remove sponsored LPToken
Account const gw2("gw2");
auto const EUR2 = gw2["EUR"];
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, gw, gw2, sponsor);
env.close();
env(fset(gw, asfAllowTrustLineClawback));
env.close();
env(trust(alice, USD(10000)));
env(trust(alice, EUR2(10000)));
env.close();
env(pay(gw, alice, USD(100)));
env(pay(gw2, alice, EUR2(100)));
env.close();
env(ammCreate(env, alice, USD(100), EUR2(100)),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor));
env.close();
env(trust(alice, USD(0)));
env(trust(alice, EUR2(0)));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1); // LPToken
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
{
// doesn't sponsor holder's new RippleState
env(amm::ammClawback(gw, alice, USD, EUR2, USD(10)),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 2); // LPToken, EUR2
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
}
{
// remove sponsored LPToken
env(amm::ammClawback(gw, alice, USD, EUR2, std::nullopt));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1); // EUR2
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
}
}
void
testCheck(bool cosigning)
{
testcase("Check");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const gw("gw");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
auto const USD = gw["USD"];
{
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, bob, sponsor, sponsor2);
env.close();
// CheckCreate -> CheckCancel
uint32_t seq;
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
seq = env.seq(alice);
submit(check::create(alice, bob, XRP(1)));
});
BEAST_EXPECT(ownerCount(env, alice) == 1); // Check
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
auto const keylet = keylet::check(alice, seq);
BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsorAccount) == sponsor.id());
if (cosigning)
{
// transfer sponsor
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
// transfer sponsor
env(sponsor::transfer(alice, keylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1); // Check
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsorAccount) == sponsor2.id());
// CheckCancel
env(check::cancel(alice, keylet.key));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
{
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, bob, sponsor);
env.close();
// CheckCreate -> CheckCash
uint32_t seq2;
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
seq2 = env.seq(alice);
submit(check::create(alice, bob, XRP(1)));
});
BEAST_EXPECT(ownerCount(env, bob) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
// CheckCash
auto const checkId2 = keylet::check(alice, seq2).key;
env(check::cash(bob, checkId2, XRP(1)));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
// RippleState sponsor (CheckCashMakesTrustLine)
{
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, bob, gw, sponsor, sponsor2);
env.close();
env.trust(USD(100), alice);
env.close();
env(pay(gw, alice, USD(100)));
env.close();
// CheckCreate -> CheckCash
uint32_t seq2;
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
seq2 = env.seq(alice);
submit(check::create(alice, bob, USD(1)));
});
BEAST_EXPECT(ownerCount(env, bob) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
auto const keylet = keylet::check(alice, seq2);
BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsorAccount) == sponsor.id());
// CheckCash
testEachSponsorship(
env,
cosigning,
sponsor,
bob,
1,
1,
tecNO_LINE_INSUF_RESERVE,
[&](Env& env, auto const& submit) { submit(check::cash(bob, keylet.key, USD(1))); },
[&]() {
BEAST_EXPECT(ownerCount(env, alice) == 1); // RippleState
BEAST_EXPECT(ownerCount(env, bob) == 1); // RippleState
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
});
}
}
void
testOffer(bool cosigning)
{
testcase("Offer");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const gw("gw");
Account const sponsor1("sponsor1");
Account const sponsor2("sponsor2");
auto USD = gw["USD"];
auto EUR = gw["EUR"];
{
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, gw, sponsor1, sponsor2);
env.close();
// OfferCreate
uint32_t seq;
testEachSponsorship(
env, cosigning, sponsor1, alice, 1, 1, tecINSUF_RESERVE_OFFER, [&](Env& env, auto const& submit) {
seq = env.seq(alice);
submit(offer(alice, USD(1), XRP(1)));
});
// transfer sponsor
auto const keylet = keylet::offer(alice, seq);
if (cosigning)
{
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, keylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsorAccount) == sponsor2.id());
// OfferCancel
env(offer_cancel(alice, seq));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
{
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, gw, sponsor1, sponsor2);
env.close();
// OfferCreate
uint32_t seq;
testEachSponsorship(
env, cosigning, sponsor1, alice, 1, 1, tecINSUF_RESERVE_OFFER, [&](Env& env, auto const& submit) {
seq = env.seq(alice);
submit(offer(alice, USD(1), XRP(1)));
});
// OfferCreate with Cancel (new sponsor)
auto const seq2 = env.seq(alice);
if (cosigning)
{
env(offer(alice, USD(1), XRP(1)),
json(jss::OfferSequence, seq),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(offer(alice, USD(1), XRP(1)),
json(jss::OfferSequence, seq),
sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// OfferCreate with Cancel (no sponsor)
env(offer(alice, USD(1), XRP(1)), json(jss::OfferSequence, seq2));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
// test Offer Execution doesn't sponsor new trustline
{
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, bob, gw, sponsor1, sponsor2);
env.close();
env(trust(alice, USD(100)));
env(trust(bob, EUR(100)));
env.close();
env(pay(gw, alice, USD(100)));
env(pay(gw, bob, EUR(100)));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(ownerCount(env, bob) == 1);
// OfferCreate
if (cosigning)
{
env(offer(alice, EUR(1), USD(1)),
sponsor::as(sponsor1, tfSponsorReserve),
sig(sfSponsorSignature, sponsor1));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor1, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(offer(alice, EUR(1), USD(1)), sponsor::as(sponsor1, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
BEAST_EXPECT(ownerCount(env, bob) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
// OfferCreate (cross offer)
if (cosigning)
{
env(offer(bob, USD(1), EUR(1)),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(bob));
env.close();
env(offer(bob, USD(1), EUR(1)), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
// does not sponsor new trustline by cross offer
BEAST_EXPECT(ownerCount(env, bob) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
}
void
testTicket(bool cosigning)
{
testcase("Ticket");
using namespace test::jtx;
Account const alice("alice");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, sponsor, sponsor2);
env.close();
// TicketCreate
uint32_t ticketSeq;
testEachSponsorship(
env, cosigning, sponsor, alice, 250, 250, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
ticketSeq = env.seq(alice) + 1;
submit(ticket::create(alice, 250));
});
auto const keylet = keylet::ticket(alice, ticketSeq);
BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsorAccount) == sponsor.id());
// transfer sponsor
if (cosigning)
{
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, keylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 250);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 250);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 249);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsorAccount) == sponsor2.id());
// use a Ticket
env(noop(alice), ticket::use(ticketSeq));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 249);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 249);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 249);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
}
void
testCredentials(bool cosigning)
{
testcase("Credentials");
using namespace test::jtx;
Account const issuer("issuer");
Account const subject("subject");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
auto const credType = std::string("credType");
auto const credTypeSlice = Slice(credType.data(), credType.size());
// CredentialsCreate
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), issuer, subject, sponsor, sponsor2);
env.close();
testEachSponsorship(
env, cosigning, sponsor, issuer, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(credentials::create(subject, issuer, credType), credentials::uri("uri"));
});
BEAST_EXPECT(ownerCount(env, subject) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, subject) == 0);
// transfer sponsor
auto const keylet = keylet::credential(subject, issuer, credTypeSlice);
if (cosigning)
{
env(sponsor::transfer(issuer, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(issuer));
env.close();
env(sponsor::transfer(issuer, keylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, issuer) == 1);
BEAST_EXPECT(ownerCount(env, subject) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, issuer) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, subject) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// CredentialsAccept
testEachSponsorship(
env, cosigning, sponsor, subject, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(credentials::accept(subject, issuer, credType));
});
BEAST_EXPECT(ownerCount(env, issuer) == 0);
BEAST_EXPECT(ownerCount(env, subject) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, issuer) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, subject) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
// transfer accepted credential
if (cosigning)
{
env(sponsor::transfer(subject, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(subject));
env.close();
env(sponsor::transfer(subject, keylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
// CredentialsDelete
env(credentials::deleteCred(subject, subject, issuer, credType));
env.close();
BEAST_EXPECT(ownerCount(env, issuer) == 0);
BEAST_EXPECT(ownerCount(env, subject) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, issuer) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, subject) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), issuer, subject, sponsor);
env.close();
// Accept Sponsored Credentials without sponsoring
testEachSponsorship(
env, cosigning, sponsor, issuer, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(credentials::create(subject, issuer, credType));
});
env(credentials::accept(subject, issuer, credType));
env.close();
// sponsorship is removed
BEAST_EXPECT(ownerCount(env, issuer) == 0);
BEAST_EXPECT(ownerCount(env, subject) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, issuer) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, subject) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(!env.le(keylet::credential(subject, issuer, credTypeSlice))->isFieldPresent(sfSponsorAccount));
env(credentials::deleteCred(subject, subject, issuer, credType));
env.close();
}
}
void
testDelegate(bool cosigning)
{
testcase("Delegate");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
// DelegateSet
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(delegate::set(alice, bob, {"Payment"}));
});
// transfer sponsor
auto const keylet = keylet::delegate(alice, bob);
if (cosigning)
{
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, keylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// delete
env(delegate::set(alice, bob, {}));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
}
void
testDepositPreauth(bool cosigning)
{
testcase("DepositPreauth");
using namespace test::jtx;
Account const alice("alice");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, sponsor, sponsor2);
env.close();
// DepositPreauthSet
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(deposit::auth(alice, sponsor));
});
// transfer sponsor
auto const keylet = keylet::depositPreauth(alice, sponsor);
if (cosigning)
{
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// DepositPreauthDelete
env(deposit::unauth(alice, sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
}
void
testDID(bool cosigning)
{
testcase("DID");
using namespace test::jtx;
Account const alice("alice");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, sponsor, sponsor2);
env.close();
// DIDSet
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(did::set(alice), did::uri("uri"));
});
// transfer sponsor
auto const keylet = keylet::did(alice);
if (cosigning)
{
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, keylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// DIDDelete
env(did::del(alice));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
}
void
testEscrow(bool cosigning)
{
testcase("Escrow");
using namespace test::jtx;
using namespace std::chrono_literals;
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
{
// Native Escrow
Env env{*this, testable_amendments()};
auto const baseFee = env.current()->fees().base;
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
// EscrowCreate
uint32_t seq;
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
seq = env.seq(alice);
submit(
escrow::create(alice, bob, XRP(100)),
escrow::condition(escrow::cb1),
escrow::cancel_time(env.now() + 100s));
});
BEAST_EXPECT(env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsorAccount) == sponsor.id());
// transfer sponsor
if (cosigning)
{
env(sponsor::transfer(alice, keylet::escrow(alice, seq).key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, keylet::escrow(alice, seq).key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
BEAST_EXPECT(env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsorAccount) == sponsor2.id());
// EscrowFinish
env(escrow::finish(bob, alice, seq),
escrow::condition(escrow::cb1),
escrow::fulfillment(escrow::fb1),
fee(baseFee * 150));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
Account const gw("gw");
auto const USD = gw["USD"];
{
// IOU Escrow
Env env{*this, testable_amendments()};
auto const baseFee = env.current()->fees().base;
env.fund(XRP(1000000), alice, bob, gw, sponsor, sponsor2);
env.close();
env(fset(gw, asfAllowTrustLineLocking));
env.close();
env.trust(USD(1000000), alice);
env.close();
env(pay(gw, alice, USD(10000)));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
// EscrowCreate
uint32_t seq;
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
seq = env.seq(alice);
submit(
escrow::create(alice, bob, USD(100)),
escrow::condition(escrow::cb1),
escrow::cancel_time(env.now() + 100s));
});
BEAST_EXPECT(env.le(keylet::escrow(alice, seq))->getAccountID(sfSponsorAccount) == sponsor.id());
// EscrowFinish
testEachSponsorship(
env, cosigning, sponsor2, bob, 1, 1, tecNO_LINE_INSUF_RESERVE, [&](Env& env, auto const& submit) {
submit(
escrow::finish(bob, alice, seq),
escrow::condition(escrow::cb1),
escrow::fulfillment(escrow::fb1),
fee(baseFee * 150));
});
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(env.le(keylet::line(bob, gw, USD.currency))->getAccountID(sfHighSponsor) == sponsor2.id());
}
}
void
testMPToken(bool cosigning)
{
testcase("MPToken");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
// MPTokenIssuanceCreate
Json::Value jv = {};
jv[sfAccount] = alice.human();
jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
MPTID mptid;
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
mptid = makeMptID(env.seq(alice), alice.id());
submit(jv);
});
// transfer sponsor
auto const mptIssuanceKeylet = keylet::mptIssuance(mptid);
if (cosigning)
{
env(sponsor::transfer(alice, mptIssuanceKeylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, mptIssuanceKeylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
// MPTokenAuthorize
jv = {};
jv[sfTransactionType] = jss::MPTokenAuthorize;
jv[sfAccount] = bob.human();
jv[sfMPTokenIssuanceID] = to_string(mptid);
if (cosigning)
{
adjustAccountXRPBalance(env, sponsor, reserve(env, 2));
env(ticket::create(sponsor, 2)); // adjust for free mptoken
env.close();
}
testEachSponsorship(
env, cosigning, sponsor, bob, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(jv);
});
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
// transfer sponsor
auto const mptTokenKeylet = keylet::mptoken(mptid, bob);
if (cosigning)
{
env(sponsor::transfer(bob, mptTokenKeylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(bob));
env.close();
env(sponsor::transfer(bob, mptTokenKeylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(ownerCount(env, bob) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 2);
// MPTokenAuthorize Unauthorize
jv = {};
jv[sfTransactionType] = jss::MPTokenAuthorize;
jv[sfAccount] = bob.human();
jv[sfMPTokenIssuanceID] = to_string(mptid);
jv[sfFlags] = tfMPTUnauthorize;
env(jv);
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(ownerCount(env, bob) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// MPTokenIssuanceDestroy
jv = {};
jv[sfTransactionType] = jss::MPTokenIssuanceDestroy;
jv[sfAccount] = alice.human();
jv[sfMPTokenIssuanceID] = to_string(mptid);
env(jv);
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
{
// check INSUFFICIENT_RESERVE for MPToken
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor);
env.close();
// MPTokenAuthorize
Json::Value jv = {};
jv[sfAccount] = alice.human();
jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
auto const mptid = makeMptID(env.seq(alice), alice.id());
env(jv);
env.close();
// for free mptoken checks
// adjustAccountXRPBalance(env, sponsor, reserve(env, 2));
std::uint32_t ticketSeq{env.seq(sponsor) + 1};
env(ticket::create(sponsor, 2));
env.close();
// adjustAccountXRPBalance(env, sponsor, reserve(env, 3) -
// drops(1));
jv = {};
jv[sfTransactionType] = jss::MPTokenAuthorize;
jv[sfAccount] = bob.human();
jv[sfMPTokenIssuanceID] = to_string(mptid);
// error (non-free mptoken)
if (cosigning)
{
adjustAccountXRPBalance(env, sponsor, reserve(env, 3) - drops(1));
env(jv,
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor),
ter(tecINSUFFICIENT_RESERVE));
env.close();
}
else
{
env(sponsor::set(sponsor, 0, std::nullopt, XRP(1)), sponsor::sponseeAcc(bob));
env.close();
env(jv, sponsor::as(sponsor, tfSponsorReserve), ter(tecINSUFFICIENT_RESERVE));
env.close();
}
env(noop(sponsor), ticket::use(ticketSeq));
env.close();
// pass (free mptoken)
if (cosigning)
{
adjustAccountXRPBalance(env, sponsor, reserve(env, 2) - drops(1));
env(jv, sponsor::as(sponsor, tfSponsorReserve), sig(sfSponsorSignature, sponsor), ter(tesSUCCESS));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor, 0, 1), sponsor::sponseeAcc(bob));
env.close();
env(jv, sponsor::as(sponsor, tfSponsorReserve), ter(tesSUCCESS));
env.close();
}
}
}
void
testNFToken(bool cosigning)
{
testcase("NFToken");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
// NFTokenMint
uint256 nftId;
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
nftId = token::getNextID(env, alice, 0);
submit(token::mint(alice));
});
// transfer sponsor
auto const keylet = keylet::nftpage_max(alice);
if (cosigning)
{
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, keylet.key), sponsor::as(sponsor2, tfSponsorReserve));
}
// NFTokenBurn
env(token::burn(alice, nftId));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
// NFTokenMintOffer
adjustAccountXRPBalance(env, sponsor, reserve(env, 2));
env(token::mint(alice),
token::amount(XRP(10000)),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor));
env.close();
// testEachSponsorship(
// env,
// cosigning,
// sponsor,
// alice,
// 2,
// 2,
// tecINSUFFICIENT_RESERVE,
// [&](Env& env, auto const& submit) {
// token::mint(alice), token::amount(XRP(100));
// });
}
{
// multiple nft page process
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor);
env.close();
auto const nftCount = 200;
// NFTokenMint
if (cosigning)
{
for (auto i = 0; i < nftCount; i++)
{
env(token::mint(alice), sponsor::as(sponsor, tfSponsorReserve), sig(sfSponsorSignature, sponsor));
}
}
else
{
env(sponsor::set_reserve(sponsor, 0, 8), sponsor::sponseeAcc(alice));
env.close();
for (auto i = 0; i < nftCount; i++)
{
env(token::mint(alice), sponsor::as(sponsor, tfSponsorReserve));
}
}
env.close();
BEAST_EXPECT(ownerCount(env, alice) == sponsoredOwnerCount(env, alice));
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == sponsoringOwnerCount(env, sponsor));
// NFTokenBurn
for (auto i = 0; i < nftCount; i++)
{
auto const nftId = token::getID(env, alice, 0, i, 0, 0);
env(token::burn(alice, nftId));
}
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
}
void
testNFTokenOffer(bool cosigning)
{
testcase("NFTokenOffer");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const broker("broker");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
auto const taxon = 0u;
{
// Mint + CreateOffer + CancelOffer
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
// Mint
uint256 const nftId{token::getNextID(env, alice, taxon, tfTransferable)};
env(token::mint(alice, taxon), txflags(tfTransferable));
env.close();
// NFTokenOfferCreate
uint256 offerIndex1;
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
offerIndex1 = keylet::nftoffer(alice, env.seq(alice)).key;
submit(token::createOffer(alice, nftId, XRP(1)), token::destination(bob), txflags(tfSellNFToken));
});
uint256 offerIndex2;
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
offerIndex2 = keylet::nftoffer(alice, env.seq(alice)).key;
submit(token::createOffer(alice, nftId, XRP(1)), token::destination(bob), txflags(tfSellNFToken));
});
// transfer sponsor
if (cosigning)
{
env(sponsor::transfer(alice, offerIndex1),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, offerIndex1), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 3);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// NFTokenOfferCancel
env(token::cancelOffer(alice, {offerIndex1, offerIndex2}));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
{
// Mint + CreateSellOffer + AcceptSellOffer
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor);
env.close();
// Mint
uint256 const nftId{token::getNextID(env, alice, taxon, tfTransferable)};
env(token::mint(alice, taxon), txflags(tfTransferable));
env.close();
// NFTokenOfferCreate
uint256 offerIndex;
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
offerIndex = keylet::nftoffer(alice, env.seq(alice)).key;
submit(token::createOffer(alice, nftId, XRP(1)), token::destination(bob), txflags(tfSellNFToken));
});
// NFTokenOfferAccept
env(token::acceptSellOffer(bob, offerIndex));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
{
// Mint + CreateBuyOffer + AcceptBuyOffer
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor);
env.close();
// Mint
uint256 const nftId{token::getNextID(env, alice, taxon, tfTransferable)};
env(token::mint(alice, taxon), txflags(tfTransferable));
env.close();
// NFTokenOfferCreate
uint256 offerIndex;
testEachSponsorship(
env, cosigning, sponsor, bob, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
offerIndex = keylet::nftoffer(bob, env.seq(bob)).key;
submit(token::createOffer(bob, nftId, XRP(1)), token::owner(alice), token::destination(alice));
});
// NFTokenOfferAccept
env(token::acceptBuyOffer(alice, offerIndex));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
{
// Broker
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, broker, sponsor, sponsor2);
env.close();
// Mint
uint256 const nftId{token::getNextID(env, alice, taxon, tfTransferable)};
env(token::mint(alice, taxon), txflags(tfTransferable));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
// NFTokenOfferCreate (BuyOffer)
uint256 buyOfferIndex;
testEachSponsorship(
env, cosigning, sponsor, bob, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
buyOfferIndex = keylet::nftoffer(bob, env.seq(bob)).key;
submit(token::createOffer(bob, nftId, XRP(1)), token::owner(alice), token::destination(broker));
});
// NFTokenOfferCreate (SellOffer)
uint256 sellOfferIndex;
testEachSponsorship(
env, cosigning, sponsor2, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
sellOfferIndex = keylet::nftoffer(alice, env.seq(alice)).key;
submit(
token::createOffer(alice, nftId, XRP(1)), txflags(tfSellNFToken), token::destination(broker));
});
// NFTokenOfferAccept
env(token::brokerOffers(broker, buyOfferIndex, sellOfferIndex));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
}
void
testPayChan(bool cosigning)
{
testcase("PayChan");
using namespace test::jtx;
using namespace std::literals::chrono_literals;
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
// PayChanCreate
auto const pk = alice.pk();
auto const settleDelay = 10s;
uint256 chan;
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
chan = paychan::channel(alice, bob, env.seq(alice));
submit(paychan::create(alice, bob, XRP(100), settleDelay, pk));
});
// transfer sponsor
if (cosigning)
{
env(sponsor::transfer(alice, chan),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, chan), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
env.close(env.now() + settleDelay);
// PayChanClaim (delete PayChan)
env(paychan::claim(bob, chan), txflags(tfClose));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
}
void
testPermissionedDomain(bool cosigning)
{
testcase("PermissionedDomain");
using namespace test::jtx;
Account const alice("alice");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, sponsor, sponsor2);
env.close();
// PermissionedDomainSet
pdomain::Credentials credentials{{alice, "first credential"}};
uint32_t seq;
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
seq = env.seq(alice);
submit(pdomain::setTx(alice, credentials));
});
// transfer sponsor
auto const keylet = keylet::permissionedDomain(alice, seq);
if (cosigning)
{
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, keylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// PermissionedDomainDelete
auto objects = pdomain::getObjects(alice, env);
auto const domain = objects.begin()->first;
env(pdomain::deleteTx(alice, domain));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
}
void
testOracle(bool cosigning)
{
testcase("Oracle");
using namespace test::jtx;
using namespace std::chrono;
using DataSeries = std::vector<std::tuple<std::string, std::string, std::uint32_t, std::uint8_t>>;
Account const alice("alice");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
auto const oracleSet = [](Env& env, Account const& account, uint8_t dataSeriesSize) {
auto const now = env.timeKeeper().now();
env.close(now + oracle::testStartTime - epoch_offset);
Json::Value jv;
jv[jss::TransactionType] = jss::OracleSet;
jv[jss::Account] = to_string(account);
jv[jss::OracleDocumentID] = 1;
jv[jss::LastUpdateTime] = to_string(
duration_cast<seconds>(env.current()->header().closeTime.time_since_epoch()).count() +
epoch_offset.count() + 100);
jv[jss::PriceDataSeries] = Json::arrayValue;
jv[jss::Provider] = strHex(std::string{"provider"});
jv[jss::AssetClass] = strHex(std::string{"currency"});
DataSeries const series = {
{"XRP", "US1", 740, 1},
{"XRP", "US2", 750, 1},
{"XRP", "US3", 740, 1},
{"XRP", "US4", 750, 1},
{"XRP", "US5", 740, 1},
{"XRP", "US6", 750, 1},
{"XRP", "US7", 740, 1},
{"XRP", "US8", 750, 1},
{"XRP", "US9", 740, 1},
{"XRP", "U10", 750, 1},
};
DataSeries actualSeries(series.begin(), series.begin() + dataSeriesSize);
Json::Value dataSeries(Json::arrayValue);
for (auto const& data : actualSeries)
{
Json::Value priceData;
Json::Value price;
price[jss::BaseAsset] = std::get<0>(data);
price[jss::QuoteAsset] = std::get<1>(data);
price[jss::AssetPrice] = std::get<2>(data);
price[jss::Scale] = std::get<3>(data);
priceData[jss::PriceData] = price;
dataSeries.append(priceData);
}
jv[jss::PriceDataSeries] = dataSeries;
return jv;
};
auto const oracleDelete = [&](Account const& account) {
Json::Value jv;
jv[jss::TransactionType] = jss::OracleDelete;
jv[jss::Account] = to_string(account);
jv[jss::OracleDocumentID] = 1;
return jv;
};
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, sponsor, sponsor2);
env.close();
{
// OracleSet (reserve 1)
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(oracleSet(env, alice, 5));
});
// transfer sponsor
auto const keylet = keylet::oracle(alice, 1);
if (cosigning)
{
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, keylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// OracleDelete
env(oracleDelete(alice));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
{
// OracleSet (reserve 2)
testEachSponsorship(
env, cosigning, sponsor, alice, 2, 2, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(oracleSet(env, alice, 6));
});
// transfer sponsor
auto const keylet = keylet::oracle(alice, 1);
if (cosigning)
{
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 2), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, keylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 2);
// OracleDelete
env(oracleDelete(alice));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
{
// OracleSet (reserve 1->2, sponsor1 -> no-sponsor)
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(oracleSet(env, alice, 5));
});
// reserve 1->2
env(oracleSet(env, alice, 6));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
// OracleDelete
env(oracleDelete(alice));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
{
// OracleSet (reserve 1->2, sponsor1 -> sponsor2)
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(oracleSet(env, alice, 5));
});
// return;
// reserve 1->2
testEachSponsorship(
env,
cosigning,
sponsor2,
alice,
1,
2,
tecINSUFFICIENT_RESERVE,
[&](Env& env, auto const& submit) { submit(oracleSet(env, alice, 6)); },
[&]() {
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 2);
});
// OracleDelete
env(oracleDelete(alice));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
{
// OracleSet (reserve 1->2, non-sponsor -> sponsor1)
env(oracleSet(env, alice, 5));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
// reserve 1->2
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 2, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(oracleSet(env, alice, 6));
});
// OracleDelete
env(oracleDelete(alice));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
for (bool isTwoOwnerCount : {false, true})
{
// test sponsor transfer
auto const dataSeriesSize = isTwoOwnerCount ? 6 : 5;
auto const ocount = isTwoOwnerCount ? 2 : 1;
testEachSponsorship(
env,
cosigning,
sponsor,
alice,
ocount,
ocount,
tecINSUFFICIENT_RESERVE,
[&](Env& env, auto const& submit) { submit(oracleSet(env, alice, dataSeriesSize)); });
// transfer sponsor
if (cosigning)
{
env(sponsor::transfer(alice, keylet::oracle(alice, 1).key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, ocount), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, keylet::oracle(alice, 1).key),
sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == ocount);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == ocount);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == ocount);
// disolve sponsor
env(sponsor::transfer(alice, keylet::oracle(alice, 1).key));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == ocount);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
// remove sponsor
env(oracleDelete(alice));
env.close();
}
}
}
void
testSignerList(bool cosigning)
{
testcase("SignerList");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
// SignerListSet
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(signers(alice, 1, {{bob, 1}}));
});
// transfer sponsor
if (cosigning)
{
// invalid signer list owner 1
// account doesn't have signer list but specified signer list exists
env(sponsor::transfer(bob, keylet::signers(alice).key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2),
ter(tecNO_PERMISSION));
// invalid signer list owner 2
// account has signer list and specified signer list exists
env(signers(bob, 1, {{alice, 1}}));
env.close();
env(sponsor::transfer(alice, keylet::signers(bob).key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2),
ter(tecNO_PERMISSION));
env(sponsor::transfer(alice, keylet::signers(alice).key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, keylet::signers(alice).key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// Delete
env(signers(alice, none));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
void
testTrustSet(bool cosigning)
{
testcase("TrustSet");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const charlie("charlie");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
{
auto const validateSponsoredTrustline =
[&](std::shared_ptr<const SLE> const& sle, bool isIssuerHigh, Account const& sponsor) {
BEAST_EXPECT(sle->getAccountID(isIssuerHigh ? sfLowSponsor : sfHighSponsor) == sponsor.id());
BEAST_EXPECT(!sle->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor));
};
auto const& highAcc = alice > bob ? alice : bob;
auto const& lowAcc = alice > bob ? bob : alice;
// create and delete
for (bool isIssuerHigh : {false, true})
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, charlie, sponsor, sponsor2);
env.close();
auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
auto const& user = isIssuerHigh ? lowAcc : highAcc;
auto const USD = issuer["USD"];
auto const currency = USD.currency;
// create TrustLine
if (cosigning)
{
adjustAccountXRPBalance(env, sponsor, reserve(env, 2));
env(ticket::create(sponsor, 2)); // adjust for free trustline
env.close();
}
testEachSponsorship(
env, cosigning, sponsor, user, 1, 1, tecNO_LINE_INSUF_RESERVE, [&](Env& env, auto const& submit) {
submit(trust(user, USD(100)));
});
auto const keylet = keylet::line(user, issuer, currency);
if (cosigning)
{
// invalid owner
env(sponsor::transfer(charlie, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2),
ter(tecNO_PERMISSION));
// invalid reserve owner
env(sponsor::transfer(issuer, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2),
ter(tecNO_PERMISSION));
env(sponsor::transfer(user, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(user));
env.close();
env(sponsor::transfer(user, keylet.key), sponsor::as(sponsor2, tfSponsorReserve));
env.close();
}
// delete TrustLine
env(trust(user, USD(0)));
env.close();
BEAST_EXPECT(ownerCount(env, user) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(!env.le(keylet));
}
// update
for (bool isIssuerHigh : {false, true})
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
auto const& user = isIssuerHigh ? lowAcc : highAcc;
auto const USD = issuer["USD"];
auto const currency = USD.currency;
// create TrustLine from issuer
env(trust(issuer, user["USD"](100)));
env.close();
BEAST_EXPECT(env.le(keylet::line(user, issuer, currency)));
if (cosigning)
{
adjustAccountXRPBalance(env, sponsor, reserve(env, 2));
env(ticket::create(sponsor, 2)); // adjust for free trustline
env.close();
}
testEachSponsorship(
env, cosigning, sponsor, user, 1, 1, tecINSUF_RESERVE_LINE, [&](Env& env, auto const& submit) {
submit(trust(user, USD(100)));
});
auto const line = env.le(keylet::line(user, issuer, currency));
validateSponsoredTrustline(line, isIssuerHigh, sponsor);
// update TrustLine from user to clear reserve
env(trust(user, USD(0)));
env.close();
BEAST_EXPECT(ownerCount(env, user) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(env.le(keylet::line(user, issuer, currency)));
// remove TrustLine from issuer
env(trust(issuer, user["USD"](0)));
env.close();
BEAST_EXPECT(!env.le(keylet::line(user, issuer, currency)));
}
}
}
void
testVault(bool cosigning)
{
testcase("Vault");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const gw("gw");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
Asset asset = gw["IOU"].asset();
// VaultCreate
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();
Vault vault{env};
auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(ticket::create(sponsor, 2));
env.close();
testEachSponsorship(
env,
cosigning,
sponsor,
alice,
3, // Vault, PseudoAccount, MPToken(Share Token)
3,
tecINSUFFICIENT_RESERVE,
[&](Env& env, auto const& submit) {
auto result = vault.create({.owner = alice, .asset = asset});
submit(std::get<0>(result));
keylet = std::get<1>(result);
});
BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsorAccount) == sponsor.id());
}
// VaultDeposit
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();
Vault vault{env};
auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(tx);
env.close();
env(trust(bob, asset(1000)));
env.close();
env(pay(gw, bob, asset(1000)));
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 1); // RippleState
auto const depositTx = vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(100)});
env(ticket::create(sponsor, 2)); // for free MPToken
env.close();
testEachSponsorship(
env, cosigning, sponsor, bob, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(depositTx);
});
}
// VaultWithdraw
{
// RippleState Vault
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();
Vault vault{env};
auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(tx);
env.close();
env(trust(bob, asset(100)));
env.close();
env(pay(gw, bob, asset(100)));
env.close();
auto const depositTx = vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(100)});
env(ticket::create(sponsor, 2)); // for free MPToken
env.close();
testEachSponsorship(
env, cosigning, sponsor, bob, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(depositTx);
});
env(trust(bob, asset(0))); // remove trustline
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 1); // MPToken(share)
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); // MPToken(share)
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); // MPToken(share)
// create Trustline with vault withdraw
testEachSponsorship(
env, cosigning, sponsor, bob, 1, 1, tecNO_LINE_INSUF_RESERVE, [&](Env& env, auto const& submit) {
submit(vault.withdraw({.depositor = bob, .id = keylet.key, .amount = asset(50)}));
});
BEAST_EXPECT(ownerCount(env, bob) == 2); // RippleState, MPToken(share)
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 2); // RippleState, MPToken(share)
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2); // RippleState, MPToken(share)
// remove sponsored MPToken(share)
env(vault.withdraw({.depositor = bob, .id = keylet.key, .amount = asset(50)}));
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 1); // RippleState
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); // RippleState
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); // RippleState
}
// MPToken Vault
{
// VaultWithdraw doesn't create MPToken for depositor
}
}
// VaultClawback
{
// remove sponsored shares MPToken
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();
env(fset(gw, asfAllowTrustLineClawback));
env.close();
Vault vault{env};
auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(tx);
env.close();
env(trust(bob, asset(100)));
env.close();
env(pay(gw, bob, asset(100)));
env.close();
auto const depositTx = vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(100)});
env(ticket::create(sponsor, 2)); // for free MPToken
env.close();
testEachSponsorship(
env, cosigning, sponsor, bob, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(depositTx);
});
BEAST_EXPECT(ownerCount(env, bob) == 2); // RippleState, MPToken(share)
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); // MPToken(share)
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); // MPToken(share)
env(vault.clawback({.issuer = gw, .id = keylet.key, .holder = bob, .amount = asset(0)}),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 1); // RippleState
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
// VaultDelete
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();
env(fset(gw, asfAllowTrustLineClawback));
env.close();
Vault vault{env};
auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(tx, sponsor::as(sponsor, tfSponsorReserve), sig(sfSponsorSignature, sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 3); // Vault, PseudoAccount, MPToken(share)
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 3);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 3);
env(vault.del({.owner = alice, .id = keylet.key}));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
}
void
testXChain(bool cosigning)
{
testcase("XChain");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const doorA("doorA");
Account const signer("signer");
Account const sponsor("sponsor");
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor, doorA);
env.close();
auto jvb = bridge(doorA, XRP, env.master, XRP);
env(signers(doorA, 1, {signer}));
env.close();
// XChainCreateBridge
{
testEachSponsorship(
env, cosigning, sponsor, doorA, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(bridge_create(doorA, jvb, XRP(1), XRP(1)));
});
}
// XChainCreateClaimID
{
testEachSponsorship(
env, cosigning, sponsor, alice, 1, 1, tecINSUFFICIENT_RESERVE, [&](Env& env, auto const& submit) {
submit(xchain_create_claim_id(alice, jvb, XRP(1), bob));
});
}
// XChainCommit
{
BEAST_EXPECT(ownerCount(env, alice) == 1); // XChainOwnedClaimID
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
if (cosigning)
{
env(xchain_commit(alice, jvb, 1, XRP(100), bob),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(xchain_commit(alice, jvb, 1, XRP(100), bob), sponsor::as(sponsor, tfSponsorReserve));
env.close();
env(sponsor::del(sponsor), sponsor::sponseeAcc(alice));
env.close();
}
// doesn't sponsor anything
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
}
// XChainAddClaimAttestation
{
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
if (cosigning)
{
env(claim_attestation(alice, jvb, bob, XRP(1), bob, false, 1, bob, signer),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor, 0, 1), sponsor::sponseeAcc(alice));
env.close();
env(claim_attestation(alice, jvb, bob, XRP(1), bob, false, 1, bob, signer),
sponsor::as(sponsor, tfSponsorReserve));
env.close();
env(sponsor::del(sponsor), sponsor::sponseeAcc(alice));
env.close();
}
// XChainOwnedClaimID deleted
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
}
// XChainClaim
{
// prepare for claim
{
env(xchain_create_claim_id(alice, jvb, XRP(1), bob),
sponsor::as(sponsor, tfSponsorReserve),
sig(sfSponsorSignature, sponsor));
env(xchain_commit(alice, jvb, 2, XRP(100))); // omit destination
env(claim_attestation(alice, jvb, bob, XRP(100), bob, false, 2, std::nullopt, signer));
env.close();
}
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
env(xchain_claim(alice, jvb, 2, XRP(100), bob));
env.close();
// XChainOwnedClaimID deleted
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
}
}
void
testDisallowIncoming()
{
testcase("DisallowIncoming");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
env.fund(XRP(1000000), alice, sponsor);
env.close();
// set DisallowIncomingSponsor
env(fset(alice, asfDisallowIncomingSponsor));
env.close();
// Create sponsor should fail
env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::sponseeAcc(alice), ter(tecNO_PERMISSION));
env.close();
// clear flag
env(fclear(alice, asfDisallowIncomingSponsor));
env.close();
// Create sponsor
env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
// set flag
env(fset(alice, asfDisallowIncomingSponsor));
env.close();
// Update sponsor should success
env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
// Delete sponsor should success
env(sponsor::set(sponsor, tfDeleteObject), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
}
void
testAccountDelete()
{
testcase("AccountDelete");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
{
// Delete Account with ltSponsorship
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor);
env.close();
// set sponsor
env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
incLgrSeqForAccDel(env, alice);
auto const keylet = keylet::sponsor(sponsor, alice);
auto const sponsorObj = env.le(keylet);
BEAST_EXPECT(sponsorObj);
// AccountDelete
auto const requiredFee = drops(env.current()->fees().increment);
env(acctdelete(alice, bob), fee(requiredFee), ter(tesSUCCESS));
env.close();
BEAST_EXPECT(!env.le(keylet));
auto const jv = sponsor::ledgerEntry(env, sponsor, alice);
BEAST_EXPECT(
jv.isObject() && jv.isMember(jss::result) && jv[jss::result].isMember(jss::error) &&
jv[jss::result][jss::error] == "entryNotFound");
}
{
// Delete SponsoredAccount
Env env{*this, testable_amendments()};
env.memoize(alice);
env.fund(XRP(1000000), bob, sponsor);
env.close();
// create SponsoredAccount
env(pay(sponsor, alice, XRP(10000)), txflags(tfSponsorCreatedAccount));
env.close();
incLgrSeqForAccDel(env, alice);
// AccountDelete: destination = non-sponsor
auto const requiredFee = drops(env.current()->fees().increment);
env(acctdelete(alice, bob), fee(requiredFee), ter(tecNO_SPONSOR_PERMISSION));
auto const sponsorSle = env.le(keylet::account(sponsor));
BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringAccountCount) == 1);
incLgrSeqForAccDel(env, alice);
// AccountDelete: destination = sponsor
env(acctdelete(alice, sponsor), fee(requiredFee), ter(tesSUCCESS));
auto const sponsorSle2 = env.le(keylet::account(sponsor));
BEAST_EXPECT(!sponsorSle2->isFieldPresent(sfSponsoringAccountCount));
}
}
void
testDelegatePermission()
{
testcase("DelegatePermission");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const carol("carol");
//
// SponsorshipTransfer
//
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, carol);
env.close();
auto const seq = env.seq(alice);
env(check::create(alice, bob, XRP(1)));
env.close();
auto const keylet = keylet::check(alice, seq);
env(sponsor::transfer(alice, keylet.key),
sponsor::as(bob, tfSponsorReserve),
sig(sfSponsorSignature, bob),
delegate::as(carol),
ter(terNO_DELEGATE_PERMISSION));
env(delegate::set(alice, carol, {"SponsorshipTransfer"}));
env.close();
env(sponsor::transfer(alice, keylet.key),
sponsor::as(bob, tfSponsorReserve),
sig(sfSponsorSignature, bob),
delegate::as(carol),
ter(tesSUCCESS));
env.close();
}
//
// SponsorshipSet
//
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, carol);
env.close();
env(sponsor::set(alice, 0, 100, XRP(100)),
sponsor::sponseeAcc(bob),
delegate::as(carol),
ter(terNO_DELEGATE_PERMISSION));
env(delegate::set(alice, carol, {"SponsorshipSet"}));
env.close();
env(sponsor::set(alice, 0, 100, XRP(100)), sponsor::sponseeAcc(bob), delegate::as(carol), ter(tesSUCCESS));
env.close();
}
//
// Permission SponsorFee
//
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, carol);
env.close();
auto const testFeePermission = [&](TER result) {
// FeeAmount
env(sponsor::set(alice, 0, std::nullopt, XRP(100)),
sponsor::sponseeAcc(bob),
delegate::as(carol),
ter(result));
// MaxFee
env(sponsor::set(alice, 0, std::nullopt, std::nullopt, XRP(100)),
sponsor::sponseeAcc(bob),
delegate::as(carol),
ter(result));
// SetRequireSignForFee flag
env(sponsor::set(alice, tfSponsorshipSetRequireSignForFee),
sponsor::sponseeAcc(bob),
delegate::as(carol),
ter(result));
env.close();
};
// no delegated
testFeePermission(terNO_DELEGATE_PERMISSION);
// set non-SponsorFee Permission
env(delegate::set(alice, carol, {"SponsorReserve"}));
env.close();
testFeePermission(terNO_DELEGATE_PERMISSION);
// set SponsorFee Permission
env(delegate::set(alice, carol, {"SponsorFee"}));
env.close();
testFeePermission(tesSUCCESS);
// test with SponsorReserve (should failed)
env(sponsor::set(alice, 0, 100, XRP(100)),
sponsor::sponseeAcc(bob),
delegate::as(carol),
ter(terNO_DELEGATE_PERMISSION));
}
//
// Permission SponsorReserve
//
{
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, carol);
env.close();
auto const testReservePermission = [&](TER result) {
// ReserveCount
env(sponsor::set(alice, 0, 100), sponsor::sponseeAcc(bob), delegate::as(carol), ter(result));
// SetRequireSignForReserve flag
env(sponsor::set(alice, tfSponsorshipSetRequireSignForReserve),
sponsor::sponseeAcc(bob),
delegate::as(carol),
ter(result));
env.close();
};
// no delegated
testReservePermission(terNO_DELEGATE_PERMISSION);
// set non-SponsorReserve Permission
env(delegate::set(alice, carol, {"SponsorFee"}));
env.close();
testReservePermission(terNO_DELEGATE_PERMISSION);
// set SponsorReserve Permission
env(delegate::set(alice, carol, {"SponsorReserve"}));
env.close();
testReservePermission(tesSUCCESS);
// test with SponsorFee (should failed)
env(sponsor::set(alice, 0, 100, XRP(100)),
sponsor::sponseeAcc(bob),
delegate::as(carol),
ter(terNO_DELEGATE_PERMISSION));
}
}
void
testBatch()
{
testcase("Batch");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
//
// Outer transaction
//
{
// test outer transaction with co-signing sponsor
Env env{*this, testable_amendments()};
env.fund(XRP(1000), alice, bob, sponsor);
env.close();
auto const seq = env.seq(alice);
env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
batch::inner(noop(alice), seq + 1),
batch::inner(ticket::create(alice, 1), seq + 2),
sponsor::as(sponsor, tfSponsorReserve | tfSponsorFee),
sig(sfSponsorSignature, sponsor),
ter(tesSUCCESS));
env.close();
// does not affect reserve
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
// fee is paid by sponsor
BEAST_EXPECT(env.balance(alice) == XRP(1000));
BEAST_EXPECT(env.balance(sponsor) == XRP(1000 - 1));
}
{
// test outer transaction with prefunded sponsor
Env env{*this, testable_amendments()};
env.fund(XRP(1000), alice, bob);
env.fund(XRP(1001), sponsor);
env.close();
env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::sponseeAcc(alice), fee(XRP(1)), ter(tesSUCCESS));
env.close();
auto const seq = env.seq(alice);
env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
batch::inner(noop(alice), seq + 1),
batch::inner(ticket::create(alice, 1), seq + 2),
sponsor::as(sponsor, tfSponsorReserve | tfSponsorFee),
ter(tesSUCCESS));
env.close();
// does not affect reserve
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
// fee is paid by sponsor object
BEAST_EXPECT(env.balance(alice) == XRP(1000));
BEAST_EXPECT(env.balance(sponsor) == XRP(900));
auto const sponsorshipSle = env.le(keylet::sponsor(sponsor, alice));
BEAST_EXPECT(sponsorshipSle);
BEAST_EXPECT(sponsorshipSle->at(sfFeeAmount) == XRP(100 - 1));
BEAST_EXPECT(sponsorshipSle->at(sfReserveCount) == 100);
}
//
// Inner transaction
//
{
// test inner transaction with co-signing sponsor
Env env{*this, testable_amendments()};
env.fund(XRP(1000), alice, bob, sponsor);
env.close();
auto jt = env.jtnofill(
noop(alice), sponsor::as(sponsor, tfSponsorReserve | tfSponsorFee), sig(sfSponsorSignature, sponsor));
auto const seq = env.seq(alice);
// should fail because inner transaction cannot include SponsorSignature
env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
batch::inner(jt.jv, seq + 1),
batch::inner(ticket::create(alice, 1), seq + 2),
ter(temBAD_SIGNATURE));
}
{
// test outer transaction with prefunded sponsor
Env env{*this, testable_amendments()};
env.fund(XRP(1000), alice, bob);
env.fund(XRP(1001), sponsor);
env.close();
env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::sponseeAcc(alice), fee(XRP(1)), ter(tesSUCCESS));
env.close();
BEAST_EXPECT(env.balance(sponsor) == XRP(900));
auto jt = env.jtnofill(ticket::create(alice, 1), sponsor::as(sponsor, tfSponsorReserve | tfSponsorFee));
// remove txn signature since it is filled by env.jtnofill()
jt.jv.removeMember(jss::TxnSignature);
auto const seq = env.seq(alice);
env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
batch::inner(noop(alice), seq + 1),
batch::inner(jt.jv, seq + 2),
ter(tesSUCCESS));
env.close();
// affect sponsor reserve
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// fee is paid by outer transaction originator (alice)
BEAST_EXPECT(env.balance(alice) == XRP(999));
BEAST_EXPECT(env.balance(sponsor) == XRP(900));
// reserve count is decreased
auto const sponsorshipSle = env.le(keylet::sponsor(sponsor, alice));
BEAST_EXPECT(sponsorshipSle);
BEAST_EXPECT(sponsorshipSle->at(sfFeeAmount) == XRP(100));
BEAST_EXPECT(sponsorshipSle->at(sfReserveCount) == 99);
}
}
void
testSponsorReserve(bool cosigning)
{
testRequireFlag();
testAMM(cosigning);
testCheck(cosigning);
testOffer(cosigning);
testTicket(cosigning);
testCredentials(cosigning);
testDelegate(cosigning);
testDepositPreauth(cosigning);
testDID(cosigning);
testEscrow(cosigning);
testMPToken(cosigning);
testNFToken(cosigning);
testNFTokenOffer(cosigning);
testPayChan(cosigning);
testPermissionedDomain(cosigning);
testOracle(cosigning);
testSignerList(cosigning);
testTrustSet(cosigning);
testVault(cosigning);
testXChain(cosigning);
}
protected:
void
testSponsor()
{
testDisabled();
testInvalidSponsorshipSet();
testSingleSigning();
testMultiSigning();
testInvalidSponsorField();
testSimpleSponsorshipSet();
testPreFundAndCosign();
testTransferSponsor();
testSponsorFee();
testSponsorAccount();
testDisallowIncoming();
testAccountDelete();
testDelegatePermission();
testBatch();
}
void
testTxSponsor(bool cosigning)
{
testSponsorReserve(cosigning);
}
public:
void
run() override
{
testSponsor();
}
};
class SponsorTxCosigning_test : public Sponsor_test
{
void
run() override
{
testTxSponsor(true);
}
};
class SponsorTxPrefunded_test : public Sponsor_test
{
void
run() override
{
testTxSponsor(false);
}
};
BEAST_DEFINE_TESTSUITE(Sponsor, app, xrpl);
BEAST_DEFINE_TESTSUITE(SponsorTxCosigning, app, xrpl);
BEAST_DEFINE_TESTSUITE(SponsorTxPrefunded, app, xrpl);
} // namespace test
} // namespace xrpl