ETHGent.com - Lucky Block Demo

We did a quick demo of walking through the ETHGent.com lucky block demo that uses IC randomness to create a simple draw-to-win game:

Here is what the Lucky block solidity code looks like:

pragma solidity ^0.8.28;

import "./LuckyToken.sol";
import "./ic/ICRandomLib.sol";

contract LuckyBlock {
    LuckyToken public immutable luckyToken;
    uint256 public immutable ticketPrice;
    uint16 public immutable winThresholdBps;
    uint256 public currentSpin;
    uint256 public pendingTicketId;
    address public pendingPlayer;
    uint256 public lastPayout;
    uint256 public lastRandomWord;
    address public lastWinner;
    bool public lastSpinWon;
    bool public drawPending;

    event TicketBought(uint256 indexed spin, address indexed player, uint256 amount, uint256 ticketId);
    event DrawSettled(uint256 indexed spin, address indexed player, bool won, uint256 payout, uint256 randomWord);
    event DrawRejected(uint256 indexed spin, address indexed player, uint256 refund, uint256 ticketId);

    error DrawAlreadyPending();
    error DrawUnavailable();
    error InvalidAmount();
    error InvalidChance();

    constructor(address token, uint256 fixedTicketPrice, uint16 fixedWinThresholdBps) {
        require(token != address(0), "LuckyBlock: zero token");
        if (fixedTicketPrice == 0) revert InvalidAmount();
        if (fixedWinThresholdBps == 0 || fixedWinThresholdBps > 10_000) revert InvalidChance();

        luckyToken = LuckyToken(token);
        ticketPrice = fixedTicketPrice;
        winThresholdBps = fixedWinThresholdBps;
    }

    function currentPot() external view returns (uint256) {
        return luckyToken.balanceOf(address(this));
    }

    function buyTicket(uint256 amount) external returns (uint256 ticketId) {
        if (drawPending) revert DrawAlreadyPending();
        if (amount != ticketPrice) revert InvalidAmount();

        bool ok = luckyToken.transferFrom(msg.sender, address(this), amount);
        require(ok, "LuckyBlock: transfer failed");

        ticketId = ICRandomLib.rawRand();
        currentSpin += 1;
        pendingTicketId = ticketId;
        pendingPlayer = msg.sender;
        drawPending = true;

        emit TicketBought(currentSpin, msg.sender, amount, ticketId);
    }

    function drawStatus() external view returns (uint256 ticketId, bool pending, bool rejected) {
        ticketId = pendingTicketId;
        if (!drawPending) {
            return (ticketId, false, false);
        }

        (, pending, rejected) = ICRandomLib.getResult(ticketId);
    }

    function finalizeDraw() external returns (address winner) {
        if (!drawPending) revert DrawUnavailable();

        uint256 ticketId = pendingTicketId;
        address player = pendingPlayer;
        (bytes memory result, bool pending, bool rejected) = ICRandomLib.getResult(ticketId);
        if (pending) revert DrawUnavailable();

        if (rejected) {
            drawPending = false;
            pendingTicketId = 0;
            pendingPlayer = address(0);

            bool refundOk = luckyToken.transfer(player, ticketPrice);
            require(refundOk, "LuckyBlock: refund failed");

            emit DrawRejected(currentSpin, player, ticketPrice, ticketId);
            return address(0);
        }

        uint256 randomWord = uint256(keccak256(result));
        uint256 payout = 0;
        bool won = randomWord % 10_000 < winThresholdBps;

        if (won) {
            payout = luckyToken.balanceOf(address(this));
            winner = player;

            bool ok = luckyToken.transfer(player, payout);
            require(ok, "LuckyBlock: payout failed");
        } else {
            winner = address(0);
        }

        drawPending = false;
        pendingTicketId = 0;
        pendingPlayer = address(0);
        lastWinner = winner;
        lastPayout = payout;
        lastRandomWord = randomWord;
        lastSpinWon = won;

        emit DrawSettled(currentSpin, player, won, payout, randomWord);
    }

    function winChancePercent() external view returns (uint256) {
        return uint256(winThresholdBps) * 100 / 10_000;
    }
}
2 Likes