Deployer

1. Overview

Per chain, the protocol deploys:

  1. The shared AccessControl singleton, one ExternalOracle, and two UUPS singletons behind ERC-1967 proxies (Admin, Flash).
  2. The Pool reference implementation and PoolFactory. The factory is the beacon: beacon() returns address(this) and implementation() is the one slot every live pool reads (PoolFactory.sol).
  3. Four deployed, linked libraries auto-deployed by forge via CREATE2 and linked into their callers: PoolConfig, PoolLiquidity, Pricing, NUQuartic. See §4.
  4. Per-pool ERC-1967 beacon proxies, deployed via PoolFactory.createPool (LibClone.deployDeterministicERC1967BeaconProxy). Each proxy has its own storage and reads PoolFactory.implementation() for its code.

The Pool implementation constructor deploys the shared LPToken implementation that every per-leg receipt clones (Pool.sol), so it is not a separate deploy step.

Three upgrade paths, all operated by the owner multisig after handover (Admin):

  • Beacon impl swap at PoolFactory (GOVERNANCE tier delay), re-pointing every live pool at once.
  • UUPS on Admin and Flash, gated by the GOVERNANCE tier delay via requestUpgrade / executeUpgrade only.
  • Oracle beacon: one immutable OracleProxy address per chain reads OracleBeacon.implementation(); the beacon swaps it through requestUpgradeLISTING delay (1 day) → executeUpgrade, guardian-cancellable. Pools hold that one address in OracleConfig; there is no per-leg repoint op.

The Router has no upgrade path: it holds no state and no funds between calls, so a new one is a new deployment (Composability §2).

Tier durations are deploy-time data, not constants of the design; the schedule is in Access Control §3. Every duration quoted on this page is the PROD_DELAYS value.

Pool bakes the Admin and Flash proxy addresses as immutables and PoolFactory._validateImplementation pins them on every fleet upgrade, so a live pool can never be re-pointed at a different governance or flash contract. Those two must be proxies from the first deploy or their logic is frozen for the fleet’s life.

Interactions between distinct contracts (Admin↔Pool, Factory↔Pool, Flash↔Pool) are normal external calls, not delegatecall. The DELEGATECALLs happen only inside a single Pool, to its linked libraries; see AIMM Overview §2.1.

2. Architecture

2.1. Core components

  1. Pool reference impl (Pool.sol)

    • Standalone AIMM contract. Reads the external-mark feed (ExternalOracle); no internal TWAP.
    • Deployed once per protocol version; never called directly by users.
    • Each pool is an ERC-1967 beacon proxy pointing at the factory, initialised via initialize(...).
  2. PoolFactory.sol

    • Deploys Pool beacon proxies (createPool(...)) and holds the beacon-swap timelock (pendingReferencePool, upgradeTimelock).
    • Maintains allPools + officialPools + isPool registries.
  3. UUPS singletons, each an ERC-1967 proxy over an implementation carrying the immutable AC (AccessControl) ref:

    • Admin.sol, per-pool timelock queue + restricted setters. Holds real per-pool state (bootstrapSealed, riskFences, pendingOps), so redeploy-and-repoint is not equivalent to an upgrade: a fresh Admin would arrive with bootstrapSealed == false fleet-wide and re-open the untimelocked addAsset / setCurve lanes on live sealed pools.
    • Flash.sol, ERC-3156-style (postFlashLoan variant) flash-loan provider. Holds no persistent state; UpgradeGate’s 50 slots are the whole layout.
  4. AccessControl.sol, single owner source of truth, plus the treasury and factory pointers, the guardian set, and the packed governance delay schedule.

  5. Fee sink: each pool’s treasury() is a plain address, not a contract. See Treasury.

3. Deployment process

3.0. Phase 0: the CREATE3 factory, and why addresses are known before the deploy

Every singleton below is deployed through a CREATE3 factory rather than plain CREATE, so its address is f(factory, deployerKey, salt) and does not depend on the contract’s bytecode. Consequences: the address is identical on every chain BTR launches on; it is known before the contract is compiled, so keeper configs, monitoring and integration constants can be filled in ahead of the ceremony; and it survives a contract-generation change, since a new oracle version deploys to the reserved address for that role.

The factory itself is deployed once per chain from a frozen initcode artifact via the canonical arachnid CREATE2 proxy, never rebuilt from source. A rebuild under different compiler settings produces different initcode, therefore a different factory, moving every address derived from it.

The deployer key is part of the address. The factory salts with keccak256(deployerEoa ++ salt), so the same salt signed by a different key lands somewhere else, silently, with no revert. Mainnet addresses are mined for one specific key; signing a mainnet deploy with any other burns the reservation unrecoverably. Deploy scripts assert the signing key against the record before broadcasting, and the reserved mainnet addresses are published in Contract Addresses, so anything appearing at one of them ahead of a published deployment is recognisably not BTR.

3.1. Phase 1: deploy shared and singletons

Phase 1 deploys the shared AccessControl singleton (owner, treasury, and the packed governance-delay word passed at construction; GOV_DELAYS is read from the environment with no default, so an unset variable aborts the deploy), then the Admin and Flash implementations, each carrying the immutable AC ref, behind ERC-1967 proxies via LibClone.deployERC1967. Every mapping starts empty: nothing to initialise, no uninitialised-proxy window.

