// SPDX-License-Identifier: MIT LICENSE pragma solidity ^0.8.7; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/math/SafeMath.sol"; import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; import "./PriceConverter.sol"; import "./RustToken.sol"; contract RustLotto is ReentrancyGuard, Ownable, VRFConsumerBaseV2 { using SafeERC20 for IERC20; using SafeMath for uint256; using PriceConverter for uint256; VRFCoordinatorV2Interface COORDINATOR; uint64 private s_subscriptionId; address private vrfCoordinator = 0x6D80646bEAdd07cE68cab36c27c626790bBcf17f; bytes32 private keyHash = 0x83d1b6e3388bed3d76426974512bb0d270e9542a765cd667242ea26c0cc0b730; uint16 private requestConfirmations = 3; uint32 private numWords = 6; uint32 private callbackGasLimit = 2500000; address private s_owner; struct RequestStatus { bool fulfilled; bool exists; uint[] randomWords; } mapping(uint256 => RequestStatus) public s_requests; uint256 public lastRequestId; /** Lottery Settings */ IERC20 rustToken; uint256 public currentLotteryId; uint256 public currentTicketId; uint256 public serviceFee = 3000; // BASIS POINTS 3000 is 30% uint256 public numberWinner; uint256 public constant TICKET_USD = 50 * 10 ** 18; enum Status { Open, // the Lotto draw is open for ticket sales Close, // the Lotto draw is closed Claimable // the Lotto draw is open for ticket sales } struct Lottery { Status status; uint256 startTime; uint256 endTime; uint256 firstTicketId; uint256 transferJackpot; uint256 lastTicketId; uint[6] winningNumbers; uint256 totalPayout; uint256 commision; uint256 winnerCount; } struct Ticket { uint256 ticketId; address owner; uint[6] chooseNumbers; } mapping(uint256 => Lottery) private _lotteries; mapping(uint256 => Ticket) private _tickets; mapping(uint256 => mapping(uint32 => uint256)) private _numberTicketsPerLotteryId; mapping(address => mapping(uint256 => uint256[])) private _userTicketIdsPerLotteryId; mapping(address => mapping(uint256 => uint256)) public _winnersPerLotteryId; event LotteryWinnerNumber(uint256 indexed lotteryId, uint[6] finalNumber); event LotteryClose(uint256 indexed lotteryId, uint256 lastTicketId); event LotteryOpen( uint256 indexed lotteryId, uint256 startTime, uint256 endTime, uint256 ticketPrice, uint256 firstTicketId, uint256 transferJackpot, uint256 lastTicketId, uint256 totalPayout ); event TicketsPurchase( address indexed buyer, uint256 indexed lotteryId, uint[6] chooseNumbers ); constructor( uint64 subscriptionId, IERC20 _rustToken ) VRFConsumerBaseV2(vrfCoordinator) { COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator); s_owner = msg.sender; s_subscriptionId = subscriptionId; rustToken = _rustToken; } /** ############################################################################################## */ function openLottery() external onlyOwner nonReentrant { currentLotteryId++; currentTicketId++; uint256 transferJackpot; uint256 totalPayout; uint256 lastTicketId; uint256 endTime; _lotteries[currentLotteryId] = Lottery({ status: Status.Open, startTime: block.timestamp, endTime: 100, firstTicketId: currentTicketId, transferJackpot: _lotteries[currentLotteryId].transferJackpot, winningNumbers: [ uint(0), uint(0), uint(0), uint(0), uint(0), uint(0) ], lastTicketId: currentTicketId, totalPayout: 0, commision: 0, winnerCount: 0 }); emit LotteryOpen( currentLotteryId, block.timestamp, endTime, TICKET_USD, currentTicketId, transferJackpot, lastTicketId, totalPayout ); } /** ############################################################################################## */ function buyTickets(uint[6] calldata numbers) public payable nonReentrant { require(msg.value.getConversionRate() >= TICKET_USD, "You need to spend more ETH!"); /** Calculate Commision Fee */ uint256 commisionFee = (TICKET_USD.mul(serviceFee)).div(10000); _lotteries[currentLotteryId].commision += commisionFee; uint256 rustEarn = TICKET_USD - commisionFee; _lotteries[currentLotteryId].transferJackpot += rustEarn; /** Lets store each ticket number array referenced to the buyer's wallet. mapping(address => mapping(uint256 => uint256[])) private _userTicketIdsPerLotteryId; */ _userTicketIdsPerLotteryId[msg.sender][currentLotteryId].push( currentTicketId ); // _tickets[currentTicketId] = Ticket({ ticketId: currentTicketId, owner: msg.sender, chooseNumbers: numbers }); currentTicketId++; _lotteries[currentLotteryId].lastTicketId = currentTicketId; emit TicketsPurchase(msg.sender, currentLotteryId, numbers); } /** ############################################################################################## */ function closeLottery() external onlyOwner { require( _lotteries[currentLotteryId].status == Status.Open, "Lottery not open" ); require( block.timestamp > _lotteries[currentLotteryId].endTime, "Lottery not over" ); _lotteries[currentLotteryId].lastTicketId = currentTicketId; _lotteries[currentLotteryId].status = Status.Close; /** Request Id Stores the ChainLink VRF request Id, this is fetched once we execute the drawNumbers() and from there we will obtain a random number that we can use to obtain the winning numbers. */ uint256 requestId; /** Here we call ChainLink VRFv2 and obtain the winning numbers from the randomness generator. */ requestId = COORDINATOR.requestRandomWords( keyHash, s_subscriptionId, requestConfirmations, callbackGasLimit, numWords ); s_requests[requestId] = RequestStatus({ randomWords: new uint256[](0), exists: true, fulfilled: false }); lastRequestId = requestId; emit LotteryClose(currentLotteryId, currentTicketId); } /** ############################################################################################## */ function drawNumbers() external onlyOwner nonReentrant { require( _lotteries[currentLotteryId].status == Status.Close, "Lottery not close" ); uint256[] memory numArray = s_requests[lastRequestId].randomWords; uint num1 = numArray[0] % 10; uint num2 = numArray[1] % 10; uint num3 = numArray[2] % 10; uint num4 = numArray[3] % 10; uint num5 = numArray[4] % 10; uint num6 = numArray[5] % 10; uint[6] memory finalNumbers = [num1, num2, num3, num4, num5, num6]; for (uint i = 0; i < finalNumbers.length; i++) { if (finalNumbers[i] == 0) { finalNumbers[i] = 1; } } _lotteries[currentLotteryId].winningNumbers = finalNumbers; _lotteries[currentLotteryId].totalPayout = _lotteries[currentLotteryId] .transferJackpot; } function sortArrays( uint[6] memory numbers ) internal pure returns (uint[6] memory) { bool swapped; for (uint i = 1; i < numbers.length; i++) { swapped = false; for (uint j = 0; j < numbers.length - i; j++) { uint next = numbers[j + 1]; uint actual = numbers[j]; if (next < actual) { numbers[j] = next; numbers[j + 1] = actual; swapped = true; } } if (!swapped) { return numbers; } } return numbers; } function countWinners() external onlyOwner { require( _lotteries[currentLotteryId].status == Status.Close, "Lottery not close" ); require( _lotteries[currentLotteryId].status != Status.Claimable, "Lottery Already Counted" ); delete numberWinner; uint256 firstTicketId = _lotteries[currentLotteryId].firstTicketId; uint256 lastTicketId = _lotteries[currentLotteryId].lastTicketId; uint[6] memory winOrder; winOrder = sortArrays(_lotteries[currentLotteryId].winningNumbers); bytes32 encodeWin = keccak256(abi.encodePacked(winOrder)); uint256 i = firstTicketId; for (i; i < lastTicketId; i++) { address buyer = _tickets[i].owner; uint[6] memory userNum = _tickets[i].chooseNumbers; bytes32 encodeUser = keccak256(abi.encodePacked(userNum)); if (encodeUser == encodeWin) { numberWinner++; _lotteries[currentLotteryId].winnerCount = numberWinner; _winnersPerLotteryId[buyer][currentLotteryId] = 1; } } if (numberWinner == 0) { uint256 nextLottoId = (currentLotteryId).add(1); _lotteries[nextLottoId].transferJackpot = _lotteries[ currentLotteryId ].totalPayout; } _lotteries[currentLotteryId].status = Status.Claimable; } function claimPrize(uint256 _lottoId) external nonReentrant { require(_lotteries[_lottoId].status == Status.Claimable, "Not Payable"); require(_lotteries[_lottoId].winnerCount > 0, "Not Payable"); require(_winnersPerLotteryId[msg.sender][_lottoId] == 1, "Not Payable"); uint256 winners = _lotteries[_lottoId].winnerCount; uint256 payout = (_lotteries[_lottoId].totalPayout).div(winners); (bool callSuccess,) = payable(msg.sender).call{value: payout}(""); require(callSuccess, "Withdraw failed"); _winnersPerLotteryId[msg.sender][_lottoId] = 0; } /** ############################################################################################## */ function getRequestStatus() external view returns (bool fulfilled, uint256[] memory randomWords) { require(s_requests[lastRequestId].exists, "request not found"); RequestStatus memory request = s_requests[lastRequestId]; return (request.fulfilled, request.randomWords); } function fulfillRandomWords( uint256 _requestId, uint256[] memory _randomWords ) internal override { require(s_requests[_requestId].exists, "request not found"); s_requests[_requestId].fulfilled = true; s_requests[_requestId].randomWords = _randomWords; } /** Lottery additional functions. */ function viewTickets( uint256 ticketId ) external view returns (address, uint[6] memory) { address buyer; buyer = _tickets[ticketId].owner; uint[6] memory numbers; numbers = _tickets[ticketId].chooseNumbers; return (buyer, numbers); } function viewLottery( uint256 _lotteryId ) external view returns (Lottery memory) { return _lotteries[_lotteryId]; } function getBalance() external view onlyOwner returns (uint256) { return address(this).balance; } function fundContract(uint256 amount) external payable onlyOwner { require (msg.value <= amount, "You need to spend more ETH!"); } function withdrawETH() public onlyOwner { (bool callSuccess,) = payable(msg.sender).call{value: address(this).balance }(""); require(callSuccess, "Withdraw failed"); } function withdrawRust() public onlyOwner { rustToken.safeTransfer( address(msg.sender), (rustToken.balanceOf(address(this))) ); } }
0.4.18