🎉 New Release: This is a beta release. This is on xrpl testnet for now. Learn More →

October 14, 2026Protocol Deep Dive

RheoFi Access Control Manager: Scoped Operator Permissions

How RheoFi's Access Control Manager scopes operator permissions per contract and function, what a keeper grant can do, and how over-broad grants fail.

Who Can Control What? Scoped operator permissions on RheoFi

What Is the RheoFi Access Control Manager?

The RheoFi Access Control Manager is the registry that decides which address may call which privileged function on which contract, hashing a contract address and a function signature into one role. Access-control exploits drained $1.83 billion across Web3 in H1 2025, mostly in Q1 (Hacken, July 2025), so RheoFi treats grant scope as the first control.

A role is a contract address plus a function signature

The verified ACM source (XRPL EVM testnet explorer, read October 14, 2026) says "Granular roles are built by hashing the contract address and its function signature." A condensed reconstruction, not verbatim:

// Condensed reconstruction of the verified testnet ACM source (read October 14, 2026). Not verbatim.
function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) public {
    bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig));
    grantRole(role, accountToPermit); // OZ grantRole reverts unless msg.sender holds the role's admin role
    emit PermissionGranted(accountToPermit, contractAddress, functionSig);
}

The functionSig string is whatever the guarded contract passes to its access check, not the ABI-canonical form. Verified sources use strings such as addMarket(AddMarketInput), so a grant typed from an ABI with expanded tuple types hashes to a different role. Copy the string from source.

What the ACM does not delegate

The ACM governs protocol-side setters: caps, collateral factors, pause flags and oracle configuration. It never delegates a user's position; an agent managing one needs account-layer delegation for agents. Operator automation here means the risk keeper, guardian or parameter bot a pool administrator runs against the per-pool risk engine those setters configure.


What Changes With Function-Level Permissions vs Named Roles?

Function-level permissions change the unit of authority: RheoFi's ACM can grant one function on one contract, while a named role bundles many functions under one label. Concentrated control is common, with 82,446 of 244,424 contracts studied, or 33.7%, carrying a centralization defect (arXiv centralization study, November 2024). Shrinking the unit, as RheoFi's ACM allows, shrinks each key's reach.

Hash-derived roles vs a fixed role list

In a named-role registry, adding a function to a role widens every holder at once. RheoFi's ACM works the other way: any (contract, signature) pair becomes a live role once a guarded contract checks it.

PropertyRheoFi ACMNamed-role registrySingle owner
Unit of permissionOne function, one contractOne role, many functionsAll owner-only functions
WildcardNull-address role, every guarded contractNot applicableNot applicable
Who grantsDefault admin role holdersRole adminOwner, by transfer
Enumerable on-chainNo, event logs onlyDesign-dependentYes, owner()
Argument limitsNone; target setter's checks onlyNoneNone

Wildcard admin roles

The source defines a second scope: "For Admin roles a null address is hashed in place of the contract address." Its rationale: "Admin roles allow for an address to call a function signature on any contract guarded by the AccessControlManager. This is particularly useful for contracts created by factories." On testnet, the pool registry uses wildcard Comptroller grants to configure each pool it creates. The cost is reach, since a wildcard setCollateralFactor grant works on every Comptroller, including pools not yet deployed.


Why Does Least-Privilege Access Control Matter in 2026?

Least-privilege access control matters in 2026 because the losses that remain sit in logic: 89% of 2025 DeFi protocol losses came from protocol logic exploits, while key compromise fell to 8.1% (Immunefi loss scoreboard, April 2026). RheoFi treats a privileged setter as part of protocol logic, so each grant should reach as little as possible.

Where losses come from now

Immunefi's series shows DeFi protocol losses falling from $2.62 billion in 2022 to $534 million in 2024, then rising to $680 million in 2025. Across all of crypto, centralized services included, private key compromises accounted for 43.8% of stolen crypto in 2024 (Chainalysis 2024 theft data, December 2024). Different years and populations, as with Hacken's Web3-wide figure above, so read them as a contrast, not a ratio: key compromise is a smaller share of DeFi protocol losses. A wildcard setter grant still puts protocol logic one key away.

Centralization defects are measurable

The arXiv study defines a centralization defect as "any error, flaw, or fault in a smart contract's design or development stage that introduces a single point of failure." Its six defect types, drawn from 597 Stack Exchange posts and 117 audit reports, feed a detector with 93.7% reported precision; the 33.7% above is our division of the paper's two counts. An over-broad grant is that defect in permission form, with one key reaching many functions on many contracts.


