Skip to content

Security: EqualFiLabs/statics

Security

SECURITY.md

Statics security model

Statics holds user assets. The standalone Genesis release is deployed on Robinhood Chain with source-verified contracts; the Phase 1 DEX-and-staking Diamond and later selector phases remain subject to independent review before production use. The repository test suite is not an external audit. The repository also records a public Robinhood Chain testnet integration beta.

Permissionless constituent risk

There is no asset registry. A basket creator can select arbitrary ERC-20 addresses, and users decide whether that basket and its constituents are worth using. Paying the native creation fee is not a compatibility certification.

Internal accounting is isolated by module, basket ID, and asset. Direct token donations are unallocated and cannot inflate a basket's recorded backing or global reward reserve. Shared physical custody additionally records both globalReservedByToken[token] and module-local reservations.

Every custody operation measures balance deltas:

  • inbound transfers credit the actual increase in the Diamond's balance, then the calling flow checks the minimum economic amount it requires;
  • outbound transfers authorize a maximum debit from one named reservation and revert if the Diamond spends more; and
  • the receiver's observed increase is checked separately against the caller's minimum-output bound where one is supplied.

This accounts for ordinary fee-on-transfer behavior without allowing a sender-extra-charge transfer to debit physical tokens reserved for another book. It does not make every token safe. Negative rebases, arbitrary burns from the Diamond, deceptive balanceOf results, blocklists, transfer pauses, or hostile callbacks can halt or damage baskets using that token. Because multiple baskets may hold one token at the same physical address, token behavior that changes the Diamond's balance outside a checked transfer can create a token-wide physical shortfall. Internal books do not socialize the loss or promise an exit order; discovery systems and user interfaces should surface constituent behavior and basket reputation.

The Diamond accepts native currency for payable basket creation and the typed Statics Dollar ETH gateway. Public pools additionally support native ETH principal and POL reservations under address(0), backed by the physical ETH balance. Its receive hook accepts configured WETH and a transiently authorized PoolManager or originating liquidity manager only during native settlement. Native revenue becomes WETH at the reward boundary; native LP and POL principal remain ETH. Forced ETH stays unallocated. Native transfers use exact value accounting and failure-reverting calls inside shared reentrancy guards. See native ETH Phase 1.

Custody and execution

Statics Dollar Core collateral remains physically held by StaticsDollarCoreDiamond, outside the shared Diamond. Dollar periphery books, basket backing, basket debt, global fees, staking custody, and recovery surplus use separate namespaced ledgers even when they reference the same token or PositionNFT. For each token, the Diamond's global reservation is the sum of its Dollar, fee, staking, and per-basket account reservations.

Ordinary ERC-20/ERC-1155 custody-mutating facets on StaticsDiamond use the same OpenZeppelin ReentrancyGuard namespaced storage slot under delegatecall. FlashLoanFacet instead uses OpenZeppelin's transient guard and acquires the persistent slot only during disbursement and repayment. Its callback can therefore use ordinary basket mint and redemption, while nested flash loans remain blocked. Per-token transient custody deficits represent only reserved principal temporarily outside the Diamond; loaned unreserved balance remains unavailable for new reservations. Repayment must restore raw physical backing before the deficit clears and flash fees accrue. This design requires Cancun/EIP-1153. The PositionNFT uses OpenZeppelin's constructorless ERC-721 implementation for the same reason; it does not introduce a UUPS, transparent, beacon, or ERC-1967 proxy. EIP-2535 Diamond cuts remain the sole implementation upgrade mechanism.

Deployment manifests record facet runtime hashes as offchain release evidence. The Diamond does not enforce those hashes at dispatch or inspect facet bytecode; standard owner-controlled EIP-2535 cuts are the upgrade boundary. Reviewers and deployment tooling must verify every proposed facet and initializer.

Statics Dollar uses OpenZeppelin EIP-2612 permit. A permit expresses only an ERC-20 allowance and can be submitted by anyone; it is not proof of intent for a particular series, receiver, or minimum output. The permit recombination entrypoints fix the owner to msg.sender, spender to StaticsDiamond, and value to the Dollar amount consumed. They tolerate a pre-submitted permit but still require the subsequent transferFrom to succeed. Exit availability is checkpointed before permit execution, and any later failure rolls back the permit with the rest of the transaction. Series risk shares remain governed by separate ERC-1155 operator approval. Successful transition finalization irreversibly freezes ordinary transfers for the predecessor's recoverable series ID so a holder cannot reshuffle an expired full-balance recovery; Core mint and burn operations, including direct Core recombination, remain available for runoff and successor rollover. A series retired directly with its profile remains transferable and ordinary-recombinable via Core (and the gateway for profile 1) during runoff.

