-
Notifications
You must be signed in to change notification settings - Fork 0
/
CyberTitansTournament.sol
255 lines (214 loc) · 11.6 KB
/
CyberTitansTournament.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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../utils/Ownable.sol";
import "../token/ILitlabGamesToken.sol";
import "../metatx/LitlabContext.sol";
/// SmartContract for CyberTitans game modality. It's a centralized SmartContract.
/// Working mode:
/// - This SmartContract is intended to manage the user tournament join, Fment and distribute prizes
/// - Previously, users have approved this contract to spend LitlabGames ERC20 token in the litlabgames webpage
/// - Then, when users want to connect to the game, when matchmaking is done (8 playes), in the server, we call the functions:
/// - createTournament: To create a new tournament. Returns a tournament id
/// - joinTournament: To join a new user to a tournament
/// - retireTournament: To retire a user from a tournament (deactivated for now. nobody could retire once joined)
/// - finalizeTournament: When tournament has finished, send the winner wallets and distribute the prizes according the prizes matrix
contract CyberTitansTournament is LitlabContext, Ownable {
using SafeERC20 for IERC20;
struct TournamentStruct {
uint256 playerBet; // Tournament bet for each player
uint256 tournamentAssuredAmount; // Tournament minimum prize
address token; // Tournament token
uint24 numOfTokenPlayers; // Num of players betting with LITTs
uint24 numOfCTTPlayers; // Num of players playing with CTT (softcoin of the game not used in blockchain)
uint64 startDate; // Tournament start date
uint64 endDate; // Tournament end date
}
mapping(uint256 => TournamentStruct) private tournaments; // Mapping with Tournaments
uint256 tournamentCounter; // To calculate next tournamentId
address public wallet; // Wallet with LITTs to take when player bets don't make the minimun tournament amount
address public manager; // Account authorized to call the contract from a backend
address public litlabToken; // LitlabGames token
uint32[][8] public prizes; // Matrix of prizes
uint32[][8] public players; // Matrix of players
uint32[][12] public tops; // Matrix of tops
uint8[8] public winners = [3, 4, 6, 8, 16, 32, 64, 128]; // Array of winners according to tournament players number
uint16 public rake = 25;
uint16 public fee = 25;
bool private pause;
event onTournamentCreated(uint256 _tournamentId);
event onTournamentFinalized(uint256 _tournamentId);
event onJoinedTournament(uint256 _id, address _player);
//event onRetiredTournament(uint256 _id, address _player);
event onTournamentStarted(uint256 _id, uint24 _litPlayers, uint24 _cttPlayers);
event onEmergencyWithdraw(uint256 _balance, address _token);
// Initializate the data and build the matrix according to the tokenomics
constructor(address _forwarder, address _manager, address _wallet, address _litlabToken) LitlabContext(_forwarder) {
manager = _manager;
wallet = _wallet;
litlabToken = _litlabToken;
_buildArrays();
}
function _buildArrays() internal {
prizes[0] = [5000000, 3000000, 2000000];
prizes[1] = [4000000, 2700000, 1900000, 1400000];
prizes[2] = [3200000, 2200000, 1650000, 1250000, 900000, 800000];
prizes[3] = [2975000, 1875000, 1475000, 1125000, 850000, 700000, 550000, 450000];
prizes[4] = [2575000, 1705000, 1100000, 850000, 625000, 500000, 400000, 317000, 241000];
prizes[5] = [2000000, 1400000, 945000, 770000, 600000, 500000, 400000, 312500, 164063, 110000];
prizes[6] = [1825000, 1325000, 842000, 700000, 562500, 460000, 360000, 265000, 130000, 73001, 45390];
prizes[7] = [1780000, 1275000, 785000, 609200, 507500, 412000, 320000, 232500, 105000, 51000, 31712, 22000];
players[0] = [1,8];
players[1] = [9,16];
players[2] = [17,32];
players[3] = [33,64];
players[4] = [65,128];
players[5] = [129,256];
players[6] = [257,512];
players[7] = [512,1024];
tops[0] = [1,1];
tops[1] = [2,2];
tops[2] = [3,3];
tops[3] = [4,4];
tops[4] = [5,5];
tops[5] = [6,6];
tops[6] = [7,7];
tops[7] = [8,8];
tops[8] = [9,16];
tops[9] = [17,32];
tops[10] = [33,64];
tops[11] = [65,128];
}
function changeWallets(address _manager, address _wallet, address _litlabToken) external onlyOwner {
manager = _manager;
wallet = _wallet;
litlabToken = _litlabToken;
}
function updateFees(uint16 _fee, uint16 _rake) external onlyOwner {
fee = _fee;
rake = _rake;
}
function changeArrays(uint32[][8] calldata _prizes, uint32[][8] calldata _players, uint32[][12] calldata _tops, uint8[8] calldata _winners) external onlyOwner {
for (uint256 i=0; i<_prizes.length; i++) prizes[i] = _prizes[i];
for (uint256 i=0; i<_players.length; i++) players[i] = _players[i];
for (uint256 i=0; i<_tops.length; i++) tops[i] = _tops[i];
for (uint256 i=0; i<_winners.length; i++) winners[i] = _winners[i];
}
function changePause() external onlyOwner {
pause = !pause;
}
// To create the tournament only need. Token address, start date, bet for each player and minimum tournament prize
// Returns the tournamentId
function createTournament(address _token, uint64 _startDate, uint256 _playerBet, uint256 _tournamentAssuredAmount) external {
require(_msgSender() == manager, "OnlyManager");
require(pause == false, "Paused");
require(_playerBet != 0, "BadAmount");
require(_tournamentAssuredAmount != 0, "BadAmount");
require(_token != address(0), "BadToken");
uint tournamentId = ++tournamentCounter;
TournamentStruct storage tournament = tournaments[tournamentId];
tournament.token = _token;
tournament.playerBet = _playerBet;
tournament.tournamentAssuredAmount = _tournamentAssuredAmount;
if (_startDate > 0) tournament.startDate = _startDate;
emit onTournamentCreated(tournamentId);
}
// A player joins the tournament only using the id
function joinTournament(uint256 _id) external {
require(pause == false, "Paused");
TournamentStruct storage tournament = tournaments[_id];
if (tournament.startDate > 0) require(block.timestamp >= tournament.startDate, "NotStarted");
if (tournament.endDate > 0) require(block.timestamp <= tournament.endDate, "Ended");
tournament.numOfTokenPlayers++;
IERC20(tournament.token).safeTransferFrom(_msgSender(), address(this), tournament.playerBet);
emit onJoinedTournament(_id, _msgSender());
}
// Get tournament data
function getTournament(uint256 _id) external view returns(TournamentStruct memory) {
return tournaments[_id];
}
// Player retires from tournament before started
function retireFromTournament(uint256 _id) external {
// In the first version, users are not allowed to retire once they joined.
// This is becuase litlabgames is paying the gas fees and we don't want users joining and retiring.
}
// Server indicates that the tournament is going to start (this is not the exact moment when starting)
// Reports the number of players with LITTs and CTT tickets.
// We need this data to calculate the prizes properly in the finalizeTournament function
function startTournament(uint256 _id, uint24 _litPlayers, uint24 _cttPlayers) external {
require(_msgSender() == manager, "OnlyManager");
require(pause == false, "Paused");
TournamentStruct storage tournament = tournaments[_id];
require(_cttPlayers == tournament.numOfTokenPlayers, "BadLITTPlayers"); // Check the server reports the same LITT users that we count in the smartcontract
tournament.numOfCTTPlayers = _cttPlayers; // Report the CTT tickets free2play players
emit onTournamentStarted(_id, _litPlayers, _cttPlayers);
}
// Server calls this function and the smartcontract give the prizes according the matrix data
// Server only reports an array with winners. We calculate the prizes
function finalizeTournament(uint256 _tournamentId, address[] calldata _winners) external {
require(_msgSender() == manager, "OnlyManager");
require(pause == false, "Paused");
TournamentStruct storage tournament = tournaments[_tournamentId];
tournament.endDate = uint64(block.timestamp);
// Get the num of players in the tournament
uint24 numOfPlayers = tournament.numOfTokenPlayers + tournament.numOfCTTPlayers;
// Get the prizes array
uint256 index = _getPrizesColumn(numOfPlayers);
require(winners[index] == _winners.length, "BadWinners");
// Calculate if we got the minimum assurance token amount for the tournament. Otherwise, the game will add the tokens
uint256 amountPlayed = tournament.playerBet * tournament.numOfTokenPlayers;
if (amountPlayed < tournament.tournamentAssuredAmount) {
IERC20(tournament.token).safeTransferFrom(wallet, address(this), tournament.tournamentAssuredAmount - amountPlayed);
amountPlayed = tournament.tournamentAssuredAmount;
}
// Burn those tokens
uint256 _rake = amountPlayed * rake / 1000;
// Fee
uint256 _fee = amountPlayed * fee / 1000;
// Tournament pot
uint256 pot = amountPlayed - (_rake + _fee);
// For each player in the winners array
uint8 i;
do {
// Get the player's prize
uint256 prizePercentage = _getPrize(index, i+1);
uint256 prize = (pot * prizePercentage) / (10 ** 7);
if (prize != 0) IERC20(tournament.token).safeTransfer(_winners[i], prize);
++i;
} while(i<_winners.length);
// Burn the rake and get the fee
if (tournament.token == litlabToken) {
// If we are using litlabtoken, burn the rake
ILitlabGamesToken(tournament.token).burn(_rake);
IERC20(tournament.token).safeTransfer(wallet, _fee);
} else {
// If we are using other token, transfer the rake instead of burning
IERC20(tournament.token).safeTransfer(wallet, (_rake + _fee));
}
emit onTournamentFinalized(_tournamentId);
}
function _getPrizesColumn(uint24 _numOfPlayers) internal view returns(uint16) {
uint16 index;
do {
if (_numOfPlayers >= players[index][0] && _numOfPlayers <= players[index][1]) break;
++index;
} while (index < 8);
assert(index < 8);
return index;
}
function _getPrize(uint256 _index, uint256 _position) internal view returns(uint32) {
uint8 index;
do {
if (_position >= tops[index][0] && _position <= tops[index][1]) break;
++index;
} while(index < 12);
assert(index < 12);
return prizes[_index][index];
}
// If something happens, owner can withdraw the LITT funds of this contract
function emergencyWithdraw(address _token) external onlyOwner {
uint256 balance = IERC20(_token).balanceOf(address(this));
IERC20(_token).safeTransfer(owner, balance);
emit onEmergencyWithdraw(balance, _token);
}
}