How Does the RheoFi Access Control Manager Check a Call?

The RheoFi Access Control Manager checks a call when the guarded contract asks isAllowedToCall whether the sender holds the contract-specific role, then the wildcard role. Caller identity is the control point: private key compromises took 43.8% of stolen crypto in 2024, centralized services included (Chainalysis, December 2024). Identity is what RheoFi's ACM gates, never arguments.

The _checkAccessAllowed hook and isAllowedToCall

In the verified testnet source (read October 14, 2026), a guarded setter calls _checkAccessAllowed(sig). The ACM uses its own msg.sender, the guarded contract, as the contract half of the role, checks the specific role, then the wildcard; on false the setter reverts with Unauthorized.

// Condensed reconstruction of the verified testnet sources (read October 14, 2026). Not verbatim.
// ACM side: msg.sender is the guarded contract asking the question.
function isAllowedToCall(address account, string calldata functionSig) public view returns (bool) {
    bytes32 role = keccak256(abi.encodePacked(msg.sender, functionSig));
    if (hasRole(role, account)) return true;                         // contract-specific grant
    role = keccak256(abi.encodePacked(address(0), functionSig));
    return hasRole(role, account);                                   // wildcard grant
}

// Consumer side (AccessControlledV8 pattern, as used by the Comptroller).
function _checkAccessAllowed(string memory signature) internal view {
    bool allowed = _accessControlManager.isAllowedToCall(msg.sender, signature);
    if (!allowed) revert Unauthorized(msg.sender, address(this), signature);
}

Who can grant: DEFAULT_ADMIN_ROLE

The ACM embeds OpenZeppelin AccessControl v4.9.0, where "only accounts that have a role's admin role can call {grantRole} and {revokeRole}" and "By default, the admin role for all roles is DEFAULT_ADMIN_ROLE" (OpenZeppelin AccessControl, v4.9.0). The verified ACM never calls _setRoleAdmin, so getRoleAdmin returns 0x00 for every role, and the file warns the admin role "is also its own admin".

Admin is not caller. On October 14, 2026, isAllowedToCall for the NormalTimelock on setCollateralFactor, simulated from the Core Comptroller, returned false: an admin must first grant the function role. Grants can be routed through a timelock, and that delay binds only if every DEFAULT_ADMIN_ROLE holder sits behind it.

Why role holders can only be read from logs

OpenZeppelin v4.9.0 is "a lightweight version that doesn't allow enumerating role members except through off-chain means by accessing the contract event logs." Interface detection (EIP-165, January 2018) confirms it on testnet: supportsInterface(0x7965db0b) (IAccessControl) returned true, supportsInterface(0x5a05180f) (IAccessControlEnumerable) false. The holder list therefore came from all 57 ACM events (29 RoleGranted, 27 PermissionGranted, 1 RoleRevoked), January 19 to February 12, 2026. PermissionGranted over-counts: two events re-granted roles already held, a no-op in OpenZeppelin that the ACM still logs. Count RoleGranted, then confirm with eth_call.


Which RheoFi Components and Functions Can an Operator Be Granted?

An operator can be granted any guarded setter, but that setter's own code sets the limit: the Comptroller caps collateral factors at 95% and holds close factors between 5% and 90% (RheoFi whitepaper v1.0 Comptroller bounds, 2025). RheoFi advises checking each setter's verified source, since other setters carry weaker bounds or none.

Guarded setters and their in-code bounds

The Comptroller reference says all administrative functions are gated through the ACM; in the verified source, nine setters call it, while five owner-only configuration functions and the inherited ownership functions do not.

SetterBound in codeDirectionGrant guidance
setCloseFactor(uint256)5% to 90%Either wayBounded: automate
setCollateralFactor(address,uint256,uint256)CF at most 95%, LT at least CF; no floorEither way, down to zeroCeiling only: keep delayed
setLiquidationIncentive(uint256)Lower bound only (1e18)Either wayKeep delayed
setMarketSupplyCaps, setMarketBorrowCapsNone beyond array checksAny value, including 0Keep delayed
setMinLiquidatableCollateral(uint256)NoneAny valueKeep delayed
setActionsPaused(address[],uint256[],bool)Market listedPauses and unpausesTwo-way switch
setForcedLiquidation(address,bool)Market listedOn and offTwo-way switch
unlistMarket(address)All nine actions paused, caps and CF at zeroOne wayNeeds prior pauses

