Files
rippled/src/test/app/Sponsor_test.cpp

2808 lines
99 KiB
C++

//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/jtx.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/Feature.h>
namespace ripple {
namespace test {
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;
if (currentBalance > balanceTo)
env(pay(account, env.master, currentBalance - balanceTo + XRP(1)),
fee(XRP(1)));
else
env(pay(env.master, account, balanceTo - currentBalance), fee(XRP(1)));
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);
env(noop(alice),
fee(XRP(1)),
sponsor::as(sponsor),
sponsor::sig(sponsor),
ter(temDISABLED));
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 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
//
// preclaim
//
// Invalid Sponsee
env(sponsor::set(sponsor, 0),
sponsor::sponseeAcc(noFunded),
ter(tecNO_DST));
// Invalid Delete operation (not found)
env(sponsor::set(sponsor, tfDeleteObject),
sponsor::sponseeAcc(alice),
ter(tecNO_ENTRY));
// DisallowIncomingSponsor: tested in other testcase
// create sponsor to use above tests
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][sfAccount.jsonName] = sponsor.human();
tx[sfSponsor.jsonName][sfSigningPubKey.jsonName] =
strHex(sponsor.pk().slice());
env(tx,
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
ter(telENV_RPC_FAILED));
// Invalid signature
tx[sfSponsor.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),
sponsor::sig(invalid),
ter(tefBAD_AUTH));
// Success
env(noop(alice),
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(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[sfSponsor.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),
sponsor::msig({signer1}),
ter(tefBAD_AUTH));
env(noop(alice),
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::msig({signer1}),
ter(tesSUCCESS));
env(signers(sponsor, 2, {{signer1, 1}, {signer2, 1}}));
env.close();
env(noop(alice),
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::msig({signer1, signer2}),
ter(tesSUCCESS));
}
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)),
sponsor::sponseeAcc(alice),
ter(tesSUCCESS));
env.close();
// delete from sponsor
env(sponsor::del(sponsor), sponsor::sponseeAcc(alice), ter(tesSUCCESS));
env.close();
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();
}
void
testTransferSponsor()
{
testcase("Transfer Sponsor");
using namespace test::jtx;
{
// sponsor account
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor1("sponsor1");
Account const sponsor2("sponsor2");
env.fund(XRP(10000), alice);
env.fund(env.current()->fees().reserve * 2 - 1, sponsor1, sponsor2);
env.close();
env(sponsor::transfer(alice),
sponsor::as(sponsor1, tfSponsorReserve),
sponsor::sig(sponsor1),
ter(tecINSUFFICIENT_RESERVE));
env(pay(alice, sponsor1, drops(1)));
env.close();
env(sponsor::transfer(alice),
sponsor::as(sponsor1, tfSponsorReserve),
sponsor::sig(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
env(sponsor::transfer(alice),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2),
ter(tecINSUFFICIENT_RESERVE));
env(pay(alice, sponsor2, drops(1)));
env.close();
env(sponsor::transfer(alice),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(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);
auto const sle2 = env.le(keylet::account(alice));
BEAST_EXPECT(sle2->isFieldPresent(sfSponsorAccount));
BEAST_EXPECT(sle2->getAccountID(sfSponsorAccount) == sponsor2.id());
// dissolve sponsor
env(pay(alice,
sponsor2,
(env.balance(alice).value() -
env.current()->fees().reserve - XRP(1) + drops(1))),
fee(XRP(1)));
env.close();
BEAST_EXPECT(
env.balance(alice) == env.current()->fees().reserve - drops(1));
env(sponsor::transfer(alice), ter(tecINSUFFICIENT_RESERVE));
env.close();
env(pay(sponsor2, alice, XRP(1)));
env.close();
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) == 0);
auto const sle3 = env.le(keylet::account(alice));
BEAST_EXPECT(!sle3->isFieldPresent(sfSponsorAccount));
}
{
// sponsor object
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);
env.fund(
env.current()->fees().reserve +
env.current()->fees().increment - drops(1),
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),
sponsor::sig(sponsor1),
ter(tecINSUFFICIENT_RESERVE));
env.close();
env(pay(alice, sponsor1, drops(1)));
env.close();
// Invalid Owner
env(sponsor::transfer(bob, checkId),
sponsor::as(sponsor1, tfSponsorReserve),
sponsor::sig(sponsor1),
ter(tecNO_PERMISSION));
env.close();
// Valid Owner
env(sponsor::transfer(alice, checkId),
sponsor::as(sponsor1, tfSponsorReserve),
sponsor::sig(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),
sponsor::sig(sponsor2),
ter(tecINSUFFICIENT_RESERVE));
env(pay(alice, sponsor2, drops(1)));
env.close();
env(sponsor::transfer(alice, checkId),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(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
env(pay(alice,
sponsor2,
(env.balance(alice).value() -
env.current()->fees().reserve -
env.current()->fees().increment - XRP(1) + drops(1))),
fee(XRP(1)));
env.close();
env(sponsor::transfer(alice, checkId),
ter(tecINSUFFICIENT_RESERVE));
env.close();
env(pay(sponsor2, alice, XRP(1)));
env.close();
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);
auto const sle3 = env.le(keylet::unchecked(checkId));
BEAST_EXPECT(!sle3->isFieldPresent(sfSponsorAccount));
}
}
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),
sponsor::sig(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),
sponsor::sig(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),
sponsor::sig(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),
sponsor::sig(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(100) + 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);
}
// 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();
// clear flag
env(sponsor::set_fee(
sponsor, tfSponsorshipClearRequireSignForFee, XRP(10)),
sponsor::sponseeAcc(alice));
env.close();
env(pay(alice, bob, XRP(100)),
fee(XRP(10)),
sponsor::as(sponsor, tfSponsorFee),
ter(tesSUCCESS));
env.close();
}
}
void
testSponsorAccount()
{
testcase("Sponsor Account");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
Account const bob("bob");
Account const charlie("charlie");
env.fund(XRP(10000), alice, sponsor);
// Account is not sponsored by normal Sponsor specification
{
env(pay(alice,
bob,
STAmount(env.current()->fees().accountReserve(0))),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(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 accoutn
env(pay(sponsor,
bob,
STAmount(env.current()->fees().accountReserve(0))),
txflags(tfSponsorCreatedAccount),
ter(tecNO_SPONSOR_PERMISSION));
// to non-funded account
env(pay(sponsor,
charlie,
STAmount(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()
{
testcase("SponsorshipRequireSignForReserve");
using namespace test::jtx;
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));
env.close();
// clear flag
env(sponsor::set_reserve(
sponsor, tfSponsorshipClearRequireSignForReserve, 1),
sponsor::sponseeAcc(alice));
env.close();
env(check::create(alice, bob, XRP(100)),
fee(XRP(10)),
sponsor::as(sponsor, tfSponsorReserve),
ter(tesSUCCESS));
env.close();
}
void
testCheck()
{
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
auto const seq = env.seq(alice);
env(check::create(alice, bob, XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
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) == 1);
auto const keylet = keylet::check(alice, seq);
BEAST_EXPECT(
env.le(keylet)->getAccountID(sfSponsorAccount) == sponsor.id());
// transfer sponsor
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
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
auto const seq2 = env.seq(alice);
env(check::create(alice, bob, XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1); // Check
BEAST_EXPECT(ownerCount(env, bob) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// CheckCash
auto const checkId2 = keylet::check(alice, seq2).key;
env(check::cash(bob, checkId2, XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
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
auto const seq2 = env.seq(alice);
env(check::create(alice, bob, USD(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 2); // RippleState + Check
BEAST_EXPECT(ownerCount(env, bob) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
auto const keylet = keylet::check(alice, seq2);
BEAST_EXPECT(
env.le(keylet)->getAccountID(sfSponsorAccount) == sponsor.id());
// CheckCash
env(check::cash(bob, keylet.key, USD(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
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);
}
{
// check INSUFFICIENT_RESERVE
{
// CheckCreate
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, bob, sponsor);
env.close();
adjustAccountXRPBalance(
env, sponsor, reserve(env, 1) - drops(1));
env(check::create(alice, bob, XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor),
ter(tecINSUFFICIENT_RESERVE));
env.close();
adjustAccountXRPBalance(env, sponsor, reserve(env, 1));
env(check::create(alice, bob, XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor),
ter(tesSUCCESS));
env.close();
}
{
// CheckCash (CheckCashMakesTrustLine)
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, bob, gw, sponsor);
env.close();
env.trust(USD(100), alice);
env.close();
env(pay(gw, alice, USD(100)));
env.close();
auto const seq = env.seq(alice);
env(check::create(alice, bob, USD(1)));
env.close();
adjustAccountXRPBalance(
env, sponsor, reserve(env, 1) - drops(1));
auto const keylet = keylet::check(alice, seq);
env(check::cash(bob, keylet.key, USD(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor),
ter(tecNO_LINE_INSUF_RESERVE));
env.close();
}
}
}
void
testOffer()
{
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
auto const seq = env.seq(alice);
env(offer(alice, USD(1), XRP(1)),
sponsor::as(sponsor1, tfSponsorReserve),
sponsor::sig(sponsor1));
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) == 1);
// transfer sponsor
auto const keylet = keylet::offer(alice, seq);
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
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
auto const seq = env.seq(alice);
env(offer(alice, USD(1), XRP(1)),
sponsor::as(sponsor1, tfSponsorReserve),
sponsor::sig(sponsor1));
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) == 1);
// OfferCreate with Cancel (new sponsor)
auto const seq2 = env.seq(alice);
env(offer(alice, USD(1), XRP(1)),
json(jss::OfferSequence, seq),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
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
env(offer(alice, EUR(1), USD(1)),
sponsor::as(sponsor1, tfSponsorReserve),
sponsor::sig(sponsor1));
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)
env(offer(bob, USD(1), EUR(1)),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
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);
}
{
// check INSUFFICIENT_RESERVE for OfferCreate
Env env{*this, testable_amendments()};
env.fund(XRP(10000), alice, bob, gw, sponsor1, sponsor2);
env.close();
env.trust(USD(100), alice);
env.close();
env(pay(gw, alice, USD(100)));
env.close();
adjustAccountXRPBalance(env, sponsor1, reserve(env, 1) - drops(1));
// fullly not crossed
env(offer(alice, USD(1), XRP(1)),
sponsor::as(sponsor1, tfSponsorReserve),
sponsor::sig(sponsor1),
ter(tecINSUF_RESERVE_OFFER));
// partially crossed
env(offer(gw, XRP(1), USD(1)));
env.close();
env(offer(alice, USD(5), XRP(5)),
sponsor::as(sponsor1, tfSponsorReserve),
sponsor::sig(sponsor1),
ter(tecINSUF_RESERVE_OFFER));
}
}
void
testTicket()
{
testcase("Ticket");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("master");
Account const sponsor2("sponsor2");
env.fund(XRP(1000000), alice, sponsor, sponsor2);
env.close();
// TicketCreate
std::uint32_t const ticketSeq{env.seq(alice) + 1};
env(ticket::create(alice, 250),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 250);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 250);
BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 250);
auto const keylet = keylet::ticket(alice, ticketSeq);
BEAST_EXPECT(
env.le(keylet)->getAccountID(sfSponsorAccount) == sponsor.id());
// transfer sponsor
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
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()
{
testcase("Credentials");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const issuer("issuer");
Account const subject("subject");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
env.fund(XRP(1000000), issuer, subject, sponsor, sponsor2);
env.close();
auto const credType = std::string("credType");
auto const credTypeSlice = Slice(credType.data(), credType.size());
// CredentialsCreate
{
env(credentials::create(subject, issuer, credType),
credentials::uri("uri"),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
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) == 1);
// transfer sponsor
auto const keylet =
keylet::credential(subject, issuer, credTypeSlice);
env(sponsor::transfer(issuer, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
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
env(credentials::accept(subject, issuer, credType),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
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);
// 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);
}
{
// Accept Sponsored Credentials without sponsoring
env(credentials::create(subject, issuer, credType),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, issuer) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, issuer) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
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()
{
testcase("Delegate");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
// DelegateSet
env(delegate::set(alice, bob, {"Payment"}),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// transfer sponsor
auto const keylet = keylet::delegate(alice, bob);
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(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);
// 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()
{
testcase("DepositPreauth");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
env.fund(XRP(1000000), alice, sponsor, sponsor2);
env.close();
// DepositPreauthSet
env(deposit::auth(alice, sponsor),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// transfer sponsor
auto const keylet = keylet::depositPreauth(alice, sponsor);
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(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()
{
testcase("DID");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
env.fund(XRP(1000000), alice, sponsor, sponsor2);
env.close();
// DIDSet
env(did::set(alice),
did::uri("uri"),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// transfer sponsor
auto const keylet = keylet::did(alice);
env(sponsor::transfer(alice, keylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(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);
// 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()
{
testcase("Escrow");
using namespace test::jtx;
using namespace std::chrono_literals;
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"];
{
// 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
auto const seq = env.seq(alice);
env(escrow::create(alice, bob, XRP(100)),
escrow::condition(escrow::cb1),
escrow::cancel_time(env.now() + 100s),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
BEAST_EXPECT(
env.le(keylet::escrow(alice, seq))
->getAccountID(sfSponsorAccount) == sponsor.id());
// transfer sponsor
env(sponsor::transfer(alice, keylet::escrow(alice, seq).key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(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);
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);
}
{
// 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
auto const seq = env.seq(alice);
env(escrow::create(alice, bob, USD(100)),
escrow::condition(escrow::cb1),
escrow::cancel_time(env.now() + 10s),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
BEAST_EXPECT(
env.le(keylet::escrow(alice, seq))
->getAccountID(sfSponsorAccount) == sponsor.id());
// EscrowFinish
env(escrow::finish(bob, alice, seq),
escrow::condition(escrow::cb1),
escrow::fulfillment(escrow::fb1),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2),
fee(baseFee * 150));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
BEAST_EXPECT(
env.le(keylet::line(bob, gw, USD.currency))
->getAccountID(sfHighSponsorAccount) == sponsor2.id());
}
}
void
testMPToken()
{
testcase("MPToken");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
// MPTokenIssuanceCreate
Json::Value jv = {};
jv[sfAccount] = alice.human();
jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
auto const mptid = makeMptID(env.seq(alice), alice.id());
env(jv, sponsor::as(sponsor, tfSponsorReserve), sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// transfer sponsor
auto const mptIssuanceKeylet = keylet::mptIssuance(mptid);
env(sponsor::transfer(alice, mptIssuanceKeylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(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);
// MPTokenAuthorize
jv = {};
jv[sfTransactionType] = jss::MPTokenAuthorize;
jv[sfAccount] = bob.human();
jv[sfMPTokenIssuanceID] = to_string(mptid);
env(jv, sponsor::as(sponsor, tfSponsorReserve), sponsor::sig(sponsor));
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) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// transfer sponsor
auto const mptTokenKeylet = keylet::mptoken(mptid, bob);
env(sponsor::transfer(bob, mptTokenKeylet.key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
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);
}
void
testNFToken()
{
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);
env.close();
// NFTokenMint
uint256 const nftId{token::getNextID(env, alice, 0)};
env(token::mint(alice),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// 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);
}
{
// multiple nft page process
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor);
env.close();
auto const nftCount = 200;
// NFTokenMint
for (auto i = 0; i < nftCount; i++)
{
env(token::mint(alice),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
}
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()
{
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();
BEAST_EXPECT(ownerCount(env, alice) == 1);
// NFTokenOfferCreate
uint256 const offerIndex1 =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId, XRP(1)),
token::destination(bob),
txflags(tfSellNFToken),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
uint256 const offerIndex2 =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId, XRP(1)),
token::destination(bob),
txflags(tfSellNFToken),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 3);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
// transfer sponsor
env(sponsor::transfer(alice, offerIndex1),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
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();
BEAST_EXPECT(ownerCount(env, alice) == 1);
// NFTokenOfferCreate
uint256 const offerIndex =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId, XRP(1)),
token::destination(bob),
txflags(tfSellNFToken),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// 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();
BEAST_EXPECT(ownerCount(env, alice) == 1);
// NFTokenOfferCreate
uint256 const offerIndex = keylet::nftoffer(bob, env.seq(bob)).key;
env(token::createOffer(bob, nftId, XRP(1)),
token::owner(alice),
token::destination(alice),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// 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 const buyOfferIndex =
keylet::nftoffer(bob, env.seq(bob)).key;
env(token::createOffer(bob, nftId, XRP(1)),
token::owner(alice),
token::destination(broker),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// NFTokenOfferCreate (SellOffer)
uint256 const sellOfferIndex =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId, XRP(1)),
txflags(tfSellNFToken),
token::destination(broker),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// 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()
{
testcase("PayChan");
using namespace test::jtx;
using namespace std::literals::chrono_literals;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
// PayChanCreate
auto const pk = alice.pk();
auto const settleDelay = 10s;
auto const chan = paychan::channel(alice, bob, env.seq(alice));
env(paychan::create(alice, bob, XRP(100), settleDelay, pk),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// transfer sponsor
env(sponsor::transfer(alice, chan),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(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);
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()
{
testcase("PermissionedDomain");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
env.fund(XRP(1000000), alice, sponsor, sponsor2);
env.close();
// PermissionedDomainSet
auto const seq = env.seq(alice);
pdomain::Credentials credentials{{alice, "first credential"}};
env(pdomain::setTx(alice, credentials),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// transfer sponsor
env(sponsor::transfer(
alice, keylet::permissionedDomain(alice, seq).key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(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);
// 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()
{
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>>;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
env.fund(XRP(1000000), alice, sponsor, sponsor2);
env.close();
auto const oracleSet =
[&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()->info().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;
};
{
// OracleSet (reserve 1)
env(oracleSet(alice, 5),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// transfer sponsor
env(sponsor::transfer(alice, keylet::oracle(alice, 1).key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(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);
// 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)
env(oracleSet(alice, 6),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
// transfer sponsor
env(sponsor::transfer(alice, keylet::oracle(alice, 1).key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
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)
env(oracleSet(alice, 5),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// reserve 1->2
env(oracleSet(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)
env(oracleSet(alice, 5),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// reserve 1->2
env(oracleSet(alice, 6),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
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, non-sponsor -> sponsor1)
env(oracleSet(alice, 5));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
// reserve 1->2
env(oracleSet(alice, 6),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 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);
}
for (bool isTwoOwnerCount : {false, true})
{
// test sponsor transfer
auto const dataSeriesSize = isTwoOwnerCount ? 6 : 5;
auto const ocount = isTwoOwnerCount ? 2 : 1;
env(oracleSet(alice, dataSeriesSize),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == ocount);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == ocount);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == ocount);
// transfer sponsor
env(sponsor::transfer(alice, keylet::oracle(alice, 1).key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
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);
}
}
void
testSignerList()
{
testcase("SignerList");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
env.fund(XRP(1000000), alice, sponsor, sponsor2);
env.close();
Account const bob("bob");
// SignerListSet
env(signers(alice, 1, {{bob, 1}}),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// transfer sponsor
env(sponsor::transfer(alice, keylet::signers(alice).key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(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);
// 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()
{
testcase("TrustSet");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
auto const& highAcc = alice > bob ? alice : bob;
auto const& lowAcc = alice > bob ? bob : alice;
for (bool isIssuerHigh : {false, true})
{
auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
auto const& user = isIssuerHigh ? lowAcc : highAcc;
auto const USD = issuer["USD"];
// create TrustLine
env(trust(user, USD(100)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, user) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, user) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
auto const line = env.le(keylet::line(user, issuer, USD.currency));
BEAST_EXPECT(
line->getAccountID(
isIssuerHigh ? sfLowSponsorAccount
: sfHighSponsorAccount) == sponsor.id());
BEAST_EXPECT(!line->isFieldPresent(
isIssuerHigh ? sfHighSponsorAccount : sfLowSponsorAccount));
// transfer sponsor
env(sponsor::transfer(
user, keylet::line(user, issuer, USD.currency).key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
env.close();
BEAST_EXPECT(ownerCount(env, user) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, user) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
auto const line2 = env.le(keylet::line(user, issuer, USD.currency));
BEAST_EXPECT(
line2->getAccountID(
isIssuerHigh ? sfLowSponsorAccount
: sfHighSponsorAccount) == sponsor2.id());
BEAST_EXPECT(!line2->isFieldPresent(
isIssuerHigh ? sfHighSponsorAccount : sfLowSponsorAccount));
// 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::line(user, issuer, USD.currency)));
}
}
void
testVault()
{
}
void
testXChain()
{
}
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");
//
// 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(tecNO_DELEGATE_PERMISSION);
// set non-SponsorFee Permission
env(delegate::set(alice, carol, {"SponsorReserve"}));
env.close();
testFeePermission(tecNO_DELEGATE_PERMISSION);
// set SponsorFee Permission
env(delegate::set(alice, carol, {"SponsorFee"}));
env.close();
testFeePermission(tesSUCCESS);
}
//
// 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(tecNO_DELEGATE_PERMISSION);
// set non-SponsorReserve Permission
env(delegate::set(alice, carol, {"SponsorFee"}));
env.close();
testReservePermission(tecNO_DELEGATE_PERMISSION);
// set SponsorReserve Permission
env(delegate::set(alice, carol, {"SponsorReserve"}));
env.close();
testReservePermission(tesSUCCESS);
}
}
void
testSponsorReserve()
{
// TODO: add checks fo InsufficientReserve for Sponsoring ledger entry
testRequireFlag();
testCheck();
testOffer();
testTicket();
testCredentials();
testDelegate();
testDepositPreauth();
testDID();
testEscrow();
testMPToken();
testNFToken();
testNFTokenOffer();
testPayChan();
testPermissionedDomain();
testOracle();
testSignerList();
testTrustSet();
// testVault();
// testXChain();
}
void
run() override
{
testDisabled();
testInvalidSponsorshipSet();
testSingleSigning();
testMultiSigning();
// testInvalidSigninig(); // borh TxnSignature and Signers are present
// -> error
testSimpleSponsorshipSet();
testTransferSponsor();
testSponsorFee();
testSponsorAccount();
testSponsorReserve();
testDisallowIncoming();
testAccountDelete();
testDelegatePermission();
}
};
BEAST_DEFINE_TESTSUITE(Sponsor, app, ripple);
} // namespace test
} // namespace ripple