Aggregated reward claims

The additive aggregated API settles the same authorized staking, LP and allocator claims before paying one combined amount per positive token. Only those three reward payout paths can defer custody. Every entry, including a zero amount, acknowledges its bound caller, receiver and active typed dispatch. Missing or incorrect acknowledgements/return values revert the complete transaction.

Positive source reservations are moved into a dedicated transient batch account; the token's global reservation remains unchanged until payout. During aggregation, the custody sum includes that transient account as well as persistent source accounts. Final exact transfers consume its reservations under the shared guard, while the dedicated batch lock remains active. Context, acknowledgements and reservations are cleared on success, and EVM rollback restores them on failure. There is no pending state to migrate or resume across transactions.

Aggregated payouts require exact Diamond debit and exact recipient increase. Taxed receipts, excess sender debit, failed transfers or independent minimum failures roll back the batch, without automatic legacy retries. Existing individual and legacy batch APIs preserve their measured taxed-token payouts. Neither interface makes malicious balanceOf, rebases or arbitrary token burns safe. Fixed input limits bound processing but do not promise a gas ceiling for arbitrary tokens or settlement histories.

Authority

  • Phase 1 deploys one StaticsTimelock. The deployment signer initially owns StaticsDiamond, installs launch integrations, and creates four initial pools while the zero pool creation fee keeps creation owner-only. It enables the 0.01 ETH public fee before transferring ownership to the governance Safe. The Safe transfers Diamond ownership to the timelock only after verifying the launch state. Later phases retain that Diamond and add only their reviewed selector and initializer delta. The fresh full-stack reference directly assigns the timelock as owner of both StaticsDiamond and StaticsDollarCoreDiamond. The delay initializes to 24 hours on production chains and can change only through a scheduled timelock call to the timelock itself.
  • The configured multisig is the timelock proposer and, under OpenZeppelin's proposer-role initialization, a canceller. Execution is open after the current delay. The emergency guardian is also an explicit timelock canceller, allowing it to veto a pending operation without gaining proposal or execution authority.
  • In Phase 1 the guardian can pause liquidity, treasury distribution, and global staking ingress and can stop all Statics-hook swaps or quarantine one registered protocol pool. It cannot unpause actions, restore swaps, release a pool quarantine, or change configuration. Additional pause and lifecycle paths become reachable only when their later-phase selectors are installed.
  • The governance Safe and guardian may be the same address, but doing so removes independence between the proposal and emergency-veto roles. A compromise or availability failure then affects both authorities. The planned Phase 1 mainnet configuration assigns both roles to the Genesis governance Safe.
  • Timelocked governance can upgrade either Diamond, manage basket-level global settings and pauses, restore swaps, release quarantine, and mark baskets ExitOnly.
  • The Dollar profile guardian can perform only the emergency actions exposed by Dollar Core governance. Core configuration derives directly from the Core Diamond owner, which is the same timelock; there is no second protocol governor or internal proposal queue.
  • Dollar redemption fees are capped at 1,000 basis points independently from mint fees, and redemption rejects raw-unit rounding that would produce zero collateral output.
  • Dollar Core binds to the freeze-capable STATICS_DOLLAR_RISK_V1 token kind. Deploy the Risk Shares token, Core, and StaticsDiamond as one coordinated stack. Per-series transfer freezes preserve Core minting and burning so users can always roll predecessor claims into the active successor series.
  • Insurance top-ups accept non-retired profiles. Pegged top-ups become immediately redeemable profile collateral; volatile top-ups remain profile-level transition reserve through ReduceOnly. Unassigned volatile reserve is not current solvency backing and cannot authorize issuance. Retirement first flushes pending periphery insurance, then assigns reserve to a live current series only when it is the profile's sole senior generation; otherwise it routes the non-claimant reserve globally without blocking retirement. Fixed historical recovery books never gain later reserve.
  • Anyone may trigger global treasury fee distribution, but the recipient is fixed to the configured treasury. Dollar insurance and opt-in routing remain governed by their isolated Dollar books; eligible Dollar fees can also enter the global fee ledger.
  • A standalone launch hook additionally permits the Safe holding its owner's existing timelock proposer role to register a new pool immediately. Initial pool terms are therefore established before trading begins without waiting for the delay. Later hook-fee and fee-receiver changes remain owner-only and must execute through the timelock.

