---
title: "Admin"
description: "The owner multisig: ownership handover, the owner param lane, the beacon and UUPS upgrade paths, timelock mechanics and emergency levers."
audience: tech
type: reference
status: live
lang: en
updated: "2026-09-17"
publish: true
---
# Admin

The owner multisig resolved by every `onlyAdmin` check across the DEX. Principals and the op → tier
map: [Access Control](/docs/3-1-overview).

## 1. Ownership

`transferOwnership` and `renounceOwnership` both revert `FeatureDisabled(TRANSFER)` (`AccessControl.sol`). The only route to a new owner is Solady's two-step handover with its 48 h request expiry. `completeOwnershipHandover` is overridden to reject `address(0)` and, once the quorum policy is armed ([Access Control §1.1](/docs/3-1-overview#11-arming-the-quorum-policy)), to require the incoming owner to satisfy the same k-of-n admin policy. That override is the complete enforcement point for the `owner` principal.

## 2. The owner lane for asset params

Owner retains the unbounded `setAssetParams`, exempt from the steward's 24 h window but not from an armed fee fence: `_requireFeeFloor` applies `minFeeHardMinPbps` on the owner lane too, and re-applies it at `executeSetAssetParams` in case the fence was armed while the op sat in the queue. Lowering below an armed fence takes two transactions (`setRiskFences` first), by design. The owner stays out of the fenced lane: it could widen the fences and write inside them in one transaction, bypassing the TUNING queue.

The fee floor is the only fence binding the owner lane. `setAssetParams` calls `_requireFeeFloor`
and nothing else; the vega hard bounds and `maxDeltaBps` are read by `setAssetParamsBounded` alone.
An armed `RiskFences` row is not a bound on the owner. The bounded lane itself:
[Risk Steward](/docs/3-1-3-risk-steward).

## 3. Upgrade mechanisms

### 3.1. Pool beacon upgrade (PoolFactory, 7-day timelock)

Canonical description of the pool upgrade model.

```solidity
factory.requestReferenceUpgrade(address(newImpl));   // candidate carries the same AC / Admin / Flash immutables
// wait the GOVERNANCE tier delay (7 days in production), then execute inside GRACE_PERIOD (7 days)
factory.executeReferenceUpgrade();   // writes the factory's own `implementation` slot
// OR cancel before exec: owner or any AC guardian.
factory.cancelReferenceUpgrade();
```

