Skip to content

Commit

Permalink
some changes
Browse files Browse the repository at this point in the history
  • Loading branch information
pashandor789 committed Feb 2, 2025
1 parent a20c714 commit 97107bc
Show file tree
Hide file tree
Showing 7 changed files with 447 additions and 78 deletions.
327 changes: 327 additions & 0 deletions contracts/contracts/LotteryAgent.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "./RandomnessGenerator.sol";

contract LotteryAgent is Ownable {
// Struct to represent a lottery ticket

struct Ticket {
address player;
bool[] chosenNumbers;
bool claimed;
bool winner;
uint256 id; // unique idx in draw
}

// Struct to represent a lottery draw
struct Draw {
uint[] randomnessIDs;
bool[] winningNumbers; // the representation of bitset. example: uint [2, 3, 5] == bool [0, 0, 1, 1, 0, 1]
uint totalWinnings;
uint winnersCount;
uint ticketRevenue;
uint stackersPoolDistributionRatio;
Ticket[] tickets;
bool prepareFinalizeCalled;
}


function getDraw(uint256 idx) public view returns (Draw memory) {
require(idx <= drawHistory.length, "Draw index out of bounds");
if (idx == drawHistory.length) {
return curDraw;
} else {
return drawHistory[idx];
}
}

function getDrawShortInfo(uint256 idx) external view returns (uint256 totalWinnings, uint256[] memory winningNumbers, uint256 winnersCount, uint256 ticketCount) {
Draw memory draw = getDraw(idx);

uint256[] memory winningIndices = new uint256[](draw.winningNumbers.length);
uint256 count = 0;

for (uint256 i = 0; i < draw.winningNumbers.length; i++) {
if (draw.winningNumbers[i]) {
winningIndices[count] = i;
count++;
}
}

uint256[] memory result = new uint256[](count);
for (uint256 j = 0; j < count; j++) {
result[j] = winningIndices[j];
}

totalWinnings = draw.totalWinnings;
winningNumbers = result;
winnersCount = draw.winnersCount;

return (totalWinnings, winningNumbers, winnersCount, draw.tickets.length);
}

function depositSupply() external payable {
curDraw.totalWinnings += msg.value;
}

receive() external payable {
curDraw.totalWinnings += msg.value;
}

function activeBalance() public view returns (uint256) {
return address(this).balance;
}

function withdrawSupply(uint256 amount, address receiver) external onlyOwner {
require(amount <= activeBalance(), "House: insufficient non-withdrawal balance");
payable(receiver).transfer(amount);
}

mapping (uint => mapping (address => uint[])) ticketIdsByUserByDrawId;

uint constant public NUMBER_TO_CHOOSE = 10;
uint constant public NUMBERS_COUNT = 20;

RandomnessGenerator public randomnessGenerator;

// Lottery parameters
uint public ticketPrice = 1 * 10 ** 18; // 1 Ether by default (adjustable)
uint public drawFrequency = 1 days; // Default to one draw per day
uint public drawBeginTime;
uint public stackersPoolDistributionRatio = 50; // 50% to prize pool, 50% to staking pool

uint public ticketCounter = 0;
Draw public curDraw;

Draw[] public drawHistory;

event TicketPurchased(address indexed player, uint ticketId, uint[] chosenNumbers);
event DrawFinalized(uint indexed drawId, bool[] winningNumbers);
event PrizeClaimed(address indexed player, uint prizeAmount);

constructor(address _owner, address _randomnessGenerator) Ownable(_owner) {
randomnessGenerator = RandomnessGenerator(_randomnessGenerator);
drawBeginTime = block.timestamp;
curDraw.stackersPoolDistributionRatio = stackersPoolDistributionRatio;
}

function validateTicket(uint[] memory _chosenNumbers) internal pure {
require(_chosenNumbers.length == NUMBER_TO_CHOOSE, "You must pick 10 numbers");

bool[] memory numberPresence = new bool[](NUMBERS_COUNT);
for (uint i = 0; i < _chosenNumbers.length; i++) {
uint number = _chosenNumbers[i];
require(number < NUMBERS_COUNT, "Number out of range");
require(numberPresence[number] == false, "Duplicate number found");
numberPresence[number] = true;
}
}

function toSet(uint[] memory _chosenNumbers) internal pure returns (bool[] memory) {
bool[] memory set = new bool[](NUMBERS_COUNT);
for (uint i = 0; i < _chosenNumbers.length; i++) {
uint number = _chosenNumbers[i];
set[number] = true;
}
return set;
}

function purchaseTicket(uint[] calldata _chosenNumbers) external payable {
require(curDraw.prepareFinalizeCalled == false, "Can't purchase tickets to draw, which was prepared to finish");
validateTicket(_chosenNumbers);
require(msg.value == ticketPrice, "Incorrect Ether value sent. Ticket costs different prize");

uint ticketId = curDraw.tickets.length;

curDraw.tickets.push(
Ticket({
player: msg.sender,
chosenNumbers: toSet(_chosenNumbers),
claimed: false,
winner : false,
id: curDraw.tickets.length
})
);

ticketIdsByUserByDrawId[drawHistory.length][msg.sender].push(curDraw.tickets.length - 1);

uint stackersFee = ticketPrice * curDraw.stackersPoolDistributionRatio / 100;
// TODO: SEND TO STACKERS
uint ticketRevenue = ticketPrice - stackersFee;
curDraw.ticketRevenue += ticketRevenue;

emit TicketPurchased(msg.sender, ticketId, _chosenNumbers);
}

// Function to check if all randomness has been posted
function allRandomnessPostedForCurDraw() public view returns (bool) {
for (uint i = 0; i < curDraw.randomnessIDs.length; i++) {
if (randomnessGenerator.getRandomness(curDraw.randomnessIDs[i]) == 0) {
return false;
}
}
return true;
}

function prepareFinalizeDraw() external {
require(block.timestamp >= drawBeginTime + drawFrequency, "It's not time for the draw yet");
require(curDraw.prepareFinalizeCalled == false, "prepareFinalizeCalled was already called");

curDraw.prepareFinalizeCalled = true;

for (uint i = 0; i < NUMBER_TO_CHOOSE; i++) {
curDraw.randomnessIDs.push(randomnessGenerator.requestRandomness());
}
}

// New function to generate winning numbers
function generateWinningNumbers(uint[] memory randomNumbers) internal pure returns (bool[] memory) {
uint[] memory lotteryDrum = new uint[](NUMBERS_COUNT);

// Initialize the array with numbers from 0 to NUMBERS_COUNT-1
for (uint i = 0; i < lotteryDrum.length; i++) {
lotteryDrum[i] = i;
}

bool[] memory winningNumbers = new bool[](NUMBERS_COUNT);
for (uint i = 0; i < winningNumbers.length; i++) {
winningNumbers[i] = false;
}

uint numbersLeft = lotteryDrum.length;
// Remove elements from lotteryDrum
for (uint i = 0; i < randomNumbers.length; i++) {
uint winningNumberIdx = randomNumbers[i] % numbersLeft;
winningNumbers[lotteryDrum[winningNumberIdx]] = true;
lotteryDrum[winningNumberIdx] = lotteryDrum[numbersLeft - 1];
numbersLeft--; // Simulate pop operation
}

return winningNumbers;
}

function finalizeDraw() external {
require(curDraw.prepareFinalizeCalled == true, "prepareFinalizeCalled wasn't called, call it first");
require(block.timestamp >= drawBeginTime + drawFrequency, "It's not time for the draw yet");

// Check if all randomness has been posted
require(allRandomnessPostedForCurDraw(), "Not all randomness has been fulfilled");

uint[] memory randomnessIDs = curDraw.randomnessIDs;
uint[] memory randomNumbers = new uint[](randomnessIDs.length);
for (uint i = 0; i < randomnessIDs.length; i++) {
randomNumbers[i] = randomnessGenerator.getRandomness(randomnessIDs[i]);
}

// Generate the winning numbers using the new function
bool[] memory winningNumbers = generateWinningNumbers(randomNumbers);

curDraw.winningNumbers = winningNumbers;

// Check for winners
for (uint i = 0; i < curDraw.tickets.length; i++) {
if (checkIfWinner(curDraw.tickets[i].chosenNumbers, curDraw.winningNumbers)) {
curDraw.winnersCount++;
curDraw.tickets[i].winner = true;
}
}

emit DrawFinalized(drawHistory.length, winningNumbers);
drawHistory.push(curDraw);

uint prevDrawWinnersCount = curDraw.winnersCount;
uint prevDrawTotalWinnings = curDraw.totalWinnings;
uint prevDrawTicketRevenue = curDraw.ticketRevenue;

// Handle the next draw's winnings
resetCurDraw();
curDraw.totalWinnings += prevDrawTicketRevenue;
if (prevDrawWinnersCount == 0) {
curDraw.totalWinnings += prevDrawTotalWinnings;
}

curDraw.stackersPoolDistributionRatio = stackersPoolDistributionRatio;
drawBeginTime = block.timestamp;
}

function resetCurDraw() internal {
delete curDraw.randomnessIDs;
delete curDraw.winningNumbers;
delete curDraw.tickets;

curDraw.totalWinnings = 0;
curDraw.winnersCount = 0;
curDraw.ticketRevenue = 0;
curDraw.stackersPoolDistributionRatio = 0;
curDraw.prepareFinalizeCalled = false;
}

function claimPrize(uint drawId, uint ticketId) external {
Ticket storage ticket = drawHistory[drawId].tickets[ticketId];
require(ticket.player == msg.sender, "You are not the owner of this ticket");
require(!ticket.claimed, "Prize already claimed");
require(ticket.winner, "The ticket is not winning one!");

uint prizeAmount = drawHistory[drawId].totalWinnings / drawHistory[drawId].winnersCount;
payable(msg.sender).transfer(prizeAmount);
ticket.claimed = true;
emit PrizeClaimed(msg.sender, prizeAmount);
}

function checkIfWinner(bool[] memory chosenNumbers, bool[] memory winningNumbers) internal pure returns (bool) {
if (chosenNumbers.length != winningNumbers.length) {
return false;
}

for (uint i = 0; i < chosenNumbers.length; i++) {
if (chosenNumbers[i] != winningNumbers[i]) {
return false;
}
}

return true;
}

// Admin functions to adjust contract parameters
function setTicketPrice(uint newTicketPrice) external onlyOwner { // TODO: change it to onlyGov
ticketPrice = newTicketPrice;
}

function setDrawFrequency(uint newFrequency) external onlyOwner {
drawFrequency = newFrequency;
}

function setStackersPoolDistributionRatio(uint newRatio) external onlyOwner {
stackersPoolDistributionRatio = newRatio;
}

function setNewRngAddress(address addr) external onlyOwner {
randomnessGenerator = RandomnessGenerator(addr);
}

// Public function for users to see their ticket IDs
function getUserTickets(uint drawId, address user) external view returns (Ticket[] memory) {
Ticket[] memory drawTickets = getDraw(drawId).tickets;

uint[] memory ticketIds = ticketIdsByUserByDrawId[drawId][user];
Ticket[] memory res = new Ticket[](ticketIds.length);
for (uint i = 0; i < ticketIds.length; ++i) {
res[i] = drawTickets[ticketIds[i]];
}
return res;
}

function getCurDrawTotalWinnings() public view returns (uint256) {
return curDraw.totalWinnings;
}

function getCurDrawRemainingTime() public view returns (uint256) {
return drawBeginTime + drawFrequency;
}

function getDrawCount() external view returns (uint256) {
return drawHistory.length + 1;
}
}
25 changes: 9 additions & 16 deletions contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

