mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 09:16:47 +00:00
5340 lines
194 KiB
C++
5340 lines
194 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>
|
|
|
|
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, spfSponsorFee),
|
|
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] = spfSponsorFee | spfSponsorReserve;
|
|
env(jt2, ter(temDISABLED));
|
|
|
|
// check Sponsor transactions
|
|
env(sponsor::transfer(alice, 0), 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::counterpartySponsor(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::counterpartySponsor(sponsor),
|
|
sponsor::sponseeAcc(alice),
|
|
ter(temMALFORMED));
|
|
|
|
// Invalid feeAmount
|
|
for (auto amt : {XRP(-1), USD(1)})
|
|
{
|
|
env(sponsor::set_fee(sponsor, 0, amt), sponsor::sponseeAcc(alice), ter(temBAD_AMOUNT));
|
|
}
|
|
// Invalid MaxFee
|
|
for (auto amt : {XRP(-1), USD(1)})
|
|
{
|
|
env(sponsor::set_fee(sponsor, 0, XRP(1), amt),
|
|
sponsor::sponseeAcc(alice),
|
|
ter(temBAD_AMOUNT));
|
|
}
|
|
|
|
// 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));
|
|
env(sponsor::set_max_fee(sponsor, tfDeleteObject, XRP(1)),
|
|
sponsor::sponseeAcc(alice),
|
|
ter(temMALFORMED));
|
|
|
|
// Invalid SponsorAccount with non-Delete operation
|
|
env(sponsor::set_reserve(sponsor, 0, 100),
|
|
sponsor::counterpartySponsor(alice),
|
|
ter(temMALFORMED));
|
|
env(sponsor::set_fee(sponsor, 0, XRP(1), XRP(1)),
|
|
sponsor::counterpartySponsor(alice),
|
|
ter(temMALFORMED));
|
|
|
|
//
|
|
// preclaim
|
|
//
|
|
|
|
// Invalid Sponsee
|
|
env(sponsor::set(sponsor, 0), sponsor::sponseeAcc(noFunded), ter(tecNO_DST));
|
|
env.close();
|
|
|
|
// Invalid Sponsor
|
|
env(sponsor::set(sponsor, tfDeleteObject),
|
|
sponsor::counterpartySponsor(noFunded),
|
|
ter(tecNO_DST));
|
|
env.close();
|
|
|
|
// Invalid Delete operation (sponsorship not found)
|
|
env(sponsor::set(sponsor, tfDeleteObject), sponsor::sponseeAcc(alice), ter(tecNO_ENTRY));
|
|
env.close();
|
|
|
|
// 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));
|
|
env.close();
|
|
|
|
// FeeAmount + Fee > Balance
|
|
/// Balance = 1000XRP, FeeAmount = 1001XRP
|
|
adjustAccountXRPBalance(env, sponsor, XRP(1000));
|
|
env(sponsor::set_fee(sponsor, 0, XRP(1001)),
|
|
sponsor::sponseeAcc(alice),
|
|
fee(XRP(1)),
|
|
ter(tecUNFUNDED));
|
|
env.close();
|
|
/// Balance = 1000XRP, FeeAmount = 999XRP, Fee=2XRP
|
|
adjustAccountXRPBalance(env, sponsor, XRP(1000));
|
|
env(sponsor::set_fee(sponsor, 0, XRP(999)),
|
|
sponsor::sponseeAcc(alice),
|
|
fee(XRP(2)),
|
|
ter(tecUNFUNDED));
|
|
env.close();
|
|
|
|
// create sponsor to use above tests
|
|
adjustAccountXRPBalance(env, sponsor, XRP(1001));
|
|
env(sponsor::set(sponsor, 0, 100, XRP(1000)),
|
|
sponsor::sponseeAcc(alice),
|
|
fee(XRP(1)),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// delta-based balance check
|
|
// After create: sponsor balance ~ 0, feeAmount = XRP(1000)
|
|
|
|
// Decreasing feeAmount should succeed (refund, negative delta)
|
|
adjustAccountXRPBalance(env, sponsor, XRP(500));
|
|
env(sponsor::set_fee(sponsor, 0, XRP(800)),
|
|
sponsor::sponseeAcc(alice),
|
|
fee(XRP(1)),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
// balance was 500, delta = 800-1000 = -200 (refund), balance = 500+200-1 = 699
|
|
|
|
// Increasing feeAmount within delta budget should succeed
|
|
adjustAccountXRPBalance(env, sponsor, XRP(500));
|
|
env(sponsor::set_fee(sponsor, 0, XRP(850)),
|
|
sponsor::sponseeAcc(alice),
|
|
fee(XRP(1)),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
// balance was 500, delta = 850-800 = 50, balance = 500-50-1 = 449
|
|
|
|
// Increasing feeAmount where delta exceeds balance should fail
|
|
adjustAccountXRPBalance(env, sponsor, XRP(310));
|
|
env(sponsor::set_fee(sponsor, 0, XRP(1200)),
|
|
sponsor::sponseeAcc(alice),
|
|
fee(XRP(1)),
|
|
ter(tecUNFUNDED));
|
|
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, spfSponsorReserve), ter(telENV_RPC_FAILED));
|
|
|
|
// Invalid signature
|
|
tx[sfSponsorSignature.jsonName][sfTxnSignature.jsonName] = "DEADBEEF";
|
|
env(tx, fee(XRP(1)), sponsor::as(sponsor, spfSponsorReserve), ter(telENV_RPC_FAILED));
|
|
|
|
// Signer account doesn't exist
|
|
env(noop(alice),
|
|
fee(XRP(1)),
|
|
sponsor::as(invalid, spfSponsorReserve),
|
|
sig(sfSponsorSignature, invalid),
|
|
ter(terNO_ACCOUNT));
|
|
|
|
// Success
|
|
env(noop(alice),
|
|
fee(XRP(1)),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
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, spfSponsorReserve), ter(telENV_RPC_FAILED));
|
|
|
|
// Signer account doesn't exist
|
|
env(noop(alice),
|
|
fee(XRP(1)),
|
|
sponsor::as(invalid, spfSponsorReserve),
|
|
msig(sfSponsorSignature, {signer1}),
|
|
ter(tefNOT_MULTI_SIGNING));
|
|
|
|
env(noop(alice),
|
|
fee(XRP(1)),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
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, spfSponsorReserve),
|
|
msig(sfSponsorSignature, {signer1, signer2}),
|
|
ter(telINSUF_FEE_P));
|
|
|
|
env(noop(alice),
|
|
fee(baseFee + 2 * baseFee),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
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, spfSponsorReserve), ter(terNO_SPONSORSHIP));
|
|
env(noop(alice),
|
|
sponsor::as(noFunded, spfSponsorReserve),
|
|
sig(sfSponsorSignature, noFunded),
|
|
ter(terNO_ACCOUNT));
|
|
|
|
// Invalid Flags
|
|
env(noop(alice),
|
|
sponsor::as(sponsor, (spfSponsorFee | spfSponsorReserve) + 1),
|
|
ter(temINVALID_FLAG));
|
|
|
|
// Invalid Flags without sponsor
|
|
auto tx = noop(alice);
|
|
tx[sfSponsorFlags.jsonName] = spfSponsorFee | spfSponsorReserve;
|
|
env(tx, 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();
|
|
|
|
{
|
|
// create sponsorship
|
|
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::counterpartySponsor(sponsor), ter(tesSUCCESS));
|
|
env.close();
|
|
BEAST_EXPECT(!env.le(keylet::sponsor(sponsor, alice)));
|
|
|
|
// create sponsorship with zero value
|
|
env(sponsor::set(sponsor, 0, 0, XRP(0), XRP(0)),
|
|
sponsor::sponseeAcc(alice),
|
|
fee(XRP(1)));
|
|
env.close();
|
|
|
|
sle = env.le(keylet::sponsor(sponsor, alice));
|
|
BEAST_EXPECT(sle);
|
|
BEAST_EXPECT(!sle->isFieldPresent(sfReserveCount));
|
|
BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount));
|
|
BEAST_EXPECT(!sle->isFieldPresent(sfMaxFee));
|
|
|
|
// update sponsorship with non-zero value
|
|
env(sponsor::set(sponsor, 0, 100, XRP(100), XRP(1)),
|
|
sponsor::sponseeAcc(alice),
|
|
fee(XRP(1)));
|
|
env.close();
|
|
|
|
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));
|
|
|
|
// update sponsorship with zero value
|
|
env(sponsor::set(sponsor, 0, 0, XRP(0), XRP(0)),
|
|
sponsor::sponseeAcc(alice),
|
|
fee(XRP(1)));
|
|
env.close();
|
|
|
|
sle = env.le(keylet::sponsor(sponsor, alice));
|
|
BEAST_EXPECT(sle);
|
|
BEAST_EXPECT(!sle->isFieldPresent(sfReserveCount));
|
|
BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount));
|
|
BEAST_EXPECT(!sle->isFieldPresent(sfMaxFee));
|
|
}
|
|
|
|
{
|
|
// 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::counterpartySponsor(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::counterpartySponsor(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::counterpartySponsor(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::counterpartySponsor(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, spfSponsorReserve | spfSponsorFee),
|
|
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, spfSponsorReserve | spfSponsorFee),
|
|
sig(sfSponsorSignature, sponsor),
|
|
fee(XRP(11)),
|
|
ter(terINSUF_FEE_B));
|
|
env.close();
|
|
|
|
// reserve insufficient
|
|
env(ticket::create(alice, 11),
|
|
sponsor::as(sponsor, spfSponsorReserve | spfSponsorFee),
|
|
sig(sfSponsorSignature, sponsor),
|
|
fee(XRP(1)),
|
|
ter(tecINSUFFICIENT_RESERVE));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testTransferSponsor()
|
|
{
|
|
testcase("Transfer Sponsor");
|
|
using namespace test::jtx;
|
|
|
|
{
|
|
// invalid fields
|
|
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();
|
|
|
|
env(sponsor::transfer(
|
|
alice, (tfSponsorshipCreate | tfSponsorshipReassign | tfSponsorshipEnd) + 1),
|
|
ter(temINVALID_FLAG));
|
|
|
|
// invalid combination of flags
|
|
for (auto flag : {
|
|
tfSponsorshipCreate | tfSponsorshipReassign,
|
|
tfSponsorshipCreate | tfSponsorshipEnd,
|
|
tfSponsorshipReassign | tfSponsorshipEnd,
|
|
tfSponsorshipCreate | tfSponsorshipReassign | tfSponsorshipEnd,
|
|
})
|
|
env(sponsor::transfer(alice, flag), ter(temINVALID_FLAG));
|
|
|
|
// invalid tfSponsorshipCreate
|
|
// no sponsor field present
|
|
env(sponsor::transfer(alice, tfSponsorshipCreate), ter(temINVALID_FLAG));
|
|
// sponsee field present
|
|
env(sponsor::transfer(alice, tfSponsorshipCreate),
|
|
sponsor::sponseeAcc(bob),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
ter(temMALFORMED));
|
|
|
|
// invalid tfSponsorshipReassign
|
|
// no sponsor field present
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign), ter(temINVALID_FLAG));
|
|
// sponsee field present
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign),
|
|
sponsor::sponseeAcc(bob),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
ter(temMALFORMED));
|
|
|
|
// invalid tfSponsorshipEnd
|
|
// sponsor field present
|
|
env(sponsor::transfer(alice, tfSponsorshipEnd),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
ter(temINVALID_FLAG));
|
|
// account = sponsee
|
|
env(sponsor::transfer(alice, tfSponsorshipEnd),
|
|
sponsor::sponseeAcc(alice),
|
|
ter(temMALFORMED));
|
|
}
|
|
|
|
{
|
|
// 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, tfSponsorshipCreate),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
|
|
adjustAccountXRPBalance(env, sponsor1, accountReserve(env, 2) - drops(1));
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipCreate),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor1),
|
|
ter(tecINSUFFICIENT_RESERVE));
|
|
env.close();
|
|
|
|
adjustAccountXRPBalance(env, sponsor1, accountReserve(env, 2));
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipCreate),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
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(sfSponsor));
|
|
BEAST_EXPECT(sle1->getAccountID(sfSponsor) == sponsor1.id());
|
|
|
|
// transfer sponsor
|
|
adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 2) - drops(1));
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2),
|
|
ter(tecINSUFFICIENT_RESERVE));
|
|
env.close();
|
|
|
|
adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 2));
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
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(sfSponsor));
|
|
BEAST_EXPECT(sle2->getAccountID(sfSponsor) == sponsor2.id());
|
|
|
|
// sponsor 2 accounts
|
|
adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 3));
|
|
env(sponsor::transfer(bob, tfSponsorshipCreate),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
|
|
// dissolve sponsors
|
|
adjustAccountXRPBalance(env, alice, accountReserve(env, 1) - drops(1));
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipEnd), ter(tecINSUFFICIENT_RESERVE));
|
|
env.close();
|
|
|
|
adjustAccountXRPBalance(env, alice, accountReserve(env, 1));
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipEnd));
|
|
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(sfSponsor));
|
|
|
|
env(sponsor::transfer(bob, tfSponsorshipEnd));
|
|
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(sfSponsor));
|
|
|
|
// not sponsored
|
|
env(sponsor::transfer(bob, tfSponsorshipEnd), ter(tecNO_PERMISSION));
|
|
env.close();
|
|
}
|
|
{
|
|
// dissolve account sponsorship from sponsor
|
|
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();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipCreate),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.le(alice)->getAccountID(sfSponsor) == sponsor.id());
|
|
BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 1);
|
|
|
|
env(sponsor::transfer(sponsor, tfSponsorshipEnd), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfSponsor));
|
|
BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0);
|
|
}
|
|
|
|
{
|
|
// 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, tfSponsorshipCreate, checkId),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor1),
|
|
ter(tecINSUFFICIENT_RESERVE));
|
|
env.close();
|
|
|
|
env(pay(alice, sponsor1, drops(1)));
|
|
env.close();
|
|
|
|
// Invalid ObjectID (not found)
|
|
env(sponsor::transfer(alice, tfSponsorshipCreate, keylet::check(alice, 0).key),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor1),
|
|
ter(tecNO_ENTRY));
|
|
env.close();
|
|
|
|
// Invalid Owner
|
|
env(sponsor::transfer(bob, tfSponsorshipCreate, checkId),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor1),
|
|
ter(tecNO_PERMISSION));
|
|
env.close();
|
|
|
|
// Valid Owner
|
|
env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
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(sfSponsor));
|
|
BEAST_EXPECT(sle1->getAccountID(sfSponsor) == sponsor1.id());
|
|
|
|
// transfer sponsor
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2),
|
|
ter(tecINSUFFICIENT_RESERVE));
|
|
|
|
env(pay(alice, sponsor2, drops(1)));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
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(sfSponsor));
|
|
BEAST_EXPECT(sle2->getAccountID(sfSponsor) == sponsor2.id());
|
|
|
|
// dissolve sponsor
|
|
adjustAccountXRPBalance(env, alice, reserve(env, 1) - drops(1));
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipEnd, 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, tfSponsorshipEnd, 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, tfSponsorshipEnd, 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(sfSponsor));
|
|
}
|
|
{
|
|
// 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, tfSponsorshipCreate, checkId),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
ter(terNO_SPONSORSHIP));
|
|
env.close();
|
|
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
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, tfSponsorshipCreate, checkId),
|
|
sponsor::as(sponsor1, spfSponsorReserve),
|
|
ter(tecINSUFFICIENT_RESERVE));
|
|
env.close();
|
|
|
|
env(sponsor::set_reserve(sponsor1, 0, 100), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
|
|
sponsor::as(sponsor1, spfSponsorReserve));
|
|
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(sfSponsor));
|
|
BEAST_EXPECT(checkSle->getAccountID(sfSponsor) == 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, tfSponsorshipReassign, checkId),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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(sfSponsor));
|
|
BEAST_EXPECT(checkSle->getAccountID(sfSponsor) == 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, tfSponsorshipEnd, 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(sfSponsor));
|
|
sponsor2Sle = env.le(keylet::sponsor(sponsor2, alice));
|
|
BEAST_EXPECT(sponsor2Sle->getFieldU32(sfReserveCount) == 100); // paybacked
|
|
}
|
|
|
|
{
|
|
// Dissolve object sponsorship from sponsor
|
|
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 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, tfSponsorshipCreate, checkId),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(
|
|
env.le(keylet::unchecked(checkId))->getAccountID(sfSponsor) == sponsor.id());
|
|
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
|
|
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
|
|
|
|
// not the owner of the object
|
|
env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), ter(tecNO_PERMISSION));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(!env.le(keylet::unchecked(checkId))->isFieldPresent(sfSponsor));
|
|
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
|
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
|
}
|
|
|
|
{
|
|
// sponsor trustline
|
|
Account const alice("alice");
|
|
Account const bob("bob");
|
|
Account const sponsor("sponsor");
|
|
|
|
auto const& highAcc = alice > bob ? alice : bob;
|
|
auto const& lowAcc = alice > bob ? bob : alice;
|
|
|
|
for (bool isIssuerHigh : {false, true})
|
|
{
|
|
Env env{*this, testable_amendments()};
|
|
env.fund(XRP(10000), alice, bob, sponsor);
|
|
env.close();
|
|
|
|
auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
|
|
auto const& user = isIssuerHigh ? lowAcc : highAcc;
|
|
|
|
auto const USD = issuer["USD"];
|
|
auto const currency = USD.currency;
|
|
|
|
env(trust(user, issuer["USD"](100)));
|
|
env.close();
|
|
|
|
auto const trustId = keylet::line(user, issuer, currency);
|
|
BEAST_EXPECT(env.le(trustId));
|
|
|
|
// transfer sponsor
|
|
env(sponsor::transfer(user, tfSponsorshipCreate, trustId.key),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.le(trustId));
|
|
|
|
BEAST_EXPECT(
|
|
env.le(trustId)->getAccountID(isIssuerHigh ? sfLowSponsor : sfHighSponsor) ==
|
|
sponsor.id());
|
|
BEAST_EXPECT(
|
|
!env.le(trustId)->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor));
|
|
|
|
// dissolve sponsor
|
|
env(sponsor::transfer(user, tfSponsorshipEnd, trustId.key));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.le(trustId));
|
|
BEAST_EXPECT(
|
|
!env.le(trustId)->isFieldPresent(isIssuerHigh ? sfLowSponsor : sfHighSponsor));
|
|
BEAST_EXPECT(
|
|
!env.le(trustId)->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor));
|
|
}
|
|
}
|
|
|
|
{
|
|
// 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, tfSponsorshipCreate, keylet.key),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
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, spfSponsorFee),
|
|
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, spfSponsorFee),
|
|
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, spfSponsorFee),
|
|
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, spfSponsorFee),
|
|
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, spfSponsorFee),
|
|
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, spfSponsorFee));
|
|
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, spfSponsorFee),
|
|
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, spfSponsorFee),
|
|
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, spfSponsorFee),
|
|
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, spfSponsorFee),
|
|
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, spfSponsorFee),
|
|
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;
|
|
|
|
Account const alice("alice");
|
|
Account const sponsor("sponsor");
|
|
Account const sponsor2("sponsor2");
|
|
Account const bob("bob");
|
|
Account const charlie("charlie");
|
|
Account const gw("gw");
|
|
auto const USD = gw["USD"];
|
|
|
|
{
|
|
// Disabled
|
|
Env env{*this, testable_amendments() - featureSponsor};
|
|
env.fund(XRP(10000), alice, sponsor);
|
|
env.close();
|
|
env(pay(alice, bob, XRP(100)), txflags(tfSponsorCreatedAccount), ter(temDISABLED));
|
|
env.close();
|
|
}
|
|
|
|
Env env{*this, testable_amendments()};
|
|
env.fund(XRP(10000), alice, sponsor, sponsor2);
|
|
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();
|
|
|
|
// Account is not sponsored by normal Sponsor specification
|
|
{
|
|
env(pay(alice, bob, drops(env.current()->fees().accountReserve(0))),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor));
|
|
env.close();
|
|
|
|
auto const bobSle = env.le(keylet::account(bob));
|
|
BEAST_EXPECT(!bobSle->isFieldPresent(sfSponsor));
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
|
BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0);
|
|
}
|
|
|
|
// Use tfSponsorCreatedAccount to sponsor an account
|
|
{
|
|
// to funded account
|
|
env(pay(sponsor2, bob, drops(1)),
|
|
txflags(tfSponsorCreatedAccount),
|
|
fee(XRP(1)),
|
|
ter(tecNO_SPONSOR_PERMISSION));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(sponsor2) == XRP(9999));
|
|
|
|
// to non-funded account / insufficient balance for reserve
|
|
env(pay(sponsor2, charlie, XRP(9999) - env.current()->fees().reserve + drops(1)),
|
|
txflags(tfSponsorCreatedAccount),
|
|
ter(tecUNFUNDED_PAYMENT));
|
|
env.close();
|
|
|
|
// to non-funded account
|
|
env(pay(sponsor2, charlie, drops(1)), txflags(tfSponsorCreatedAccount), fee(XRP(1)));
|
|
env.close();
|
|
|
|
auto const charlieSle = env.le(keylet::account(charlie));
|
|
BEAST_EXPECT(charlieSle->isFieldPresent(sfSponsor));
|
|
BEAST_EXPECT(charlieSle->getAccountID(sfSponsor) == sponsor2.id());
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, charlie) == 0);
|
|
BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 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, spfSponsorReserve),
|
|
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, spfSponsorFee),
|
|
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, spfSponsorReserve), *sponsorSig, ter(_ter));
|
|
else
|
|
env(jv, fN..., sponsor::as(sponsor, spfSponsorReserve), 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(sfSponsor));
|
|
});
|
|
|
|
auto const ammKeylet = keylet::amm(USD.issue(), EUR.issue());
|
|
if (cosigning)
|
|
{
|
|
env(sponsor::transfer(alice, tfSponsorshipCreate, ammKeylet.key),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
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, tfSponsorshipCreate, ammKeylet.key),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
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, spfSponsorReserve),
|
|
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, spfSponsorReserve),
|
|
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, spfSponsorReserve),
|
|
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, spfSponsorReserve),
|
|
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(sfSponsor) == sponsor.id());
|
|
|
|
if (cosigning)
|
|
{
|
|
// transfer sponsor
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
// transfer sponsor
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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(sfSponsor) == 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(sfSponsor) == 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, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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(sfSponsor) == 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, spfSponsorReserve),
|
|
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, spfSponsorReserve));
|
|
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, spfSponsorReserve),
|
|
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, spfSponsorReserve));
|
|
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, spfSponsorReserve),
|
|
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, spfSponsorReserve));
|
|
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(sfSponsor) == sponsor.id());
|
|
|
|
// transfer sponsor
|
|
if (cosigning)
|
|
{
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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(sfSponsor) == 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, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(issuer));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(issuer, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(subject));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(subject, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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(sfSponsor));
|
|
|
|
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, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
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, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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(sfSponsor) == sponsor.id());
|
|
|
|
// transfer sponsor
|
|
if (cosigning)
|
|
{
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::escrow(alice, seq).key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::escrow(alice, seq).key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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(sfSponsor) == 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(sfSponsor) == 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());
|
|
}
|
|
{
|
|
// MPT Escrow
|
|
Env env{*this, testable_amendments()};
|
|
env.fund(XRP(1000000), bob, sponsor);
|
|
env.close();
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice}});
|
|
mptGw.create(
|
|
{.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env.close();
|
|
|
|
// create Escrow from alice to bob
|
|
auto const seq = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(100)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::cancel_time(env.now() + 100s));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(ownerCount(env, alice) == 2);
|
|
BEAST_EXPECT(ownerCount(env, bob) == 0);
|
|
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
|
|
|
// finish Escrow
|
|
env(escrow::finish(bob, alice, seq),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor),
|
|
fee(XRP(1)));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
|
BEAST_EXPECT(ownerCount(env, bob) == 1);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
|
|
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
|
|
}
|
|
}
|
|
|
|
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, tfSponsorshipReassign, mptIssuanceKeylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, mptIssuanceKeylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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, tfSponsorshipReassign, mptTokenKeylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(bob));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(bob, tfSponsorshipReassign, mptTokenKeylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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, spfSponsorReserve),
|
|
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, spfSponsorReserve), 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, spfSponsorReserve),
|
|
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, spfSponsorReserve), 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, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
}
|
|
// 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, spfSponsorReserve),
|
|
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, spfSponsorReserve),
|
|
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, spfSponsorReserve));
|
|
}
|
|
}
|
|
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, tfSponsorshipReassign, offerIndex1),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, offerIndex1),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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, tfSponsorshipReassign, chan),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, chan),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 2), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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, tfSponsorshipReassign, keylet::oracle(alice, 1).key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, ocount), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
env(sponsor::transfer(
|
|
alice, tfSponsorshipReassign, keylet::oracle(alice, 1).key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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);
|
|
|
|
// dissolve sponsor
|
|
env(sponsor::transfer(alice, tfSponsorshipEnd, 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, tfSponsorshipReassign, keylet::signers(alice).key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
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, tfSponsorshipReassign, keylet::signers(bob).key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2),
|
|
ter(tecNO_PERMISSION));
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signers(alice).key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(alice));
|
|
env.close();
|
|
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signers(alice).key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2),
|
|
ter(tecNO_PERMISSION));
|
|
// invalid reserve owner
|
|
env(sponsor::transfer(issuer, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2),
|
|
ter(tecNO_PERMISSION));
|
|
env(sponsor::transfer(user, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor2));
|
|
env.close();
|
|
}
|
|
else
|
|
{
|
|
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::sponseeAcc(user));
|
|
env.close();
|
|
env(sponsor::transfer(user, tfSponsorshipReassign, keylet.key),
|
|
sponsor::as(sponsor2, spfSponsorReserve));
|
|
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)));
|
|
}
|
|
|
|
// both High and Low sponsored
|
|
{
|
|
Env env{*this, testable_amendments()};
|
|
env.fund(XRP(1000000), alice, bob, sponsor);
|
|
env.close();
|
|
|
|
// create TrustLines
|
|
env(trust(alice, bob["USD"](100)),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor));
|
|
env.close();
|
|
env(trust(bob, alice["USD"](100)),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor));
|
|
env.close();
|
|
|
|
auto sle = env.le(keylet::line(alice, bob, alice["USD"].currency));
|
|
BEAST_EXPECT(sle);
|
|
BEAST_EXPECT(sle->isFlag(lsfHighReserve));
|
|
BEAST_EXPECT(sle->isFlag(lsfLowReserve));
|
|
BEAST_EXPECT(sle->getAccountID(sfHighSponsor) == sponsor.id());
|
|
BEAST_EXPECT(sle->getAccountID(sfLowSponsor) == sponsor.id());
|
|
|
|
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
|
|
BEAST_EXPECT(ownerCount(env, bob) == 1);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
|
|
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
|
|
|
|
// clear TrustLines
|
|
env(trust(alice, bob["USD"](0)));
|
|
env.close();
|
|
env(trust(bob, alice["USD"](0)));
|
|
env.close();
|
|
|
|
sle = env.le(keylet::line(alice, bob, alice["USD"].currency));
|
|
BEAST_EXPECT(!sle);
|
|
BEAST_EXPECT(ownerCount(env, alice) == 0);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
|
BEAST_EXPECT(ownerCount(env, bob) == 0);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
|
|
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
|
}
|
|
}
|
|
|
|
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(sfSponsor) == 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, spfSponsorReserve),
|
|
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, spfSponsorReserve), 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, spfSponsorReserve),
|
|
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, spfSponsorReserve));
|
|
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, spfSponsorReserve),
|
|
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, spfSponsorReserve));
|
|
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, spfSponsorReserve),
|
|
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
|
|
testLending(bool cosigning)
|
|
{
|
|
testcase("Lending");
|
|
using namespace test::jtx;
|
|
Account const alice("alice");
|
|
Account const bob("bob");
|
|
Account const issuer("issuer");
|
|
Account const sponsor("sponsor");
|
|
|
|
// LoanBrokerSet / LoanBrokerDelete
|
|
{
|
|
Env env{*this, testable_amendments()};
|
|
env.fund(XRP(1000000), alice, bob, sponsor);
|
|
env.close();
|
|
|
|
PrettyAsset const asset{xrpIssue(), 1'000'000};
|
|
|
|
Vault vault{env};
|
|
auto const [tx, keylet] = vault.create({.owner = alice, .asset = asset});
|
|
env(tx);
|
|
env.close();
|
|
|
|
BEAST_EXPECT(
|
|
ownerCount(env, alice) == 3); // Vault, PseudoAccount(Vault), MPToken(Vault)
|
|
|
|
// LoanBrokerSet
|
|
testEachSponsorship(
|
|
// Both the Pseudo-account and LoanBroker objects are created, but only the
|
|
// LoanBroker is sponsored.
|
|
env,
|
|
cosigning,
|
|
sponsor,
|
|
alice,
|
|
2,
|
|
1,
|
|
tecINSUFFICIENT_RESERVE,
|
|
[&](Env& env, auto const& submit) {
|
|
submit(loanBroker::set(alice, keylet.key, 0));
|
|
});
|
|
|
|
BEAST_EXPECT(
|
|
ownerCount(env, alice) ==
|
|
5); // LoanBroker, PseudoAccount(LB), (Vault, PseudoAccount(Vault), MPToken(Vault))
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
|
|
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
|
|
|
|
// LoanBrokerDelete
|
|
auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice) - 1);
|
|
env(loanBroker::del(alice, brokerKeylet.key, 0));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(ownerCount(env, alice) == 3);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
|
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
|
}
|
|
|
|
// LoanBrokerConverDeposit/Withdraw/Clawback
|
|
{
|
|
Env env{*this, testable_amendments()};
|
|
env.fund(XRP(1000), alice, bob, issuer, sponsor);
|
|
env.close();
|
|
|
|
MPTTester mptt{env, issuer, mptInitNoFund};
|
|
mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
|
|
env.close();
|
|
PrettyAsset const asset = mptt["MPT"];
|
|
mptt.authorize({.account = alice});
|
|
env.close();
|
|
|
|
env(pay(issuer, alice, asset(100)));
|
|
env.close();
|
|
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
|
|
|
Vault vault{env};
|
|
auto const [tx, keylet] = vault.create({.owner = alice, .asset = asset});
|
|
env(tx);
|
|
env.close();
|
|
|
|
env(loanBroker::set(alice, keylet.key, 0));
|
|
env.close();
|
|
BEAST_EXPECT(
|
|
ownerCount(env, alice) ==
|
|
6); // LoanBroker, PseudoAccount(LB), (Vault, PseudoAccount(Vault),
|
|
// MPToken(Vault), MPToken(issuer))
|
|
|
|
auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice) - 1);
|
|
// LoanBrokerCoverDeposit
|
|
// doesn't sponsor anything
|
|
env(loanBroker::coverDeposit(alice, brokerKeylet.key, asset(100)),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor));
|
|
env.close();
|
|
BEAST_EXPECT(ownerCount(env, alice) == 6);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
|
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
|
|
|
// remove MPToken(issuer)
|
|
mptt.authorize({.account = alice, .flags = tfMPTUnauthorize});
|
|
env.close();
|
|
BEAST_EXPECT(ownerCount(env, alice) == 5);
|
|
|
|
env(ticket::create(sponsor, 2)); // for avoid free MPToken
|
|
env.close();
|
|
|
|
// LoanBrokerCoverWithdraw
|
|
testEachSponsorship(
|
|
env,
|
|
cosigning,
|
|
sponsor,
|
|
alice,
|
|
1,
|
|
1,
|
|
tecINSUFFICIENT_RESERVE,
|
|
[&](Env& env, auto const& submit) {
|
|
submit(loanBroker::coverWithdraw(alice, brokerKeylet.key, asset(10)));
|
|
});
|
|
|
|
BEAST_EXPECT(ownerCount(env, alice) == 6);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
|
|
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
|
|
|
|
// LoanBrokerCoverClawback
|
|
// doesn't sponsor anything
|
|
env(loanBroker::coverClawback(issuer),
|
|
loanBroker::loanBrokerID(brokerKeylet.key),
|
|
amount(asset(1)),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(ownerCount(env, alice) == 6);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
|
|
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
|
|
}
|
|
// LoanSet
|
|
{
|
|
Env env{*this, testable_amendments()};
|
|
env.fund(XRP(1000000), alice, bob, issuer, sponsor);
|
|
env.close();
|
|
|
|
MPTTester mptt{env, issuer, mptInitNoFund};
|
|
mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
|
|
env.close();
|
|
PrettyAsset const asset = mptt["MPT"];
|
|
mptt.authorize({.account = alice});
|
|
mptt.authorize({.account = bob});
|
|
env.close();
|
|
|
|
env(pay(issuer, alice, asset(1000)));
|
|
env(pay(issuer, bob, asset(1000)));
|
|
env.close();
|
|
|
|
Vault vault{env};
|
|
auto const [tx, keylet] = vault.create({.owner = bob, .asset = asset});
|
|
env(tx);
|
|
env.close();
|
|
env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(100)}));
|
|
env.close();
|
|
|
|
auto const brokerKeylet = keylet::loanbroker(bob.id(), env.seq(bob));
|
|
env(loanBroker::set(bob, keylet.key, 0));
|
|
env.close();
|
|
env(loanBroker::coverDeposit(bob, brokerKeylet.key, asset(100)));
|
|
env.close();
|
|
|
|
auto broker = env.le(brokerKeylet);
|
|
BEAST_EXPECT(broker->getFieldU32(sfOwnerCount) == 0);
|
|
BEAST_EXPECT(!broker->isFieldPresent(sfSponsoredOwnerCount));
|
|
BEAST_EXPECT(!broker->isFieldPresent(sfSponsoringOwnerCount));
|
|
|
|
auto const loanSeq = broker->getFieldU32(sfLoanSequence);
|
|
testEachSponsorship(
|
|
env,
|
|
cosigning,
|
|
sponsor,
|
|
alice,
|
|
1,
|
|
1,
|
|
tecINSUFFICIENT_RESERVE,
|
|
[&](Env& env, auto const& submit) {
|
|
submit(
|
|
loan::set(alice, brokerKeylet.key, 10),
|
|
sig(sfCounterpartySignature, bob),
|
|
fee(XRP(1)));
|
|
});
|
|
broker = env.le(brokerKeylet);
|
|
// broker'object doesn't sponsored
|
|
BEAST_EXPECT(broker->getFieldU32(sfOwnerCount) == 1);
|
|
BEAST_EXPECT(!broker->isFieldPresent(sfSponsoredOwnerCount));
|
|
BEAST_EXPECT(!broker->isFieldPresent(sfSponsoringOwnerCount));
|
|
|
|
auto const loanKeylet = keylet::loan(brokerKeylet.key, loanSeq);
|
|
|
|
auto sponsorSle = env.le(keylet::account(sponsor));
|
|
BEAST_EXPECT(sponsorSle->getFieldU32(sfOwnerCount) == 0);
|
|
BEAST_EXPECT(!sponsorSle->isFieldPresent(sfSponsoredOwnerCount));
|
|
BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringOwnerCount) == 1);
|
|
|
|
// LoanManage
|
|
env(loan::manage(bob, loanKeylet.key, lsfLoanImpaired),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor));
|
|
env.close();
|
|
|
|
// doesn't sponsor anything
|
|
sponsorSle = env.le(keylet::account(sponsor));
|
|
BEAST_EXPECT(sponsorSle->getFieldU32(sfOwnerCount) == 0);
|
|
BEAST_EXPECT(!sponsorSle->isFieldPresent(sfSponsoredOwnerCount));
|
|
BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringOwnerCount) == 1);
|
|
|
|
// LoanPay
|
|
env(loan::pay(alice, loanKeylet.key, asset(10)),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor));
|
|
env.close();
|
|
|
|
// doesn't sponsor anything
|
|
sponsorSle = env.le(keylet::account(sponsor));
|
|
BEAST_EXPECT(sponsorSle->getFieldU32(sfOwnerCount) == 0);
|
|
BEAST_EXPECT(!sponsorSle->isFieldPresent(sfSponsoredOwnerCount));
|
|
BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringOwnerCount) == 1);
|
|
|
|
BEAST_EXPECT(ownerCount(env, alice) == 2);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
|
|
|
|
// LoanDelete
|
|
env(loan::del(alice, loanKeylet.key),
|
|
sponsor::as(sponsor, spfSponsorReserve),
|
|
sig(sfSponsorSignature, sponsor));
|
|
env.close();
|
|
|
|
// Sponsored ltLoan is deleted
|
|
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
|
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
|
// Sponsor for ltLoan object is deleted
|
|
sponsorSle = env.le(keylet::account(sponsor));
|
|
BEAST_EXPECT(sponsorSle->getFieldU32(sfOwnerCount) == 0);
|
|
BEAST_EXPECT(!sponsorSle->isFieldPresent(sfSponsoredOwnerCount));
|
|
}
|
|
}
|
|
|
|
void
|
|
testAccountDelete()
|
|
{
|
|
testcase("AccountDelete");
|
|
using namespace test::jtx;
|
|
Account const alice("alice");
|
|
Account const bob("bob");
|
|
Account const sponsor("sponsor");
|
|
|
|
{
|
|
// Delete Sponsee 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 Sponsor 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, sponsor);
|
|
|
|
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(sponsor, alice), 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, tfSponsorshipCreate, keylet.key),
|
|
sponsor::as(bob, spfSponsorReserve),
|
|
sig(sfSponsorSignature, bob),
|
|
delegate::as(carol),
|
|
ter(terNO_DELEGATE_PERMISSION));
|
|
|
|
env(delegate::set(alice, carol, {"SponsorshipTransfer"}));
|
|
env.close();
|
|
|
|
env(sponsor::transfer(alice, tfSponsorshipCreate, keylet.key),
|
|
sponsor::as(bob, spfSponsorReserve),
|
|
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, spfSponsorReserve | spfSponsorFee),
|
|
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, spfSponsorReserve | spfSponsorFee),
|
|
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, spfSponsorReserve | spfSponsorFee),
|
|
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, spfSponsorReserve | spfSponsorFee));
|
|
// 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);
|
|
testLending(cosigning);
|
|
}
|
|
|
|
protected:
|
|
void
|
|
testSponsor()
|
|
{
|
|
testDisabled();
|
|
testInvalidSponsorshipSet();
|
|
|
|
testSingleSigning();
|
|
testMultiSigning();
|
|
|
|
testInvalidSponsorField();
|
|
|
|
testSimpleSponsorshipSet();
|
|
|
|
testPreFundAndCosign();
|
|
|
|
testTransferSponsor();
|
|
testSponsorFee();
|
|
testSponsorAccount();
|
|
|
|
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
|