Security Design

In-depth coverage of the security patterns and invariants enforced across the Fira Protocol smart contract stack.

Access control

Ownership model

All privileged Fira contracts inherit BoringOwnableUpgradeable, which provides:

  • Single owner with a two-step transfer (transferOwnershipclaimOwnership) to prevent accidental misassignment.

  • onlyOwner modifier gating admin functions (pause, parameter updates, upgrades).

Router selector guards

The FiraRouterV4 uses a diamond-like facet dispatch table. Each facet registers its function selectors during initialize(). Calls to unregistered selectors revert with Errors.RouterInvalidAction. This ensures that only explicitly whitelisted facets can be invoked through the Router.

Minting privileges

BondToken and CouponToken restrict mintBC() and redeemBC() to the YieldContractFactory address set at initialization, preventing arbitrary minting.

The LiquidityInjector holds a separate minting privilege on BondToken via the LIQUIDITY_INJECTOR role, constrained by the injectLiquidityLimit cap.

Reentrancy protection

Fira applies the nonReentrant modifier (OpenZeppelin ReentrancyGuard) to all external state-mutating functions across:

Contract
Protected functions

FWBase

deposit, redeem, claimRewards

FiraMarket

mint, burn, swapExactBtForFw, swapFwForExactBt

CouponToken

mintBC, redeemBC, redeemInterest, redeemInterestAndTransfer

LiquidityInjector

injectLiquidity, withdrawLiquidity

RehypothecationModule

rebalance

The Router's _safeTransferWithFeeExtraction and callback patterns (flash swaps) are additionally protected by the facet-level lock state in ActionStorageV4.

Pause controls

FW token contracts (FWBase, USDCFW) inherit OpenZeppelin's PausableUpgradeable:

  • pause() — Owner freezes all deposit and redeem operations.

  • unpause() — Owner resumes normal operations.

  • whenNotPaused modifier — Applied to deposit(), redeem(), and related transfer functions.

circle-exclamation

Upgrade patterns

Initializable contracts

All upgradeable contracts use OpenZeppelin's Initializable with initializer / reinitializer(n) modifiers to prevent double-initialization attacks.

Router versioning

The Router facet system supports additive upgrades:

  1. Deploy a new facet contract.

  2. Call Router.addToSelectorMapping(selectors, facetAddress) to register new selectors.

  3. Existing selectors can be overwritten to point to the new facet, enabling in-place upgrades without redeploying the Router proxy.

Old facets remain deployed on-chain but become unreachable once their selectors are reassigned.

Storage layout safety

Upgradeable contracts follow the "storage gap" convention and use dedicated storage slots (e.g., ActionStorageV4.STORAGE_SLOT) to avoid slot collisions across upgrades.

Oracle defence-in-depth

The Fira oracle pipeline stacks multiple validation layers:

Each layer can reject stale or invalid data independently. ChainlinkOracleV2 enforces a configurable heartbeat and reverts with StalePrice if the feed has not updated within the window.

Fixed-point arithmetic safety

All yield and pricing math uses the PMath and LogExpMath libraries with 18-decimal fixed-point representation. Key invariants:

  • No division by zero — All divisors are checked or guaranteed non-zero by prior validation.

  • Overflow protection — Solidity 0.8.x built-in overflow checks apply; LogExpMath additionally validates input ranges before exponentiation.

  • Rounding direction — Deposit/mint operations round against the user (ceiling); redeem/burn operations round in favor of the protocol (floor), preventing rounding exploits.

Contract size management

Several Fira contracts approach the EIP-170 24 KB limit. The codebase uses two mitigation strategies:

  1. Library extraction — Heavy logic is moved to external libraries (e.g., MarketMathCore, MarketApproxPtInLib) that are deployed separately and linked via DELEGATECALL.

  2. Split-code deployment — The FiraMarket factory uses a two-part deployment (MarketMathCore + FiraMarketV3) to keep each deployment unit under the size limit.

Invariants summary

Invariant
Enforced by

FW exchange rate monotonically non-decreasing

FWBase._afterUpdateBCIndex + rehyp rebalance

BT total supply = minted via Factory + LI inject

BondToken.mintBC access control

CT interest accrual ≥ 0

CouponToken._updateAndDistributeInterest

LP pool BT + FW reserves ≥ total LP supply

FiraMarket mint/burn invariant checks

Rehyp ratio stays within [phiMin, phiMax]

RehypothecationModule.rebalance bounds check

Lending market solvency

LendingMarket liquidation + oracle health checks

Last updated