Представьте: игрок ставит 0.1 ETH на красное в блокчейн-рулетке на Polygon. Без верифицируемого рандома он не может быть уверен, что число не было предопределено майнером или оператором. Chainlink VRF (Verifiable Random Function) — это децентрализованный генератор случайных чисел, который предоставляет криптографическое доказательство корректности каждого результата. На практике мы выполнили более 15 интеграций VRF в проекты на Ethereum, Polygon и BNB Chain для DeFi-казино, слотов и лотерей. Каждый раз убеждались: правильная настройка параметров — subscriptionId, keyHash, callbackGasLimit — критична для надёжности и защиты от задержек.
Хотите внедрить честный рандом в своё казино? Свяжитесь с нами — мы проанализируем проект и предложим оптимальное решение.
Основные проблемы при интеграции VRF — выбор режима оплаты, установка requestConfirmations, расчёт callbackGasLimit. Ошибка в любом из этих параметров приводит к потере запроса или перерасходу газа. Далее мы разберём каждую настройку на примере реального кода.
Почему Chainlink VRF — стандарт для честного рандома?
Процесс генерации состоит из трёх шагов:
- Контракт игры вызывает
requestRandomWordsна Coordinator Chainlink. - Chainlink собирает seed из блоков и подписывает его своим ключом, формируя доказательство.
- Coordinator вызывает
fulfillRandomWordsс результатом, контракт проверяет доказательство и вычисляет итоговое число.
VRF генерирует случайное число с криптографическим доказательством его корректности. On-chain верификация доказательства происходит до того, как число используется в логике игры. Манипуляция исключена — ни оператор казино, ни игроки не могут повлиять на результат. Согласно Chainlink VRF v2.5 Documentation, каждый запрос содержит доказательство, которое верифицируется on-chain.
VRF v2.5: подписка vs Direct Funding
Актуальная версия — VRF v2.5. Два режима оплаты: Subscription model и Direct Funding.
| Параметр | Subscription | Direct Funding (VRFV2PlusWrapper) |
|---|---|---|
| Управление | Пополнение баланса в подписке | Автоматически из контракта |
| Гибкость | Несколько контрактов на одном балансе | Каждый запрос оплачивается отдельно |
| Стоимость | Ниже при частых запросах | Чуть дороже за запрос |
| Идеально для | Рулетка, слоты, карточные игры | NFT mint, разовые турниры |
Для high-load казино выбирайте subscription. Для проектов с переменной нагрузкой — Direct Funding проще операционно. При правильной настройке subscription экономия газа может достигать 25%.
Интеграция в контракт казино
// 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 RouletteGame is VRFConsumerBaseV2Plus {
uint256 public immutable subscriptionId;
bytes32 public immutable keyHash;
struct Bet {
address player;
uint256 amount;
uint8 betType; // 0=red, 1=black, 2=number
uint8 number;
}
mapping(uint256 requestId => Bet) public pendingBets;
event BetPlaced(uint256 indexed requestId, address indexed player);
event GameResult(uint256 indexed requestId, uint8 result, bool won);
function placeBet(uint8 betType, uint8 number) external payable {
require(msg.value >= MIN_BET, "Below minimum");
require(msg.value <= MAX_BET, "Above maximum");
uint256 requestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: keyHash,
subId: subscriptionId,
requestConfirmations: 3,
callbackGasLimit: 150_000,
numWords: 1,
extraArgs: VRFV2PlusClient._argsToBytes(
VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
)
})
);
pendingBets[requestId] = Bet({
player: msg.sender,
amount: msg.value,
betType: betType,
number: number
});
emit BetPlaced(requestId, msg.sender);
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords)
internal override {
Bet memory bet = pendingBets[requestId];
delete pendingBets[requestId];
uint8 result = uint8(randomWords[0] % 37); // 0-36
bool won = _checkWin(bet, result);
if (won) {
uint256 payout = _calculatePayout(bet);
payable(bet.player).transfer(payout);
}
emit GameResult(requestId, result, won);
}
}
Критичные детали конфигурации
- callbackGasLimit должен быть с запасом. Если газа не хватает в fulfillRandomWords, Chainlink не повторит вызов автоматически — запрос потерян. Считайте реальный газ через forge test --gas-report. Для рулетки с выплатой 150K газа достаточно, для сложной логики — увеличивайте.
- requestConfirmations: 3 — минимум. На Ethereum при реорге в 2 блока Chainlink может получить другой seed. Для jackpot ставок ставьте 5–10 подтверждений.
- Храните ставки в mapping requestId => Bet. Массив ставок с поиском — O(n) в коллбэке, что ведёт к gas griefing.
Как избежать потери запросов VRF?
Chainlink предоставляет несколько keyHash для одной сети — они отличаются максимальным gas price, который Chainlink готов потратить на доставку random. На Ethereum mainnet:
| Lane | Max gas price | Задержка | Цена |
|---|---|---|---|
| 200 gwei | 200 gwei | Возможна задержка при пиках | Дешевле |
| 500 gwei | 500 gwei | Минимальная | Дороже |
Для казино с мгновенными играми берите 500 gwei lane — игрок не должен ждать часами при перегрузке сети. Стоимость каждого запроса VRF при 500 gwei составляет около 0.00015 LINK.
Защита от злоупотреблений и timeout
Ставка размещена — отмена невозможна. Это правильно, так как random уже запрошен. Однако если random не пришёл в течение 24 часов (проблемы с Coordinator или баланс подписки исчерпан), необходима функция возврата:
function refundExpiredBet(uint256 requestId) external {
Bet memory bet = pendingBets[requestId];
require(bet.player == msg.sender, "Not your bet");
require(block.timestamp > betTimestamps[requestId] + 24 hours, "Not expired");
delete pendingBets[requestId];
payable(msg.sender).transfer(bet.amount);
}
Тестирование с Foundry
Chainlink предоставляет VRFCoordinatorV2_5Mock для локальных тестов. Вручную вызывайте fulfillRandomWords с заданным random:
vrfCoordinator.fulfillRandomWords(requestId, address(game));
Fuzz-тест проверяет выплаты для всех значений randomWords[0] от 0 до 2^256-1. Граничный случай: randomWords[0] % 37 == 0 — зеро на рулетке. Ваш контракт должен корректно обрабатывать этот edge case. Мы также используем forge fuzz с более чем 10 000 итераций для выявления скрытых багов.
Что входит в работу по интеграции VRF
- Аудит текущего контракта и анализ игровой механики.
- Проектирование архитектуры: выбор Subscription или Direct Funding.
- Реализация интеграции с VRF v2.5 (Solidity, Foundry).
- Разработка защит: timeout refund, requestConfirmations.
- Тестирование на Sepolia с реальным VRF, включая fuzz-тесты.
- Документация кода и инструкция по деплою.
- Передача доступа к репозиторию и поддержка в течение месяца.
Процесс работы
- Аналитика — обсуждаем механику игры, частоту запросов, требования к честности.
- Проектирование — выбираем режим VRF, keyHash, requestConfirmations.
- Реализация — пишем контракт с интеграцией, подключаем тесты.
- Тестирование — на тестовой сети (Sepolia) проверяем работу с реальным VRF.
- Деплой — развёртываем в mainnet с настройкой подписки или wrapper.
Сроки
- Базовая интеграция VRF в существующий контракт: 1–2 дня.
- Новый контракт казино с VRF, выплатами и защитами: 2–3 дня.
- Тестирование на Sepolia с реальным VRF — включено.
Стоимость рассчитывается после уточнения типа игры и механики ставок. Для оценки напишите нам — мы проанализируем проект и предложим оптимальное решение.
Типичные ошибки при интеграции VRF
- Недостаточный callbackGasLimit — запрос не выполняется.
- Слишком малое requestConfirmations — уязвимость к реоргу.
- Отсутствие функции timeout — средства зависают при сбое Coordinator.
- Использование массива ставок вместо mapping — перерасход газа.
Внедрите верифицируемый рандом в ваше казино — свяжитесь с нами для консультации. Закажите разработку смарт-контракта с VRF под ключ.







