Деплой смарт-контрактов в 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 determinism: компиляция дважды должна давать идентичный 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 и приоритетные fees (EIP-1559)
После London hard fork транзакции используют maxFeePerGas и maxPriorityFeePerGas вместо единого gasPrice. Для деплоя используем динамическую оценку:
const feeData = await provider.getFeeData();
// maxFeePerGas: baseFee * 2 + maxPriorityFeePerGas (2x buffer на рост 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) на критичные события
Upgradeable контракты
Если нужна возможность обновления логики — деплой через proxy паттерн (UUPS или Transparent Proxy из OpenZeppelin). Это добавляет сложность и gas overhead, но даёт возможность исправить баги после деплоя. UUPS предпочтительнее Transparent Proxy — логика апгрейда в implementation контракте, что дешевле на ~30% gas при каждом вызове через proxy.







