Разработка Dice-контракта с VRF: честное крипто-казино без манипуляций
Представьте: вы запускаете Dice-игру на Base. Через 10 минут первый игрок жалуется, что результат не совпадает с расчётом. Ошибка — в переполнении uint256 при расчёте множителя. Решение — использовать SafeMath и проверять границы. Такие баги встречаются у 30% проектов до аудита. Мы разрабатываем смарт-контракты Dice более 5 лет и устранили эти риски на десятках продакшен-проектов. Наш опыт включает интеграцию с Chainlink VRF, gas-оптимизацию для L2 и формальную верификацию контрактов.
Принцип работы VRF в Dice
Без верифицируемой случайности игроки не могут проверить, не жульничает ли оператор. VRF (Verifiable Random Function) генерирует число, которое можно проверить на цепочке. Мы используем Chainlink VRF — отраслевой стандарт, корректность которого гарантируется децентрализованной сетью оракулов. Как подтверждает отраслевой стандарт: Chainlink VRF — проверенная децентрализованная технология случайности. Подробнее о VRF можно прочитать в Wikipedia.
Математика и реализация контракта
Стандартный диапазон: 1-100 (или 0.00-99.99 в дробном варианте). При ставке roll over 50: шанс выиграть = 50%, справедливый множитель = 2x. Реальный множитель с house edge 1% = 1.98x. Формула: multiplier = (100 - houseEdge) / winProbability. При roll over 75: winProbability = 25%, multiplier = 99/25 = 3.96x. При roll under 10: winProbability = 9% (числа 1-9), multiplier = 99/9 = 11x. Диапазон допустимых ставок: обычно roll over 2-97 и roll under 3-98 (чтобы house edge оставался разумным).
Smart contract
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; contract BlockchainDice is VRFConsumerBaseV2Plus { uint256 public houseEdge = 100; // 1% в basis points (10000 = 100%) struct DiceBet { address player; uint256 amount; uint8 target; // 1-99 bool isOver; // roll over или roll under uint256 potentialPayout; bool settled; } mapping(uint256 => DiceBet) public bets; event BetPlaced(uint256 indexed requestId, address player, uint256 amount, uint8 target, bool isOver, uint256 payout); event BetResult(uint256 indexed requestId, uint8 roll, bool win, uint256 payout); function roll(uint8 target, bool isOver) external payable returns (uint256 requestId) { require(msg.value >= MIN_BET && msg.value <= getMaxBet(), "Invalid bet"); require(target >= 2 && target <= 98, "Invalid target"); uint256 payout = calculatePayout(msg.value, target, isOver); require(address(this).balance >= payout, "Insufficient bankroll"); requestId = _requestVRF(); bets[requestId] = DiceBet({ player: msg.sender, amount: msg.value, target: target, isOver: isOver, potentialPayout: payout, settled: false, }); emit BetPlaced(requestId, msg.sender, msg.value, target, isOver, payout); } function calculatePayout( uint256 betAmount, uint8 target, bool isOver ) public view returns (uint256) { uint256 winProbability; if (isOver) { winProbability = 100 - uint256(target); // числа от target+1 до 100 } else { winProbability = uint256(target) - 1; // числа от 1 до target-1 } require(winProbability > 0 && winProbability < 100, "Invalid probability"); // multiplier = (10000 - houseEdge) / winProbability / 100 uint256 multiplier = ((10000 - houseEdge) * 100) / winProbability; return (betAmount * multiplier) / 10000; } function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override { DiceBet storage bet = bets[requestId]; require(!bet.settled, "Already settled"); bet.settled = true; // Генерируем число 1-100 uint8 roll = uint8((randomWords[0] % 100) + 1); bool win = bet.isOver ? roll > bet.target : roll < bet.target; if (win) { payable(bet.player).transfer(bet.potentialPayout); } emit BetResult(requestId, roll, win, win ? bet.potentialPayout : 0); } // Функция верификации: воспроизвести результат из requestId function verifyResult(uint256 requestId, uint256 vrfOutput) external view returns (uint8 roll, bool win) { DiceBet storage bet = bets[requestId]; roll = uint8((vrfOutput % 100) + 1); win = bet.isOver ? roll > bet.target : roll < bet.target; } function getMaxBet() public view returns (uint256) { // Максимальная ставка = bankroll / 100 (не рискуем более 1% банкролла) return address(this).balance / 100; } } High-Low вариант (расширенная механика)
Популярный вариант: игрок выбирает диапазон (например, от 25 до 75), и выигрывает если roll попадает в диапазон. Более интуитивный UI.
function rollRange(uint8 lowerBound, uint8 upperBound) external payable { require(upperBound > lowerBound, "Invalid range"); require(lowerBound >= 1 && upperBound <= 100); uint8 winRange = upperBound - lowerBound + 1; // включительно // Минимальный выигрышный диапазон = 3 (иначе house edge > 30%) require(winRange >= 3 && winRange <= 97); uint256 payout = ((10000 - houseEdge) * msg.value * 100) / (uint256(winRange) * 10000); // ... запрос VRF } Почему VRF, а не blockhash?
Многие проекты используют blockhash или timestamp как источник случайности. Это опасно: майнеры могут подобрать блок, чтобы повлиять на результат. VRF от Chainlink исключает такую возможность, так как запрос обрабатывается децентрализованными оракулами, и результат криптографически доказуем. Даже оператор контракта не может повлиять на значение.
Безопасность и производительность
Наш опыт — более 5 лет в блокчейн-разработке, десятки реализованных проектов крипто-казино. Мы гарантируем прозрачность кода: каждый смарт-контракт проходит аудит и проверку на уязвимости (reentrancy, flash loan attacks). Используем формальную верификацию для критически важных функций.
| Характеристика | Ethereum L1 | Arbitrum/Polygon | Solana |
|---|---|---|---|
| VRF delay | 10-30 сек | 3-15 сек | <1 сек |
| Комиссии | ~0.1-1 ETH | ~0.001-0.01 USD | ~0.0001 USD |
| Сложность разработки | Средняя | Средняя | Высокая (Rust) |
Детали производительности
Для быстрого геймплея рекомендуем L2: задержка VRF не чувствуется, а комиссии позволяют делать ставки от $0.01.Типичные слабые места в Dice-контрактах
| Проблема | Последствие | Решение |
|---|---|---|
| Неограниченный house edge | Игроки быстро теряют интерес | Установить фиксированную математику с проверкой |
| Отсутствие проверки банкролла | Контракт может стать неплатёжеспособным | Ввести лимит ставки (1% резерва) |
| Использование blockhash как энтропии | Майнеры могут повлиять на результат | Только VRF от Chainlink |
| Неоптимизированный код | Высокий gas и отток игроков | Использовать foundry, профилировать gas |
Процесс разработки и аудита
- Анализ требований — определяем целевую сеть, house edge, минимальные ставки.
- Проектирование контракта — математика, интерфейс, security-модель.
- Реализация — пишем код с учётом gas optimization (используем мутации с помощью foundry).
- Тестирование — фаззинг Echidna, unit-тесты с Foundry. Пример: недавно мы оптимизировали контракт Dice для Polygon: сократили число вызовов VRF с 2 до 1, что снизило gas на 40%.
- Аудит — статический анализ Slither/Mythril, ручной ревью.
- Развёртывание — деплой на выбранную сеть, верификация в блокчейн-эксплорере.
- Поддержка — мониторинг событий, помощь в интеграции с фронтендом.
Настройка house edge
House edge — комиссия казино, встроенная в математику. Стандартное значение 1% (100 б.п.). Чем выше house edge, тем быстрее банкролл казино растёт, но тем ниже привлекательность для игроков. Оптимальный баланс — 0.5-2%.
Что входит в работу и сроки
- Смарт-контракт Dice с VRF, математикой house edge и верификацией
- High-Low вариант (опционально)
- Фронтенд на React/Next.js с поддержкой MetaMask, WalletConnect
- Автоматический режим с настраиваемыми стратегиями
- Аудит безопасности (Slither, Mythril, Echidna)
- Документация для интеграции
- Развертывание на выбранной сети (Ethereum, Polygon, Arbitrum, Solana)
- Техническая поддержка на этапе запуска
Ориентировочные сроки
- Базовый смарт-контракт с VRF: 2-3 недели
- Полный стек (контракт + UI + auto-play): 4-5 недель
Стоимость рассчитывается индивидуально после оценки ваших требований. Свяжитесь с нами для консультации — проанализируем ваш проект и предложим оптимальное решение. Закажите разработку под ключ и получите готовый продукт с гарантией качества.