Diamond ownership uses immediate ERC-173 transfer by the current owner. A governance migration must execute through the timelock and verify Diamond owners, guardian roles, and treasury configuration after execution. The already deployed standalone Genesis contracts and their existing authorities are outside the phased Diamond launch. No Phase 1 deployment or configuration ceremony calls them, transfers their ownership, or changes their bindings.

Staged production surface

The Phase 1 launcher installs 32 facets and 224 selectors for the Diamond kernel, public general Statics-hook pools, a separate permissioned venue path, protocol revenue and managed public POL, PositionNFT, reward restrictions, and global STATICS staking/reward opt-ins. It also installs public-pool PositionNFT range gauges and the Diamond-bound liquidity-manager selectors. Permissioned pools use their own hook, creator-selected controller, trusted exact-input router, and non-transferable LP positions. General public pools start with POL disabled; the creator may pay the governed Treasury fee to activate prospective managed-POL funding. Permissioned pools create no Statics POL. It does not install or advertise basket, credit, flash-loan, Genesis-integration, Dollar, Morpho, BorrowLiquidity, ERC-1155 receiver, or series-migration interfaces.

Public range gauges reserve slot 0 for custody-backed protocol STATICS and keep four separate direct-funding slots for creator, partner, and community rewards. Raw staked STATICS may direct protocol rewards to eligible public PoolIds only, with each position's allocations persisting until explicitly changed. Allocation changes settle accrued rewards first and apply immediately, followed by a governed cooldown that permits reductions and removals but prevents increases or redirection. Every positive STATICS stake ingress starts or extends that same PositionNFT deadline, preventing a user from bypassing routing stability by moving stake into a new position. Existing allocations remain active, and the deadline does not lock unallocated principal. The reserve commits a percentage-backed budget every seven days and accounts for that budget continuously through a cumulative routing index. Each PoolId lazily settles its pro-rata entitlement without requiring pool iteration. A stale or ineligible allocation remains in the denominator as an abstention, and its share recycles instead of being reallocated. Zero-liquidity protocol rewards also recycle into the reserve. Directly funded slots stream continuously to LPs and may include a creator-selected allocator share, while slot 0 remains LP-only and cannot fund allocator rewards. Allocator entitlements are bounded by the pool eligibility generation in which they accrued. An unresolved allocator claim or fractional remainder keeps its PositionNFT leg active until the owner claims it or explicitly forfeits it to Treasury.

Phase 1 requires a nonzero general-pool creation fee, enabling public creation with exact native payment from every caller, including the governance Safe. The launch does not add TVL, position-notional, volume, or pool-count caps. The Safe completes configuration and initial pool creation before the timelock handoff. Later public pool creation remains direct and fee-paid; owner-only permissioned pool creation and administration use the timelock. Guardian stops, monitoring, and asset disclosure are additional initial controls. These controls do not create a protocol-level endorsement of pool assets.

The reward-restriction map is a technical delivery policy, not an asset allowlist or legal classification. The guardian may add a restriction immediately; only the timelock may remove one. Existing earned claims and exit paths remain available. For permissioned pools, creator and treasury revenue remain in the original output currency. If both currencies are restricted, the pool-specific allocation is overridden with an 80% creator / 20% treasury split and no reward liability.

A permissioned pool's controller may be replaced only through an exact creator EIP-712 or ERC-1271 authorization accepted by the Diamond owner through the timelock. The replacement must be a compatible controller with a nonzero operator and must report the pool halted before installation. Replacement does not call or require consent from the old controller, preserving recovery from a broken or abandoned provider. The pool key, creator, economics, liquidity, and user exit rights do not change, and the guardian cannot replace controllers.

ERC-1271 wallets used for creator authorizations or as permissioned-position owners are supported only when signature validation binds the digest to that wallet's own account domain, such as an account-specific digest or ERC-7739 defensive rehashing. The position manager retains the canonical Uniswap permit surface. Wallets that intentionally accept one raw digest across multiple accounts are outside the supported permission model.