A collateral-factor grant has a ceiling and no floor; the unlist row requires a factor of zero, so the grant can cut the share of collateral value a borrower may borrow against to nothing. setActionsPaused takes the verified source's nine actions, MINT through EXIT_MARKET, and one signature both pauses and unpauses.

Owner-only, contract-only and permissionless functions

Several jobs need no grant, and some functions sit outside the ACM:

  1. healAccount(address) on the Comptroller: not restricted in verified source.
  2. liquidateAccount(address, LiquidationOrder[]): not restricted.
  3. liquidateBorrow on each rToken: not restricted; per the whitepaper, "Anyone may then call the liquidation path."
  4. reduceReserves(uint256), addReserves, accrueInterest() on rTokens: not restricted.
  5. Shortfall auctions: "Any account may trigger an auction once the pool's total bad debt exceeds the configured minimum threshold; any account may bid" (RheoFi Whitepaper v1.0, 2025).
  6. healBorrow on rTokens: contract-only, Comptroller caller.
  7. badDebtRecovered: contract-only, shortfall caller.
  8. supportMarket(address): callable only by the pool registry, not via the ACM; the PoolRegistry reference covers market addition.
  9. setPriceOracle, setAccessControlManager and three other Comptroller configuration functions: onlyOwner, outside the ACM, as are the inherited transferOwnership and renounceOwnership.

What the testnet role map holds

Testnet bootstrap configuration, read on the XRPL EVM testnet on October 14, 2026, at pattern level. Holders can be an externally owned key or a contract.

Measure (testnet, October 14, 2026)CountShare
Function grants logged, all still held25100%
Distinct holders2n/a
Wildcard grants20 of 2580%
Grants naming a signature no verified contract checks3 of 2512%
Guarded signatures with a holder (25 across 6 verified contracts)14 of 2556%

The three dead grants are two for supportMarket(address), which the Comptroller never checks against the ACM, and one for listMarket(address), which no verified contract checks. Eleven guarded signatures had no holder, including setForcedLiquidation and the rToken setters setInterestRateModel, setReserveFactor and setProtocolSeizeShare.

IMPORTANT

When we rebuilt our own testnet permission map from its event logs: AccessControlManager Testnet Role Map Read (2026-10-14) Context: The XRPL EVM testnet ACM (chain ID 1449000) is not enumerable, so its role holders were reconstructed from all 57 ACM event logs (January 19 to February 12, 2026) and re-checked with hasRole, hasPermission and isAllowedToCall reads on October 14, 2026. Finding: 25 function-level grants, all still held, across 2 holders (one externally owned key, one factory contract); 20 of 25 (80%) are wildcard grants and 3 of 25 (12%) name a function signature no verified contract checks. Result: hasPermission(account, contract, sig) returns false for a wildcard holder while isAllowedToCall from the guarded contract returns true. This is testnet bootstrap configuration only; RheoFi has not published a mainnet role map.

Check Every Operator Grant Before It Runs

RheoFi's Access Control Manager can scope each privileged call to one contract and one function signature.

Read the ACM address from the docs, then test your operator's grant with isAllowedToCall on testnet.

The inherited audit scope covers the comptroller; a RheoFi-specific audit is planned before mainnet.

Launch App → | Read the Docs →


How to Scope and Integrate an Operator Grant on RheoFi?

Scoping an operator grant on RheoFi takes ten steps, from proving a grant is needed to revoking it. Governance sets collateral factors "typically between 40% and 90%" (RheoFi whitepaper v1.0 collateral range, 2025), so an operator touching them needs the narrowest grant. RheoFi recommends contract-specific grants, verified from the target.

Prerequisites

Work on the XRPL EVM testnet (chain ID 1449000). Take the ACM address from the treasury and governance addresses page. You need each target's verified source and an RPC that accepts eth_call with an arbitrary from. Granting and revoking need a DEFAULT_ADMIN_ROLE holder; anyone can run the read-only checks. For wider call flows, see the Solidity integration walkthrough.

