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 para a instalação

Informações da origem

Repositório
ccashwell/evm-cortex
Última atividade na origem
10 de abril de 2026 às 16:31
Idioma detectado do SKILL.md
inglês
Estrelas
131
Forks
18

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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 no GitHub