Core/Periphery Design Pattern for Immutable Protocols
For teams that decide not to use upgradeable contracts (see the gradual immutability path), the core/periphery split is a practical design choice that can minimize security issues while still allowing controlled changes of functionality.
What Is the Core/Periphery Split?
The core is the immutable, minimal set of contracts that define protocol-critical logic and invariants. The periphery is the replaceable layer that handles user interactions, UX improvements, and convenience features.
Core
Immutable once deployed.
Holds critical state and enforces protocol-level invariants.
Example: Uniswap V2/V3 Pool contracts (AMM math, reserves, invariant checks).
Periphery
Can be re-deployed and versioned over time.
Abstracts away complexity and adds features like batching, multicall, safer UX.
Example: Uniswap Router, NonfungiblePositionManager.
Why Use This Pattern?
Security: Core stays locked down, surface area minimized.
Flexibility: Teams can adapt UX, improve efficiency, or patch integration bugs without touching core.
Clarity: Easier to communicate security guarantees: "The pool logic is immutable. Routers can change."
Upgrade Strategy Without Proxies
Even if core is immutable, periphery contracts can evolve. Typical approaches include:
Versioned Routers: Deploy new routers with improved logic; users migrate voluntarily.
Registries: Maintain a registry contract pointing to the latest periphery; frontends can read from it.
Migrations: Encourage/force users to migrate state from old periphery to new periphery.
Security Model
Core: must be fully audited, formally verified if possible, and designed for immutability.
Periphery: can be updated, but still requires review since it can influence user behavior.
Interaction: Core contracts should never trust periphery; only enforce strict invariants.
Common Patterns
Router → Pool Calls: Router bundles user actions and calls into pools.
Callback Interfaces: Core pools define strict callback requirements, which routers must satisfy.
Position Managers: Higher-level contracts for abstracting LP positions.
Fee Hooks / Incentives: Kept in periphery to avoid contaminating invariant logic.
Pitfalls
Too Much in Periphery: If periphery enforces invariants instead of core, security assumptions break.
Silent Upgrades: Replacing periphery without notice can create governance or trust issues.
Registry Risks: If the registry is compromised, users may be routed to malicious periphery contracts.
Example: Minimal Core/Periphery Split
// Core: immutable pool
contract Pool {
uint112 reserve0;
uint112 reserve1;
function swap(uint amount0Out, uint amount1Out) external {
// Invariant: x * y >= k
require(amount0Out == 0 || amount1Out == 0, "Only one side out");
// ...
}
}
// Periphery: router that interacts with core
contract Router {
function swapExactTokensForTokens(...) external {
// Handle approvals, slippage checks, path routing
Pool(pool).swap(...);
}
}
Last updated