Ten steps to a scoped grant

  1. Inventory the job. Liquidation, healing, reserve reduction, accrual and auctions need no grant; see the permissionless keeper paths on the roadmap.
  2. Copy the exact signature string from the guarded contract's verified source.
  3. Choose contract-specific scope: giveCallPermission(comptroller, sig, operator), never address(0).
  4. Prefer bounded setters for automation; keep unbounded setters and two-way switches behind a delayed grant.
  5. Use a dedicated operator address: one job per key.
  6. Route the grant through a DEFAULT_ADMIN_ROLE holder that sits behind a delay.
  7. Verify from the target: simulate isAllowedToCall(operator, sig) with from set to the target, and confirm RoleGranted, not only PermissionGranted.
  8. Read owner(), pendingOwner() and accessControlManager() on the target contract.
  9. Monitor RoleGranted and RoleRevoked from logs, since the ACM cannot list holders.
  10. Revoke with revokeCallPermission when the job ends, then repeat step 7 and expect false.

Verifying the grant took effect

Transaction success does not prove the intended role exists, so check both hashes and simulate the guarded path (illustrative script):

# Illustrative monitor (web3.py v6+). Not RheoFi code; take addresses from the docs.
from web3 import Web3

ZERO = "0x" + "00" * 20

def grant_status(w3, acm, target, operator, sig):
    if not (Web3.is_checksum_address(target) and Web3.is_checksum_address(operator)):
        raise ValueError("checksummed addresses required")
    specific = Web3.solidity_keccak(["address", "string"], [target, sig])
    wildcard = Web3.solidity_keccak(["address", "string"], [ZERO, sig])
    # isAllowedToCall uses msg.sender as the guarded contract, so simulate from the target
    allowed = acm.functions.isAllowedToCall(operator, sig).call({"from": target})
    has = lambda role: acm.functions.hasRole(role, operator).call()
    return {"specific": has(specific), "wildcard": has(wildcard), "allowed_from_target": allowed}

A grant is live only when allowed_from_target is true and the intended scope flag is set; a true wildcard on a single-Comptroller key is a finding. Liquidation needs no grant, as the liquidation guide for borrowers shows.


What Are the Risks and Security Failure Modes of Over-Broad ACM Grants?

The main risks of over-broad ACM grants are wildcard reach, unbounded setters, grants that succeed without effect, and monitors that miss wildcard holders. Infrastructure failures fell from 30.7% of DeFi losses in 2022 to 10.3% in 2025 (Immunefi infrastructure-loss data, April 2026). For RheoFi, a smaller key-loss share does not shrink a wildcard grant's reach.

Wildcard reach, unbounded setters and two-way switches

A wildcard grant follows the signature into every guarded contract, including later pools. Paired with an unbounded setter, a "narrow" grant turns wide, because a cap grant can set any market's cap to zero or the maximum. A guardian built to pause can also unpause.

The grant that succeeded and did nothing

On January 23, 2026, a timelock-executed testnet grant passed both a signature string and calldata already beginning with the grantRole selector, so the selector was doubled. The ACM decoded a junk role and a shifted account, granted it and emitted RoleGranted; nothing reverted, and hasRole for that pair still read true on October 14, 2026, for a role no contract checks.

The view that misses wildcard holders

hasPermission(account, contract, sig) hashes only the contract-specific role and has no wildcard fallback. On October 14, 2026, for the same key and setCollateralFactor(address,uint256,uint256) on the Core Comptroller, hasPermission returned false while isAllowedToCall simulated from that Comptroller returned true. A monitor that queries hasPermission with each contract's own address misses all 20 wildcard grants on the testnet map. Check both hashes, or simulate from the target.

The owner sits outside the ACM

setAccessControlManager(address) is onlyOwner in the verified Comptroller source, so the owner() holder can repoint a contract to another ACM, bypassing every old grant. A review that skips owner() and pendingOwner() misses that key. The reverse: on October 14, 2026, one testnet risk-fund contract returned a zero accessControlManager() pointer, so every ACM-guarded function on it reverts for every caller, governance included, until the owner sets the pointer.

What a stale admin grant cost a vault in February 2025

Rekt's February 2025 vault-drain post-mortem describes "a rogue developer who maintained admin privileges months after completing their work", quoting a forensic finding that "This account had been granted a special role (0x8e0b) that enabled them to withdraw all funds from the vault." Its diagnosis included: "No mandatory privilege transfers. No time-based access expirations." (Rekt News, February 2025). Step 10, revocation when the job ends, targets that gap.

IMPORTANT

