mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-24 15:40:26 +00:00
Fix granular permission for Attackathon (#6831)
Co-authored-by: yinyiqian1 <yqian@ripple.com>
This commit is contained in:
@@ -26,10 +26,7 @@ checkTxPermission(std::shared_ptr<SLE const> const& delegate, STTx const& tx);
|
||||
* @param granularPermissions Granted granular permissions tied to the
|
||||
* transaction type.
|
||||
*/
|
||||
void
|
||||
loadGranularPermission(
|
||||
std::shared_ptr<SLE const> const& delegate,
|
||||
TxType const& type,
|
||||
std::unordered_set<GranularPermissionType>& granularPermissions);
|
||||
std::unordered_set<GranularPermissionType>
|
||||
getGranularPermission(std::shared_ptr<SLE const> const& delegate, TxType const& type);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class STTx;
|
||||
|
||||
/**
|
||||
* We have both transaction type permissions and granular type permissions.
|
||||
* Since we will reuse the TransactionFormats to parse the Transaction
|
||||
@@ -17,15 +21,15 @@ namespace xrpl {
|
||||
* greater than the maximum value of uint16.
|
||||
*/
|
||||
enum GranularPermissionType : std::uint32_t {
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
#pragma push_macro("GRANULAR_PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
|
||||
#define PERMISSION(type, txType, value) type = value,
|
||||
#define GRANULAR_PERMISSION(name, txType, value, allowedFlags, allowedFields) name = value,
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef PERMISSION
|
||||
#pragma pop_macro("PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
#pragma pop_macro("GRANULAR_PERMISSION")
|
||||
};
|
||||
|
||||
enum Delegation { delegable, notDelegable };
|
||||
@@ -36,15 +40,16 @@ private:
|
||||
Permission();
|
||||
|
||||
std::unordered_map<std::uint16_t, uint256> txFeatureMap_;
|
||||
|
||||
std::unordered_map<std::uint16_t, Delegation> delegableTx_;
|
||||
|
||||
std::unordered_map<std::string, GranularPermissionType> granularPermissionMap_;
|
||||
|
||||
std::unordered_map<GranularPermissionType, std::string> granularNameMap_;
|
||||
|
||||
std::unordered_map<GranularPermissionType, TxType> granularTxTypeMap_;
|
||||
|
||||
std::unordered_map<GranularPermissionType, std::uint32_t> granularPermittedFlags_;
|
||||
std::unordered_map<GranularPermissionType, SOTemplate> granularTemplates_;
|
||||
std::unordered_set<TxType> granularTxTypes_;
|
||||
|
||||
public:
|
||||
static Permission const&
|
||||
getInstance();
|
||||
@@ -71,6 +76,9 @@ public:
|
||||
bool
|
||||
isDelegable(std::uint32_t const& permissionValue, Rules const& rules) const;
|
||||
|
||||
bool
|
||||
hasGranularPermissions(TxType txType) const;
|
||||
|
||||
// for tx level permission, permission value is equal to tx type plus one
|
||||
static uint32_t
|
||||
txToPermissionType(TxType const& type);
|
||||
@@ -78,6 +86,23 @@ public:
|
||||
// tx type value is permission value minus one
|
||||
static TxType
|
||||
permissionToTxType(uint32_t const& value);
|
||||
|
||||
/**
|
||||
* @brief Verifies a delegated transaction against its granular permission template.
|
||||
*
|
||||
* @note WARNING: Do not move this check before standard transaction-level
|
||||
* format checks, which is in preclaim. This function assumes the transaction's
|
||||
* base structural integrity (fees, sequence, signatures) has already been
|
||||
* validated.
|
||||
*
|
||||
* @param tx The transaction to verify.
|
||||
* @param heldPermissions The granular permissions that the sender hold.
|
||||
* @return true if the transaction fields and flags comply with the granular template.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
checkGranularSandbox(
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldPermissions) const;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,49 +1,80 @@
|
||||
#if !defined(PERMISSION)
|
||||
#error "undefined macro: PERMISSION"
|
||||
#if !defined(GRANULAR_PERMISSION)
|
||||
#error "undefined macro: GRANULAR_PERMISSION"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* PERMISSION(name, type, txType, value)
|
||||
* GRANULAR_PERMISSION(name, txType, value, allowedFlags, allowedFields)
|
||||
*
|
||||
* This macro defines a permission:
|
||||
* name: the name of the permission.
|
||||
* type: the GranularPermissionType enum.
|
||||
* txType: the corresponding TxType for this permission.
|
||||
* value: the uint32 numeric value for the enum type.
|
||||
* Defines a granular permission:
|
||||
* name: the granular permission name.
|
||||
* txType: the corresponding TxType for this permission.
|
||||
* value: the uint32 numeric value for the enum type.
|
||||
* allowedFlags: transaction flags permitted under this permission.
|
||||
* allowedFields: transaction fields permitted under this permission.
|
||||
*/
|
||||
|
||||
/** This permission grants the delegated account the ability to authorize a trustline. */
|
||||
PERMISSION(TrustlineAuthorize, ttTRUST_SET, 65537)
|
||||
/** Grants the ability to authorize a trustline. */
|
||||
GRANULAR_PERMISSION(TrustlineAuthorize, ttTRUST_SET, 65537, tfUniversal | tfSetfAuth,
|
||||
({{sfLimitAmount, soeREQUIRED}}))
|
||||
|
||||
/** This permission grants the delegated account the ability to freeze a trustline. */
|
||||
PERMISSION(TrustlineFreeze, ttTRUST_SET, 65538)
|
||||
/** Grants the ability to freeze a trustline. */
|
||||
GRANULAR_PERMISSION(TrustlineFreeze, ttTRUST_SET, 65538, tfUniversal | tfSetFreeze,
|
||||
({{sfLimitAmount, soeREQUIRED}}))
|
||||
|
||||
/** This permission grants the delegated account the ability to unfreeze a trustline. */
|
||||
PERMISSION(TrustlineUnfreeze, ttTRUST_SET, 65539)
|
||||
/** Grants the ability to unfreeze a trustline. */
|
||||
GRANULAR_PERMISSION(TrustlineUnfreeze, ttTRUST_SET, 65539, tfUniversal | tfClearFreeze,
|
||||
({{sfLimitAmount, soeREQUIRED}}))
|
||||
|
||||
/** This permission grants the delegated account the ability to set Domain. */
|
||||
PERMISSION(AccountDomainSet, ttACCOUNT_SET, 65540)
|
||||
/** Grants the ability to set Domain. */
|
||||
GRANULAR_PERMISSION(AccountDomainSet, ttACCOUNT_SET, 65540, tfUniversal,
|
||||
({{sfDomain, soeOPTIONAL}}))
|
||||
|
||||
/** This permission grants the delegated account the ability to set EmailHashSet. */
|
||||
PERMISSION(AccountEmailHashSet, ttACCOUNT_SET, 65541)
|
||||
/** Grants the ability to set EmailHash. */
|
||||
GRANULAR_PERMISSION(AccountEmailHashSet, ttACCOUNT_SET, 65541, tfUniversal,
|
||||
({{sfEmailHash, soeOPTIONAL}}))
|
||||
|
||||
/** This permission grants the delegated account the ability to set MessageKey. */
|
||||
PERMISSION(AccountMessageKeySet, ttACCOUNT_SET, 65542)
|
||||
/** Grants the ability to set MessageKey. */
|
||||
GRANULAR_PERMISSION(AccountMessageKeySet, ttACCOUNT_SET, 65542, tfUniversal,
|
||||
({{sfMessageKey, soeOPTIONAL}}))
|
||||
|
||||
/** This permission grants the delegated account the ability to set TransferRate. */
|
||||
PERMISSION(AccountTransferRateSet, ttACCOUNT_SET, 65543)
|
||||
/** Grants the ability to set TransferRate. */
|
||||
GRANULAR_PERMISSION(AccountTransferRateSet, ttACCOUNT_SET, 65543, tfUniversal,
|
||||
({{sfTransferRate, soeOPTIONAL}}))
|
||||
|
||||
/** This permission grants the delegated account the ability to set TickSize. */
|
||||
PERMISSION(AccountTickSizeSet, ttACCOUNT_SET, 65544)
|
||||
/** Grants the ability to set TickSize. */
|
||||
GRANULAR_PERMISSION(AccountTickSizeSet, ttACCOUNT_SET, 65544, tfUniversal,
|
||||
({{sfTickSize, soeOPTIONAL}}))
|
||||
|
||||
/** This permission grants the delegated account the ability to mint payment, which means sending a payment for a currency where the sending account is the issuer. */
|
||||
PERMISSION(PaymentMint, ttPAYMENT, 65545)
|
||||
/** Grants the ability to mint payment (sending account is the issuer). Cross-currency payments are disallowed. */
|
||||
GRANULAR_PERMISSION(PaymentMint, ttPAYMENT, 65545,
|
||||
tfUniversal | tfPartialPayment | tfLimitQuality | tfNoRippleDirect,
|
||||
({{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfSendMax, soeOPTIONAL},
|
||||
{sfInvoiceID, soeOPTIONAL},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfDeliverMin, soeOPTIONAL},
|
||||
{sfCredentialIDs, soeOPTIONAL},
|
||||
{sfDomainID, soeOPTIONAL}}))
|
||||
|
||||
/** This permission grants the delegated account the ability to burn payment, which means sending a payment for a currency where the destination account is the issuer */
|
||||
PERMISSION(PaymentBurn, ttPAYMENT, 65546)
|
||||
/** Grants the ability to burn payment (destination account is the issuer). Cross-currency payments are disallowed. */
|
||||
GRANULAR_PERMISSION(PaymentBurn, ttPAYMENT, 65546,
|
||||
tfUniversal | tfPartialPayment | tfLimitQuality | tfNoRippleDirect,
|
||||
({{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfSendMax, soeOPTIONAL},
|
||||
{sfInvoiceID, soeOPTIONAL},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfDeliverMin, soeOPTIONAL},
|
||||
{sfCredentialIDs, soeOPTIONAL},
|
||||
{sfDomainID, soeOPTIONAL}}))
|
||||
|
||||
/** This permission grants the delegated account the ability to lock MPToken. */
|
||||
PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547)
|
||||
/** Grants the ability to lock an MPToken. */
|
||||
GRANULAR_PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547, tfUniversal | tfMPTLock,
|
||||
({{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfHolder, soeOPTIONAL}}))
|
||||
|
||||
/** This permission grants the delegated account the ability to unlock MPToken. */
|
||||
PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548)
|
||||
/** Grants the ability to unlock an MPToken. */
|
||||
GRANULAR_PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548, tfUniversal | tfMPTUnlock,
|
||||
({{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfHolder, soeOPTIONAL}}))
|
||||
|
||||
@@ -206,8 +206,47 @@ public:
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function can be overridden to introduce additional semantic constraints beyond the
|
||||
* granular template validation for granular permissions. It is called by the base
|
||||
* checkPermission method only after the transaction has successfully passed
|
||||
* checkGranularSandbox.
|
||||
*/
|
||||
static NotTEC
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
checkGranularSemantics(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldGranularPermissions)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the transaction is authorized to be executed by the delegated account.
|
||||
* This function enforces the strict permission check hierarchy. It is explicitly
|
||||
* designed NOT to be overridden. Derived transactors must instead implement
|
||||
* checkGranularSemantics to add custom validation logic for granular permissions.
|
||||
*
|
||||
* The evaluation proceeds as follows:
|
||||
* - If transaction-level permission is granted, the function immediately returns tesSUCCESS.
|
||||
* - If transaction-level permission is not granted, the function checks whether the transaction
|
||||
* matches the granular permission template defined in permissions.macro. If it does, it then
|
||||
* calls checkGranularSemantics to perform any additional, fine-grained validation.
|
||||
*
|
||||
*/
|
||||
template <class T>
|
||||
static NotTEC
|
||||
checkPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
std::unordered_set<GranularPermissionType> heldGranularPermissions;
|
||||
if (NotTEC const result = checkPermissionImpl(view, tx, heldGranularPermissions);
|
||||
!isTesSuccess(result) || heldGranularPermissions.empty())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return T::checkGranularSemantics(view, tx, heldGranularPermissions);
|
||||
}
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
// Interface used by AccountDelete
|
||||
@@ -294,6 +333,12 @@ protected:
|
||||
unit::ValueUnit<Unit, T> min = unit::ValueUnit<Unit, T>{});
|
||||
|
||||
private:
|
||||
static NotTEC
|
||||
checkPermissionImpl(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType>& heldGranularPermissions);
|
||||
|
||||
std::pair<TER, XRPAmount>
|
||||
reset(XRPAmount fee);
|
||||
|
||||
|
||||
@@ -23,9 +23,6 @@ public:
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
|
||||
@@ -32,7 +32,10 @@ public:
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
checkGranularSemantics(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldGranularPermissions);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
@@ -22,9 +22,6 @@ public:
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@ public:
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
checkPermission(ReadView const& view, STTx const& tx);
|
||||
checkGranularSemantics(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldGranularPermissions);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -32,41 +34,70 @@ Permission::Permission()
|
||||
};
|
||||
|
||||
granularPermissionMap_ = {
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
#pragma push_macro("GRANULAR_PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
|
||||
#define PERMISSION(type, txType, value) {#type, type},
|
||||
#define GRANULAR_PERMISSION(type, txType, value, flags, fields) {#type, type},
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef PERMISSION
|
||||
#pragma pop_macro("PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
#pragma pop_macro("GRANULAR_PERMISSION")
|
||||
};
|
||||
|
||||
granularNameMap_ = {
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
#pragma push_macro("GRANULAR_PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
|
||||
#define PERMISSION(type, txType, value) {type, #type},
|
||||
#define GRANULAR_PERMISSION(type, txType, value, flags, fields) {type, #type},
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef PERMISSION
|
||||
#pragma pop_macro("PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
#pragma pop_macro("GRANULAR_PERMISSION")
|
||||
};
|
||||
|
||||
granularTxTypeMap_ = {
|
||||
#pragma push_macro("PERMISSION")
|
||||
#undef PERMISSION
|
||||
#pragma push_macro("GRANULAR_PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
|
||||
#define PERMISSION(type, txType, value) {type, txType},
|
||||
#define GRANULAR_PERMISSION(type, txType, value, flags, fields) {type, txType},
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef PERMISSION
|
||||
#pragma pop_macro("PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
#pragma pop_macro("GRANULAR_PERMISSION")
|
||||
};
|
||||
|
||||
{
|
||||
#pragma push_macro("GRANULAR_PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
|
||||
#define GRANULAR_PERMISSION(type, txType, value, flags, fields) \
|
||||
granularPermittedFlags_[type] = (flags);
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef GRANULAR_PERMISSION
|
||||
#pragma pop_macro("GRANULAR_PERMISSION")
|
||||
}
|
||||
|
||||
{
|
||||
#pragma push_macro("GRANULAR_PERMISSION")
|
||||
#undef GRANULAR_PERMISSION
|
||||
|
||||
#define GRANULAR_PERMISSION(type, txType, value, flags, fields) \
|
||||
granularTemplates_.emplace( \
|
||||
std::piecewise_construct, \
|
||||
std::forward_as_tuple(type), \
|
||||
std::forward_as_tuple(std::vector<SOElement> fields, TxFormats::getCommonFields()));
|
||||
|
||||
#include <xrpl/protocol/detail/permissions.macro>
|
||||
|
||||
#undef GRANULAR_PERMISSION
|
||||
#pragma pop_macro("GRANULAR_PERMISSION")
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
txFeatureMap_.size() == delegableTx_.size(),
|
||||
"xrpl::Permission : txFeatureMap_ and delegableTx_ must have same "
|
||||
@@ -79,6 +110,9 @@ Permission::Permission()
|
||||
"xrpl::Permission::granularPermissionMap_ : granular permission "
|
||||
"value must not exceed the maximum uint16_t value.");
|
||||
}
|
||||
|
||||
for (auto const& [gp, txType] : granularTxTypeMap_)
|
||||
granularTxTypes_.insert(txType);
|
||||
}
|
||||
|
||||
Permission const&
|
||||
@@ -133,6 +167,12 @@ Permission::getGranularTxType(GranularPermissionType const& gpType) const
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool
|
||||
Permission::hasGranularPermissions(TxType txType) const
|
||||
{
|
||||
return granularTxTypes_.contains(txType);
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<uint256 const>>
|
||||
Permission::getTxFeature(TxType txType) const
|
||||
{
|
||||
@@ -192,4 +232,48 @@ Permission::permissionToTxType(uint32_t const& value)
|
||||
return static_cast<TxType>(value - 1);
|
||||
}
|
||||
|
||||
bool
|
||||
Permission::checkGranularSandbox(
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldPermissions) const
|
||||
{
|
||||
auto const txFlags = tx.getFlags();
|
||||
|
||||
// Build union of flags and templates across all held permissions.
|
||||
std::uint32_t unionFlags = 0;
|
||||
for (auto const& gp : heldPermissions)
|
||||
{
|
||||
auto const it = granularPermittedFlags_.find(gp);
|
||||
if (it != granularPermittedFlags_.end())
|
||||
unionFlags |= it->second;
|
||||
}
|
||||
|
||||
// Check if flags are permitted
|
||||
if ((txFlags & ~unionFlags) != 0)
|
||||
return false;
|
||||
|
||||
// Check if fields are permitted. Every present field must appear in at least one held
|
||||
// permission's template. The common fields are included in the constructor.
|
||||
for (auto const& field : tx)
|
||||
{
|
||||
if (field.getSType() == STI_NOTPRESENT)
|
||||
continue;
|
||||
|
||||
bool fieldAllowed = false;
|
||||
for (auto const& gp : heldPermissions)
|
||||
{
|
||||
auto const it = granularTemplates_.find(gp);
|
||||
if (it != granularTemplates_.end() && it->second.getIndex(field.getFName()) != -1)
|
||||
{
|
||||
fieldAllowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!fieldAllowed)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -256,19 +256,33 @@ Transactor::preflightSigValidated(PreflightContext const& ctx)
|
||||
}
|
||||
|
||||
NotTEC
|
||||
Transactor::checkPermission(ReadView const& view, STTx const& tx)
|
||||
Transactor::checkPermissionImpl(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType>& heldGranularPermissions)
|
||||
{
|
||||
auto const delegate = tx[~sfDelegate];
|
||||
if (!delegate)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
|
||||
auto const sle = view.read(delegateKey);
|
||||
|
||||
auto const sle = view.read(keylet::delegate(tx[sfAccount], *delegate));
|
||||
if (!sle)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
return checkTxPermission(sle, tx);
|
||||
if (isTesSuccess(checkTxPermission(sle, tx)))
|
||||
return tesSUCCESS;
|
||||
|
||||
if (!Permission::getInstance().hasGranularPermissions(tx.getTxnType()))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
heldGranularPermissions = getGranularPermission(sle, tx.getTxnType());
|
||||
if (heldGranularPermissions.empty())
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (!Permission::getInstance().checkGranularSandbox(tx, heldGranularPermissions))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
|
||||
@@ -174,7 +174,7 @@ invoke_preclaim(PreclaimContext const& ctx)
|
||||
if (NotTEC const result = T::checkPriorTxAndLastLedger(ctx))
|
||||
return result;
|
||||
|
||||
if (NotTEC const result = T::checkPermission(ctx.view, ctx.tx))
|
||||
if (NotTEC const result = Transactor::checkPermission<T>(ctx.view, ctx.tx))
|
||||
return result;
|
||||
|
||||
if (NotTEC const result = T::checkSign(ctx))
|
||||
|
||||
@@ -155,55 +155,6 @@ AccountSet::preflight(PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
AccountSet::checkPermission(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
// AccountSet is prohibited to be granted on a transaction level,
|
||||
// but some granular permissions are allowed.
|
||||
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 terNO_DELEGATE_PERMISSION;
|
||||
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(sle, ttACCOUNT_SET, granularPermissions);
|
||||
|
||||
auto const uSetFlag = tx.getFieldU32(sfSetFlag);
|
||||
auto const uClearFlag = tx.getFieldU32(sfClearFlag);
|
||||
auto const uTxFlags = tx.getFlags();
|
||||
// We don't support any flag based granular permission under
|
||||
// AccountSet transaction. If any delegated account is trying to
|
||||
// update the flag on behalf of another account, it is not
|
||||
// authorized.
|
||||
if (uSetFlag != 0 || uClearFlag != 0 || ((uTxFlags & tfUniversalMask) != 0u))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfEmailHash) && !granularPermissions.contains(AccountEmailHashSet))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfWalletLocator) || tx.isFieldPresent(sfNFTokenMinter))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfMessageKey) && !granularPermissions.contains(AccountMessageKeySet))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfDomain) && !granularPermissions.contains(AccountDomainSet))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfTransferRate) && !granularPermissions.contains(AccountTransferRateSet))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfTickSize) && !granularPermissions.contains(AccountTickSizeSet))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
AccountSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
|
||||
@@ -21,14 +21,12 @@ checkTxPermission(std::shared_ptr<SLE const> const& delegate, STTx const& tx)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
}
|
||||
|
||||
void
|
||||
loadGranularPermission(
|
||||
std::shared_ptr<SLE const> const& delegate,
|
||||
TxType const& txType,
|
||||
std::unordered_set<GranularPermissionType>& granularPermissions)
|
||||
std::unordered_set<GranularPermissionType>
|
||||
getGranularPermission(std::shared_ptr<SLE const> const& delegate, TxType const& txType)
|
||||
{
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
if (!delegate)
|
||||
return;
|
||||
return granularPermissions; // LCOV_EXCL_LINE
|
||||
|
||||
auto const permissionArray = delegate->getFieldArray(sfPermissions);
|
||||
for (auto const& permission : permissionArray)
|
||||
@@ -39,6 +37,8 @@ loadGranularPermission(
|
||||
if (type && *type == txType)
|
||||
granularPermissions.insert(granularValue);
|
||||
}
|
||||
|
||||
return granularPermissions;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -239,38 +239,24 @@ Payment::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
|
||||
NotTEC
|
||||
Payment::checkPermission(ReadView const& view, STTx const& tx)
|
||||
Payment::checkGranularSemantics(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldGranularPermissions)
|
||||
{
|
||||
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 terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (isTesSuccess(checkTxPermission(sle, tx)))
|
||||
return tesSUCCESS;
|
||||
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(sle, ttPAYMENT, granularPermissions);
|
||||
|
||||
auto const& dstAmount = tx.getFieldAmount(sfAmount);
|
||||
auto const& amountAsset = dstAmount.asset();
|
||||
|
||||
// Granular permissions are only valid for direct payments.
|
||||
if ((tx.isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset) ||
|
||||
tx.isFieldPresent(sfPaths))
|
||||
if (tx.isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
// PaymentMint and PaymentBurn apply to both IOU and MPT direct payments.
|
||||
if (granularPermissions.contains(PaymentMint) && !isXRP(amountAsset) &&
|
||||
if (heldGranularPermissions.contains(PaymentMint) && !isXRP(amountAsset) &&
|
||||
amountAsset.getIssuer() == tx[sfAccount])
|
||||
return tesSUCCESS;
|
||||
|
||||
if (granularPermissions.contains(PaymentBurn) && !isXRP(amountAsset) &&
|
||||
if (heldGranularPermissions.contains(PaymentBurn) && !isXRP(amountAsset) &&
|
||||
amountAsset.getIssuer() == tx[sfDestination])
|
||||
return tesSUCCESS;
|
||||
|
||||
|
||||
@@ -151,41 +151,6 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
MPTokenIssuanceSet::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 terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (isTesSuccess(checkTxPermission(sle, tx)))
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const txFlags = tx.getFlags();
|
||||
|
||||
// this is added in case more flags will be added for MPTokenIssuanceSet
|
||||
// in the future. Currently unreachable.
|
||||
if ((txFlags & tfMPTokenIssuanceSetMask) != 0u)
|
||||
return terNO_DELEGATE_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(sle, ttMPTOKEN_ISSUANCE_SET, granularPermissions);
|
||||
|
||||
if (((txFlags & tfMPTLock) != 0u) && !granularPermissions.contains(MPTokenIssuanceLock))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (((txFlags & tfMPTUnlock) != 0u) && !granularPermissions.contains(MPTokenIssuanceUnlock))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
|
||||
@@ -107,53 +107,21 @@ TrustSet::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
|
||||
NotTEC
|
||||
TrustSet::checkPermission(ReadView const& view, STTx const& tx)
|
||||
TrustSet::checkGranularSemantics(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
std::unordered_set<GranularPermissionType> const& heldGranularPermissions)
|
||||
{
|
||||
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 terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (isTesSuccess(checkTxPermission(sle, tx)))
|
||||
return tesSUCCESS;
|
||||
|
||||
std::uint32_t const txFlags = tx.getFlags();
|
||||
|
||||
// Currently we only support TrustlineAuthorize, TrustlineFreeze and
|
||||
// TrustlineUnfreeze granular permission. Setting other flags returns
|
||||
// error.
|
||||
if ((txFlags & tfTrustSetPermissionMask) != 0u)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (tx.isFieldPresent(sfQualityIn) || tx.isFieldPresent(sfQualityOut))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
auto const saLimitAmount = tx.getFieldAmount(sfLimitAmount);
|
||||
auto const sleRippleState = view.read(
|
||||
keylet::line(
|
||||
tx[sfAccount], saLimitAmount.getIssuer(), saLimitAmount.get<Issue>().currency));
|
||||
|
||||
// if the trustline does not exist, granular permissions are
|
||||
// not allowed to create trustline
|
||||
// granular permissions are not allowed to create a trustline
|
||||
if (!sleRippleState)
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(sle, ttTRUST_SET, granularPermissions);
|
||||
|
||||
if (((txFlags & tfSetfAuth) != 0u) && !granularPermissions.contains(TrustlineAuthorize))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
if (((txFlags & tfSetFreeze) != 0u) && !granularPermissions.contains(TrustlineFreeze))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
if (((txFlags & tfClearFreeze) != 0u) && !granularPermissions.contains(TrustlineUnfreeze))
|
||||
return terNO_DELEGATE_PERMISSION;
|
||||
|
||||
// updating LimitAmount is not allowed only with granular permissions,
|
||||
// updating LimitAmount is not allowed with granular permissions,
|
||||
// unless there's a new granular permission for this in the future.
|
||||
auto const curLimit = tx[sfAccount] > saLimitAmount.getIssuer()
|
||||
? sleRippleState->getFieldAmount(sfHighLimit)
|
||||
|
||||
@@ -870,6 +870,54 @@ class Delegate_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
// PaymentMint/PaymentBurn with sfSendMax of the same asset is allowed,
|
||||
// same-asset SendMax is still a direct payment, not cross-currency.
|
||||
{
|
||||
Env env(*this, features);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const gw{"gw"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), alice, bob, gw);
|
||||
env.trust(USD(200), alice);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(gw, bob, {"PaymentMint"}));
|
||||
env.close();
|
||||
|
||||
// sfSendMax with same asset as sfAmount, still a direct payment
|
||||
env(pay(gw, alice, USD(50)), sendmax(USD(50)), delegate::as(bob));
|
||||
env.require(balance(alice, USD(50)));
|
||||
|
||||
env(delegate::set(alice, bob, {"PaymentBurn"}));
|
||||
env.close();
|
||||
|
||||
env(pay(alice, gw, USD(30)), sendmax(USD(30)), delegate::as(bob));
|
||||
env.require(balance(alice, USD(20)));
|
||||
}
|
||||
|
||||
// Delegate account holds no granular permissions for the tx type:
|
||||
// getGranularPermission returns empty set.
|
||||
{
|
||||
Env env(*this, features);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const gw{"gw"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), alice, bob, gw);
|
||||
env.trust(USD(200), alice);
|
||||
env.close();
|
||||
|
||||
// Bob holds only an AccountSet granular permission.
|
||||
env(delegate::set(alice, bob, {"AccountDomainSet"}));
|
||||
env.close();
|
||||
|
||||
// Payment has granular permissions defined in permissions.macro,
|
||||
// but bob only holds AccountSet's granular permission,
|
||||
// getGranularPermission returns empty.
|
||||
env(pay(alice, gw, USD(50)), delegate::as(bob), ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
|
||||
// PaymentMint and PaymentBurn for MPT
|
||||
{
|
||||
std::string logs;
|
||||
@@ -926,6 +974,40 @@ class Delegate_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(env.balance(bob, MPT) == bobMPT + MPT(100));
|
||||
}
|
||||
}
|
||||
|
||||
// Verify granular permissions of different tx types in the same SLE are scoped
|
||||
// correctly. AccountSet permissions don't apply to Payment and vice versa
|
||||
{
|
||||
Env env(*this);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const gw{"gw"};
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), alice, bob, gw);
|
||||
env.trust(USD(200), alice);
|
||||
env.close();
|
||||
|
||||
// Alice granted bob with both AccountDomainSet and PaymentMint.
|
||||
env(delegate::set(alice, bob, {"AccountDomainSet", "PaymentMint"}));
|
||||
env.close();
|
||||
|
||||
// PaymentMint fails at granular semantic check because alice is not the issuer.
|
||||
env(pay(alice, gw, USD(50)), delegate::as(bob), ter(terNO_DELEGATE_PERMISSION));
|
||||
|
||||
// AccountDomainSet applies correctly to AccountSet
|
||||
std::string const domain = "example.com";
|
||||
auto jt = noop(alice);
|
||||
jt[sfDomain] = strHex(domain);
|
||||
jt[sfDelegate] = bob.human();
|
||||
env(jt);
|
||||
BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
|
||||
|
||||
// gw gives bob PaymentMint and bob can mint on gw's behalf
|
||||
env(delegate::set(gw, bob, {"PaymentMint"}));
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(50)), delegate::as(bob));
|
||||
env.require(balance(alice, USD(50)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1108,6 +1190,34 @@ class Delegate_test : public beast::unit_test::suite
|
||||
env(trust(gw, gw["USD"](0), alice, tfSetfAuth | tfFullyCanonicalSig),
|
||||
delegate::as(bob));
|
||||
}
|
||||
|
||||
{
|
||||
Env env(*this);
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), gw, alice, bob);
|
||||
|
||||
env(fset(gw, asfRequireAuth));
|
||||
env.close();
|
||||
env(trust(alice, gw["USD"](50)));
|
||||
env.close();
|
||||
env(delegate::set(gw, bob, {"TrustlineAuthorize"}));
|
||||
env.close();
|
||||
|
||||
env(trust(gw, gw["USD"](0), alice, tfSetfAuth), delegate::as(bob));
|
||||
env.close();
|
||||
|
||||
// sfQualityOut is a valid TrustSet field, but not permitted in granular template
|
||||
Json::Value txJson = trust(gw, gw["USD"](0), alice, tfSetfAuth);
|
||||
txJson[sfQualityOut.jsonName] = 100;
|
||||
env(txJson, delegate::as(bob), ter(terNO_DELEGATE_PERMISSION));
|
||||
|
||||
// tfSetNoRipple is a valid flag for TrustSet, but not permitted in granular template
|
||||
env(trust(gw, gw["USD"](0), alice, tfSetfAuth | tfSetNoRipple),
|
||||
delegate::as(bob),
|
||||
ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1263,7 +1373,9 @@ class Delegate_test : public beast::unit_test::suite
|
||||
env(jv2, ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
|
||||
// can not set AccountSet flags on behalf of other account
|
||||
// can not set AccountSet flags on behalf of other account,
|
||||
// in permissions.macro, the template for AccountSet does
|
||||
// not allow any flag set or clear.
|
||||
{
|
||||
Env env(*this);
|
||||
auto const alice = Account{"alice"};
|
||||
@@ -1359,6 +1471,71 @@ class Delegate_test : public beast::unit_test::suite
|
||||
env(jt);
|
||||
BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
|
||||
}
|
||||
|
||||
// setting invalid field not in permissions.macro template will be rejected.
|
||||
{
|
||||
Env env(*this);
|
||||
auto const alice = Account{"alice"};
|
||||
auto const bob = Account{"bob"};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// Alice gives Bob permission to set her Domain
|
||||
env(delegate::set(alice, bob, {"AccountDomainSet"}));
|
||||
env.close();
|
||||
|
||||
std::string const domain = "example.com";
|
||||
auto txJson = noop(alice);
|
||||
txJson[sfDomain] = strHex(domain);
|
||||
txJson[sfDelegate] = bob.human();
|
||||
|
||||
// sfNFTokenMinter is a valid field in AccountSet tx, but
|
||||
// it is not permitted for granular template
|
||||
txJson[sfNFTokenMinter] = bob.human();
|
||||
|
||||
env(txJson, ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
|
||||
// Delegated AccountSet with no fields and no flags is allowed,
|
||||
// because it is allowed in the non-delegated case as well.
|
||||
{
|
||||
Env env(*this);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(alice, bob, {"AccountDomainSet"}));
|
||||
env.close();
|
||||
|
||||
auto jt = noop(alice);
|
||||
jt[sfDelegate] = bob.human();
|
||||
env(jt);
|
||||
}
|
||||
|
||||
// Revoking all permissions deletes the SLE and subsequent attempts are rejected.
|
||||
{
|
||||
Env env(*this);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(alice, bob, {"AccountDomainSet"}));
|
||||
env.close();
|
||||
|
||||
std::string const domain = "example.com";
|
||||
auto jt = noop(alice);
|
||||
jt[sfDomain] = strHex(domain);
|
||||
jt[sfDelegate] = bob.human();
|
||||
env(jt);
|
||||
|
||||
// empty DelegateSet deletes the SLE
|
||||
env(delegate::set(alice, bob, {}));
|
||||
env.close();
|
||||
|
||||
env(jt, ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1479,6 +1656,37 @@ class Delegate_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
mpt.set({.account = alice, .flags = tfMPTLock | tfFullyCanonicalSig, .delegate = bob});
|
||||
}
|
||||
|
||||
// field not permitted to exist in granular delegation
|
||||
{
|
||||
Env env(*this);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
|
||||
MPTTester mpt(env, alice, {.fund = false});
|
||||
mpt.create({.flags = tfMPTCanLock});
|
||||
env.close();
|
||||
|
||||
// alice gives granular permission to bob for MPTokenIssuanceLock
|
||||
env(delegate::set(alice, bob, {"MPTokenIssuanceLock"}));
|
||||
env.close();
|
||||
|
||||
// Field is not permitted, permitted fields for delegation is defined in
|
||||
// permissions.macro.
|
||||
mpt.set(
|
||||
{.account = alice,
|
||||
.mutableFlags = 2,
|
||||
.delegate = bob,
|
||||
.err = terNO_DELEGATE_PERMISSION});
|
||||
|
||||
// Notice: flags not defined in permissions.macro are not permitted for delegation.
|
||||
// Since preflight will check invalid flag for the tx, it is not reachable.
|
||||
// If any new flag is defined into the transaction in the future,
|
||||
// but is not allowed for delegation, the transaction will be rejected with
|
||||
// terNO_DELEGATE_PERMISSION. The set of permitted flags for delegation is defined in
|
||||
// permissions.macro.
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1807,6 +2015,46 @@ class Delegate_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testGranularSandboxCheckOrder()
|
||||
{
|
||||
testcase("Make sure GranularSandbox is checked after transaction-level permission");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this);
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), gw, alice, bob);
|
||||
|
||||
env(fset(gw, asfRequireAuth));
|
||||
env.close();
|
||||
env(trust(alice, gw["USD"](50)));
|
||||
env.close();
|
||||
env(delegate::set(gw, bob, {"TrustlineAuthorize"}));
|
||||
env.close();
|
||||
|
||||
env(trust(gw, gw["USD"](0), alice, tfSetfAuth), delegate::as(bob));
|
||||
env.close();
|
||||
|
||||
// sfQualityOut is a valid TrustSet field, but not permitted in granular template
|
||||
Json::Value txJson = trust(gw, gw["USD"](0), alice, tfSetfAuth);
|
||||
txJson[sfQualityOut.jsonName] = 100;
|
||||
env(txJson, delegate::as(bob), ter(terNO_DELEGATE_PERMISSION));
|
||||
|
||||
// Now Alice grants Bob with transaction level permission
|
||||
env(delegate::set(gw, bob, {"TrustlineAuthorize", "TrustSet"}));
|
||||
env.close();
|
||||
|
||||
// NOTE: This case is to ensure that if a delegate possesses a
|
||||
// transaction-level permission (e.g., TrustSet), the granular sandbox must not incorrectly
|
||||
// block the transaction. The function checkGranularSandbox MUST be called after the
|
||||
// transaction-level permission check. This test case is to avoid future refactor mistakes,
|
||||
// modifying the order will fail here.
|
||||
env(txJson, delegate::as(bob));
|
||||
}
|
||||
|
||||
void
|
||||
testTxDelegableCount()
|
||||
{
|
||||
@@ -1866,9 +2114,8 @@ class Delegate_test : public beast::unit_test::suite
|
||||
STTx const tx{ttPAYMENT, [](STObject&) {}};
|
||||
BEAST_EXPECT(checkTxPermission(nullptr, tx) == terNO_DELEGATE_PERMISSION);
|
||||
|
||||
// loadGranularPermission nullptr check
|
||||
std::unordered_set<GranularPermissionType> granularPermissions;
|
||||
loadGranularPermission(nullptr, ttPAYMENT, granularPermissions);
|
||||
// getGranularPermission nullptr check
|
||||
auto const granularPermissions = getGranularPermission(nullptr, ttPAYMENT);
|
||||
BEAST_EXPECT(granularPermissions.empty());
|
||||
}
|
||||
|
||||
@@ -1896,6 +2143,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
testMultiSignQuorumNotMet();
|
||||
testPermissionValue(all);
|
||||
testTxRequireFeatures(all);
|
||||
testGranularSandboxCheckOrder();
|
||||
testTxDelegableCount();
|
||||
testDelegateUtilsNullptrCheck();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user