-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrafflev2.sol
231 lines (176 loc) · 7.25 KB
/
rafflev2.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
//SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
enum State {
RUNNING,
SUSPENDED,
ABANDONED,
COMPLETED
}
struct GameInterface {
State state;
address[] allPlayersAddress;
uint[] allSlotsTaken;
mapping( address => uint[]) playersWithSelectedSlots;
}
contract Raffle {
uint public entranceFee;
uint public pricePool;
uint public gameId;
uint maxSlots = 6;
uint adminFee;
uint public dummy;
uint public dummy2;
address payable admin;
address genesis = 0x0000000000000000000000000000000000000000;
mapping(uint => GameInterface) Game;
event ReadyToDrawRaffle(uint indexed _gameId, uint indexed numSlotsClosed, uint pricePool);
event RaffleWinner(uint indexed _gameId, address indexed winner, uint winnings, uint winningDigit);
event NewPlayer(uint indexed _gameId, address indexed player, uint[] slotsTaken);
constructor() {
entranceFee = 1000000000000000000;
adminFee = entranceFee / 10; // 10% admin fee
admin = payable(msg.sender);
gameId = 1;
Game[gameId].state = State.RUNNING;
}
/****************************** GAME LOGIC *******************************/
modifier gameIsRunning {
require(Game[gameId].state == State.RUNNING, "Game is suspended");
_;
}
function playerSelectMultipleSlots(uint[] memory slots) external payable gameIsRunning returns(bool success) {
GameInterface storage game = Game[gameId];
uint[] storage selectedSlotsOfPlayer = game.playersWithSelectedSlots[msg.sender];
uint[] storage slotsTaken = game.allSlotsTaken;
require(msg.value == entranceFee * slots.length, "Please input the exact amount to proceed");
require(slotsTaken.length < maxSlots, "All Slots taken");
if (selectedSlotsOfPlayer.length == 0) {
game.allPlayersAddress.push(msg.sender);
}
for (uint i; i<slots.length; i++) {
require(slots[i] < maxSlots, "Pick slot less than max slot");
if (slotsTaken.length != 0) {
for (uint j;j<slotsTaken.length;j++) {
require(slots[i] != slotsTaken[j], "Slot taken");
}
}
slotsTaken.push(slots[i]);
selectedSlotsOfPlayer.push(slots[i]);
}
uint lessFees = (msg.value * (entranceFee - adminFee)) / entranceFee;
pricePool += lessFees;
emit NewPlayer(gameId, msg.sender, slots);
if (slotsTaken.length == maxSlots) {
emit ReadyToDrawRaffle(gameId, slotsTaken.length, pricePool);
}
success = true;
}
function drawRaffle() private {
(uint winningDigit, uint[] memory allSlots) = generateWinningDigit();
(uint totalClosedSlots,) = getSlotsClosed();
require(totalClosedSlots > 0, "Minimum number of players not met");
uint orig = winningDigit;
dummy = orig;
address payable winner;
if (totalClosedSlots != maxSlots) {
bool found = false;
while (found == false) {
for (uint i; i < totalClosedSlots; i++) {
if (winningDigit == allSlots[i]) {
found = true;
break;
}
continue;
}
if (found == false) {
(winningDigit,) = generateWinningDigit(); // reroll the winning digit if not found;
dummy2 = winningDigit;
}
}
}
winner = payable(findWinner(winningDigit));
assert(winner != genesis);
// uint adminsTF = getContractBalance() - pricePool; // for transparency sake
// winner.transfer(pricePool);
// admin.transfer(adminsTF);
emit RaffleWinner(gameId, winner, pricePool, winningDigit);
// reset(State.COMPLETED);
}
function playerForceDrawRaffle() external payable gameIsRunning {
uint feesRequired = exactFeeRequiredToClose();
require(msg.value >= feesRequired, "Not enough fees");
drawRaffle();
}
/****************************** UTILS *******************************/
function generateWinningDigit() public view returns(uint, uint[] memory) { // make private
(uint slotsClosed,uint[] memory slots) = getSlotsClosed();
bytes memory encodedBytes = abi.encodePacked(block.difficulty, block.timestamp, slotsClosed);
return ((uint(keccak256(encodedBytes)) % (slotsClosed + 1)), slots); // temporary random func, need to add 1 to include last player
}
function getContractBalance() public view returns(uint) {
return address(this).balance;
}
function findWinner(uint winningDigit) private view returns(address) {
GameInterface storage game = Game[gameId];
address[] memory players = game.allPlayersAddress;
address winner;
assert(players.length > 0);
for (uint i;i<players.length;i++) {
address currentPlayerIndex = players[i];
for (uint j;j< game.playersWithSelectedSlots[currentPlayerIndex].length;j++) {
if (winningDigit == game.playersWithSelectedSlots[currentPlayerIndex][j]) {
winner = players[i];
break;
}
}
}
require(winner != genesis, "Winner not found");
return winner;
}
function getSlotsClosed() public view returns(uint, uint[] memory) {
uint[] memory slotsClosed = Game[gameId].allSlotsTaken;
return (slotsClosed.length, slotsClosed);
}
function exactFeeRequiredToClose() public view returns(uint) {
(uint slotsClosed,) = getSlotsClosed();
uint slotsOpen = maxSlots - slotsClosed;
return slotsOpen * entranceFee;
}
function reset(State state) private {
pricePool = 0;
Game[gameId].state = state;
gameId++;
}
/****************************** ADMIN SETTER FUNCTIONS *******************************/
modifier isAdmin() {
require(msg.sender == admin, "admin access required");
_;
}
function adminForceDraw() external isAdmin {
drawRaffle();
}
function adminUpdateEntranceFee(uint amount) external isAdmin {
(uint slotsClosed,) = getSlotsClosed();
require(slotsClosed == 0, "Some slots have been closed already");
entranceFee = amount;
adminFee = amount / 10;
}
function adminSuspendGame(bool restart) external isAdmin returns(bool success) {
Game[gameId].state = State.SUSPENDED;
if (restart) {
reset(State.ABANDONED);
}
success = true;
}
function adminResumeGame() external isAdmin returns(bool success) {
Game[gameId].state = State.RUNNING;
success = true;
}
/****************************** ADMIN GETTER FUNCTIONS *******************************/
function adminGetAllPlayersAddress(uint _gameId) external view isAdmin returns(address[] memory) {
return Game[_gameId].allPlayersAddress;
}
function adminGetPlayerSlots(uint _gameId, address _player) external view isAdmin returns(uint[] memory) {
return Game[_gameId].playersWithSelectedSlots[_player];
}
}