A chain whose oracle stack shipped first reuses that chain’s existing AccessControl rather than minting a second one. Two ACs would split protocol governance and leave the oracle’s guardian unable to halt the pools it feeds. ExternalOracle takes the AC address as a constructor argument, so this is a deploy-script decision, not contract-enforced. The one deliberate exception is a reference oracle instance, governed separately from the primary it polices; see Oracle Keeper.

3.2. Phase 2: deploy pool reference and factory

Phase 2 deploys the reference Pool implementation plus the PoolFactory, which is itself the beacon holding the fleet implementation slot. The Pool AC / Admin / Flash wiring is immutable and re-asserted on every fleet swap, with admin and flash the PROXY addresses, never the implementations. Its constructor also deploys the LPToken implementation the receipts clone.

3.3. Phase 3: deploy a pool

// Permissionless: any address may call this. Deployment ≠ administration - // there is no `owner` param; every pool's admin functions resolve to the // single protocol-wide AccessControl owner regardless of who deployed it. address pool = factory.createPool( baseToken, // anchor token (e.g. USDC) tokens, // address[] of assets to register on the pool initdata // abi-encoded call forwarded to the pool (typically `initialize(baseToken, wnative, feeParams)`) ); // Pool is now an initialised ERC-1967 beacon proxy reading `factory.implementation()`.

3.4. Phase 4: wire per-pool config

admin.requestOp(pool, uint8(IPool.OpType.ADD_ASSET), subject, payload); // wait the LISTING tier delay admin.executeAddAsset(pool, token); // execute paths stay named, one per op // The three per-asset params are a separate owner call (instant pre-seal or on a tighten): admin.setAssetParams(pool, token, minLiquidity, minFeePbps, vegaBps);

decimals is not an argument: it is read from the token at listing. Inventory skew takes no per-asset argument either; it is a fixed protocol law.

setAssetParams applies immediately only before bootstrapSealed[pool] or on a defensive tighten; otherwise it queues itself at TUNING. Policy: Access Control §4.

4. Non-upgradeable components

A library is deployed and linked when it declares at least one external or public function; otherwise the compiler inlines it into its callers.

  • Deployed, linked: PoolConfig.sol, PoolLiquidity.sol, Pricing.sol, NUQuartic.sol. Pool links the first three directly; NUQuartic is linked into PoolConfig and Pricing additionally into PoolLiquidity, so a link check that inspects only Pool reports three and misses one.
  • Inlined: AnchorTreeLib.sol, PoolIOLib.sol, PoolHooksLib.sol, FeedMathLib.sol, TransientCacheLib.sol, PoolConstantsLib.sol.
  • PoolFactory, the LPToken implementation, ExternalOracle.
  • Shared Constants.sol, Errors.sol, Timelock.sol, AccessControl.sol.

5. Governance delays are deploy-time data

The delay schedule is a constructor argument, not a chain-dependent branch. AccessControl(owner, treasury, govDelays) stores the packed word in the immutable GOV_DELAYS, and every governed contract derives its own tier delays from it at construction. No contract reads block.chainid to pick a delay, so testnet and mainnet run identical code on different data.

Deploy scripts read GOV_DELAYS from the environment with no default: an unset variable aborts the deploy rather than silently picking a side. Production passes Constants.PROD_DELAYS; public testnets pass Constants.TESTNET_DELAYS. There is no throwaway zero-delay option: the deploy path rejects any schedule under Constants.MIN_ARMED_DELAY, so every deployed fleet arms its timelocks. Every delay quoted in these docs is the PROD_DELAYS value; a testnet fleet runs the shorter schedule for the same tier.

6. Permissionless pool deployment

address myPool = factory.createPool(baseToken, tokens, initdata); admin.requestOp(myPool, uint8(IPool.OpType.ADD_ASSET), subject, payload); // ... wait timelock, then execute (as the AC owner - the deployer does NOT // automatically gain admin rights over the pool they deployed).
  • Timelocks are enforced on every pool, which protects pool users regardless of who administers it.
  • Each asset picks its oracle mode at listing, and every non-base spoke carries a mandatory reference band (requireExternalSpokeBound, PoolConfig.sol); the modes are defined in Oracle Keeper and the band in Flow Guards.

7. Best practices for pool reference-impl developers

  1. Storage layout is append-only: PoolStorage field order and types are frozen across implementation versions, new fields appended only. Load-bearing, because a beacon swap moves every live pool onto the new code while keeping its existing storage.
  2. No constructors with side effects. Pool runs behind a proxy; only initialize(...) runs per-instance.
  3. No new external dependencies without an explicit migration story. A beacon swap migrates the whole fleet in one transaction, so a new dependency must be safe for every live pool simultaneously.
  4. Test the implementation AND a deployed proxy. A proxy is what users hit; impl in isolation skips the real DELEGATECALL edge cases, since the linked-library DELEGATECALLs only trigger through a deployed proxy.

8. Upgrade checklist

  • PoolStorage layout unchanged or safely extended (append-only).
  • No constructor side effects in the reference impl.
  • forge test green against a deployed proxy, not the implementation alone.
  • Fleet-wide blast radius reviewed: the swap re-points every live pool, including third-party ones.
  • Timelock requested and announced; the delay window is the review period.
  • Testnet swap executed and observed.
  • ABIs regenerated and republished to the backend ABI endpoint (GET /v1/abis/<ContractName>); abi-freshness check green.
  • Emergency halt paths verified.
  • Monitoring + alerting wired to the new impl + factory.
  • Cancel path tested (so the swap can be aborted if needed).