From 759f8430202c5dbd31d5d744bb77278e5926a7b2 Mon Sep 17 00:00:00 2001 From: tequ Date: Tue, 16 Sep 2025 23:11:19 +0900 Subject: [PATCH] Sponsor permissions --- .../xrpl/protocol/detail/permissions.macro | 6 ++ src/test/app/Sponsor_test.cpp | 89 +++++++++++++++++++ src/xrpld/app/tx/detail/SponsorshipSet.cpp | 37 ++++++++ src/xrpld/app/tx/detail/SponsorshipSet.h | 3 + 4 files changed, 135 insertions(+) diff --git a/include/xrpl/protocol/detail/permissions.macro b/include/xrpl/protocol/detail/permissions.macro index ec19c5767f..5ed1831276 100644 --- a/include/xrpl/protocol/detail/permissions.macro +++ b/include/xrpl/protocol/detail/permissions.macro @@ -66,3 +66,9 @@ PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547) /** This permission grants the delegated account the ability to unlock MPToken. */ PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548) + +/** This permission grants the delegated account the ability to set SponsorFee. */ +PERMISSION(SponsorFee, ttSPONSORSHIP_SET, 65549) + +/** This permission grants the delegated account the ability to set SponsorReserve. */ +PERMISSION(SponsorReserve, ttSPONSORSHIP_SET, 65550) diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index e0c095b263..df5f7c31ed 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -1327,6 +1327,93 @@ public: } } + void + testDelegatePermission() + { + testcase("DelegatePermission"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + + // + // Permission SponsorFee + // + { + Env env{*this, testable_amendments()}; + env.fund(XRP(1000000), alice, bob, carol); + env.close(); + auto const testFeePermission = [&](TER result) { + // FeeAmount + env(sponsor::set(alice, bob, 0, std::nullopt, XRP(100)), + delegate::as(carol), + ter(result)); + // MaxFee + env(sponsor::set( + alice, bob, 0, std::nullopt, std::nullopt, XRP(100)), + delegate::as(carol), + ter(result)); + // SetRequireSignForFee flag + env(sponsor::set(alice, bob, tfSponsorshipSetRequireSignForFee), + delegate::as(carol), + ter(result)); + env.close(); + }; + + // no delegated + testFeePermission(tecNO_DELEGATE_PERMISSION); + + // set non-SponsorFee Permission + env(delegate::set(alice, carol, {"SponsorReserve"})); + env.close(); + + testFeePermission(tecNO_DELEGATE_PERMISSION); + + // set SponsorFee Permission + env(delegate::set(alice, carol, {"SponsorFee"})); + env.close(); + + testFeePermission(tesSUCCESS); + } + + // + // Permission SponsorReserve + // + { + Env env{*this, testable_amendments()}; + env.fund(XRP(1000000), alice, bob, carol); + env.close(); + + auto const testReservePermission = [&](TER result) { + // ReserveCount + env(sponsor::set(alice, bob, 0, 100), + delegate::as(carol), + ter(result)); + // SetRequireSignForReserve flag + env(sponsor::set( + alice, bob, tfSponsorshipSetRequireSignForReserve), + delegate::as(carol), + ter(result)); + env.close(); + }; + + // no delegated + testReservePermission(tecNO_DELEGATE_PERMISSION); + + // set non-SponsorReserve Permission + env(delegate::set(alice, carol, {"SponsorFee"})); + env.close(); + + testReservePermission(tecNO_DELEGATE_PERMISSION); + + // set SponsorReserve Permission + env(delegate::set(alice, carol, {"SponsorReserve"})); + env.close(); + + testReservePermission(tesSUCCESS); + } + } + void testSponsorReserve() { @@ -1368,6 +1455,8 @@ public: testDisallowIncoming(); testAccountDelete(); + + testDelegatePermission(); } }; diff --git a/src/xrpld/app/tx/detail/SponsorshipSet.cpp b/src/xrpld/app/tx/detail/SponsorshipSet.cpp index 5724d380f1..352d25cc2a 100644 --- a/src/xrpld/app/tx/detail/SponsorshipSet.cpp +++ b/src/xrpld/app/tx/detail/SponsorshipSet.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include #include #include @@ -122,6 +123,42 @@ SponsorshipSet::preflight(PreflightContext const& ctx) return preflight2(ctx); } +TER +SponsorshipSet::checkPermission(ReadView const& view, STTx const& tx) +{ + auto const delegate = tx[~sfDelegate]; + if (!delegate) + return tesSUCCESS; + + auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate); + auto const sle = view.read(delegateKey); + + if (!sle) + return tecNO_DELEGATE_PERMISSION; + + if (checkTxPermission(sle, tx) == tesSUCCESS) + return tesSUCCESS; + + std::unordered_set granularPermissions; + loadGranularPermission(sle, ttSPONSORSHIP_SET, granularPermissions); + + auto const sponsoringFee = tx.isFieldPresent(sfFeeAmount) || + tx.isFieldPresent(sfMaxFee) || + tx.isFlag(tfSponsorshipSetRequireSignForFee); + auto const sponsoringReserve = tx.isFieldPresent(sfReserveCount) || + tx.isFlag(tfSponsorshipSetRequireSignForReserve); + + if (granularPermissions.contains(SponsorFee) && sponsoringFee) + return tesSUCCESS; + + if (granularPermissions.contains(SponsorReserve) && sponsoringReserve) + return tesSUCCESS; + + // TODO: needs to check permission to delete sponsorship? + + return tecNO_DELEGATE_PERMISSION; +} + TER SponsorshipSet::preclaim(PreclaimContext const& ctx) { diff --git a/src/xrpld/app/tx/detail/SponsorshipSet.h b/src/xrpld/app/tx/detail/SponsorshipSet.h index ec22cca67e..ea037a686c 100644 --- a/src/xrpld/app/tx/detail/SponsorshipSet.h +++ b/src/xrpld/app/tx/detail/SponsorshipSet.h @@ -36,6 +36,9 @@ public: static NotTEC preflight(PreflightContext const& ctx); + static TER + checkPermission(ReadView const& view, STTx const& tx); + static TER preclaim(PreclaimContext const& ctx);