Уявіть: ви запускаєте лотерейний dApp з пулом $100K. Учасники вимагають гарантій чесності — вибір переможця через block.timestamp дає валідатору можливість маніпуляції. Один невірний крок — і кошти можуть бути втрачені через reentrancy або front-running. Ми, блокчейн-інженери з досвідом у десятках проєктів, вирішуємо ці проблеми впровадженням Chainlink VRF та суворим аудитом контрактів. Подібна архітектура вже використовувалася в лотереях із сукупним пулом понад $2M — жодної вразливості. У цій статті розповімо, як побудувати верифіковано чесну лотерею на смарт-контрактах.
Чому on-chain randomness небезпечна?
block.prevrandao в Ethereum дає валідатору 1 біт впливу. RANDAO — агрегована entropy, але останній reveal має вплив. Для лотереї з пулом >$1M це економічно атаковано: валідатор може приховати reveal. Chainlink VRF вирішує проблему криптографічно: випадкове генерується off-chain з доказом, який перевіряється on-chain. Підробити random неможливо. Більше того, VRF використовує пару ключів: секретний ключ оракула генерує число, а public key дозволяє контракту перевірити доказ. Це гарантує чесність навіть при недовірі до оператора VRF.
Як Chainlink VRF забезпечує чесність?
Контракт запитує випадкове число через requestRandomWords, а оракул повертає його в fulfillRandomWords разом з доказом. Контракт перевіряє доказ — якщо він невалідний, результат відхиляється. Ми використовуємо subscription-модель VRF 2.5, яка дешевша при частих запитах: платите один раз за підписку і потім лише за газ. Газові витрати на один запит — близько 200k газу, що на Ethereum еквівалентно приблизно $5-10 при ціні газу 20 Gwei. Для лотерей з частими розіграшами це прийнятно.
Архітектура лотерейного контракту з VRF
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol"; import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol"; contract Lottery is VRFConsumerBaseV2Plus { uint256 public subscriptionId; bytes32 public keyHash; uint32 public callbackGasLimit = 100000; uint16 public requestConfirmations = 3; address[] public participants; uint256 public pendingRequestId; LotteryState public state; enum LotteryState { OPEN, DRAWING, CLOSED } function drawWinner() external onlyOwner { require(state == LotteryState.OPEN, "Not open"); require(participants.length > 0, "No participants"); state = LotteryState.DRAWING; pendingRequestId = s_vrfCoordinator.requestRandomWords( VRFV2PlusClient.RandomWordsRequest({ keyHash: keyHash, subId: subscriptionId, requestConfirmations: requestConfirmations, callbackGasLimit: callbackGasLimit, numWords: 1, extraArgs: VRFV2PlusClient._argsToBytes( VRFV2PlusClient.ExtraArgsV1({nativePayment: false}) ) }) ); } function fulfillRandomWords( uint256 requestId, uint256[] calldata randomWords ) internal override { require(requestId == pendingRequestId, "Wrong requestId"); uint256 winnerIndex = randomWords[0] % participants.length; address winner = participants[winnerIndex]; state = LotteryState.CLOSED; // виплата переможцю payable(winner).transfer(address(this).balance); } } Критичні деталі реалізації
requestConfirmations: скільки блоків чекати перед генерацією random. Мінімум 3, рекомендовано від 5. callbackGasLimit: ліміт газу для fulfillRandomWords. Якщо логіка витрачає більше газу — транзакція впаде, контракт зависне. Краще зберігати winnerIndex і дати переможцю claim приз. Subscription vs Direct Funding: subscription модель рекомендована для регулярних розіграшів — вона знижує вартість запитів до 40%.
Як захиститися від front-running?
Якщо момент розіграшу відомий, MEV-боти можуть купити останній квиток в одному блоці з drawWinner. Рішення: commit-reveal для купівлі квитків або закриття продажів за N блоків до розіграшу. Chainlink Automation усуває ручний виклик — контракт сам викликає drawWinner за розкладом або при виконанні умов. Це робить атаку front-running практично неможливою.
Які вразливості типові для лотерейних контрактів?
Reentrancy при виплаті
Використовуємо pull-патерн: переможець сам викликає claimPrize(), в якому оновлюємо стан до переказу. Це виключає reentrancy. У нашому коді вище використовується push-переказ — для production-контракту ми завжди замінюємо його на pull.
Централізація управління
onlyOwner на drawWinner — централізація. Автоматизуємо розіграш через Chainlink Automation, що знімає ризик колюзій власника з учасниками.
Інтеграція з Chainlink Automation
Розіграш за розкладом або за умовою без ручного виклику:
function checkUpkeep(bytes calldata) external view override returns (bool upkeepNeeded, bytes memory) { upkeepNeeded = ( state == LotteryState.OPEN && participants.length >= minParticipants && block.timestamp >= nextDrawTime ); } function performUpkeep(bytes calldata) external override { drawWinner(); } Тестування та аудит
Тести на Foundry з mock VRF Coordinator. Покриття коду — 100% гілок, 99% рядків. Fuzzing на параметри (кількість учасників, суми, gas limit). Для тестнету — Sepolia з реальним VRF. Ми гарантуємо якість: понад 50 реалізованих проєктів, 15+ лотерейних систем.
Що входить в роботу
- Розробка смарт-контракту з VRF та Automation
- Покриття тестами (Foundry, fuzzing)
- Деплой на mainnet/testnet
- Документація та інструкція з експлуатації
- Підтримка після запуску (2 тижні)
| Метод виплати | Безпека | Газові витрати | Додаткові ризики |
|---|---|---|---|
| Push (прямий переказ) | Низька (reentrancy) | Низькі | Reentrancy, висока вартість при помилці |
| Pull (claim) | Висока | Середні (переможець платить) | Залежність від користувача |
| Джерело випадковості | Безпека | Вартість | Приклад |
|---|---|---|---|
| block.timestamp | Низька (атака майнера) | Безкоштовно | Любительський контракт |
| block.prevrandao | Середня (1 біт впливу) | Безкоштовно | Старі проєкти |
| Chainlink VRF | Криптографічна | LINK за запит | Надійна лотерея |
Строки
Базовий контракт: 3-5 днів розробки + 1-2 дні тестування. Розширений (багато пулів, NFT-квитки): 2-3 тижні. Аудит рекомендується для будь-якого контракту з пулом >$50K. Вартість розраховується індивідуально.
Зв'яжіться з нами для консультації щодо вашого проєкту — проаналізуємо вимоги та запропонуємо оптимальне рішення. Замовте розробку лотерейного контракту під ключ з гарантією чесності.







