Деплойментт смарт-контрактів в Ethereum mainnet
Деплойментт в mainnet — це не повторення команди з тестнету з іншим RPC. Це крапка неповернення: контракт без upgradeable паттерна змінити неможливо, помилки в логіці коштують реальних грошей, а неправильно встановлені параметри gas можуть привести до залипання транзакції в момент пікового навантаження мережі.
Передеплойментний чеклист
Перш ніж витрачати ETH на деплойментт, проходимо обов'язкові кроки:
Аудит та тестування:
- Покриття тестами ≥ 95% по statement coverage (Hardhat Coverage або Foundry
forge coverage) - Прогон Slither — статичний аналізатор знаходить reentrancy, integer overflow, невикористані return values
- Перевірка Mythril або Aderyn для глибокого symbolic execution
- Для контрактів з TVL > $100k — обов'язковий зовнішній аудит
Верифікація компілятора:
- Фіксована версія Solidity без
^(не^0.8.20, а=0.8.20) - Optimizer runs — стандарт 200 для балансу газу деплойменту і газу викликів; для контрактів з дуже частими викликами — 1000+
- Перевірка bytecode детермінізму: компіляція двічі має давати ідентичний bytecode
Процес деплойменту через Hardhat
// hardhat.config.ts
const config: HardhatUserConfig = {
networks: {
mainnet: {
url: process.env.MAINNET_RPC_URL!, // Infura/Alchemy/Quicknode
accounts: [process.env.DEPLOYER_PRIVATE_KEY!],
gasPrice: 'auto',
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY!,
},
};
// deploy script
async function main() {
const [deployer] = await ethers.getSigners();
console.log('Deployer balance:', ethers.formatEther(
await deployer.provider.getBalance(deployer.address)
));
const Contract = await ethers.getContractFactory('MyContract');
const contract = await Contract.deploy(/* constructor args */);
await contract.waitForDeployment();
const address = await contract.getAddress();
console.log('Deployed to:', address);
// Верифікація на Etherscan
await run('verify:verify', {
address,
constructorArguments: [/* args */],
});
}
Gas та пріоритетні комісії (EIP-1559)
Після London hard fork транзакції використовують maxFeePerGas і maxPriorityFeePerGas замість єдиного gasPrice. Для деплойменту використовуємо динамічну оцінку:
const feeData = await provider.getFeeData();
// maxFeePerGas: baseFee * 2 + maxPriorityFeePerGas (2x буфер на зростання baseFee)
// maxPriorityFeePerGas: 1-3 Gwei для звичайного деплойменту
Для термінового деплойменту в момент перевантаження мережі — збільшуємо maxPriorityFeePerGas до 5–10 Gwei. Моніторинг поточного стану мережі: eth_gasPrice через Blocknative або ethgasstation API.
Управління приватним ключем деплойєра
Ніколи не деплоїмо з ключем, який використовується для інших операцій. Схема:
- Окремий гаманець тільки для деплойменту
- Поповнення з multisig (Safe) точною сумою для деплойменту + невеликий буфер
- Після деплойменту — передача ownership на multisig:
contract.transferOwnership(safeAddress) - Приватний ключ деплойєра — в secrets manager (AWS Secrets Manager, HashiCorp Vault), не в
.env
Після деплойменту
- Верифікація вихідного коду на Etherscan (через
hardhat-etherscanабоhardhat-verify) - Запис адреси контракту в deployment manifest з chainId, blockNumber, txHash
- Перевірка всіх view-функцій через Etherscan Read Contract
- Тестовий виклик кожної write-функції з мінімальними параметрами
- Налаштування моніторингу (Tenderly Alerts або OpenZeppelin Defender) на критичні події
Апгрейдуємі контракти
Якщо потрібна можливість оновлення логіки — деплойментт через proxy паттерн (UUPS або Transparent Proxy з OpenZeppelin). Це додає складність і gas overhead, але дає можливість виправити баги після деплойменту. UUPS краще за Transparent Proxy — логіка апгрейду в implementation контракті, що дешевше на ~30% gas при кожному виклику через proxy.







