Skip to main content

constructor-patterns

Constructor and initializer patterns for deploying Solidity contracts. Use when designing deployment flows, upgradeable proxies, factory patterns, or CREATE2 deployments. Covers constructor validation, initializer safety, and factory patterns.

Ir a la instalación

Datos de origen

Repositorio
ccashwell/evm-cortex
Última actividad en el origen
10 de abril de 2026 a las 16:31
Idioma detectado de SKILL.md
inglés
Estrellas
129
Forks
18

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
constructor-patterns
description
Constructor and initializer patterns for deploying Solidity contracts. Use when designing deployment flows, upgradeable proxies, factory patterns, or CREATE2 deployments. Covers constructor validation, initializer safety, and factory patterns.
# Constructor & Initializer Patterns ## Constructor for Non-Upgradeable Contracts Constructors run once at deployment. Validate all parameters and set immutables. ```solidity contract Vault { error ZeroAddress(); error InvalidFee(uint256 fee); address public immutable TOKEN; address public immutable ORACLE; uint256 public immutable FEE_BPS; uint256 public immutable DEPLOYMENT_BLOCK; constructor(address token, address oracle, uint256 feeBps) { if (token == address(0)) revert ZeroAddress(); if (oracle == address(0)) revert ZeroAddress(); if (feeBps > 10_000) revert InvalidFee(feeBps); TOKEN = token; ORACLE = oracle; FEE_BPS = feeBps; DEPLOYMENT_BLOCK = block.number; } } ``` ## Initializer for Upgradeable Contracts Proxies never call the implementation's constructor. Use `initializer` from OpenZeppelin. ```solidity import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; contract VaultV1 is Initializable, OwnableUpgradeable, PausableUpgradeable { uint256 public depositCap; address public feeRecipient; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize( address admin, address _feeRecipient, uint256 _depositCap ) external initializer { if (admin == address(0)) revert ZeroAddress(); if (_feeRecipient == address(0)) revert ZeroAddress(); __Ownable_init(admin); __Pausable_init(); feeRecipient = _feeRecipient; depositCap = _depositCap; } } ``` ### Critical: Disable Initializers in Implementation Without `_disableInitializers()`, an attacker can call `initialize()` on the implementation contract directly and potentially `selfdestruct` it (pre-Dencun) or manipulate its state. ```solidity /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } ``` ### Reinitializer for Upgrades When adding new state in V2, use `reinitializer(2)` instead of `initializer`. ```solidity contract VaultV2 is VaultV1 { uint256 public withdrawalDelay; function initializeV2(uint256 _withdrawalDelay) external reinitializer(2) { withdrawalDelay = _withdrawalDelay; } } ``` ## Immutables + Initializers Immutables can coexist with initializers. They're set in the implementation's constructor. ```solidity contract VaultImplementation is Initializable, OwnableUpgradeable { address public immutable WETH; address public immutable FACTORY; /// @custom:oz-upgrades-unsafe-allow constructor constructor(address weth, address factory) { if (weth == address(0)) revert ZeroAddress(); if (factory == address(0)) revert ZeroAddress(); WETH = weth; FACTORY = factory; _disableInitializers(); } function initialize(address admin) external initializer { __Ownable_init(admin); } } ``` ## Factory Patterns ### Minimal Clone Factory (EIP-1167) Cheapest deployment for contracts with identical logic but different state. ```solidity import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; contract VaultFactory { using Clones for address; address public immutable IMPLEMENTATION; event VaultCreated(address indexed vault, address indexed admin); constructor(address implementation) { IMPLEMENTATION = implementation; } function createVault(address admin, bytes32 salt) external returns (address vault) { vault = IMPLEMENTATION.cloneDeterministic(salt); IVault(vault).initialize(admin); emit VaultCreated(vault, admin); } function predictVaultAddress(bytes32 salt) external view returns (address) { return IMPLEMENTATION.predictDeterministicAddress(salt); } } ``` ### CREATE2 Factory Deterministic addresses — compute the address before deployment. ```solidity contract Create2Factory { event Deployed(address indexed addr, bytes32 indexed salt); function deploy(bytes memory bytecode, bytes32 salt) external returns (address addr) { assembly { addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt) if iszero(addr) { revert(0, 0) } } emit Deployed(addr, salt); } function computeAddress(bytes memory bytecode, bytes32 salt) external view returns (address) { return address(uint160(uint256(keccak256(abi.encodePacked( bytes1(0xff), address(this), salt, keccak256(bytecode) ))))); } } ``` ### CREATE2 + Constructor Args ```solidity function deployWithArgs(bytes32 salt, address token, uint256 fee) external returns (address vault) { bytes memory bytecode = abi.encodePacked( type(Vault).creationCode, abi.encode(token, fee) ); assembly { vault := create2(0, add(bytecode, 0x20), mload(bytecode), salt) if iszero(vault) { revert(0, 0) } } } ``` ## Constructor vs Initializer Decision Tree ``` Is the contract upgradeable? ├── No → Use constructor + immutables └── Yes → Use initializer + _disableInitializers() │ Are there deploy-time constants (WETH, factory)? ├── Yes → Set as immutables in constructor └── No → Everything in initializer ``` ## Anti-Patterns - **Missing `_disableInitializers()`** — allows initialization of implementation - **Constructor logic in upgradeable contract** — runs on implementation, not proxy - **Initializer without reentrancy protection** — OpenZeppelin's `initializer` handles this - **Forgetting `reinitializer(n)`** for V2+ upgrades — reusing `initializer` fails silently - **Setting storage in constructor of upgradeable** — sets implementation storage, not proxy ## Checklist - [ ] Non-upgradeable: constructor sets immutables, validates all parameters - [ ] Upgradeable: `_disableInitializers()` in constructor - [ ] Upgradeable: `initializer` modifier on `initialize()` - [ ] Upgradeable V2+: `reinitializer(n)` for upgrade initialization - [ ] All address parameters checked against `address(0)` - [ ] Numeric parameters validated against bounds - [ ] Factory: deterministic deployment uses salt from meaningful context - [ ] CREATE2: address pre-computation tested before mainnet deployment
Ver en GitHub