const config: HardhatUserConfig = {
solidity: "0.8.28",
require("@nomicfoundation/hardhat-toolbox");

module.exports = {
solidity: '0.8.20',
networks: {
localhost: {
url: "http://127.0.0.1:8545",
accounts:{
mnemonic: "depend version wrestle document episode celery nuclear main penalty hundred trap scale candy donate search glory build valve round athlete become beauty indicate hamster",
}
rollapp: {
url: 'https://json-rpc.ra-2.rollapp.network',
accounts: {
mnemonic: 'chalk excess welcome pool sea session pencil health region lamp library today',
},
},
/*
hardhat: {
chainId: 1337,
}
*/
},
};

export default config;
8 changes: 4 additions & 4 deletions contracts/ignition/coinflip/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ async function main() {

const deployOptions = {
maxFeePerGas: ethers.parseUnits('30', 'gwei'), // Adjust as needed
maxPriorityFeePerGas: ethers.parseUnits('2', 'gwei'), // Adjust as needed
maxPriorityFeePerGas: ethers.parseUnits('20', 'gwei'), // Adjust as needed
};

try {
// Address of already deployed RandomnessGenerator contract
const randomnessGeneratorAddress = "0xf3911f024f42Ee885bD79c7fc4858909D3312cc1"; // Replace with actual address
const randomnessGeneratorAddress = "0x45b625A16B1304f67488b5D9c2FBB8AA4d649CA5"; // Replace with actual address

// Deploy CoinFlip contract
const CoinFlip = await ethers.getContractFactory("CoinFlip", deployer);
const CoinFlip = await ethers.getContractFactory("LotteryAgent", deployer);
const writerAddress = deployer.address; // assuming deployer is the writer for testing
const coinFlip = await CoinFlip.deploy(writerAddress, randomnessGeneratorAddress, deployOptions);
const coinFlip = await CoinFlip.deploy(writerAddress, randomnessGeneratorAddress);
await coinFlip.waitForDeployment();
console.log("CoinFlip deployed at:", coinFlip.target);

Expand Down
Loading

0 comments on commit 97107bc

Please sign in to comment.