What the inherited audit scope names, and what it leaves out: Whitepaper v1.0 Publication Context: Whitepaper v1.0 discloses the audit lineage the RheoFi isolated-pool codebase inherits. Finding: Fifteen prior engagements across PeckShield, Hacken, Certik, Quantstamp, FairyProof, and Pessimistic cover the isolated-pool core, rewards distributor, risk fund and shortfall auction, comptroller, forced liquidations, time-based accrual, and native-token gateway. Result: The inherited comptroller code, whose setters the ACM guards, is in that listed scope; the AccessControlManager is not named in it (RheoFi Whitepaper v1.0 security section, 2025).

Whitepaper v1.0 also states that a RheoFi-specific mainnet audit is planned prior to mainnet deployment.


How Do Regulatory and Compliance Frameworks Treat Admin Permissions?

Regulation has not settled admin permissions in DeFi lending: MiCA Article 142(2)(b) asks only for an assessment of regulating crypto lending and borrowing (EUR-Lex MiCA text, May 2023), while lending protocols held $52.75B as of October 2026 (DeFiLlama lending, October 2026). A permission map anyone can rebuild from on-chain logs is the transparency evidence RheoFi points to.

MiCA: Recital 22 and the Article 142 lending review

Regulation (EU) 2023/1114 applies from 30 December 2024 under Article 149(2), Titles III and IV from 30 June 2024. Recital 22 excludes services "provided in a fully decentralised manner without any intermediary." A protocol whose setters answer to admin roles cannot assume that carve-out; RheoFi leaves it to counsel. Article 142(1) required a Commission report by 30 December 2024, after consulting EBA and ESMA.

What the FSB says about administrative keys

The FSB notes that "code for these DApps is written by protocol developers who might also retain governance or other tokens and administrative keys. This can mean that a purportedly decentralised protocol may in fact be controlled by a concentrated set of interests" (FSB, February 2023). A function-level role map does not remove that concentration, but it makes it measurable by showing who holds which signature, on which contract, since when.

Compliance Corner

Regulation (EU) 2023/1114 (MiCA), signed 31 May 2023; applies from 30 December 2024, Titles III and IV from 30 June 2024. Relevance: Recital 22 carve-out; Article 142 lending review.

FSB, The Financial Stability Risks of Decentralised Finance, 16 February 2023. Relevance: retained administrative keys and concentrated control.


Conclusion: What Should Operators Take From the ACM?

Operators should take one rule from the ACM: a grant is only as safe as its scope, its setter's bounds, and the checks run after it lands. Crypto theft passed $3.4 billion in 2025, centralized services included (Chainalysis 2025 theft review, December 2025). RheoFi asks operators to verify grants on-chain.

Operator checklist

Grant contract-specific, automate bounded setters, keep the rest behind a delay, and never grant a permissionless job. Verify from the target, watch logs, and read owner() too. Per the whitepaper, "deployed values should always be verified on-chain before use." Start from the per-contract technical reference and run the read-only checks in steps 7 to 9 against testnet; steps 3, 6 and 10 need a DEFAULT_ADMIN_ROLE holder. RheoFi is pre-mainnet: nothing here is financial advice, and a contract, oracle or governance failure can mean the total loss of supplied capital.

References

  1. Hacken, July 2025 · Hacken
  2. arXiv centralization study, November 2024 · arXiv centralization study
  3. Immunefi loss scoreboard, April 2026 · Immunefi loss scoreboard
  4. Chainalysis 2024 theft data, December 2024 · Chainalysis 2024 theft data
  5. OpenZeppelin AccessControl, v4.9.0 · OpenZeppelin AccessControl
  6. EIP-165, January 2018 · EIP-165
  7. RheoFi whitepaper v1.0 Comptroller bounds, 2025 · RheoFi whitepaper v1.0 Comptroller bounds
  8. Rekt News, February 2025 · Rekt News
  9. EUR-Lex MiCA text, May 2023 · EUR-Lex MiCA text
  10. DeFiLlama lending, October 2026 · DeFiLlama lending
  11. FSB, February 2023 · FSB
  12. Chainalysis 2025 theft review, December 2025 · Chainalysis 2025 theft review

FAQs

The RheoFi Access Control Manager is the contract that decides which address may call which privileged function on which contract. Each role is the hash of a contract address and a function signature, so a grant can cover one setter on one Comptroller. Guarded contracts ask it isAllowedToCall before running a setter. It governs protocol-side administration such as caps and collateral factors, not user positions.