Skip to content
Show file tree
Hide file tree
Showing 351 changed files with 42,522 additions and 56 deletions.
59 changes: 59 additions & 0 deletions lottery-agent/contracts/EventManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
* @dev This contract provides simple event managing functionality:
* inserting, deleting and polling events.
*
* This module is used through inheritance.
*/
abstract contract EventManager {
struct Event {
uint64 eventId;
uint16 eventType;
bytes data;
}

struct EventEntries {
Event[] data;
mapping(uint64 => uint64) dataIdxByEventId;
}

mapping(uint16 => EventEntries) private _eventsByType;

uint private _maxEventsPerType;

constructor(uint maxEventsPerType) {
_maxEventsPerType = maxEventsPerType;
}

function insertEvent(uint64 eventId, uint16 eventType, bytes memory data) internal {
Event[] storage events = _eventsByType[eventType].data;
require(events.length < _maxEventsPerType, "Event buffer is full");

events.push(Event(eventId, eventType, data));
_eventsByType[eventType].dataIdxByEventId[eventId] = uint64(events.length) - 1;
}

function eraseEvent(uint64 eventId, uint16 eventType) internal {
EventEntries storage entries = _eventsByType[eventType];

uint64 index = entries.dataIdxByEventId[eventId];
require(index < entries.data.length, "Event does not exist");

// Swap the last event with the one to delete and then pop the last
uint64 lastIndex = uint64(entries.data.length) - 1;
if (index != lastIndex) {
Event storage lastEvent = entries.data[lastIndex];
entries.data[index] = lastEvent;
entries.dataIdxByEventId[lastEvent.eventId] = index;
}
entries.data.pop();
delete entries.dataIdxByEventId[eventId];
}

function getEvents(uint16 eventType) public view returns (Event[] memory) {
EventEntries storage entries = _eventsByType[eventType];
return entries.data;
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

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

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

abstract contract Governance {
address constant public govAddress = 0x7b5Fe22B5446f7C62Ea27B8BD71CeF94e03f3dF2;

modifier onlyGovernance() {
require(msg.sender == govAddress, "Governance: caller is not the governance");
_;
}
}

contract LotteryAgent is Ownable, Governance {
// Token contract address (DYM token)
IERC20 public dymToken;

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

struct Ticket {
address player;
bool[] chosenNumbers;
Expand All @@ -37,48 +22,39 @@ contract LotteryAgent is Ownable, Governance {
uint winnersCount;
uint ticketRevenue;
uint stackersPoolDistributionRatio;
mapping(uint => Ticket[]) tickets;
Ticket[] tickets;
bool prepareFinalizeCalled;
}

// Struct to represent a user's ticket ID (drawId + ticketId)
struct TicketId {
uint drawId;
uint ticketId;
}
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 DYM by default (adjustable)
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;

// Mapping from drawId to Draw struct
Draw[] public drawHistory;

// Mapping from user address to their ticket IDs
mapping(address => TicketId[]) public userTickets;

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 _dymToken, address _randomnessGenerator) Ownable(_owner) {
constructor(address _owner, address _randomnessGenerator) Ownable(_owner) {
randomnessGenerator = RandomnessGenerator(_randomnessGenerator);
dymToken = IERC20(_dymToken);
drawBeginTime = block.timestamp;
curDraw.stackersPoolDistributionRatio = stackersPoolDistributionRatio;
}

function validateTicket(uint[] memory _chosenNumbers) internal view {
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);
Expand All @@ -99,11 +75,10 @@ contract LotteryAgent is Ownable, Governance {
return set;
}

function purchaseTicket(uint[] calldata _chosenNumbers) external {
function purchaseTicket(uint[] calldata _chosenNumbers) external payable {
require(curDraw.prepareFinalizeCalled == false, "Can't purchase tickets to draw, which was prepared to finish");
validateTicket(_chosenNumbers);

dymToken.transferFrom(msg.sender, address(this), ticketPrice);
require(msg.value == ticketPrice, "Incorrect Ether value sent. Ticket costs different prize");

uint ticketId = curDraw.tickets.length;

Expand All @@ -116,8 +91,7 @@ contract LotteryAgent is Ownable, Governance {
})
);

// Store the ticket in userTickets mapping with the (drawId, ticketId) pair
userTickets[msg.sender].push(TicketId(drawHistory.length, ticketId));
ticketIdsByUserByDrawId[drawHistory.length][msg.sender].push(curDraw.tickets.length - 1);

uint stackersFee = ticketPrice * curDraw.stackersPoolDistributionRatio / 100;
// TODO: SEND TO STACKERS
Expand Down Expand Up @@ -158,16 +132,16 @@ contract LotteryAgent is Ownable, Governance {
}

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

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

Expand Down Expand Up @@ -202,18 +176,28 @@ contract LotteryAgent is Ownable, Governance {

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

// Handle the next draw's winnings
Draw memory nextDraw = Draw();
nextDraw.totalWinnings += curDraw.ticketRevenue;
resetCurDraw();
curDraw.totalWinnings += curDraw.ticketRevenue;
if (curDraw.winnersCount == 0) {
nextDraw.totalWinnings += curDraw.totalWinnings;
curDraw.totalWinnings += curDraw.totalWinnings;
}

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

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 {
Expand All @@ -223,7 +207,7 @@ contract LotteryAgent is Ownable, Governance {
require(ticket.winner, "The ticket is not winning one!");

uint prizeAmount = drawHistory[drawId].totalWinnings / drawHistory[drawId].winnersCount;
dymToken.transfer(msg.sender, prizeAmount);
payable(msg.sender).transfer(prizeAmount);
ticket.claimed = true;
emit PrizeClaimed(msg.sender, prizeAmount);
}
Expand All @@ -243,25 +227,42 @@ contract LotteryAgent is Ownable, Governance {
}

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

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

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

function setDYMTokenAddress(address newTokenAddress) external onlyGovernance {
dymToken = IERC20(newTokenAddress);
// Public function for users to see their ticket IDs
function getUserTickets(uint drawId, address user) external view returns (Ticket[] memory) {
require(drawId <= drawHistory.length, "draw idx overflow");

Ticket[] memory drawTickets;
if (drawId < drawHistory.length) {
drawTickets = drawHistory[drawId].tickets;
} else {
drawTickets = curDraw.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;
}

// Public function for users to see their ticket IDs
function getUserTickets(address user) external view returns (TicketId[] memory) {
return userTickets[user];
function getCurDrawRemainingTime() public view returns (uint256) {
return drawBeginTime + drawFrequency - block.timestamp;
}
}

60 changes: 60 additions & 0 deletions lottery-agent/contracts/RandomnessGenerator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./EventManager.sol";

contract RandomnessGenerator is EventManager {
uint64 public randomnessId;
mapping(uint256 => uint256) public randomnessJobs;
address public writer;

// Don't change the order of the entries in enum declaration. Backend relies on integer number under the enum
enum EventType {
RandomnessRequested
}

constructor(address _writer) EventManager(10240) {
randomnessId = 0;
writer = _writer;
}

function requestRandomness() external returns (uint256) {
randomnessId += 1;
bytes memory requestData = abi.encode(randomnessId);
insertEvent(randomnessId, uint16(EventType.RandomnessRequested), requestData);
return randomnessId;
}

function postRandomness(uint64 id, uint256 randomness) external {
require(msg.sender == writer, "Only writer can post randomness");
require(randomnessJobs[id] == 0, "Randomness already posted");

randomnessJobs[id] = randomness;
eraseEvent(randomnessId, uint16(EventType.RandomnessRequested));
}

function getRandomness(uint256 id) external view returns (uint256) {
uint256 storedRandomness = randomnessJobs[id];
require(storedRandomness != 0, "Randomness not posted");
return storedRandomness;
}

struct UnprocessedRandomness {
uint64 randomnessId;
}

function decodeUnprocessedRandomness(bytes memory data) internal pure returns (UnprocessedRandomness memory) {
uint64 id;
(id) = abi.decode(data, (uint64));
return UnprocessedRandomness(id);
}

function getUnprocessedRandomness() external view returns (UnprocessedRandomness[] memory) {
Event[] memory events = getEvents(uint16(EventType.RandomnessRequested));
UnprocessedRandomness[] memory res = new UnprocessedRandomness[](events.length);
for (uint64 i = 0; i < events.length; i++) {
res[i] = decodeUnprocessedRandomness(events[i].data);
}
return res;
}
}
16 changes: 16 additions & 0 deletions lottery-agent/frontend/.next/app-build-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"pages": {
"/page": [
"static/chunks/webpack.js",
"static/chunks/main-app.js",
"static/css/app/page.css",
"static/chunks/app/page.js"
],
"/layout": [
"static/chunks/webpack.js",
"static/chunks/main-app.js",
"static/css/app/layout.css",
"static/chunks/app/layout.js"
]
}
}
30 changes: 30 additions & 0 deletions lottery-agent/frontend/.next/build-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"polyfillFiles": [
"static/chunks/polyfills.js"
],
"devFiles": [
"static/chunks/react-refresh.js"
],
"ampDevFiles": [],
"lowPriorityFiles": [
"static/development/_buildManifest.js",
"static/development/_ssgManifest.js"
],
"rootMainFiles": [
"static/chunks/webpack.js",
"static/chunks/main-app.js"
],
"pages": {
"/_app": [
"static/chunks/webpack.js",
"static/chunks/main.js",
"static/chunks/pages/_app.js"
],
"/_error": [
"static/chunks/webpack.js",
"static/chunks/main.js",
"static/chunks/pages/_error.js"
]
},
"ampFirstPages": []
}
1 change: 1 addition & 0 deletions lottery-agent/frontend/.next/cache/.rscinfo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"encryption.key":"X/Lnb33sWqTRshY26L81USriUtm1tFoqhtjOEN/E4gA=","encryption.expire_at":1739299280451}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 2e5115a

Please sign in to comment.