Phase 2 adds baskets, self-secured credit, flash composition, basket liquidity, and Genesis integration; Phase 3 adds Statics Dollar; Phase 4 adds Morpho. All four selector deltas and one-time initializers exist now and derive from one canonical plan. CI proves staged-to-fresh selector and runtime parity for both Diamonds, and later-phase preparation rejects drifted earlier facet bytecode. Every live transition still requires its own review and timelocked execution.

Economic and liveness assumptions

Holding a BasketToken in a wallet does not earn basket-specific fees. Deposited BasketTokens, including collateral locked for a basket loan, enter the isolated basket reward denominator. Global rewards separately require staking the deployment-configured ERC-20 in a PositionNFT. Position owners or approved operators must claim rewards through transactions; nothing runs in the background. A guardian staking pause blocks new global stake and reward-asset opt-ins. It does not block unstaking, reward-asset opt-outs, reward claims, or Position closure. Undeployed global stake has no withdrawal cooldown, but newly supplied weight is subject to the governed gauge-allocation cooldown before it can increase or redirect PoolId allocations. Stake supplied to Morpho must first be recalled. Initial stake, reward-asset selections, and top-ups mature through a per-asset hourly ring no earlier than 24 hours after scheduling. Basket collateral uses the same delayed hourly eligibility model, but unlocked and undeployed shares have no separate withdrawal-time gate. Fee and position interactions roll due buckets. Dollar passive Risk Share reward eligibility does not exist: supplied Risk Shares are immediately consumable by the pairing vault and earn only through actual consumption.

Permanent protocol pools use a creator-selected static native LP fee from 0 through 999,999 pips, plus separate governed input/output hook fees. Dynamic fees and the 100% static-fee boundary are rejected. Protocol-owned liquidity is a portfolio of explicit PositionManager NFTs held by the installed liquidity manager and bound as protocol POL. The strategy operator may select ranges and amounts but cannot select a principal, refund, fee, or NFT recipient. All principal remains in PoolId-specific Diamond custody or in a bound POL position. Ordinary PositionManager NFTs stay in user custody and earn native v4 fees through standard pool accounting. A user may instead opt a position into the public range gauge, which transfers that NFT to the immutable liquidity manager until managed exit while the associated PositionNFT controls it. Native fees earned by managed POL positions route only to Treasury and never become POL principal. Operator POL position actions use a transaction-local PoolId swap block so token callbacks cannot accrue fees after harvest and before principal accounting. Other pools remain available for swaps, and the block clears when the action ends.

Basket creation initializes and seeds its canonical pools atomically. General-pool creation registers and initializes the pool but does not require a liquidity seed: it is owner-only while the creation fee is zero and permissionless with exact payment while the fee is nonzero. The bilateral default initializes to 5 BPS per leg. A general-pool creator may select a higher initial rate, but each leg must be at least the live default and the combined rate cannot exceed 200 BPS. Selecting the exact default leaves the pool inheriting future default changes; selecting either leg above the default installs a fixed PoolId override for both legs. Governance may change the global default and set or clear registered PoolId overrides after creation. Creators cannot change hook fees after creation or administer allocation profiles. Governance also controls the creation gate, POL operator, creator-paid POL activation fee, per-pool POL funding overrides, and staged general-pool decommissioning. General pools start with POL disabled. Creator activation is permanent and affects future fees only; setting the effective share to zero stops new funding without closing existing positions. Inventory settlement is permissionless. Position management is operator or owner authorized, and exits remain available while liquidity ingress is paused.

Public swap STATICS-staker entitlement is indexed during the authenticated swap callback using the eligible weight at fee generation. The matching PoolManager claim may settle later, but settlement timing cannot change the owners of the indexed reward. Claims and Treasury distribution settle the exact funding shortfall before transferring value.

Range-gauge boundary synchronization is atomic with each public-pool swap. Its gas cost grows with the number of distinct managed boundaries crossed and with the number of assigned reward assets. A sufficiently wide swap across a densely populated span can exceed the transaction or block gas limit and must be split into smaller price movements. Statics deliberately does not cap distinct gauge boundaries because a fixed slot cap would let early positions deny later gauge participation and would create a separate hard market limit. Pool creators, governance, integrators, and monitors must treat boundary density as an availability and gas-cost signal.

