Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/xrpl/protocol/detail/transactions.macro
Original file line number Diff line number Diff line change
Expand Up @@ -1084,7 +1084,7 @@ TRANSACTION(ttLOAN_PAY, 84, LoanPay,
TRANSACTION(ttSPONSORSHIP_TRANSFER, 85, SponsorshipTransfer,
Delegation::Delegable,
featureSponsor,
NoPriv,
MayModifyVault,
({
{sfObjectID, SoeOptional},
{sfSponsee, SoeOptional},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class SponsorshipTransferBuilder;
* Type: ttSPONSORSHIP_TRANSFER (85)
* Delegable: Delegation::Delegable
* Amendment: featureSponsor
* Privileges: NoPriv
* Privileges: MayModifyVault
*
* Immutable wrapper around STTx providing type-safe field access.
* Use SponsorshipTransferBuilder to construct new transactions.
Expand Down
4 changes: 4 additions & 0 deletions src/libxrpl/tx/invariants/VaultInvariant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,10 @@ ValidVault::finalize(
// TBD
return true;
}
case ttSPONSORSHIP_TRANSFER: {
// SponsorshipTransfer may update a vault's sfSponsor
return true;
}

default:
// LCOV_EXCL_START
Expand Down
3 changes: 3 additions & 0 deletions src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ getLedgerEntryOwnerCount(T const& sle)
case ltORACLE: {
return OracleSet::calculateOracleReserve(sle->getFieldArray(sfPriceDataSeries).size());
}
// Vaults require 2 owner counts (the vault and a pseudo-account)
case ltVAULT:
return 2;
default:
return 1;
}
Expand Down
3 changes: 3 additions & 0 deletions src/libxrpl/tx/transactors/payment/Payment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ Payment::preflight(PreflightContext const& ctx)
if (tx.isFlag(tfNoRippleDirect) || tx.isFlag(tfPartialPayment) || tx.isFlag(tfLimitQuality))
return temINVALID_FLAG;

if (tx.isFieldPresent(sfSendMax) || tx.isFieldPresent(sfPaths))
return temINVALID;

if (!dstAmount.native())
return temBAD_AMOUNT;
}
Expand Down
71 changes: 71 additions & 0 deletions src/test/app/Sponsor_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
#include <test/jtx/multisign.h>
#include <test/jtx/noop.h>
#include <test/jtx/offer.h>
#include <test/jtx/paths.h>
#include <test/jtx/pay.h>
#include <test/jtx/permissioned_domains.h>
#include <test/jtx/sendmax.h>
#include <test/jtx/seq.h>
#include <test/jtx/sig.h>
#include <test/jtx/sponsor.h>
Expand Down Expand Up @@ -1848,6 +1850,20 @@ class Sponsor_test : public beast::unit_test::Suite
env(pay(alice, bob, usd(100)), Txflags(tfSponsorCreatedAccount), Ter(temBAD_AMOUNT));
env.close();

// Sponsored account creation is reserve sponsorship and is only supported for direct XRP
// payments.
env(pay(alice, bob, drops(1)),
Txflags(tfSponsorCreatedAccount),
Sendmax(usd(2)),
Ter(temINVALID));
env.close();

env(pay(alice, bob, drops(1)),
Txflags(tfSponsorCreatedAccount),
Path(~XRP),
Ter(temINVALID));
env.close();

// Account is not sponsored by normal Sponsor specification
{
env(pay(alice, bob, drops(baseAccountReserve(*env.current(), 0))),
Expand Down Expand Up @@ -4985,6 +5001,61 @@ class Sponsor_test : public beast::unit_test::Suite
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
// SponsorshipTransfer
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();

Vault const vault{env};
auto const [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
env(tx);
env.close();

// Alice owns the vault, pseudo account and MPToken
BEAST_EXPECT(ownerCount(env, alice) == 3);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);

if (cosigning)
{
// Alice lets sponsor to sponsor her Vault
env(sponsor::transfer(alice, tfSponsorshipCreate, vaultKeylet.key),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor));
env.close();
}
else
{
// Create sponsorship with reserve count being 2 (for vault and pseudo account)
env(sponsor::set_reserve(sponsor, 0, 2), sponsor::SponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, tfSponsorshipCreate, vaultKeylet.key),
sponsor::As(sponsor, spfSponsorReserve));
env.close();

auto const sponsorshipSle = env.le(keylet::sponsor(sponsor, alice));
if (!BEAST_EXPECT(sponsorshipSle))
return;
BEAST_EXPECT(sponsorshipSle->getFieldU32(sfReserveCount) == 0);
}

BEAST_EXPECT(env.le(vaultKeylet)->getAccountID(sfSponsor) == sponsor.id());
BEAST_EXPECT(ownerCount(env, alice) == 3);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2);
// Vault counts for 2 reserves, vault and the pseudo account
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);

// End sponsorship
env(sponsor::transfer(alice, tfSponsorshipEnd, vaultKeylet.key));
env.close();

BEAST_EXPECT(!env.le(vaultKeylet)->isFieldPresent(sfSponsor));
BEAST_EXPECT(ownerCount(env, alice) == 3);
// Sponsorship ended and the sponsored owner count should be 0.
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
}

void
Expand Down
Loading