- Fleet-wide, not opt-in. Every deployed pool is an ERC-1967 beacon proxy reading `PoolFactory.implementation()`. `executeReferenceUpgrade()` writes that one slot, so a single owner transaction replaces the executable code of every live pool at once, including pools deployed permissionlessly by third parties. No per-pool opt-out, no version pinning. This is the protocol's largest single trust assumption.
- New-impl compatibility is asserted on request: `requestReferenceUpgrade` requires the candidate to be a contract carrying the same `AC` / `admin` / `flash` immutables as the live impl (`PoolFactory.sol`). Storage-layout compatibility is not on-chain-checkable and is pinned at build time by `ArtifactGuards.t.sol`, which asserts `Pool` declares exactly one storage entry (`$` at slot 0).
- Delay: the `GOVERNANCE` tier of `AccessControl.GOV_DELAYS()`, read once into the factory's `DELAY_UPGRADE` immutable at construction (`PoolFactory.sol`). Under `Constants.PROD_DELAYS` that is 7 days.
- [Grace window](/docs/glossary#grace-period): the matured request expires `SC.GRACE_PERIOD` (7 days) after its eta and then reverts `Err.Expired` (`PoolFactory.sol`). A stale request must be re-requested, with no `cancelReferenceUpgrade` first: an expired `pendingReferencePool` is overwritten in place, announced as `ReferencePoolUpgradeCancelled` then `ReferencePoolUpgradeRequested`, at the full `GOVERNANCE` delay ([§4.1](#41-timelock-mechanics)). Longest tier, so likeliest to age out unnoticed.
- Cancellable by the owner or any `isGuardian` address via `cancelReferenceUpgrade()` (`PoolFactory.sol`). Residual, stated in the source: a fully compromised owner can `setGuardian(false)` and re-request, so the guardian veto raises the bar rather than being absolute.
- Monitor `ReferencePoolUpgradeRequested` and `ReferencePoolUpgraded` on `PoolFactory`. The beacon address is `PoolFactory.beacon()`, the factory itself.
- `PoolStorage` field order/types are append-only across versions: live pools keep their storage across a beacon swap.

### 3.2. UUPS upgrades (Admin / Flash)

`Admin` and `Flash` are ERC-1967 proxies over implementations inheriting `UpgradeGate`. A direct `upgradeToAndCall` reverts `Ownable.Unauthorized()` even from the authority: `_authorizeUpgrade` accepts only the matured self-call with the one-shot [transient](/docs/glossary#transient-storage) flag set (`UpgradeGate.sol`). The request/execute timelock is the sole upgrade path.

```solidity
admin.requestUpgrade(newImpl);
// ... wait the GOVERNANCE tier delay (7 days in production), then execute within SC.GRACE_PERIOD = 7 days
admin.executeUpgrade();          // takes no initData

// Veto before execution: authority or any AC guardian.
admin.cancelUpgrade();
// Freeze a matured request without cancelling it: authority or any AC guardian.
admin.pause();
```

- Authority is resolved per contract by `UpgradeGate._upgradeAuthority()`: `AccessControl.owner()` for `Admin` and `Flash`.
- The candidate implementation must resolve to the same governance root. `UpgradeGate._pinGovernanceRoot` compares its `AC` against the live one and reverts at `requestUpgrade` and again at `executeUpgrade`, so an AC-mismatched implementation never reaches the proxy. Asymmetric with [§3.1](#31-pool-beacon-upgrade-poolfactory-7-day-timelock): the gate pins `AC` only, where `PoolFactory._validateImplementation` pins `AC`, `admin` and `flash`.
- The upgrade-pending flag is held in EIP-1153 transient storage so it cannot persist across unrelated calls.
- `UpgradeGate` occupies the first 50 storage slots of both contracts. Nothing may be inserted above `Admin.pendingOps`; the layout is pinned by `AdminFlashUUPS.t.sol`.

Residual, stated in the source: `Pool.flashSend` books no repayment obligation, so the only enforcement of a flash repayment is the balance check inside `Flash.flashLoan`. Proxying puts that check behind an upgrade. The same authority already swaps `Pool` itself through the beacon under the same `GOVERNANCE`-tier timelock, so this grants no capability it did not already hold.

### 3.3. Oracle beacon upgrade (OracleBeacon, 1-day timelock)

The oracle is one `OracleProxy` per chain, an immutable address that delegatecalls `OracleBeacon.implementation()`. The gate lives in the beacon, not in the implementation, so a bad implementation cannot brick its own replacement.

```solidity
beacon.requestUpgrade(newImpl);   // owner; candidate must carry the same AC and storageVersion
// wait the LISTING tier delay (1 day in production), then execute inside GRACE_PERIOD (7 days)
beacon.executeUpgrade();
beacon.cancelUpgrade();           // owner or any AC guardian, any time before execute
```

Pools, foreign GEN-1 pools and external readers bind the proxy address forever. The Arc V4 pair predates the beacon; its V4→V5 migration is this upgrade, not a repoint.

### 3.4. Contracts with no upgrade path

`PoolFactory`, `OracleBeacon`, the `LPToken` implementation and the linked libraries have no upgrade path at all: none of them sits behind a proxy. They are replaced only by deploying new instances and re-pointing what references them: a new `Pool` implementation plus a beacon swap ([§3.1](#31-pool-beacon-upgrade-poolfactory-7-day-timelock)) for the libraries and the receipt implementation.

A pool cannot be re-pointed at a different `Admin` or `Flash`. Those addresses are `Pool` immutables, they live in implementation code rather than proxy storage, and `PoolFactory.requestReferenceUpgrade` requires a candidate implementation to carry byte-identical `AC` / `admin` / `flash` immutables (`PoolFactory.sol`). That is why both ship behind proxies from the first deploy.

## 4. Security through timelocks

Dangerous operations are tiered per the on-chain constants. Duration table: [Access Control §3](/docs/3-1-overview#3-timelock-parameters). Untimelocked emergency levers and their authority: [Access Control §2](/docs/3-1-overview#2-halt-authority).

### 4.1. Timelock mechanics

`Timelock.sol` is a two-function library (`pack(delay, grace)`, `validate(packed)`), not a queue contract. The queue lives on `Admin`: one generic request, one cancel, one named execute per op. The request side is plumbing (pick a tier, key it, store the blob, emit) and is shared; the execute side is not, since each op has its own decode shape, validation, pool setter and event, so it stays named and individually testable. There is no generic `OperationExecuted` event.

```solidity
admin.requestOp(pool, opType, subject, payload);  // emits TimelockRequested(pool, key, opType, executableAt)
// wait the tier delay
admin.executeAddAsset(pool, token);               // emits the operation's own event, e.g. AssetAdded
// or, owner OR any guardian, any time before execution
admin.cancelTimelock(pool, opType, subject);      // emits TimelockCancelled(pool, key, opType)
```

`opType` is the `IPool.OpType` ordinal; declaration order and the op→tier map are in [Access Control §4](/docs/3-1-overview#4-owner-gated-op-classes). `subject` is the third key component:

- The left-padded asset address for token-keyed ops.
- The preset id for `UPDATE_CURVE`.
- Ignored for the three pool-wide ops.

Request and cancel share one key derivation, so every queueable op is cancellable by the shape of the code rather than by two functions agreeing.

A live pending op cannot be silently re-queued: `requestOp` reverts `AlreadyPending` while the key holds an entry inside `eta + GRACE_PERIOD`. Cancel first, then re-request. Otherwise a payload swap plus an eta reset would restart the LP exit-notice clock unobserved.

An expired entry is overwritten rather than refused. Past `eta + GRACE_PERIOD` an op can never execute (`TimelockLib.validate` reverts `Expired`), so it is nobody's notice period: a dead key holding its own lane shut, and nothing on chain enumerates which keys are in that state. A fresh request on such a key emits `TimelockCancelled`, then `TimelockRequested` with a full fresh delay. No clock is shortened, and recovery costs one transaction instead of two.

The predicate is `TimelockLib.isLive`, one definition across six queues:

- `Admin.requestOp`, which `setAssetParams` also routes through;
- `ExternalOracleV4.requestFeedWiden`;
- both `NxrSignerSet` queues;
- `UpgradeGate.requestUpgrade`;
- `PoolFactory.requestReferenceUpgrade`, which spells the same test out inline because its `upgradeTimelock` is a raw eta with no packed op word.

Each of those queues previously charged an extra cancel transaction to recover from its own dead entry, on levers whose purpose is to be reachable during an incident.

Event signatures: `TimelockRequested(address indexed pool, bytes32 indexed id, uint8 opType, uint48 executableAt)` and `TimelockCancelled(address indexed pool, bytes32 indexed id, uint8 opType)` (`IAdmin.sol`). Execution emits the operation-specific event, never a generic one.

Ops auto-expire after `eta + grace`. `PoolFactory.referenceUpgrade` is no exception: it reverts `Err.Expired` past `upgradeTimelock + SC.GRACE_PERIOD` ([§3.1](#31-pool-beacon-upgrade-poolfactory-7-day-timelock)).

## 5. Mainnet configuration

- AccessControl owner: a governance multisig whose threshold satisfies `Quorum.admin(n) = ceil(2n/3)` over `n` in `[3, 16]`, so 2-of-3, 3-of-4 or 4-of-5 (transitions to elected DAO council). A 3-of-5 is rejected: `armQuorumPolicy()` reverts `Err.ThresholdViolation(3, 4)`. This is the owner resolved by every `onlyAdmin` check across the DEX: `Admin`, `PoolFactory`, `ExternalOracle`, and every `Pool`, official or permissionless. Arming preflight: [Access Control §1.1](/docs/3-1-overview#11-arming-the-quorum-policy).
- Treasury: `AccessControl.treasury()` is a pointer, not a principal. The owner rotates it at the `GOVERNANCE` tier, guardian-cancellable while pending. Custody detail: [Treasury](/docs/3-1-6-treasury).
- Pool deployment is permissionless: any address may call `PoolFactory.createPool`. Pool *administration* (add asset, halt, risk config) is gated to the single AC owner for every pool, regardless of deployer. There is no per-pool owner or curator role in the deployed contracts.

## 6. Best practices

### 6.1. For pool operators

1. Use a multisig for the `AccessControl` owner, with a threshold satisfying `ceil(2n/3)`: 2-of-3, 3-of-4, 4-of-5.
2. Monitor `TimelockRequested` and `TimelockCancelled` on `Admin` for your pool, plus the per-operation execution events (`AssetAdded`, `RiskConfigUpdated`, `AnchorUpdated`, `CurveUpdated`, `FeeParamsUpdated`, `TreasuryUpdated`, `BaseTokenMigrated`). There is no generic `OperationExecuted`.

### 6.2. For users

1. Verify pool address and admin. Query `factory.isOfficialPool(pool)` to distinguish official from permissionless pools; admin authority is the same single owner either way ([§5](#5-mainnet-configuration)).
2. Monitor timelock events. Subscribe to `TimelockRequested` for your pool, review pending ops, exit if suspicious.
3. The upgrade model:
   - Your pool's code is not immutable. It is a beacon proxy: one owner transaction at the factory, after the `GOVERNANCE` delay, changes the code of every live pool including yours. Watch `ReferencePoolUpgradeRequested` on `PoolFactory` for the full notice window ([§3.1](#31-pool-beacon-upgrade-poolfactory-7-day-timelock)).
   - A queued upgrade can be vetoed by the owner or any guardian, and expires 7 days after maturity if not executed.
   - `Admin` and `Flash` are themselves upgradeable behind proxies, under the same `GOVERNANCE` delay; watch `UpgradeRequested` on both.

## 7. Emergency procedures

Authority, delays and the full lever list: [Access Control §2](/docs/3-1-overview#2-halt-authority). The two calls this page owns:

```solidity
factory.cancelReferenceUpgrade();    // veto a queued fleet swap: owner or any guardian
admin.pause();                       // freeze a matured UUPS request without cancelling it
```
