/
GilgameshTokenSale.sol
512 lines (421 loc) · 14.1 KB
/
GilgameshTokenSale.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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
pragma solidity ^0.4.18;
import "./SafeMath.sol";
import "./GilgameshToken.sol";
/*
Copyright 2017, Skiral Inc
*/
contract GilgameshTokenSale is SafeMath{
// creationBlock is the block number that the Token was created
uint256 public creationBlock;
// startBlock token sale starting block
uint256 public startBlock;
// endBlock token sale ending block
// end block is not a valid block for crowdfunding. endBlock - 1 is the last valid block
uint256 public endBlock;
// total Wei rasised
uint256 public totalRaised = 0;
// Has Gilgamesh stopped the sale
bool public saleStopped = false;
// Has Gilgamesh finalized the sale
bool public saleFinalized = false;
// Minimum purchase - 0.1 Ether
uint256 constant public minimumInvestment = 100 finney;
// Maximum hard Cap
uint256 public hardCap = 50000 ether;
// number of wei GIL tokens for sale - 60 Million GIL Tokens
uint256 public tokenCap = 60000000 * 10**18;
// Minimum cap
uint256 public minimumCap = 1250 ether;
/* Contract Info */
// the deposit address for the Eth that is raised.
address public fundOwnerWallet;
// the deposit address for the tokens that is minted for the dev team.
address public tokenOwnerWallet;
// owner the address of the contract depoloyer
address public owner;
// List of stage bonus percentages in every stage
// this will get generated in the constructor
uint[] public stageBonusPercentage;
// number of participants
uint256 public totalParticipants;
// a map of userId to wei
mapping(uint256 => uint256) public paymentsByUserId;
// a map of user address to wei
mapping(address => uint256) public paymentsByAddress;
// total number of bonus stages.
uint8 public totalStages;
// max bonus percentage on first stage
uint8 public stageMaxBonusPercentage;
// number of wei-GIL tokens for 1 wei (18 decimals)
uint256 public tokenPrice;
// the team owns 25% of the tokens - 3 times more than token purchasers.
uint8 public teamTokenRatio = 3;
// GIL token address
GilgameshToken public token;
// if Ether or Token cap has been reached
bool public isCapReached = false;
// log when token sale has been initialized
event LogTokenSaleInitialized(
address indexed owner,
address indexed fundOwnerWallet,
uint256 startBlock,
uint256 endBlock,
uint256 creationBlock
);
// log each contribution
event LogContribution(
address indexed contributorAddress,
address indexed invokerAddress,
uint256 amount,
uint256 totalRaised,
uint256 userAssignedTokens,
uint256 indexed userId
);
// log when crowd fund is finalized
event LogFinalized(address owner, uint256 teamTokens);
// Constructor
function GilgameshTokenSale(
uint256 _startBlock, // starting block number
uint256 _endBlock, // ending block number
address _fundOwnerWallet, // fund owner wallet address - transfer ether to this address during and after fund has been closed
address _tokenOwnerWallet, // token fund owner wallet address - transfer GIL tokens to this address after fund is finalized
uint8 _totalStages, // total number of bonus stages
uint8 _stageMaxBonusPercentage, // maximum percentage for bonus in the first stage
uint256 _tokenPrice, // price of each GIL token in wei
address _gilgameshToken, // address of the gilgamesh ERC20 token contract
uint256 _minimumCap, // minimum cap, minimum amount of wei to be raised
uint256 _tokenCap // tokenCap
)
public
validate_address(_fundOwnerWallet) {
if (
_gilgameshToken == 0x0 ||
_tokenOwnerWallet == 0x0 ||
// start block needs to be in the future
_startBlock < getBlockNumber() ||
// start block should be less than ending block
_startBlock >= _endBlock ||
// minimum number of stages is 2
_totalStages < 2 ||
// verify stage max bonus
_stageMaxBonusPercentage < 0 ||
_stageMaxBonusPercentage > 100 ||
// stage bonus percentage needs to be devisible by number of stages
_stageMaxBonusPercentage % (_totalStages - 1) != 0 ||
// total number of blocks needs to be devisible by the total stages
(_endBlock - _startBlock) % _totalStages != 0
) revert();
owner = msg.sender; // make the contract creator the `owner`
token = GilgameshToken(_gilgameshToken);
endBlock = _endBlock;
startBlock = _startBlock;
creationBlock = getBlockNumber();
fundOwnerWallet = _fundOwnerWallet;
tokenOwnerWallet = _tokenOwnerWallet;
tokenPrice = _tokenPrice;
totalStages = _totalStages;
minimumCap = _minimumCap;
stageMaxBonusPercentage = _stageMaxBonusPercentage;
totalRaised = 0; // total number of wei raised
tokenCap = _tokenCap;
// spread bonuses evenly between stages - e.g 27 / 9 = 3%
uint spread = stageMaxBonusPercentage / (totalStages - 1);
// loop through [10 to 1] => ( 9 to 0) * 3% = [27%, 24%, 21%, 18%, 15%, 12%, 9%, 6%, 3%, 0%]
for (uint stageNumber = totalStages; stageNumber > 0; stageNumber--) {
stageBonusPercentage.push((stageNumber - 1) * spread);
}
LogTokenSaleInitialized(
owner,
fundOwnerWallet,
startBlock,
endBlock,
creationBlock
);
}
// --------------
// Public Funtions
// --------------
/// @notice Function to stop sale for an emergency.
/// @dev Only Gilgamesh Dev can do it after it has been activated.
function emergencyStopSale()
public
only_sale_active
onlyOwner {
saleStopped = true;
}
/// @notice Function to restart stopped sale.
/// @dev Only Gilgamesh Dev can do it after it has been disabled and sale has stopped.
/// can it's in a valid time range for sale
function restartSale()
public
only_during_sale_period
only_sale_stopped
onlyOwner {
// if sale is finalized fail
if (saleFinalized) revert();
saleStopped = false;
}
/// @notice Function to change the fund owner wallet address
/// @dev Only Gilgamesh Dev can trigger this function
function changeFundOwnerWalletAddress(address _fundOwnerWallet)
public
validate_address(_fundOwnerWallet)
onlyOwner {
fundOwnerWallet = _fundOwnerWallet;
}
/// @notice Function to change the token fund owner wallet address
/// @dev Only Gilgamesh Dev can trigger this function
function changeTokenOwnerWalletAddress(address _tokenOwnerWallet)
public
validate_address(_tokenOwnerWallet)
onlyOwner {
tokenOwnerWallet = _tokenOwnerWallet;
}
/// @notice finalize the sale
/// @dev Only Gilgamesh Dev can trigger this function
function finalizeSale()
public
onlyOwner {
doFinalizeSale();
}
/// @notice change hard cap and if it reaches hard cap finalize sale
function changeCap(uint256 _cap)
public
onlyOwner {
if (_cap < minimumCap) revert();
if (_cap <= totalRaised) revert();
hardCap = _cap;
if (totalRaised + minimumInvestment >= hardCap) {
isCapReached = true;
doFinalizeSale();
}
}
/// @notice change minimum cap, in case Ether price fluctuates.
function changeMinimumCap(uint256 _cap)
public
onlyOwner {
if (minimumCap < _cap) revert();
minimumCap = _cap;
}
/// @notice remove conttact only when sale has been finalized
/// transfer all the fund to the contract owner
/// @dev only Gilgamesh Dev can trigger this function
function removeContract()
public
onlyOwner {
if (!saleFinalized) revert();
selfdestruct(msg.sender);
}
/// @notice only the owner is allowed to change the owner.
/// @param _newOwner the address of the new owner
function changeOwner(address _newOwner)
public
validate_address(_newOwner)
onlyOwner {
require(_newOwner != owner);
owner = _newOwner;
}
/// @dev The fallback function is called when ether is sent to the contract
/// Payable is a required solidity modifier to receive ether
/// every contract only has one unnamed function
/// 2300 gas available for this function
/*function () public payable {
return deposit();
}*/
/**
* Pay on a behalf of the sender.
*
* @param customerId Identifier in the central database, UUID v4
*
*/
/// @dev allow purchasers to deposit ETH for GIL Tokens.
function depositForMySelf(uint256 userId)
public
only_sale_active
minimum_contribution()
payable {
deposit(userId, msg.sender);
}
/// @dev deposit() is an public function that accepts a userId and userAddress
/// contract receives ETH in return of GIL tokens
function deposit(uint256 userId, address userAddress)
public
payable
only_sale_active
minimum_contribution()
validate_address(userAddress) {
// if it passes hard cap throw
if (totalRaised + msg.value > hardCap) revert();
uint256 userAssignedTokens = calculateTokens(msg.value);
// if user tokens are 0 throw
if (userAssignedTokens <= 0) revert();
// if number of tokens exceed the token cap stop execution
if (token.totalSupply() + userAssignedTokens > tokenCap) revert();
// send funds to fund owner wallet
if (!fundOwnerWallet.send(msg.value)) revert();
// mint tokens for the user
if (!token.mint(userAddress, userAssignedTokens)) revert();
// save total number wei raised
totalRaised = safeAdd(totalRaised, msg.value);
// if cap is reached mark it
if (totalRaised >= hardCap) {
isCapReached = true;
}
// if token supply has exceeded or reached the token cap stop
if (token.totalSupply() >= tokenCap) {
isCapReached = true;
}
// increase the number of participants for the first transaction
if (paymentsByUserId[userId] == 0) {
totalParticipants++;
}
// increase the amount that the user has payed
paymentsByUserId[userId] += msg.value;
// total wei based on address
paymentsByAddress[userAddress] += msg.value;
// log contribution event
LogContribution(
userAddress,
msg.sender,
msg.value,
totalRaised,
userAssignedTokens,
userId
);
}
/// @notice calculate number of tokens need to be issued based on the amount received
/// @param amount number of wei received
function calculateTokens(uint256 amount)
public
view
returns (uint256) {
// return 0 if the crowd fund has ended or it hasn't started
if (!isDuringSalePeriod(getBlockNumber())) return 0;
// get the current stage number by block number
uint8 currentStage = getStageByBlockNumber(getBlockNumber());
// if current stage is more than the total stage return 0 - something is wrong
if (currentStage > totalStages) return 0;
// calculate number of tokens that needs to be issued for the purchaser
uint256 purchasedTokens = safeMul(amount, tokenPrice);
// calculate number of tokens that needs to be rewraded to the purchaser
uint256 rewardedTokens = calculateRewardTokens(purchasedTokens, currentStage);
// add purchasedTokens and rewardedTokens
return safeAdd(purchasedTokens, rewardedTokens);
}
/// @notice calculate reward based on amount of tokens that will be issued to the purchaser
/// @param amount number tokens that will be minted for the purchaser
/// @param stageNumber number of current stage in the crowd fund process
function calculateRewardTokens(uint256 amount, uint8 stageNumber)
public
view
returns (uint256 rewardAmount) {
// throw if it's invalid stage number
if (
stageNumber < 1 ||
stageNumber > totalStages
) revert();
// get stage index for the array
uint8 stageIndex = stageNumber - 1;
// calculate reward - e.q 100 token creates 100 * 20 /100 = 20 tokens for reward
return safeDiv(safeMul(amount, stageBonusPercentage[stageIndex]), 100);
}
/// @notice get crowd fund stage by block number
/// @param _blockNumber block number
function getStageByBlockNumber(uint256 _blockNumber)
public
view
returns (uint8) {
// throw error, if block number is out of range
if (!isDuringSalePeriod(_blockNumber)) revert();
uint256 totalBlocks = safeSub(endBlock, startBlock);
uint256 numOfBlockPassed = safeSub(_blockNumber, startBlock);
// since numbers round down we need to add one to number of stage
return uint8(safeDiv(safeMul(totalStages, numOfBlockPassed), totalBlocks) + 1);
}
// --------------
// Internal Funtions
// --------------
/// @notice check if the block number is during the sale period
/// @param _blockNumber block number
function isDuringSalePeriod(uint256 _blockNumber)
view
internal
returns (bool) {
return (_blockNumber >= startBlock && _blockNumber < endBlock);
}
/// @notice finalize the crowdfun sale
/// @dev Only Gilgamesh Dev can trigger this function
function doFinalizeSale()
internal
onlyOwner {
if (saleFinalized) revert();
// calculate the number of tokens that needs to be assigned to Gilgamesh team
uint256 teamTokens = safeMul(token.totalSupply(), teamTokenRatio);
if (teamTokens > 0){
// mint tokens for the team
if (!token.mint(tokenOwnerWallet, teamTokens)) revert();
}
// if there is any fund drain it
if(this.balance > 0) {
// send ether funds to fund owner wallet
if (!fundOwnerWallet.send(this.balance)) revert();
}
// finalize sale flag
saleFinalized = true;
// stop sale flag
saleStopped = true;
// log finalized
LogFinalized(tokenOwnerWallet, teamTokens);
}
/// @notice returns block.number
function getBlockNumber() constant internal returns (uint) {
return block.number;
}
// --------------
// Modifiers
// --------------
/// continue only when sale has stopped
modifier only_sale_stopped {
if (!saleStopped) revert();
_;
}
/// validates an address - currently only checks that it isn't null
modifier validate_address(address _address) {
if (_address == 0x0) revert();
_;
}
/// continue only during the sale period
modifier only_during_sale_period {
// if block number is less than starting block fail
if (getBlockNumber() < startBlock) revert();
// if block number has reach to the end block fail
if (getBlockNumber() >= endBlock) revert();
// otherwise safe to continue
_;
}
/// continue when sale is active and valid
modifier only_sale_active {
// if sale is finalized fail
if (saleFinalized) revert();
// if sale is stopped fail
if (saleStopped) revert();
// if cap is reached
if (isCapReached) revert();
// if block number is less than starting block fail
if (getBlockNumber() < startBlock) revert();
// if block number has reach to the end block fail
if (getBlockNumber() >= endBlock) revert();
// otherwise safe to continue
_;
}
/// continue if minimum contribution has reached
modifier minimum_contribution() {
if (msg.value < minimumInvestment) revert();
_;
}
/// continue when the invoker is the owner
modifier onlyOwner() {
if (msg.sender != owner) revert();
_;
}
}