mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 09:16:47 +00:00
v2. SponsorSet
This commit is contained in:
@@ -174,6 +174,10 @@ static ticket_t const ticket{};
|
||||
Keylet
|
||||
signers(AccountID const& account) noexcept;
|
||||
|
||||
/** A Sponsor */
|
||||
Keylet
|
||||
sponsor(AccountID const& sponsor, AccountID const& sponsee) noexcept;
|
||||
|
||||
/** A Check */
|
||||
/** @{ */
|
||||
Keylet
|
||||
|
||||
@@ -149,6 +149,8 @@ enum LedgerSpecificFlags {
|
||||
0x40000000, // True, enable trustline locking
|
||||
lsfAllowTrustLineClawback =
|
||||
0x80000000, // True, enable clawback
|
||||
lsfDisallowIncomingSponsor =
|
||||
0x00004000, // True, reject new sponsor
|
||||
|
||||
// ltOFFER
|
||||
lsfPassive = 0x00010000,
|
||||
@@ -196,6 +198,10 @@ enum LedgerSpecificFlags {
|
||||
|
||||
// ltVAULT
|
||||
lsfVaultPrivate = 0x00010000,
|
||||
|
||||
// ltSPONSORSHIP
|
||||
lsfSponsorshipRequireSignForFee = 0x00010000,
|
||||
lsfSponsorshipRequireSignForReserve = 0x00020000,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -362,6 +362,7 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecPSEUDO_ACCOUNT = 196,
|
||||
tecPRECISION_LOSS = 197,
|
||||
tecNO_DELEGATE_PERMISSION = 198,
|
||||
tecNO_SPONSOR_PERMISSION = 199,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -62,9 +62,10 @@ constexpr std::uint32_t tfInnerBatchTxn = 0x40000000;
|
||||
constexpr std::uint32_t tfUniversal = tfFullyCanonicalSig | tfInnerBatchTxn;
|
||||
constexpr std::uint32_t tfUniversalMask = ~tfUniversal;
|
||||
|
||||
// Sponsor flags:
|
||||
// Sponsor flags (Global):
|
||||
constexpr std::uint32_t tfSponsorFee = 0x00000001;
|
||||
constexpr std::uint32_t tfSponsorReserve = 0x00000002;
|
||||
constexpr std::uint32_t tfSponsorMask = tfSponsorFee | tfSponsorReserve;
|
||||
|
||||
// AccountSet flags:
|
||||
constexpr std::uint32_t tfRequireDestTag = 0x00010000;
|
||||
@@ -97,6 +98,7 @@ constexpr std::uint32_t asfDisallowIncomingPayChan = 14;
|
||||
constexpr std::uint32_t asfDisallowIncomingTrustline = 15;
|
||||
constexpr std::uint32_t asfAllowTrustLineClawback = 16;
|
||||
constexpr std::uint32_t asfAllowTrustLineLocking = 17;
|
||||
constexpr std::uint32_t asfDisallowIncomingSponsor = 19;
|
||||
|
||||
// OfferCreate flags:
|
||||
constexpr std::uint32_t tfPassive = 0x00010000;
|
||||
@@ -253,6 +255,14 @@ constexpr std::uint32_t tfIndependent = 0x00080000;
|
||||
constexpr std::uint32_t const tfBatchMask =
|
||||
~(tfUniversal | tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent) | tfInnerBatchTxn;
|
||||
|
||||
// SponsorSet flags:
|
||||
constexpr std::uint32_t tfSponsorshipSetRequireSignForFee = 0x00010000;
|
||||
constexpr std::uint32_t tfSponsorshipClearRequireSignForFee = 0x00020000;
|
||||
constexpr std::uint32_t tfSponsorshipSetRequireSignForReserve = 0x00040000;
|
||||
constexpr std::uint32_t tfSponsorshipClearRequireSignForReserve = 0x00080000;
|
||||
constexpr std::uint32_t tfDeleteObject = 0x00100000;
|
||||
constexpr std::uint32_t tfSponsorSetMask = ~(tfUniversal | tfSponsorshipSetRequireSignForFee | tfSponsorshipClearRequireSignForFee | tfSponsorshipSetRequireSignForReserve | tfSponsorshipClearRequireSignForReserve | tfDeleteObject);
|
||||
|
||||
// clang-format on
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -507,5 +507,17 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
|
||||
// no PermissionedDomainID ever (use MPTIssuance.sfDomainID)
|
||||
}))
|
||||
|
||||
/** A ledger object representing a sponsorship.
|
||||
\sa keylet::sponsor
|
||||
*/
|
||||
LEDGER_ENTRY(ltSPONSORSHIP, 0x0085, Sponsorship, sponsorship, ({
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfSponsee, soeREQUIRED},
|
||||
{sfSponsorNode, soeREQUIRED},
|
||||
{sfSponseeNode, soeREQUIRED},
|
||||
{sfFeeAmount, soeOPTIONAL},
|
||||
{sfReserveCount, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
#undef EXPAND
|
||||
#undef LEDGER_ENTRY_DUPLICATE
|
||||
|
||||
@@ -117,6 +117,7 @@ TYPED_SFIELD(sfPermissionValue, UINT32, 52)
|
||||
TYPED_SFIELD(sfSponsoredOwnerCount, UINT32, 53)
|
||||
TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 54)
|
||||
TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 55)
|
||||
TYPED_SFIELD(sfReserveCount, UINT32, 56)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -148,6 +149,8 @@ TYPED_SFIELD(sfMPTAmount, UINT64, 26, SField::sMD_BaseTen|SFie
|
||||
TYPED_SFIELD(sfIssuerNode, UINT64, 27)
|
||||
TYPED_SFIELD(sfSubjectNode, UINT64, 28)
|
||||
TYPED_SFIELD(sfLockedAmount, UINT64, 29, SField::sMD_BaseTen|SField::sMD_Default)
|
||||
TYPED_SFIELD(sfSponsorNode, UINT64, 30)
|
||||
TYPED_SFIELD(sfSponseeNode, UINT64, 31)
|
||||
|
||||
// 128-bit
|
||||
TYPED_SFIELD(sfEmailHash, UINT128, 1)
|
||||
@@ -200,6 +203,7 @@ TYPED_SFIELD(sfHookSetTxnID, UINT256, 33)
|
||||
TYPED_SFIELD(sfDomainID, UINT256, 34)
|
||||
TYPED_SFIELD(sfVaultID, UINT256, 35)
|
||||
TYPED_SFIELD(sfParentBatchID, UINT256, 36)
|
||||
TYPED_SFIELD(sfObjectID, UINT256, 37)
|
||||
|
||||
// number (common)
|
||||
TYPED_SFIELD(sfNumber, NUMBER, 1)
|
||||
@@ -244,6 +248,7 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
|
||||
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
|
||||
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
|
||||
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
|
||||
TYPED_SFIELD(sfFeeAmount, AMOUNT, 32)
|
||||
|
||||
// variable length (common)
|
||||
TYPED_SFIELD(sfPublicKey, VL, 1)
|
||||
@@ -293,6 +298,7 @@ TYPED_SFIELD(sfEmitCallback, ACCOUNT, 10)
|
||||
TYPED_SFIELD(sfHolder, ACCOUNT, 11)
|
||||
TYPED_SFIELD(sfDelegate, ACCOUNT, 12)
|
||||
TYPED_SFIELD(sfSponsorAccount, ACCOUNT, 13)
|
||||
TYPED_SFIELD(sfSponsee, ACCOUNT, 14)
|
||||
|
||||
// account (uncommon)
|
||||
TYPED_SFIELD(sfHookAccount, ACCOUNT, 16)
|
||||
|
||||
@@ -526,9 +526,17 @@ TRANSACTION(ttBATCH, 71, Batch, Delegation::notDelegatable, ({
|
||||
{sfBatchSigners, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction transfer sponsor */
|
||||
TRANSACTION(ttSPONSOR_TRANSFER, 72, SponsorTransfer, Delegation::notDelegatable, ({
|
||||
{sfLedgerIndex, soeOPTIONAL},
|
||||
/** This transaction transfer sponsorship */
|
||||
TRANSACTION(ttSPONSORSHIP_TRANSFER, 72, SponsorTransfer, Delegation::notDelegatable, ({
|
||||
{sfObjectID, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction create sponsorship object */
|
||||
TRANSACTION(ttSPONSORSHIP_SET, 73, SponsorSet, Delegation::notDelegatable, ({
|
||||
{sfSponsorAccount, soeOPTIONAL},
|
||||
{sfSponsee, soeREQUIRED},
|
||||
{sfFeeAmount, soeOPTIONAL},
|
||||
{sfReserveCount, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the status of the various amendments.
|
||||
|
||||
@@ -540,7 +540,7 @@ JSS(reserve_inc_xrp); // out: NetworkOPs
|
||||
JSS(response); // websocket
|
||||
JSS(result); // RPC
|
||||
JSS(ripple_lines); // out: NetworkOPs
|
||||
JSS(ripple_state); // in: LedgerEntr
|
||||
JSS(ripple_state); // in: LedgerEntry
|
||||
JSS(ripplerpc); // ripple RPC version
|
||||
JSS(role); // out: Ping.cpp
|
||||
JSS(rpc);
|
||||
@@ -580,6 +580,7 @@ JSS(source_account); // in: PathRequest, RipplePathFind
|
||||
JSS(source_amount); // in: PathRequest, RipplePathFind
|
||||
JSS(source_currencies); // in: PathRequest, RipplePathFind
|
||||
JSS(source_tag); // out: AccountChannels
|
||||
JSS(sponsee); // in: LedgerEntry
|
||||
JSS(stand_alone); // out: NetworkOPs
|
||||
JSS(standard_deviation); // out: get_aggregate_price
|
||||
JSS(start); // in: TxHistory
|
||||
|
||||
@@ -96,6 +96,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
PERMISSIONED_DOMAIN = 'm',
|
||||
DELEGATE = 'E',
|
||||
VAULT = 'V',
|
||||
SPONSORSHIP = 'N',
|
||||
|
||||
// No longer used or supported. Left here to reserve the space
|
||||
// to avoid accidental reuse.
|
||||
@@ -332,6 +333,14 @@ signers(AccountID const& account) noexcept
|
||||
return signers(account, 0);
|
||||
}
|
||||
|
||||
Keylet
|
||||
sponsor(AccountID const& sponsor, AccountID const& sponsee) noexcept
|
||||
{
|
||||
return {
|
||||
ltSPONSORSHIP,
|
||||
indexHash(LedgerNameSpace::SPONSORSHIP, sponsor, sponsee)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
check(AccountID const& id, std::uint32_t seq) noexcept
|
||||
{
|
||||
|
||||
@@ -128,6 +128,7 @@ transResults()
|
||||
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tecNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
|
||||
MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Does not have permission to sponsored this transaction."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
|
||||
@@ -49,6 +49,110 @@ public:
|
||||
ter(temDISABLED));
|
||||
|
||||
env(sponsor::transfer(alice), ter(temDISABLED));
|
||||
env(sponsor::set(sponsor, alice, 0), ter(temDISABLED));
|
||||
}
|
||||
|
||||
void
|
||||
testInvalidSponsorSet()
|
||||
{
|
||||
testcase("Invalid SponsorSet");
|
||||
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, alice, ~tfSponsorSetMask - tfInnerBatchTxn),
|
||||
ter(temINVALID_FLAG));
|
||||
|
||||
env(sponsor::set(
|
||||
sponsor,
|
||||
alice,
|
||||
tfSponsorshipSetRequireSignForFee |
|
||||
tfSponsorshipClearRequireSignForFee),
|
||||
ter(temINVALID_FLAG));
|
||||
|
||||
env(sponsor::set(
|
||||
sponsor,
|
||||
alice,
|
||||
tfSponsorshipSetRequireSignForReserve |
|
||||
tfSponsorshipClearRequireSignForReserve),
|
||||
ter(temINVALID_FLAG));
|
||||
|
||||
for (auto flag :
|
||||
{tfSponsorshipSetRequireSignForFee,
|
||||
tfSponsorshipClearRequireSignForFee,
|
||||
tfSponsorshipSetRequireSignForReserve,
|
||||
tfSponsorshipClearRequireSignForReserve})
|
||||
{
|
||||
env(sponsor::set(sponsor, alice, tfDeleteObject | flag),
|
||||
ter(temINVALID_FLAG));
|
||||
}
|
||||
}
|
||||
|
||||
// invalid SponsorAccount
|
||||
env(sponsor::set(alice, sponsor, tfDeleteObject),
|
||||
sponsor::sponsorAcc(alice),
|
||||
ter(temMALFORMED));
|
||||
env(sponsor::set(alice, sponsor, tfDeleteObject),
|
||||
sponsor::sponsorAcc(bob),
|
||||
ter(temMALFORMED));
|
||||
env(sponsor::set(alice, alice, 0),
|
||||
sponsor::sponsorAcc(sponsor),
|
||||
ter(temMALFORMED));
|
||||
|
||||
// Invalid Sponsee
|
||||
env(sponsor::set(sponsor, sponsor, 0), ter(temMALFORMED));
|
||||
|
||||
// Invalid feeAmount
|
||||
env(sponsor::set(
|
||||
sponsor, alice, tfSponsorshipClearRequireSignForFee, 0, XRP(1)),
|
||||
ter(temMALFORMED));
|
||||
|
||||
for (auto amt : {XRP(-1), XRP(0), USD(1)})
|
||||
{
|
||||
env(sponsor::set(sponsor, alice, 0, 1, amt), ter(temBAD_AMOUNT));
|
||||
}
|
||||
|
||||
// Invalid reserveCount
|
||||
env(sponsor::set(
|
||||
sponsor, alice, tfSponsorshipClearRequireSignForReserve, 1),
|
||||
ter(temMALFORMED));
|
||||
env(sponsor::set(sponsor, alice, 0, 0), ter(temMALFORMED));
|
||||
|
||||
// Invalid Delete operation
|
||||
env(sponsor::set(sponsor, alice, tfDeleteObject, 1), ter(temMALFORMED));
|
||||
env(sponsor::set(sponsor, alice, tfDeleteObject, std::nullopt, XRP(1)),
|
||||
ter(temMALFORMED));
|
||||
|
||||
//
|
||||
// preclaim
|
||||
//
|
||||
|
||||
// Invalid Sponsee
|
||||
env(sponsor::set(sponsor, noFunded, 0), ter(tecNO_DST));
|
||||
|
||||
// Invalid Delete operation (not found)
|
||||
env(sponsor::set(sponsor, alice, tfDeleteObject), ter(tecNO_ENTRY));
|
||||
|
||||
// DisallowIncomingSponsor: tested in other testcase
|
||||
|
||||
// create sponsor to use above tests
|
||||
env(sponsor::set(sponsor, alice, 0, 100, XRP(100)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -116,12 +220,6 @@ public:
|
||||
env(signers(sponsor, 1, {{signer1, 1}, {signer2, 1}}));
|
||||
env.close();
|
||||
|
||||
// Signature doesn't exist
|
||||
env(noop(alice),
|
||||
fee(XRP(1)),
|
||||
sponsor::as(sponsor, tfSponsorReserve),
|
||||
ter(telENV_RPC_FAILED));
|
||||
|
||||
// Invalid signature
|
||||
auto tx = noop(alice);
|
||||
auto& signers1 =
|
||||
@@ -367,19 +465,57 @@ public:
|
||||
using namespace test::jtx;
|
||||
|
||||
testcase("Sponsor Fee");
|
||||
Env env{*this, testable_amendments()};
|
||||
Account const alice("alice");
|
||||
Account const sponsor("sponsor");
|
||||
env.fund(XRP(10000), alice, sponsor);
|
||||
|
||||
env(noop(alice),
|
||||
fee(XRP(1)),
|
||||
sponsor::as(sponsor, tfSponsorFee),
|
||||
sponsor::sig(sponsor));
|
||||
env.close();
|
||||
{
|
||||
// co-signing
|
||||
Env env{*this, testable_amendments()};
|
||||
Account const alice("alice");
|
||||
Account const sponsor("sponsor");
|
||||
env.fund(XRP(10000), alice, sponsor);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(alice) == XRP(10000));
|
||||
BEAST_EXPECT(env.balance(sponsor) == XRP(9999));
|
||||
env(noop(alice),
|
||||
fee(XRP(1)),
|
||||
sponsor::as(sponsor, tfSponsorFee),
|
||||
sponsor::sig(sponsor),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(alice) == XRP(10000));
|
||||
BEAST_EXPECT(env.balance(sponsor) == XRP(9999));
|
||||
}
|
||||
{
|
||||
// pre funded
|
||||
Env env{*this, testable_amendments()};
|
||||
Account const alice("alice");
|
||||
Account const sponsor("sponsor");
|
||||
env.fund(XRP(10000), alice, sponsor);
|
||||
env.close();
|
||||
|
||||
env(sponsor::set(sponsor, alice, 0, std::nullopt, XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const sle = env.le(keylet::sponsor(sponsor, alice));
|
||||
BEAST_EXPECT(sle->getFieldAmount(sfFeeAmount) == XRP(1));
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfReserveCount));
|
||||
|
||||
auto const sponsorBalanceBefore = env.balance(sponsor);
|
||||
auto const aliceBalanceBefore = env.balance(alice);
|
||||
|
||||
env(noop(alice),
|
||||
fee(drops(500)),
|
||||
sponsor::as(sponsor, tfSponsorFee),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(alice) == aliceBalanceBefore);
|
||||
BEAST_EXPECT(env.balance(sponsor) == sponsorBalanceBefore);
|
||||
|
||||
auto const sle2 = env.le(keylet::sponsor(sponsor, alice));
|
||||
BEAST_EXPECT(
|
||||
sle2->getFieldAmount(sfFeeAmount) == XRP(1) - drops(500));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -418,6 +554,7 @@ public:
|
||||
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.fund(drops(reserve) + drops(increment) - drops(1), sponsor);
|
||||
env.close();
|
||||
|
||||
// check sponsor balance
|
||||
env(check::create(alice, bob, XRP(1)),
|
||||
@@ -483,6 +620,7 @@ public:
|
||||
auto USD = gw["USD"];
|
||||
|
||||
env.fund(XRP(10000), alice, gw, sponsor);
|
||||
env.close();
|
||||
|
||||
// OfferCreate
|
||||
auto const seq = env.seq(alice);
|
||||
@@ -518,6 +656,7 @@ public:
|
||||
Account const sponsor("master");
|
||||
|
||||
env.fund(XRP(1000000), alice, sponsor);
|
||||
env.close();
|
||||
|
||||
// TicketCreate
|
||||
std::uint32_t const ticketSeq{env.seq(alice) + 1};
|
||||
@@ -551,6 +690,7 @@ public:
|
||||
Account const sponsor("sponsor");
|
||||
|
||||
env.fund(XRP(1000000), issuer, subject, sponsor);
|
||||
env.close();
|
||||
|
||||
// CredentialsCreate
|
||||
env(credentials::create(subject, issuer, "credType"),
|
||||
@@ -599,6 +739,7 @@ public:
|
||||
Account const sponsor("sponsor");
|
||||
|
||||
env.fund(XRP(1000000), alice, bob, sponsor);
|
||||
env.close();
|
||||
|
||||
// DelegateSet
|
||||
env(delegate::set(alice, bob, {"Payment"}),
|
||||
@@ -634,6 +775,7 @@ public:
|
||||
Account const sponsor("sponsor");
|
||||
|
||||
env.fund(XRP(1000000), alice, sponsor);
|
||||
env.close();
|
||||
|
||||
// DIDSet
|
||||
env(did::set(alice),
|
||||
@@ -700,6 +842,7 @@ public:
|
||||
Account const sponsor("sponsor");
|
||||
|
||||
env.fund(XRP(1000000), alice, sponsor);
|
||||
env.close();
|
||||
|
||||
Account const bob("bob");
|
||||
|
||||
@@ -737,6 +880,73 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
testDisallowIncoming()
|
||||
{
|
||||
testcase("DisallowIncoming");
|
||||
using namespace test::jtx;
|
||||
Env env{*this, testable_amendments()};
|
||||
Account const alice("alice");
|
||||
Account const sponsor("sponsor");
|
||||
|
||||
env.fund(XRP(1000000), alice, sponsor);
|
||||
env.close();
|
||||
|
||||
// set DisallowIncomingSponsor
|
||||
env(fset(alice, asfDisallowIncomingSponsor));
|
||||
env.close();
|
||||
|
||||
// Create sponsor should fail
|
||||
env(sponsor::set(sponsor, alice, 0, 100, XRP(100)),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// clear flag
|
||||
env(fclear(alice, asfDisallowIncomingSponsor));
|
||||
env.close();
|
||||
|
||||
// Create sponsor
|
||||
env(sponsor::set(sponsor, alice, 0, 100, XRP(100)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// set flag
|
||||
env(fset(alice, asfDisallowIncomingSponsor));
|
||||
env.close();
|
||||
|
||||
// Update sponsor should success
|
||||
env(sponsor::set(sponsor, alice, 0, 100, XRP(100)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// Delete sponsor shoud success
|
||||
env(sponsor::set(sponsor, alice, tfDeleteObject), ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
void
|
||||
testAccountDelete()
|
||||
{
|
||||
testcase("AccountDelete");
|
||||
using namespace test::jtx;
|
||||
Env env{*this, testable_amendments()};
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
Account const sponsor("sponsor");
|
||||
|
||||
env.fund(XRP(1000000), alice, bob, sponsor);
|
||||
env.close();
|
||||
|
||||
// set sponsor
|
||||
env(sponsor::set(sponsor, alice, 0, 100, XRP(100)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// AccountDelete
|
||||
env(acctdelete(alice, bob));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testSponsorReserve()
|
||||
{
|
||||
@@ -764,12 +974,19 @@ public:
|
||||
run() override
|
||||
{
|
||||
testDisabled();
|
||||
testInvalidSponsorSet();
|
||||
|
||||
testSingleSigning();
|
||||
testMultiSigning();
|
||||
// testInvalidSigninig(); // borh TxnSignature and Signers are present
|
||||
// -> error
|
||||
testTransferSponsor();
|
||||
testSponsorFee();
|
||||
testSponsorAccount();
|
||||
testSponsorReserve();
|
||||
testDisallowIncoming();
|
||||
|
||||
// testAccountDelete();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <xrpl/protocol/Sign.h>
|
||||
#include <xrpl/protocol/Sponsor.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -30,6 +31,36 @@ namespace jtx {
|
||||
|
||||
namespace sponsor {
|
||||
|
||||
Json::Value
|
||||
set(jtx::Account const& account,
|
||||
jtx::Account const& sponsee,
|
||||
uint32_t flags,
|
||||
std::optional<uint32_t> reserveCount,
|
||||
std::optional<STAmount> feeAmount)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::SponsorSet;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[sfSponsee.jsonName] = sponsee.human();
|
||||
jv[sfFlags.jsonName] = flags;
|
||||
if (reserveCount)
|
||||
jv[sfReserveCount.jsonName] = *reserveCount;
|
||||
if (feeAmount)
|
||||
jv[sfFeeAmount.jsonName] = feeAmount->getJson(JsonOptions::none);
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
del(jtx::Account const& account, jtx::Account const& sponsee)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::SponsorSet;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[sfSponsee.jsonName] = sponsee.human();
|
||||
jv[sfFlags.jsonName] = tfDeleteObject;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
transfer(jtx::Account const& account, std::optional<uint256> const& index)
|
||||
{
|
||||
@@ -37,10 +68,16 @@ transfer(jtx::Account const& account, std::optional<uint256> const& index)
|
||||
jv[jss::TransactionType] = jss::SponsorTransfer;
|
||||
jv[jss::Account] = account.human();
|
||||
if (index)
|
||||
jv[sfLedgerIndex.jsonName] = to_string(*index);
|
||||
jv[sfObjectID.jsonName] = to_string(*index);
|
||||
return jv;
|
||||
}
|
||||
|
||||
void
|
||||
sponsorAcc::operator()(Env& env, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfSponsorAccount.jsonName] = sponsor_.human();
|
||||
}
|
||||
|
||||
void
|
||||
as::operator()(Env& env, JTx& jt) const
|
||||
{
|
||||
@@ -85,7 +122,6 @@ sig::operator()(Env& env, JTx& jt) const
|
||||
void
|
||||
msig::operator()(Env& env, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfSponsor.jsonName][sfSigningPubKey.jsonName] = "";
|
||||
auto const mySigners = signers;
|
||||
jt.sponsorSigner = [mySigners, &env](Env&, JTx& jtx) {
|
||||
std::optional<STObject> st;
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include "test/jtx/SignerUtils.h"
|
||||
#include <test/jtx/SignerUtils.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -30,11 +29,34 @@ namespace jtx {
|
||||
|
||||
namespace sponsor {
|
||||
|
||||
Json::Value
|
||||
set(jtx::Account const& account,
|
||||
jtx::Account const& sponsee,
|
||||
std::uint32_t flags,
|
||||
std::optional<std::uint32_t> reserveCount = std::nullopt,
|
||||
std::optional<STAmount> feeAmount = std::nullopt);
|
||||
|
||||
Json::Value
|
||||
del(jtx::Account const& account, jtx::Account const& sponsee);
|
||||
|
||||
Json::Value
|
||||
transfer(
|
||||
jtx::Account const& account,
|
||||
std::optional<uint256> const& index = std::nullopt);
|
||||
|
||||
struct sponsorAcc
|
||||
{
|
||||
private:
|
||||
jtx::Account sponsor_;
|
||||
|
||||
public:
|
||||
sponsorAcc(jtx::Account const& account) : sponsor_(account)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(jtx::Env&, jtx::JTx& jtx) const;
|
||||
};
|
||||
struct as
|
||||
{
|
||||
private:
|
||||
|
||||
@@ -87,7 +87,8 @@ public:
|
||||
if (flag == asfDisallowIncomingCheck ||
|
||||
flag == asfDisallowIncomingPayChan ||
|
||||
flag == asfDisallowIncomingNFTokenOffer ||
|
||||
flag == asfDisallowIncomingTrustline)
|
||||
flag == asfDisallowIncomingTrustline ||
|
||||
flag == asfDisallowIncomingSponsor)
|
||||
{
|
||||
// These flags are part of the DisallowIncoming amendment
|
||||
// and are tested elsewhere
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <xrpld/app/tx/detail/DepositPreauth.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
#include <xrpld/app/tx/detail/SetSignerList.h>
|
||||
#include <xrpld/app/tx/detail/SponsorSet.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
@@ -194,6 +195,18 @@ removeDelegateFromLedger(
|
||||
return DelegateSet::deleteDelegate(view, sleDel, account, j);
|
||||
}
|
||||
|
||||
TER
|
||||
removeSponsorshipFromLedger(
|
||||
Application& app,
|
||||
ApplyView& view,
|
||||
AccountID const&,
|
||||
uint256 const& delIndex,
|
||||
std::shared_ptr<SLE> const& sleDel,
|
||||
beast::Journal j)
|
||||
{
|
||||
return SponsorSet::deleteSponsorship(view, sleDel, j);
|
||||
}
|
||||
|
||||
// Return nullptr if the LedgerEntryType represents an obligation that can't
|
||||
// be deleted. Otherwise return the pointer to the function that can delete
|
||||
// the non-obligation
|
||||
@@ -220,6 +233,8 @@ nonObligationDeleter(LedgerEntryType t)
|
||||
return removeCredentialFromLedger;
|
||||
case ltDELEGATE:
|
||||
return removeDelegateFromLedger;
|
||||
case ltSPONSORSHIP:
|
||||
return removeSponsorshipFromLedger;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -112,6 +112,10 @@ XRPNotCreated::visitEntry(
|
||||
if (isXRP((*before)[sfAmount]))
|
||||
drops_ -= (*before)[sfAmount].xrp().drops();
|
||||
break;
|
||||
case ltSPONSORSHIP:
|
||||
if (before->isFieldPresent(sfFeeAmount))
|
||||
drops_ -= (*before)[sfFeeAmount].xrp().drops();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -134,6 +138,10 @@ XRPNotCreated::visitEntry(
|
||||
if (!isDelete && isXRP((*after)[sfAmount]))
|
||||
drops_ += (*after)[sfAmount].xrp().drops();
|
||||
break;
|
||||
case ltSPONSORSHIP:
|
||||
if (!isDelete && after->isFieldPresent(sfFeeAmount))
|
||||
drops_ += (*after)[sfFeeAmount].xrp().drops();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -543,6 +551,7 @@ LedgerEntryTypesMatch::visitEntry(
|
||||
case ltCREDENTIAL:
|
||||
case ltPERMISSIONED_DOMAIN:
|
||||
case ltVAULT:
|
||||
case ltSPONSORSHIP:
|
||||
break;
|
||||
default:
|
||||
invalidTypeAdded_ = true;
|
||||
|
||||
@@ -648,6 +648,14 @@ SetAccount::doApply()
|
||||
uFlagsOut |= lsfDisallowIncomingTrustline;
|
||||
else if (uClearFlag == asfDisallowIncomingTrustline)
|
||||
uFlagsOut &= ~lsfDisallowIncomingTrustline;
|
||||
|
||||
if (ctx_.view().rules().enabled(featureSponsor))
|
||||
{
|
||||
if (uSetFlag == asfDisallowIncomingSponsor)
|
||||
uFlagsOut |= lsfDisallowIncomingSponsor;
|
||||
else if (uClearFlag == asfDisallowIncomingSponsor)
|
||||
uFlagsOut &= ~lsfDisallowIncomingSponsor;
|
||||
}
|
||||
}
|
||||
|
||||
// Set or clear flags for disallowing escrow
|
||||
|
||||
273
src/xrpld/app/tx/detail/SponsorSet.cpp
Normal file
273
src/xrpld/app/tx/detail/SponsorSet.cpp
Normal file
@@ -0,0 +1,273 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/SponsorSet.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
SponsorSet::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureSponsor))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ter = preflight1(ctx))
|
||||
return ter;
|
||||
|
||||
// check Flags
|
||||
{
|
||||
if (ctx.tx.getFlags() & tfSponsorSetMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
if (ctx.tx.isFlag(tfSponsorshipSetRequireSignForFee) &&
|
||||
ctx.tx.isFlag(tfSponsorshipClearRequireSignForFee))
|
||||
return temINVALID_FLAG;
|
||||
|
||||
if (ctx.tx.isFlag(tfSponsorshipSetRequireSignForReserve) &&
|
||||
ctx.tx.isFlag(tfSponsorshipClearRequireSignForReserve))
|
||||
return temINVALID_FLAG;
|
||||
|
||||
if (ctx.tx.isFlag(tfDeleteObject))
|
||||
{
|
||||
// check Flags
|
||||
if (ctx.tx.getFlags() &
|
||||
(tfSponsorshipSetRequireSignForFee |
|
||||
tfSponsorshipSetRequireSignForReserve |
|
||||
tfSponsorshipClearRequireSignForFee |
|
||||
tfSponsorshipClearRequireSignForReserve))
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfSponsorAccount))
|
||||
{
|
||||
// SponsorAccount is used when sponsee Deleting ltSponsorship
|
||||
// Account => Sponsee of Sponsorshop
|
||||
// SponsorAccount => Sponsor of Sponsorshop
|
||||
// Sponsee => Sponsee of Sponsorshop
|
||||
if (ctx.tx.getAccountID(sfAccount) ==
|
||||
ctx.tx.getAccountID(sfSponsorAccount) ||
|
||||
ctx.tx.getAccountID(sfSponsee) !=
|
||||
ctx.tx.getAccountID(sfSponsorAccount) ||
|
||||
!ctx.tx.isFlag(tfDeleteObject))
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const sponsor = ctx.tx.isFieldPresent(sfSponsorAccount)
|
||||
? ctx.tx.getAccountID(sfSponsorAccount)
|
||||
: ctx.tx.getAccountID(sfAccount);
|
||||
auto const sponsee = ctx.tx.getAccountID(sfSponsee);
|
||||
|
||||
if (sponsee == sponsor)
|
||||
return temMALFORMED;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfFeeAmount))
|
||||
{
|
||||
if (ctx.tx.getFlags() & tfSponsorshipClearRequireSignForFee)
|
||||
return temMALFORMED;
|
||||
|
||||
auto const feeAmount = ctx.tx.getFieldAmount(sfFeeAmount);
|
||||
|
||||
if (!isXRP(feeAmount))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (feeAmount.xrp().drops() <= 0)
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfReserveCount))
|
||||
{
|
||||
if (ctx.tx.getFlags() & tfSponsorshipClearRequireSignForReserve)
|
||||
return temMALFORMED;
|
||||
|
||||
auto const reserveCount = ctx.tx.getFieldU32(sfReserveCount);
|
||||
// TODO: max reserveCount?
|
||||
if (reserveCount < 1)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFlag(tfDeleteObject))
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfFeeAmount) ||
|
||||
ctx.tx.isFieldPresent(sfReserveCount))
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
SponsorSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sponsor = ctx.tx.isFieldPresent(sfSponsorAccount)
|
||||
? ctx.tx.getAccountID(sfSponsorAccount)
|
||||
: ctx.tx.getAccountID(sfAccount);
|
||||
auto const sponsee = ctx.tx[sfSponsee];
|
||||
|
||||
// check Sponsee
|
||||
auto const sponseeSle = ctx.view.read(keylet::account(sponsee));
|
||||
if (!sponseeSle)
|
||||
return tecNO_DST;
|
||||
|
||||
// check if object exists
|
||||
auto const sponsorObjSle = ctx.view.read(keylet::sponsor(sponsor, sponsee));
|
||||
|
||||
if (ctx.tx.isFlag(tfDeleteObject) && !sponsorObjSle)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
if (sponseeSle->isFlag(lsfDisallowIncomingSponsor) && !sponsorObjSle)
|
||||
// new sponsor creation is not allowed by disallowIncomingSponsor flag
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SponsorSet::doApply()
|
||||
{
|
||||
auto const sponseeAcc = ctx_.tx[sfSponsee];
|
||||
auto const keylet = keylet::sponsor(account_, sponseeAcc);
|
||||
|
||||
auto const sponsorAcc = ctx_.tx.isFieldPresent(sfSponsorAccount)
|
||||
? ctx_.tx.getAccountID(sfSponsorAccount)
|
||||
: account_;
|
||||
|
||||
auto const sponsorAccSle = ctx_.view().peek(keylet::account(sponsorAcc));
|
||||
if (!sponsorAccSle)
|
||||
return tecINTERNAL;
|
||||
|
||||
auto const sponsorObjSle = ctx_.view().peek(keylet);
|
||||
|
||||
if (ctx_.tx.isFlag(tfDeleteObject))
|
||||
{
|
||||
// Delete
|
||||
if (!sponsorObjSle)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sponsor =
|
||||
getLedgerEntryReserveSponsor(ctx_.view(), sponsorObjSle);
|
||||
adjustOwnerCount(ctx_.view(), sponsorAccSle, sponsor, -1, ctx_.journal);
|
||||
|
||||
ctx_.view().dirRemove(
|
||||
keylet::ownerDir(sponsorAcc),
|
||||
(*sponsorObjSle)[sfSponsorNode],
|
||||
sponsorObjSle->key(),
|
||||
false);
|
||||
ctx_.view().dirRemove(
|
||||
keylet::ownerDir(sponseeAcc),
|
||||
(*sponsorObjSle)[sfSponseeNode],
|
||||
sponsorObjSle->key(),
|
||||
false);
|
||||
|
||||
// transfer feeAmount from ledger entry
|
||||
auto const feeAmount = sponsorObjSle->getFieldAmount(sfFeeAmount);
|
||||
(*sponsorAccSle)[sfBalance] += feeAmount;
|
||||
|
||||
ctx_.view().erase(sponsorObjSle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
auto const feeAmount = ctx_.tx[~sfFeeAmount];
|
||||
auto const reserveCount = ctx_.tx[~sfReserveCount];
|
||||
|
||||
auto reserveSponsorAccSle = getTxReserveSponsor(view(), ctx_.tx);
|
||||
|
||||
if (!sponsorObjSle)
|
||||
{
|
||||
// Create
|
||||
auto newSle = std::make_shared<SLE>(keylet);
|
||||
|
||||
if (auto const ret = checkInsufficientReserve(
|
||||
ctx_.view(),
|
||||
sponsorAccSle,
|
||||
mPriorBalance,
|
||||
reserveSponsorAccSle,
|
||||
1);
|
||||
!isTesSuccess(ret))
|
||||
return tecUNFUNDED;
|
||||
|
||||
(*newSle)[sfAccount] = sponsorAcc;
|
||||
(*newSle)[sfSponsee] = sponseeAcc;
|
||||
(*newSle)[sfFlags] = ctx_.tx.getFlags();
|
||||
if (feeAmount)
|
||||
{
|
||||
(*sponsorAccSle)[sfBalance] -= *feeAmount;
|
||||
(*newSle)[sfFeeAmount] = *feeAmount;
|
||||
}
|
||||
if (reserveCount)
|
||||
{
|
||||
(*newSle)[sfReserveCount] = *reserveCount;
|
||||
}
|
||||
|
||||
auto const sponsorPage = view().dirInsert(
|
||||
keylet::ownerDir(sponsorAcc), keylet, describeOwnerDir(sponsorAcc));
|
||||
(*newSle)[sfSponsorNode] = *sponsorPage;
|
||||
|
||||
auto const sponseePage = view().dirInsert(
|
||||
keylet::ownerDir(sponseeAcc), keylet, describeOwnerDir(sponseeAcc));
|
||||
(*newSle)[sfSponseeNode] = *sponseePage;
|
||||
|
||||
auto viewJ = ctx_.app.journal("View");
|
||||
|
||||
adjustOwnerCount(view(), sponsorAccSle, reserveSponsorAccSle, 1, viewJ);
|
||||
addSponsorToLedgerEntry(newSle, reserveSponsorAccSle);
|
||||
|
||||
ctx_.view().insert(newSle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// Update
|
||||
if (feeAmount)
|
||||
{
|
||||
// TODO: transfer feeAmount to ledger entry
|
||||
(*sponsorAccSle)[sfBalance] -= *feeAmount;
|
||||
(*sponsorObjSle)[sfFeeAmount] += *feeAmount;
|
||||
}
|
||||
|
||||
if (reserveCount)
|
||||
(*sponsorObjSle)[sfReserveCount] =
|
||||
(*sponsorObjSle)[sfReserveCount] + *reserveCount;
|
||||
|
||||
// TODO: update Flags?
|
||||
auto flags = sponsorObjSle->getFieldU32(sfFlags);
|
||||
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee))
|
||||
flags |= lsfSponsorshipRequireSignForFee;
|
||||
|
||||
if (ctx_.tx.isFlag(tfSponsorshipClearRequireSignForFee))
|
||||
flags &= ~lsfSponsorshipRequireSignForFee;
|
||||
|
||||
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve))
|
||||
flags |= lsfSponsorshipRequireSignForReserve;
|
||||
|
||||
if (ctx_.tx.isFlag(tfSponsorshipClearRequireSignForReserve))
|
||||
flags &= ~lsfSponsorshipRequireSignForReserve;
|
||||
|
||||
if (flags != (*sponsorObjSle)[sfFlags])
|
||||
(*sponsorObjSle)[sfFlags] = flags;
|
||||
|
||||
ctx_.view().update(sponsorObjSle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
48
src/xrpld/app/tx/detail/SponsorSet.h
Normal file
48
src/xrpld/app/tx/detail/SponsorSet.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_SPONSORSET_H_INCLUDED
|
||||
#define RIPPLE_TX_SPONSORSET_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class SponsorSet : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit SponsorSet(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -79,8 +79,8 @@ getLedgerEntryOwner(
|
||||
auto const signerList = view.read(keylet::signers(account));
|
||||
if (!signerList)
|
||||
return std::nullopt;
|
||||
if (signerList->getFieldH256(sfLedgerIndex) ==
|
||||
sle->getFieldH256(sfLedgerIndex))
|
||||
if (signerList->getFieldH256(sfObjectID) ==
|
||||
sle->getFieldH256(sfObjectID))
|
||||
return account;
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -110,7 +110,7 @@ getLedgerEntryOwner(
|
||||
TER
|
||||
SponsorTransfer::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const index = ctx.tx[~sfLedgerIndex];
|
||||
auto const index = ctx.tx[~sfObjectID];
|
||||
auto const newSponsor = getTxReserveSponsor(ctx.view, ctx.tx);
|
||||
|
||||
bool const isObjectSponsor = index != std::nullopt;
|
||||
@@ -198,7 +198,7 @@ SponsorTransfer::doApply()
|
||||
{
|
||||
auto const& tx = ctx_.tx;
|
||||
|
||||
auto const index = tx[~sfLedgerIndex];
|
||||
auto const index = tx[~sfObjectID];
|
||||
bool const isObjectSponsor = index != std::nullopt;
|
||||
|
||||
auto const accSle = view().peek(keylet::account(account_));
|
||||
|
||||
@@ -167,18 +167,11 @@ preflight1(PreflightContext const& ctx)
|
||||
JLOG(ctx.j.fatal()) << "preflight1: invalid sponsor account";
|
||||
return temMALFORMED;
|
||||
}
|
||||
if (!(sponsor[sfFlags] & tfSponsorFee) &&
|
||||
!(sponsor[sfFlags] & tfSponsorReserve))
|
||||
if (!(sponsor.getFlags() & tfSponsorMask))
|
||||
{
|
||||
JLOG(ctx.j.fatal()) << "preflight1: invalid sponsor flags";
|
||||
return temMALFORMED;
|
||||
}
|
||||
if (!sponsor.isFieldPresent(sfTxnSignature) &&
|
||||
!sponsor.isFieldPresent(sfSigners))
|
||||
{
|
||||
JLOG(ctx.j.fatal()) << "preflight1: no sfTxnSignature or sfSigners";
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -255,6 +248,41 @@ Transactor::checkPermission(ReadView const& view, STTx const& tx)
|
||||
return checkTxPermission(sle, tx);
|
||||
}
|
||||
|
||||
TER
|
||||
Transactor::checkSponsor(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
if (!tx.isFieldPresent(sfSponsor))
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const txSponsor = tx.getFieldObject(sfSponsor);
|
||||
|
||||
auto const sponsorAcc = txSponsor.getAccountID(sfAccount);
|
||||
auto const sponseeAcc = tx.getAccountID(sfAccount);
|
||||
|
||||
auto const sponsorSle = view.read(keylet::sponsor(sponsorAcc, sponseeAcc));
|
||||
if (!sponsorSle)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const hasSignature = txSponsor.isFieldPresent(sfTxnSignature) ||
|
||||
!txSponsor.getFieldVL(sfSigningPubKey).empty() ||
|
||||
txSponsor.isFieldPresent(sfSigners);
|
||||
|
||||
if (txSponsor.isFlag(tfSponsorFee) &&
|
||||
sponsorSle->isFlag(lsfSponsorshipRequireSignForFee))
|
||||
{
|
||||
if (!hasSignature)
|
||||
return tecNO_SPONSOR_PERMISSION;
|
||||
}
|
||||
if (txSponsor.isFlag(tfSponsorReserve) &&
|
||||
sponsorSle->isFlag(lsfSponsorshipRequireSignForReserve))
|
||||
{
|
||||
if (!hasSignature)
|
||||
return tecNO_SPONSOR_PERMISSION;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
@@ -263,6 +291,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
// The computation has two parts:
|
||||
// * The base fee, which is the same for most transactions.
|
||||
// * The additional cost of each multisignature on the transaction.
|
||||
// * The additional cost of each multisignature on the sponsor.
|
||||
XRPAmount const baseFee = view.fees().base;
|
||||
|
||||
// Each signer adds one more baseFee to the minimum required fee
|
||||
@@ -270,7 +299,16 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
std::size_t const signerCount =
|
||||
tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0;
|
||||
|
||||
return baseFee + (signerCount * baseFee);
|
||||
std::size_t sponsorSignerCount = 0;
|
||||
if (tx.isFieldPresent(sfSponsor))
|
||||
{
|
||||
auto const sponsorObj = tx.getFieldObject(sfSponsor);
|
||||
sponsorSignerCount += sponsorObj.isFieldPresent(sfSigners)
|
||||
? sponsorObj.getFieldArray(sfSigners).size()
|
||||
: 0;
|
||||
}
|
||||
|
||||
return baseFee + ((signerCount + sponsorSignerCount) * baseFee);
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
@@ -352,6 +390,17 @@ Transactor::payFee()
|
||||
{
|
||||
auto const feePaid = ctx_.tx[sfFee].xrp();
|
||||
|
||||
auto const isFeeSponsorObj = [&]() -> bool {
|
||||
if (ctx_.tx.isFieldPresent(sfSponsor))
|
||||
{
|
||||
auto const sponsor = ctx_.tx.getFieldObject(sfSponsor);
|
||||
if (sponsor.getFieldVL(sfSigningPubKey).empty() &&
|
||||
!sponsor.isFieldPresent(sfSigners))
|
||||
return sponsor.getFlags() & tfSponsorFee;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfDelegate))
|
||||
{
|
||||
// Delegated transactions are paid by the delegated account.
|
||||
@@ -364,6 +413,19 @@ Transactor::payFee()
|
||||
sfBalance, delegatedSle->getFieldAmount(sfBalance) - feePaid);
|
||||
view().update(delegatedSle);
|
||||
}
|
||||
else if (isFeeSponsorObj())
|
||||
{
|
||||
auto const sponsor = ctx_.tx.getFieldObject(sfSponsor);
|
||||
auto const sponsorAcc = sponsor.getAccountID(sfAccount);
|
||||
auto const sponsorSle =
|
||||
view().peek(keylet::sponsor(sponsorAcc, account_));
|
||||
if (!sponsorSle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
sponsorSle->setFieldAmount(
|
||||
sfFeeAmount, sponsorSle->getFieldAmount(sfFeeAmount) - feePaid);
|
||||
view().update(sponsorSle);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const id = ctx_.tx.getFeePayer();
|
||||
@@ -649,8 +711,15 @@ Transactor::checkSign(PreclaimContext const& ctx)
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfSponsor))
|
||||
{
|
||||
if (auto const ret = checkSponsorSign(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
auto const sponsorObj = ctx.tx.getFieldObject(sfSponsor);
|
||||
auto const isCoSigned = sponsorObj.isFieldPresent(sfTxnSignature) ||
|
||||
!sponsorObj.getFieldVL(sfSigningPubKey).empty() ||
|
||||
sponsorObj.isFieldPresent(sfSigners);
|
||||
if (isCoSigned)
|
||||
{
|
||||
if (auto const ret = checkSponsorSign(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// Check Single Sign
|
||||
|
||||
@@ -209,6 +209,9 @@ public:
|
||||
|
||||
static TER
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
checkSponsor(ReadView const& view, STTx const& tx);
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
// Interface used by DeleteAccount
|
||||
|
||||
@@ -85,12 +85,19 @@ checkValidity(
|
||||
|
||||
if (tx.isFieldPresent(sfSponsor) && rules.enabled(featureSponsor))
|
||||
{
|
||||
auto const sigVerify =
|
||||
tx.checkSponsorSign(requireCanonicalSig, rules);
|
||||
if (!sigVerify)
|
||||
auto const sponsorObj = tx.getFieldObject(sfSponsor);
|
||||
auto const isCoSigned = sponsorObj.isFieldPresent(sfTxnSignature) ||
|
||||
!sponsorObj.getFieldVL(sfSigningPubKey).empty() ||
|
||||
sponsorObj.isFieldPresent(sfSigners);
|
||||
if (isCoSigned)
|
||||
{
|
||||
router.setFlags(id, SF_SIGBAD);
|
||||
return {Validity::SigBad, sigVerify.error()};
|
||||
auto const sigVerify =
|
||||
tx.checkSponsorSign(requireCanonicalSig, rules);
|
||||
if (!sigVerify)
|
||||
{
|
||||
router.setFlags(id, SF_SIGBAD);
|
||||
return {Validity::SigBad, sigVerify.error()};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
#include <xrpld/app/tx/detail/SetRegularKey.h>
|
||||
#include <xrpld/app/tx/detail/SetSignerList.h>
|
||||
#include <xrpld/app/tx/detail/SetTrust.h>
|
||||
#include <xrpld/app/tx/detail/SponsorSet.h>
|
||||
#include <xrpld/app/tx/detail/SponsorTransfer.h>
|
||||
#include <xrpld/app/tx/detail/VaultClawback.h>
|
||||
#include <xrpld/app/tx/detail/VaultCreate.h>
|
||||
@@ -206,6 +207,11 @@ invoke_preclaim(PreclaimContext const& ctx)
|
||||
|
||||
result = T::checkPermission(ctx.view, ctx.tx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return result;
|
||||
|
||||
result = T::checkSponsor(ctx.view, ctx.tx);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return result;
|
||||
|
||||
|
||||
@@ -629,6 +629,27 @@ parseVault(Json::Value const& params, Json::StaticString const fieldName)
|
||||
return keylet::vault(*id, *seq).key;
|
||||
}
|
||||
|
||||
static Expected<uint256, Json::Value>
|
||||
parseSponsorship(Json::Value const& params, Json::StaticString const fieldName)
|
||||
{
|
||||
if (!params.isObject())
|
||||
{
|
||||
return parseObjectID(params, fieldName);
|
||||
}
|
||||
|
||||
auto const id = LedgerEntryHelpers::requiredAccountID(
|
||||
params, jss::owner, "malformedOwner");
|
||||
if (!id)
|
||||
return Unexpected(id.error());
|
||||
|
||||
auto const sponsee = LedgerEntryHelpers::requiredAccountID(
|
||||
params, jss::sponsee, "malformedAddress");
|
||||
if (!sponsee)
|
||||
return Unexpected(sponsee.error());
|
||||
|
||||
return keylet::sponsor(*id, *sponsee).key;
|
||||
}
|
||||
|
||||
static Expected<uint256, Json::Value>
|
||||
parseXChainOwnedClaimID(
|
||||
Json::Value const& claim_id,
|
||||
|
||||
Reference in New Issue
Block a user