AUT_Roles_v2 contract that lists which roles can call each permissioned function of the market’s contracts.
The authorizer, the market’s freeze guardian (MarketFreezeGuardian_v1), the market’s admin execution core (AdminExecutionCore) and the protocol governance contracts (Governor_v1, GovernorBeaconController_v1) together set what is fixed after launch, what can be changed, who can change it and how long each change waits.
A Safe on this page is a Safe 1.4.1 multisig contract. The freeze guardian checks that its approval and emergency Safes have code. Recovery authorities are checked against the canonical Safe 1.4.1 runtime (SafeIdentity141Lib).
How a permission check works
A market function with thepermissioned modifier asks the authorizer whether the caller can call it (hasPermission(caller, target, selector)). The authorizer answers in this order:
- If the selector is
transferLoan, the caller is the presale module paired with that credit facility, and the presale module holds an unclaimed loan or has a looped purchase in progress, the answer is yes (Early access). - If the selector is a funding selector and the market is not yet in production, the answer is no. The funding selectors are
enableBuy(),enableBuy(uint256),buy,buyForandsetPresaleState. The market is in production after the admin handover is locked and the admin role is sealed (isProductionActive). - If the caller holds
DEFAULT_ADMIN_ROLE, the answer is yes. - If any role listed for that target and selector is
PUBLIC_ROLE, the answer is yes. - If the caller holds any role listed for that target and selector, the answer is yes.
- Otherwise the answer is no, and the call reverts.
permissioned function also reverts while the market is frozen. See Freeze.
The authorizer applies these rules to every grant:
The critical set is compiled into the authorizer and matched on the selector alone. It holds the authorizer’s own administration functions, the reserve manager’s position and unbacked-draw functions, the curve’s segment, tolerance and virtual collateral functions, the trading switches and fee setters, the credit facility setters, the presale setters, the staking setters and strategy calls, the splitter setters and the elevation treasury’s
elevate and setThreshold. User entry and exit functions are not in the set.
Roles at launch
The launch route creates eight named roles on each market’s authorizer.DEFAULT_ADMIN_ROLE is the admin role of all eight.
After early access closes,
DEFAULT_ADMIN_ROLE can grant three more selectors to PUBLIC_ROLE with addAccessPermission: curve buy, credit facility buyAndBorrow and credit facility borrow. The launch readback requires all three to be absent until early access is closed. After it closes, the readback requires all three or none.
The launch route grants no role these selectors: buyFor, sellTo, borrowFor, buyAndBorrowFor, repayFor and the staking manager’s rebalance. Any permissioned function that the launch route does not grant to a role is callable only by DEFAULT_ADMIN_ROLE.
The authorizer also reserves two endowment roles, ENDOWMENT_ADMIN_ROLE and ENDOWMENT_CONTROLLER_ROLE. DEFAULT_ADMIN_ROLE creates both in one call to bootstrapEndowmentRoles, and the call reverts if the roles already exist. The launch route does not call it. Endowed reference segment describes these roles.
Authorities outside the authorizer
Some functions check a fixed address instead of a role.
The launch route requires the factory’s Safe pair, the Governor beacon controller’s Safe pair and the governance pair in the deployment addresses to be the same two Safes.
The admin execution core
AdminExecutionCore is a non-upgradeable timelock built on OpenZeppelin’s TimelockController. At launch it holds DEFAULT_ADMIN_ROLE and ECONOMIC_ADMIN on the market’s authorizer. Every call it makes follows three steps:
- The proposer schedules the call, or a batch of calls (
schedule,scheduleBatch). The delay is at least the core’s minimum delay. - The minimum delay passes.
- The executor executes the call inside the execution window (
execute,executeBatch). A batch executes in one transaction.
setTargetSelectorAllowed). An operation past its execution window cannot execute. The canceller can cancel a scheduled operation.
Admin handover at launch
The deploy signer holdsDEFAULT_ADMIN_ROLE while it configures the market. The authorizer records one required core at initialization (productionAdminCore), and only that core can receive the role. The production handover runs in this order (LaunchMarketProduction.s.sol):
- Admission. The core schedules and then executes two self-calls that admit
acceptDefaultAdminandsealDefaultAdminon the market’s authorizer. - Offer. The deploy signer calls
proposeDefaultAdmin(core, expiresAt). The expiry must be at least the core’s minimum delay plus its execution window plus 1 hour from now. - Acceptance. The core schedules
acceptDefaultAdmin(nonce), waits the minimum delay and executes it before the offer expires. The authorizer records the core’s code hash and grants it the role. - Retirement. The deploy signer renounces
DEFAULT_ADMIN_ROLE. This renounce requires exactly two holders and a confirmed core. It locks the handover (productionAdminHandoffLocked). - Factory allowance. The deploy signer renounces its
ReferenceCurveFactory_v1deployer allowance. - Seal. The core schedules
sealDefaultAdmin, waits the minimum delay and executes it. The authorizer sets the admin ofDEFAULT_ADMIN_ROLEto the burned admin role.
isProductionActive returns true. The funding selectors then follow the ordinary permission check.
After step 4, the handover functions revert: proposeDefaultAdmin, acceptDefaultAdmin, cancelDefaultAdminOffer and confirmDefaultAdmin. Revoking or renouncing DEFAULT_ADMIN_ROLE also reverts. The core stays the only holder of the role.
The route’s step 5 in LaunchMarket.s.sol, _handOverAdmin, grants the role with grantRole and renounces the deploy signer in one step. It runs only on non-production plans, which use the generic role authorizer. A production plan stops before it and runs the sequence above. From configuration to live market lists the full launch sequence.
What is fixed after initialization
No function in the market’s contracts sets these values after initialization:
The market’s modules are beacon proxies. A beacon upgrade replaces their code, and new code can add a setter for any of the module values above. The guardian, the issuance token and the admin execution core have no proxy. See Upgrades.
Settable parameters
Each row lists the role that holds the setter at launch.DEFAULT_ADMIN_ROLE can also call every permissioned setter. Every call by DEFAULT_ADMIN_ROLE or ECONOMIC_ADMIN waits the core’s minimum delay.
Keepers
A keeper is an address that holds a keeper role and calls a function on a schedule set off chain. Two keeper roles exist at launch. The holder ofREBALANCER is the rebalance keeper. The holders of ELEVATION_KEEPER are the elevation keepers.
REBALANCER
The rebalance keeper callsrebalanceSegments(newSegments, suppliedCollateral, expectedSegmentsHash, expectedVirtualCollateral, deadline). The call works with trading open or closed. It reverts unless all of these hold:
- The block time is at or before
deadline. - The current segments hash equals
expectedSegmentsHash, and the virtual collateral supply equalsexpectedVirtualCollateral. - The new reference price is at or above the current one.
- The new curve’s total capacity is at least the current supply and at most the issuance token cap.
- The virtual collateral supply rises by exactly
suppliedCollateral. - The reserve for the current supply under the new segments is at most the virtual collateral supply.
- The reserve for the current supply does not decrease.
- The spot price at the current supply does not decrease.
- The spot price rises by at most the spot tolerance (
getSpotToleranceBps).
ELEVATION_KEEPER
An elevation keeper callsElevationTreasury_v1.elevate(maxIssuanceSupply, deadline). The call reverts unless all of these hold:
DEFAULT_ADMIN_ROLEhas calledauthorizeElevation(cap, adminDeadline), and the block time is at or before both deadlines.- The current issuance supply is at or below the smaller of
maxIssuanceSupplyand the admin cap. - The elevation treasury’s collateral balance is at or above the threshold.
maxElevatable(balance) returns. A curve-side revert leaves the funds in the treasury and does not revert the elevation keeper’s call.
Freeze
Each market has oneMarketFreezeGuardian_v1. The guardian has no proxy, no owner and no self-destruct.
While the market is frozen:
- Every
permissionedfunction reverts, includingbuy,sell,repayandrebalanceSegments. - Every
whenNotFrozenfunction reverts, includingsetSpotToleranceBps, the splitters’fetchFunds,buyPresaleandclaimAll. - The endowment controller’s
arm,drawandforwardSurplusrevert. - Executing an issuance token rebind reverts.
disableBuyanddisableSell, byOPERATORorDEFAULT_ADMIN_ROLE.StakingManager_v1.initiateStrategyRemoval, and the strategy functionsenableEmergencyModeandrescueFunds(emergency mode only).- Every authorizer function. The authorizer is exempt from the freeze, so roles can be revoked during a freeze.
- Issuance token transfers. The token contract has no freeze check on transfers.
Freeze and reopen
A freeze while frozen re-arms the freeze. It advances the epoch, and a pending approval no longer executes.
Upgrades
The market’s modules areInverterBeaconProxy_v1 proxies. Each proxy reads its code address from one InverterBeacon_v1 on every call. A beacon serves every market that uses that module, so a beacon upgrade changes the code of all of them in one transaction.
Governor_v1 owns the module beacons, the ModuleFactory_v1 and the ReferenceCurveFactory_v1. GovernorBeaconController_v1 owns the governor’s own beacon.
The Governor timelock period has bounds of 48 hours to 30 days. The deployment script sets it to 1 week (
governor_timelockPeriod). setTimelockPeriod always reverts. The period changes only through a governor admin change.
A beacon upgrade’s version must be higher than the current one. Neither the governor nor the controller accepts the beacon’s reverter code as an implementation. A shutdown points the beacon at the reverter, and every call to its proxies reverts until a restart or a forced upgrade.
Authority recovery
The guardian, the core and the Governor beacon controller each have a recovery Safe and a recovery delay (DelayedAuthorityRecovery).
- The recovery Safe proposes new authorities, with the expected recovery nonce.
- Each new authority accepts the proposal in an earlier block than the execution.
- After the recovery delay, any address can execute it.
The recovery delay is set at construction, with bounds of 7 to 30 days. The launch route requires the guardian’s recovery Safe to be the emergency Safe. A guardian recovery clears any pending reopen. A core recovery invalidates every operation scheduled before it.
Scenarios
Edge cases
- Funding before the seal. Before
isProductionActivereturns true,enableBuy,buy,buyForandsetPresaleStateare refused for every caller,DEFAULT_ADMIN_ROLEincluded. - Granting the admin role.
grantRole(DEFAULT_ADMIN_ROLE, …)always reverts. - After the seal. The
DEFAULT_ADMIN_ROLEholder cannot change. The core’s own operational roles can change through its scheduled self-calls or a recovery. - Spot tolerance grants.
setSpotToleranceBpschecksDEFAULT_ADMIN_ROLEdirectly. A grant of its selector to another role has no effect. - Stale epochs. An
enableBuy(uint256),enableSell(uint256),freezeorapproveReopencall with an old epoch reverts. - Expired operations. A core operation, governor queue or guardian reopen past its window cannot execute. A governor queue past its window must be cancelled before a new queue for the same item.
- Ceiling change queuer. The approval Safe cannot queue an unbacked draw ceiling change. The account that queued a change cannot execute it.
- Internal test network. On chain ID 412,346, every delay that passes through
ProtocolTimelocks.effectiveDelayis 10 minutes. Execution windows and upper bounds do not change.
Code
Source:
house-markets/contracts at 0ebbc89a (branch integration/v12-post-qa).