Each market has one role authorizer, an 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 the permissioned modifier asks the authorizer whether the caller can call it (hasPermission(caller, target, selector)). The authorizer answers in this order:
  1. 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).
  2. 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, buyFor and setPresaleState. The market is in production after the admin handover is locked and the admin role is sealed (isProductionActive).
  3. If the caller holds DEFAULT_ADMIN_ROLE, the answer is yes.
  4. If any role listed for that target and selector is PUBLIC_ROLE, the answer is yes.
  5. If the caller holds any role listed for that target and selector, the answer is yes.
  6. Otherwise the answer is no, and the call reverts.
Before the authorizer check, a 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:
  1. The proposer schedules the call, or a batch of calls (schedule, scheduleBatch). The delay is at least the core’s minimum delay.
  2. The minimum delay passes.
  3. The executor executes the call inside the execution window (execute, executeBatch). A batch executes in one transaction.
The core calls an external target only for target and selector pairs it has admitted. Admission is itself a scheduled call to the core (setTargetSelectorAllowed). An operation past its execution window cannot execute. The canceller can cancel a scheduled operation.

Admin handover at launch

The deploy signer holds DEFAULT_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):
  1. Admission. The core schedules and then executes two self-calls that admit acceptDefaultAdmin and sealDefaultAdmin on the market’s authorizer.
  2. 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.
  3. 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.
  4. Retirement. The deploy signer renounces DEFAULT_ADMIN_ROLE. This renounce requires exactly two holders and a confirmed core. It locks the handover (productionAdminHandoffLocked).
  5. Factory allowance. The deploy signer renounces its ReferenceCurveFactory_v1 deployer allowance.
  6. Seal. The core schedules sealDefaultAdmin, waits the minimum delay and executes it. The authorizer sets the admin of DEFAULT_ADMIN_ROLE to the burned admin role.
After step 6, 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 of REBALANCER is the rebalance keeper. The holders of ELEVATION_KEEPER are the elevation keepers.

REBALANCER

The rebalance keeper calls rebalanceSegments(newSegments, suppliedCollateral, expectedSegmentsHash, expectedVirtualCollateral, deadline). The call works with trading open or closed. It reverts unless all of these hold:
  1. The block time is at or before deadline.
  2. The current segments hash equals expectedSegmentsHash, and the virtual collateral supply equals expectedVirtualCollateral.
  3. The new reference price is at or above the current one.
  4. The new curve’s total capacity is at least the current supply and at most the issuance token cap.
  5. The virtual collateral supply rises by exactly suppliedCollateral.
  6. The reserve for the current supply under the new segments is at most the virtual collateral supply.
  7. The reserve for the current supply does not decrease.
  8. The spot price at the current supply does not decrease.
  9. The spot price rises by at most the spot tolerance (getSpotToleranceBps).
Each call is bounded on its own. The contract applies no limit across calls: no window cap, cooldown or delay. The rebalance keeper cannot change the spot tolerance. How the reference price increases describes what a rebalance does.

ELEVATION_KEEPER

An elevation keeper calls ElevationTreasury_v1.elevate(maxIssuanceSupply, deadline). The call reverts unless all of these hold:
  1. DEFAULT_ADMIN_ROLE has called authorizeElevation(cap, adminDeadline), and the block time is at or before both deadlines.
  2. The current issuance supply is at or below the smaller of maxIssuanceSupply and the admin cap.
  3. The elevation treasury’s collateral balance is at or above the threshold.
The call uses up the admin authorization, including when nothing is elevated. The treasury offers the curve the amount that 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 one MarketFreezeGuardian_v1. The guardian has no proxy, no owner and no self-destruct. While the market is frozen:
  • Every permissioned function reverts, including buy, sell, repay and rebalanceSegments.
  • Every whenNotFrozen function reverts, including setSpotToleranceBps, the splitters’ fetchFunds, buyPresale and claimAll.
  • The endowment controller’s arm, draw and forwardSurplus revert.
  • Executing an issuance token rebind reverts.
These stay callable while the market is frozen:
  • disableBuy and disableSell, by OPERATOR or DEFAULT_ADMIN_ROLE.
  • StakingManager_v1.initiateStrategyRemoval, and the strategy functions enableEmergencyMode and rescueFunds (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.
A freeze pauses no timer. Delays, deadlines and early access timestamps keep running.

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 are InverterBeaconProxy_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).
  1. The recovery Safe proposes new authorities, with the expected recovery nonce.
  2. Each new authority accepts the proposal in an earlier block than the execution.
  3. 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 isProductionActive returns true, enableBuy, buy, buyFor and setPresaleState are refused for every caller, DEFAULT_ADMIN_ROLE included.
  • Granting the admin role. grantRole(DEFAULT_ADMIN_ROLE, …) always reverts.
  • After the seal. The DEFAULT_ADMIN_ROLE holder cannot change. The core’s own operational roles can change through its scheduled self-calls or a recovery.
  • Spot tolerance grants. setSpotToleranceBps checks DEFAULT_ADMIN_ROLE directly. A grant of its selector to another role has no effect.
  • Stale epochs. An enableBuy(uint256), enableSell(uint256), freeze or approveReopen call 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.effectiveDelay is 10 minutes. Execution windows and upper bounds do not change.

Code

Source: house-markets/contracts at 0ebbc89a (branch integration/v12-post-qa).