Per-position reward calculations round down to whole raw token units. A position that exits discards any remaining sub-unit arithmetic fraction, while the corresponding whole-token reservation stays classified as indexed surplus. That surplus remains backed but is recoverable by treasury only through final reconciliation after the pool gauge is stopped. Low-decimal and unusually high-value reward assets therefore require additional governance review before allowlisting. Final reconciliation is intentionally pool-wide: any unresolved claim or remainder in any reward slot delays treasury surplus recovery for every slot, without blocking user principal exits or reward claims.

Range-gauge indexes use Q160 precision. Stream numerator carry persists across checkpoints while active gauge liquidity is unchanged, so user-controlled claim frequency cannot erase rewards. A genuine active-liquidity change resets that carry once for the net denominator transition. Since active gauge liquidity is bounded to uint128, the reset value is always less than 2^-32 of one raw token unit per stream. It is never immediately transferred to treasury or recycled from protocol slot 0. Existing ABI member names ending in Ray are retained for compatibility, but those range-gauge-only values are Q160-scaled.

Each pool reward slot also has a lifetime index-capacity limit of floor(type(uint256).max / 2^160), or 2^96 - 1, raw reward units. The stream view exposes the amount already consumed as indexCapacityUsed, and funding reverts before the current schedule plus a new contribution could exceed the remaining capacity. This conservative lifetime limit prevents the Q160-scaled global index from wrapping while a position remains unsettled. The bound is far above practical token supplies. When the error reports a committed budget below the maximum, the funder may retry with an amount no greater than the remaining capacity. An equal committed and maximum budget means the pool reward slot is permanently exhausted. A protocol slot-0 epoch share that does not fit in the remaining capacity is resolved and recycled in full rather than reverting an automatic pool checkpoint. Capacity exhaustion therefore does not block swaps, LP principal exits, claims, or decommissioning.

Basket loans have no price-oracle liquidation. Their debt is the proportional constituent vector and their LTV cannot exceed 95%. Repayment is open in every basket lifecycle state. Recovery becomes permissionless after maturity plus one hour. The recovery caller receives 20% of configured penalty backing, while the remaining 80% enters the protocol fee route; principal is not paid to the caller.

ExitOnly preserves redemption, repayment, recovery, and treasury claims in the installed facets. Timelocked Diamond upgradeability means governance can still replace those rules.

Shared Diamond kernel code built from this source recomputes the standard IERC-165, IDiamondCut, IDiamondLoupe, and IERC-173 declarations from final selector routing after every successful cut, including after its initializer. The invalid 0xffffffff interface ID always remains false. Protocol-specific interface declarations remain governed metadata, while the generic metadata setter rejects the four selector-derived standard IDs. Deployment and governance tests verify selector counts and routing self-consistency; an independently maintained release manifest remains necessary for exact expected-set review. Recorded runtime hashes remain offchain release metadata rather than live dispatch controls.

Batch reward claim boundary

BatchRewardsFacet accepts only typed global, LP and allocator claim groups. It constructs the three existing claim selectors itself, resolves registered facet implementations and delegatecalls them with the original sender. It accepts no target, selector or arbitrary calldata from the caller. Existing NFT owner/approval checks, funding, custody reservations, measured payouts, minimum checks, event emission and claim-stub cleanup stay in those facets. Any delegated failure bubbles unchanged and rolls back the entire batch.

The dispatcher has a dedicated namespaced persistent lock and rejects entry while the shared custody/claim guard is entered. It does not acquire, clear or bypass that shared guard: each delegated claim acquires its original guard. During token callbacks both batch reentry and individual guarded custody actions remain blocked. Locks reset after success and revert with transaction state on failure. Owner-controlled selector cuts remain the implementation trust boundary.

Limits of 16 claim groups and 64 entries bound request size and duplicate-check work. They do not bound arbitrary token execution costs or settlement topology. Required bounded catch-up remains explicit, and a failing token or empty global claim can prevent an entire batch. Clients should simulate the complete batch, estimate gas and split further if needed. Separate transactions do not provide sequence-wide atomicity. Existing reward and token-compatibility assumptions continue to apply.

There aren't any published security advisories