diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp index a810ee30b92..1cf0a6cc67c 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp @@ -106,6 +106,7 @@ MPTokenIssuanceSet::doApply() bool bUnlock = txFlags & tfMPTUnlock; if (ctx_.isDelegated && !ctx_.gpSet.empty()) { + // if gpSet is not empty, granular delegation is happening. if (bLock && ctx_.gpSet.find(gpMPTokenIssuanceLock) == ctx_.gpSet.end()) return terNO_AUTH; if (bUnlock && diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp index 44a77558e3c..39e68f891e1 100644 --- a/src/xrpld/app/tx/detail/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -344,15 +344,19 @@ Payment::doApply() if (ctx_.isDelegated && !ctx_.gpSet.empty()) { + // If gpSet is not empty, granular delegation is happening. + // Currently we only support PaymentMint and PaymentBurn granular + // permission. PaymentMint means the sender is the issuer. PaymentBurn + // means the destination is the issuer. bool authorized = false; auto const amountIssue = dstAmount.issue(); if (isXRP(amountIssue)) return tecNO_AUTH; if (amountIssue.account == account_ && - ctx_.gpSet.find(gpPaymentMint) == ctx_.gpSet.end()) + ctx_.gpSet.find(gpPaymentMint) != ctx_.gpSet.end()) authorized = true; if (amountIssue.account == dstAccountID && - ctx_.gpSet.find(gpPaymentBurn) == ctx_.gpSet.end()) + ctx_.gpSet.find(gpPaymentBurn) != ctx_.gpSet.end()) authorized = true; if (!authorized) diff --git a/src/xrpld/app/tx/detail/SetAccount.cpp b/src/xrpld/app/tx/detail/SetAccount.cpp index c0e115c2497..d1d2496747b 100644 --- a/src/xrpld/app/tx/detail/SetAccount.cpp +++ b/src/xrpld/app/tx/detail/SetAccount.cpp @@ -268,6 +268,20 @@ SetAccount::doApply() // legacy AccountSet flags std::uint32_t const uTxFlags{tx.getFlags()}; + + bool granularDelegated = false; + if (ctx_.isDelegated && !ctx_.gpSet.empty()) + { + // if gpSet is not empty, granular delegation is happening. + granularDelegated = true; + + // We don't support any flag based granular permission under AccountSet + // transaction. If any delegated account is trying to update the flag + // onbehalf of another account, it is not authorized. + if (uSetFlag != 0 || uClearFlag != 0 || uTxFlags != 0) + return tecNO_AUTH; + } + bool const bSetRequireDest{ (uTxFlags & tfRequireDestTag) || (uSetFlag == asfRequireDest)}; bool const bClearRequireDest{ @@ -450,6 +464,10 @@ SetAccount::doApply() // if (tx.isFieldPresent(sfEmailHash)) { + if (granularDelegated && + ctx_.gpSet.find(gpAccountEmailHashSet) == ctx_.gpSet.end()) + return tecNO_AUTH; + uint128 const uHash = tx.getFieldH128(sfEmailHash); if (!uHash) @@ -469,6 +487,9 @@ SetAccount::doApply() // if (tx.isFieldPresent(sfWalletLocator)) { + if (granularDelegated) + return tecNO_AUTH; + uint256 const uHash = tx.getFieldH256(sfWalletLocator); if (!uHash) @@ -488,6 +509,10 @@ SetAccount::doApply() // if (tx.isFieldPresent(sfMessageKey)) { + if (granularDelegated && + ctx_.gpSet.find(gpAccountMessageKeySet) == ctx_.gpSet.end()) + return tecNO_AUTH; + Blob const messageKey = tx.getFieldVL(sfMessageKey); if (messageKey.empty()) @@ -507,6 +532,10 @@ SetAccount::doApply() // if (tx.isFieldPresent(sfDomain)) { + if (granularDelegated && + ctx_.gpSet.find(gpAccountDomainSet) == ctx_.gpSet.end()) + return tecNO_AUTH; + Blob const domain = tx.getFieldVL(sfDomain); if (domain.empty()) @@ -526,6 +555,10 @@ SetAccount::doApply() // if (tx.isFieldPresent(sfTransferRate)) { + if (granularDelegated && + ctx_.gpSet.find(gpAccountTransferRateSet) == ctx_.gpSet.end()) + return tecNO_AUTH; + std::uint32_t uRate = tx.getFieldU32(sfTransferRate); if (uRate == 0 || uRate == QUALITY_ONE) @@ -545,6 +578,10 @@ SetAccount::doApply() // if (tx.isFieldPresent(sfTickSize)) { + if (granularDelegated && + ctx_.gpSet.find(gpAccountTickSizeSet) == ctx_.gpSet.end()) + return tecNO_AUTH; + auto uTickSize = tx[sfTickSize]; if ((uTickSize == 0) || (uTickSize == Quality::maxTickSize)) { diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index 44976c1d705..ba45fbefb15 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -243,9 +243,14 @@ SetTrust::doApply() bool const bSetFreeze = (uTxFlags & tfSetFreeze); bool const bClearFreeze = (uTxFlags & tfClearFreeze); + bool granularDelegated = false; if (ctx_.isDelegated && !ctx_.gpSet.empty()) { - if (bSetNoRipple || bClearNoRipple) + granularDelegated = true; + // If granular permission is delegated under the TrustSet transaction. + // Currently we only support TrustlineAuthorize, TrustlineFreeze and + // TrustlineUnfreeze granular permission. + if (bSetNoRipple || bClearNoRipple || bQualityIn || bQualityOut) return terNO_AUTH; if (bSetAuth && ctx_.gpSet.find(gpTrustlineAuthorize) == ctx_.gpSet.end()) @@ -315,6 +320,18 @@ SetTrust::doApply() // // Limits // + if (granularDelegated) + { + // Currently we only support TrustlineAuthorize, TrustlineFreeze and + // TrustlineUnfreeze granular permission. So updating the + // LimitAmount is not allowed unless the delegated account has full + // transaction level permission. + auto const curLimit = bHigh + ? sleRippleState->getFieldAmount(sfHighLimit) + : sleRippleState->getFieldAmount(sfLowLimit); + if (curLimit != saLimitAllow) + return tecNO_AUTH; + } sleRippleState->setFieldAmount( !bHigh ? sfLowLimit : sfHighLimit, saLimitAllow); @@ -551,6 +568,13 @@ SetTrust::doApply() } else { + if (granularDelegated) + // currently we only allow TrustlineAuthorize, TrustlineFreeze and + // TrustlineUnfreeze granular permission delegation, a delegated + // account can not create a new trust line if it is not fully + // delegated with the whole TrustSet transaction based permission. + return tecNO_PERMISSION; + // Zero balance in currency. STAmount saBalance(Issue{currency, noAccount()});