Уявіть: ви запускаєте лотерейний 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. Вартість розраховується індивідуально.
Зв'яжіться з нами для консультації щодо вашого проєкту — проаналізуємо вимоги та запропонуємо оптимальне рішення. Замовте розробку лотерейного контракту під ключ з гарантією чесності.







