#include #include #include #include #include #include #include #include #include // IWYU pragma: keep #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { Permission::GranularPermissionEntry::GranularPermissionEntry( std::string name, TxType txType, std::uint32_t permittedFlags, std::vector permittedFields) : name(std::move(name)) , txType(txType) , permittedFlags(permittedFlags) , permittedFields(std::move(permittedFields), TxFormats::getCommonFields()) { } Permission::Permission() { { #pragma push_macro("TRANSACTION") #undef TRANSACTION #define TRANSACTION(tag, value, name, delegable, amendment, ...) \ txDelegationMap_[static_cast(value)] = {amendment, delegable}; #include #undef TRANSACTION #pragma pop_macro("TRANSACTION") } granularPermissionsByName_ = { #pragma push_macro("GRANULAR_PERMISSION") #undef GRANULAR_PERMISSION #define GRANULAR_PERMISSION(type, ...) {#type, type}, #include #undef GRANULAR_PERMISSION #pragma pop_macro("GRANULAR_PERMISSION") }; { #pragma push_macro("GRANULAR_PERMISSION") #undef GRANULAR_PERMISSION // NOLINTBEGIN(bugprone-macro-parentheses) #define GRANULAR_PERMISSION(type, txType, value, flags, fields) \ granularPermissions_.emplace( \ std::piecewise_construct, \ std::forward_as_tuple(GranularPermissionType::type), \ std::forward_as_tuple( \ #type, txType, static_cast(flags), std::vector fields)); // NOLINTEND(bugprone-macro-parentheses) #include #undef GRANULAR_PERMISSION #pragma pop_macro("GRANULAR_PERMISSION") } if (granularPermissionsByName_.size() != granularPermissions_.size()) { // LCOV_EXCL_START Throw( "granularPermissionsByName_ and granularPermissions_ must have same size"); // LCOV_EXCL_STOP } for (auto const& [name, type] : granularPermissionsByName_) { if (type <= UINT16_MAX) { // LCOV_EXCL_START Throw( "Granular permission value must exceed the maximum uint16_t value: " + name); // LCOV_EXCL_STOP } } for (auto const& [type, entry] : granularPermissions_) granularTxTypes_.insert(entry.txType); // Validate that all fields listed in permissions.macro exist in the // corresponding transaction type's format, catching typos at startup. for (auto const& [type, entry] : granularPermissions_) { if (!txDelegationMap_.contains(entry.txType)) { // LCOV_EXCL_START Throw("Invalid granular permission txType in txDelegationMap_"); // LCOV_EXCL_STOP } auto const* fmt = TxFormats::getInstance().findByType(entry.txType); if (fmt == nullptr) { // LCOV_EXCL_START Throw("Invalid granular permission txType"); // LCOV_EXCL_STOP } for (auto const& field : entry.permittedFields) { if (fmt->getSOTemplate().getIndex(field.sField()) == -1) { // LCOV_EXCL_START Throw("Invalid granular permission field"); // LCOV_EXCL_STOP } } } } Permission const& Permission::getInstance() { static Permission const kInstance; return kInstance; } std::optional Permission::getPermissionName(std::uint32_t value) const { if (value == 0) return std::nullopt; auto const permissionValue = static_cast(value); if (auto const granular = getGranularName(permissionValue)) return granular; // not a granular permission, check if it maps to a transaction type auto const txType = permissionToTxType(value); if (auto const* item = TxFormats::getInstance().findByType(txType); item != nullptr) return item->getName(); return std::nullopt; } std::optional Permission::getGranularValue(std::string const& name) const { auto const it = granularPermissionsByName_.find(name); if (it != granularPermissionsByName_.end()) return static_cast(it->second); return std::nullopt; } std::optional Permission::getGranularName(GranularPermissionType value) const { auto const it = granularPermissions_.find(value); if (it != granularPermissions_.end()) return it->second.name; return std::nullopt; } std::optional Permission::getGranularTxType(GranularPermissionType gpType) const { auto const it = granularPermissions_.find(gpType); if (it != granularPermissions_.end()) return it->second.txType; return std::nullopt; } bool Permission::hasGranularPermissions(TxType txType) const { return granularTxTypes_.contains(txType); } std::optional> Permission::getTxFeature(TxType txType) const { auto const it = txDelegationMap_.find(txType); XRPL_ASSERT( it != txDelegationMap_.end(), "xrpl::Permission::getTxFeature : tx exists in txDelegationMap_"); if (it->second.amendment == uint256{}) return std::nullopt; return std::optional{std::cref(it->second.amendment)}; } bool Permission::isDelegable(std::uint32_t permissionValue, Rules const& rules) const { if (permissionValue == 0) return false; // LCOV_EXCL_LINE auto const amendmentEnabled = [&rules](TxDelegationEntry const& entry) { return entry.amendment == uint256{} || rules.enabled(entry.amendment); }; // Granular permissions may authorize a limited subset of a tx type even // when the full tx type is not delegable. They still require the // underlying transaction amendment to be enabled. if (auto const granularIt = granularPermissions_.find(static_cast(permissionValue)); granularIt != granularPermissions_.end()) { auto const txIt = txDelegationMap_.find(granularIt->second.txType); return txIt != txDelegationMap_.end() && amendmentEnabled(txIt->second); } auto const txType = permissionToTxType(permissionValue); auto const txIt = txDelegationMap_.find(txType); // Tx-level permissions require the transaction type itself to be delegable, and // the corresponding amendment enabled. return txIt != txDelegationMap_.end() && txIt->second.delegable != NotDelegable && amendmentEnabled(txIt->second); } uint32_t Permission::txToPermissionType(TxType const type) { return static_cast(type) + 1; } TxType Permission::permissionToTxType(uint32_t value) { XRPL_ASSERT(value > 0, "xrpl::Permission::permissionToTxType : value is greater than 0"); return static_cast(value - 1); } bool Permission::checkGranularSandbox( STTx const& tx, std::unordered_set const& heldPermissions) const { // Build union of flags upfront to enable an early exit. Fields are not stored and // grouped in advance to avoid heap allocation. std::uint32_t unionFlags = 0; for (auto const& gp : heldPermissions) { auto const it = granularPermissions_.find(gp); if (it != granularPermissions_.end()) unionFlags |= it->second.permittedFlags; } // Check if flags are permitted if ((tx.getFlags() & ~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; if (!std::ranges::any_of(heldPermissions, [&](auto const& gp) { auto const it = granularPermissions_.find(gp); return it != granularPermissions_.end() && it->second.permittedFields.getIndex(field.getFName()) != -1; })) return false; } return true; } } // namespace xrpl