-
Notifications
You must be signed in to change notification settings - Fork 15
/
Presale01.sol
395 lines (347 loc) · 18.2 KB
/
Presale01.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
// SPDX-License-Identifier: UNLICENSED
// @Credits Unicrypt Network 2021
// Presale contract. Version 1
/**
Allows a decentralised presale to take place, and on success creates a uniswap pair and locks liquidity on Octofi.
B_TOKEN, or base token, is the token the presale attempts to raise. (Usally ETH).
S_TOKEN, or sale token, is the token being sold, which investors buy with the base token.
If the base currency is set to the WETH9 address, the presale is in ETH.
Otherwise it is for an ERC20 token - such as DAI, USDC, WBTC etc.
For the Base token - It is advised to only use tokens such as ETH (WETH), DAI, USDC or tokens that have no rebasing, or complex fee on transfers. 1 token should ideally always be 1 token.
Token withdrawls are done on a percent of total contribution basis (opposed to via a hardcoded 'amount'). This allows
fee on transfer, rebasing, or any magically changing balances to still work for the Sale token.
*/
pragma solidity 0.6.12;
import "./TransferHelper.sol";
import "./EnumerableSet.sol";
import "./SafeMath.sol";
import "./ReentrancyGuard.sol";
import "./IERC20.sol";
interface IUniswapV2Factory {
function getPair(address tokenA, address tokenB) external view returns (address pair);
function createPair(address tokenA, address tokenB) external returns (address pair);
}
interface IPresaleLockForwarder {
function lockLiquidity (IERC20 _baseToken, IERC20 _saleToken, uint256 _baseAmount, uint256 _saleAmount, uint256 _unlock_date, address payable _withdrawer) external;
function uniswapPairIsInitialised (address _token0, address _token1) external view returns (bool);
}
interface IWETH {
function deposit() external payable;
function transfer(address to, uint value) external returns (bool);
function withdraw(uint) external;
}
interface IPresaleSettings {
function getMaxPresaleLength () external view returns (uint256);
function getRound1Length () external view returns (uint256);
function userHoldsSufficientRound1Token (address _user) external view returns (bool);
function referrerIsValid(address _referrer) external view returns (bool);
function getBaseFee () external view returns (uint256);
function getTokenFee () external view returns (uint256);
function getEthAddress () external view returns (address payable);
function getTokenAddress () external view returns (address payable);
function getReferralFee () external view returns (uint256);
function getEthCreationFee () external view returns (uint256);
}
contract Presale01 is ReentrancyGuard {
using SafeMath for uint256;
using EnumerableSet for EnumerableSet.AddressSet;
/// @notice Presale Contract Version, used to choose the correct ABI to decode the contract
uint256 public CONTRACT_VERSION = 1;
struct PresaleInfo {
address payable PRESALE_OWNER;
IERC20 S_TOKEN; // sale token
IERC20 B_TOKEN; // base token // usually WETH (ETH)
uint256 TOKEN_PRICE; // 1 base token = ? s_tokens, fixed price
uint256 MAX_SPEND_PER_BUYER; // maximum base token BUY amount per account
uint256 AMOUNT; // the amount of presale tokens up for presale
uint256 HARDCAP;
uint256 SOFTCAP;
uint256 LIQUIDITY_PERCENT; // divided by 1000
uint256 LISTING_RATE; // fixed rate at which the token will list on uniswap
uint256 START_BLOCK;
uint256 END_BLOCK;
uint256 LOCK_PERIOD; // unix timestamp -> e.g. 2 weeks
bool PRESALE_IN_ETH; // if this flag is true the presale is raising ETH, otherwise an ERC20 token such as DAI
}
struct PresaleFeeInfo {
uint256 OCTOFI_BASE_FEE; // divided by 1000
uint256 OCTOFI_TOKEN_FEE; // divided by 1000
uint256 REFERRAL_FEE; // divided by 1000
address payable BASE_FEE_ADDRESS;
address payable TOKEN_FEE_ADDRESS;
address payable REFERRAL_FEE_ADDRESS; // if this is not address(0), there is a valid referral
}
struct PresaleStatus {
bool WHITELIST_ONLY; // if set to true only whitelisted members may participate
bool LP_GENERATION_COMPLETE; // final flag required to end a presale and enable withdrawls
bool FORCE_FAILED; // set this flag to force fail the presale
uint256 TOTAL_BASE_COLLECTED; // total base currency raised (usually ETH)
uint256 TOTAL_TOKENS_SOLD; // total presale tokens sold
uint256 TOTAL_TOKENS_WITHDRAWN; // total tokens withdrawn post successful presale
uint256 TOTAL_BASE_WITHDRAWN; // total base tokens withdrawn on presale failure
uint256 ROUND1_LENGTH; // in blocks
uint256 NUM_BUYERS; // number of unique participants
}
struct BuyerInfo {
uint256 baseDeposited; // total base token (usually ETH) deposited by user, can be withdrawn on presale failure
uint256 tokensOwed; // num presale tokens a user is owed, can be withdrawn on presale success
}
PresaleInfo public PRESALE_INFO;
PresaleFeeInfo public PRESALE_FEE_INFO;
PresaleStatus public STATUS;
address public PRESALE_GENERATOR;
IPresaleLockForwarder public PRESALE_LOCK_FORWARDER;
IPresaleSettings public PRESALE_SETTINGS;
address OCTOFI_FEE_ADDRESS;
IUniswapV2Factory public UNI_FACTORY;
IWETH public WETH;
mapping(address => BuyerInfo) public BUYERS;
EnumerableSet.AddressSet private WHITELIST;
constructor(address _presaleGenerator) public {
PRESALE_GENERATOR = _presaleGenerator;
UNI_FACTORY = IUniswapV2Factory(0x0044583d20C5C7E80c2Ac84A6D2E9Ee3521F7aA2);
WETH = IWETH(0xa6c3c0c397DE062Df60c080341b4900eeB27acb2);
PRESALE_SETTINGS = IPresaleSettings(0x21876F9B3e7aA5F3604784de2724F55B92ceFA9d);
PRESALE_LOCK_FORWARDER = IPresaleLockForwarder(0x677d300E2748C463530BAf7810b9B815995D0d9B);
OCTOFI_FEE_ADDRESS = 0x6D9a8766d8D1CE814FfC8871EA54B1d9DDaBFd3e;
}
function init1 (
address payable _presaleOwner,
uint256 _amount,
uint256 _tokenPrice,
uint256 _maxEthPerBuyer,
uint256 _hardcap,
uint256 _softcap,
uint256 _liquidityPercent,
uint256 _listingRate,
uint256 _startblock,
uint256 _endblock,
uint256 _lockPeriod
) external {
require(msg.sender == PRESALE_GENERATOR, 'FORBIDDEN');
PRESALE_INFO.PRESALE_OWNER = _presaleOwner;
PRESALE_INFO.AMOUNT = _amount;
PRESALE_INFO.TOKEN_PRICE = _tokenPrice;
PRESALE_INFO.MAX_SPEND_PER_BUYER = _maxEthPerBuyer;
PRESALE_INFO.HARDCAP = _hardcap;
PRESALE_INFO.SOFTCAP = _softcap;
PRESALE_INFO.LIQUIDITY_PERCENT = _liquidityPercent;
PRESALE_INFO.LISTING_RATE = _listingRate;
PRESALE_INFO.START_BLOCK = _startblock;
PRESALE_INFO.END_BLOCK = _endblock;
PRESALE_INFO.LOCK_PERIOD = _lockPeriod;
}
function init2 (
IERC20 _baseToken,
IERC20 _presaleToken,
uint256 _unicryptBaseFee,
uint256 _unicryptTokenFee,
uint256 _referralFee,
address payable _baseFeeAddress,
address payable _tokenFeeAddress,
address payable _referralAddress
) external {
require(msg.sender == PRESALE_GENERATOR, 'FORBIDDEN');
// require(!PRESALE_LOCK_FORWARDER.uniswapPairIsInitialised(address(_presaleToken), address(_baseToken)), 'PAIR INITIALISED');
PRESALE_INFO.PRESALE_IN_ETH = address(_baseToken) == address(WETH);
PRESALE_INFO.S_TOKEN = _presaleToken;
PRESALE_INFO.B_TOKEN = _baseToken;
PRESALE_FEE_INFO.OCTOFI_BASE_FEE = _unicryptBaseFee;
PRESALE_FEE_INFO.OCTOFI_TOKEN_FEE = _unicryptTokenFee;
PRESALE_FEE_INFO.REFERRAL_FEE = _referralFee;
PRESALE_FEE_INFO.BASE_FEE_ADDRESS = _baseFeeAddress;
PRESALE_FEE_INFO.TOKEN_FEE_ADDRESS = _tokenFeeAddress;
PRESALE_FEE_INFO.REFERRAL_FEE_ADDRESS = _referralAddress;
STATUS.ROUND1_LENGTH = PRESALE_SETTINGS.getRound1Length();
}
modifier onlyPresaleOwner() {
require(PRESALE_INFO.PRESALE_OWNER == msg.sender, "NOT PRESALE OWNER");
_;
}
function presaleStatus () public view returns (uint256) {
if (STATUS.FORCE_FAILED) {
return 3; // FAILED - force fail
}
if ((block.number > PRESALE_INFO.END_BLOCK) && (STATUS.TOTAL_BASE_COLLECTED < PRESALE_INFO.SOFTCAP)) {
return 3; // FAILED - softcap not met by end block
}
if (STATUS.TOTAL_BASE_COLLECTED >= PRESALE_INFO.HARDCAP) {
return 2; // SUCCESS - hardcap met
}
if ((block.number > PRESALE_INFO.END_BLOCK) && (STATUS.TOTAL_BASE_COLLECTED >= PRESALE_INFO.SOFTCAP)) {
return 2; // SUCCESS - endblock and soft cap reached
}
if ((block.number >= PRESALE_INFO.START_BLOCK) && (block.number <= PRESALE_INFO.END_BLOCK)) {
return 1; // ACTIVE - deposits enabled
}
return 0; // QUED - awaiting start block
}
// accepts msg.value for eth or _amount for ERC20 tokens
function userDeposit (uint256 _amount) external payable nonReentrant {
require(presaleStatus() == 1, 'NOT ACTIVE'); // ACTIVE
if (STATUS.WHITELIST_ONLY) {
require(WHITELIST.contains(msg.sender), 'NOT WHITELISTED');
}
// Presale Round 1 - require participant to hold a certain token and balance
if (block.number < PRESALE_INFO.START_BLOCK + STATUS.ROUND1_LENGTH) { // 276 blocks = 1 hour
require(PRESALE_SETTINGS.userHoldsSufficientRound1Token(msg.sender), 'INSUFFICENT ROUND 1 TOKEN BALANCE');
}
BuyerInfo storage buyer = BUYERS[msg.sender];
uint256 amount_in = PRESALE_INFO.PRESALE_IN_ETH ? msg.value : _amount;
uint256 allowance = PRESALE_INFO.MAX_SPEND_PER_BUYER.sub(buyer.baseDeposited);
uint256 remaining = PRESALE_INFO.HARDCAP - STATUS.TOTAL_BASE_COLLECTED;
allowance = allowance > remaining ? remaining : allowance;
if (amount_in > allowance) {
amount_in = allowance;
}
uint256 tokensSold = amount_in.mul(PRESALE_INFO.TOKEN_PRICE).div(10 ** uint256(PRESALE_INFO.B_TOKEN.decimals()));
require(tokensSold > 0, 'ZERO TOKENS');
if (buyer.baseDeposited == 0) {
STATUS.NUM_BUYERS++;
}
buyer.baseDeposited = buyer.baseDeposited.add(amount_in);
buyer.tokensOwed = buyer.tokensOwed.add(tokensSold);
STATUS.TOTAL_BASE_COLLECTED = STATUS.TOTAL_BASE_COLLECTED.add(amount_in);
STATUS.TOTAL_TOKENS_SOLD = STATUS.TOTAL_TOKENS_SOLD.add(tokensSold);
// return unused ETH
if (PRESALE_INFO.PRESALE_IN_ETH && amount_in < msg.value) {
msg.sender.transfer(msg.value.sub(amount_in));
}
// deduct non ETH token from user
if (!PRESALE_INFO.PRESALE_IN_ETH) {
TransferHelper.safeTransferFrom(address(PRESALE_INFO.B_TOKEN), msg.sender, address(this), amount_in);
}
}
// withdraw presale tokens
// percentile withdrawls allows fee on transfer or rebasing tokens to still work
function userWithdrawTokens () external nonReentrant {
require(STATUS.LP_GENERATION_COMPLETE, 'AWAITING LP GENERATION');
BuyerInfo storage buyer = BUYERS[msg.sender];
uint256 tokensRemainingDenominator = STATUS.TOTAL_TOKENS_SOLD.sub(STATUS.TOTAL_TOKENS_WITHDRAWN);
uint256 tokensOwed = PRESALE_INFO.S_TOKEN.balanceOf(address(this)).mul(buyer.tokensOwed).div(tokensRemainingDenominator);
require(tokensOwed > 0, 'NOTHING TO WITHDRAW');
STATUS.TOTAL_TOKENS_WITHDRAWN = STATUS.TOTAL_TOKENS_WITHDRAWN.add(buyer.tokensOwed);
buyer.tokensOwed = 0;
TransferHelper.safeTransfer(address(PRESALE_INFO.S_TOKEN), msg.sender, tokensOwed);
}
// on presale failure
// percentile withdrawls allows fee on transfer or rebasing tokens to still work
function userWithdrawBaseTokens () external nonReentrant {
require(presaleStatus() == 3, 'NOT FAILED'); // FAILED
BuyerInfo storage buyer = BUYERS[msg.sender];
uint256 baseRemainingDenominator = STATUS.TOTAL_BASE_COLLECTED.sub(STATUS.TOTAL_BASE_WITHDRAWN);
uint256 remainingBaseBalance = PRESALE_INFO.PRESALE_IN_ETH ? address(this).balance : PRESALE_INFO.B_TOKEN.balanceOf(address(this));
uint256 tokensOwed = remainingBaseBalance.mul(buyer.baseDeposited).div(baseRemainingDenominator);
require(tokensOwed > 0, 'NOTHING TO WITHDRAW');
STATUS.TOTAL_BASE_WITHDRAWN = STATUS.TOTAL_BASE_WITHDRAWN.add(buyer.baseDeposited);
buyer.baseDeposited = 0;
TransferHelper.safeTransferBaseToken(address(PRESALE_INFO.B_TOKEN), msg.sender, tokensOwed, !PRESALE_INFO.PRESALE_IN_ETH);
}
// on presale failure
// allows the owner to withdraw the tokens they sent for presale & initial liquidity
function ownerWithdrawTokens () external onlyPresaleOwner {
require(presaleStatus() == 3); // FAILED
TransferHelper.safeTransfer(address(PRESALE_INFO.S_TOKEN), PRESALE_INFO.PRESALE_OWNER, PRESALE_INFO.S_TOKEN.balanceOf(address(this)));
}
// Can be called at any stage before or during the presale to cancel it before it ends.
// If the pair already exists on uniswap and it contains the presale token as liquidity
// the final stage of the presale 'addLiquidity()' will fail. This function
// allows anyone to end the presale prematurely to release funds in such a case.
function forceFailIfPairExists () external {
require(!STATUS.LP_GENERATION_COMPLETE && !STATUS.FORCE_FAILED);
if (PRESALE_LOCK_FORWARDER.uniswapPairIsInitialised(address(PRESALE_INFO.S_TOKEN), address(PRESALE_INFO.B_TOKEN))) {
STATUS.FORCE_FAILED = true;
}
}
// if something goes wrong in LP generation
function forceFailByOctofi () external {
require(msg.sender == OCTOFI_FEE_ADDRESS);
STATUS.FORCE_FAILED = true;
}
// on presale success, this is the final step to end the presale, lock liquidity and enable withdrawls of the sale token.
// This function does not use percentile distribution. Rebasing mechanisms, fee on transfers, or any deflationary logic
// are not taken into account at this stage to ensure stated liquidity is locked and the pool is initialised according to
// the presale parameters and fixed prices.
function addLiquidity() external nonReentrant {
require(!STATUS.LP_GENERATION_COMPLETE, 'GENERATION COMPLETE');
require(presaleStatus() == 2, 'NOT SUCCESS'); // SUCCESS
// Fail the presale if the pair exists and contains presale token liquidity
if (PRESALE_LOCK_FORWARDER.uniswapPairIsInitialised(address(PRESALE_INFO.S_TOKEN), address(PRESALE_INFO.B_TOKEN))) {
STATUS.FORCE_FAILED = true;
return;
}
uint256 unicryptBaseFee = STATUS.TOTAL_BASE_COLLECTED.mul(PRESALE_FEE_INFO.OCTOFI_BASE_FEE).div(1000);
// base token liquidity
uint256 baseLiquidity = STATUS.TOTAL_BASE_COLLECTED.sub(unicryptBaseFee).mul(PRESALE_INFO.LIQUIDITY_PERCENT).div(1000);
if (PRESALE_INFO.PRESALE_IN_ETH) {
WETH.deposit{value : baseLiquidity}();
}
TransferHelper.safeApprove(address(PRESALE_INFO.B_TOKEN), address(PRESALE_LOCK_FORWARDER), baseLiquidity);
// sale token liquidity
uint256 tokenLiquidity = baseLiquidity.mul(PRESALE_INFO.LISTING_RATE).div(10 ** uint256(PRESALE_INFO.B_TOKEN.decimals()));
TransferHelper.safeApprove(address(PRESALE_INFO.S_TOKEN), address(PRESALE_LOCK_FORWARDER), tokenLiquidity);
PRESALE_LOCK_FORWARDER.lockLiquidity(PRESALE_INFO.B_TOKEN, PRESALE_INFO.S_TOKEN, baseLiquidity, tokenLiquidity, block.timestamp + PRESALE_INFO.LOCK_PERIOD, PRESALE_INFO.PRESALE_OWNER);
// transfer fees
uint256 unicryptTokenFee = STATUS.TOTAL_TOKENS_SOLD.mul(PRESALE_FEE_INFO.OCTOFI_TOKEN_FEE).div(1000);
// referrals are checked for validity in the presale generator
if (PRESALE_FEE_INFO.REFERRAL_FEE_ADDRESS != address(0)) {
// Base token fee
uint256 referralBaseFee = unicryptBaseFee.mul(PRESALE_FEE_INFO.REFERRAL_FEE).div(1000);
TransferHelper.safeTransferBaseToken(address(PRESALE_INFO.B_TOKEN), PRESALE_FEE_INFO.REFERRAL_FEE_ADDRESS, referralBaseFee, !PRESALE_INFO.PRESALE_IN_ETH);
unicryptBaseFee = unicryptBaseFee.sub(referralBaseFee);
// Token fee
uint256 referralTokenFee = unicryptTokenFee.mul(PRESALE_FEE_INFO.REFERRAL_FEE).div(1000);
TransferHelper.safeTransfer(address(PRESALE_INFO.S_TOKEN), PRESALE_FEE_INFO.REFERRAL_FEE_ADDRESS, referralTokenFee);
unicryptTokenFee = unicryptTokenFee.sub(referralTokenFee);
}
TransferHelper.safeTransferBaseToken(address(PRESALE_INFO.B_TOKEN), PRESALE_FEE_INFO.BASE_FEE_ADDRESS, unicryptBaseFee, !PRESALE_INFO.PRESALE_IN_ETH);
TransferHelper.safeTransfer(address(PRESALE_INFO.S_TOKEN), PRESALE_FEE_INFO.TOKEN_FEE_ADDRESS, unicryptTokenFee);
// burn unsold tokens
uint256 remainingSBalance = PRESALE_INFO.S_TOKEN.balanceOf(address(this));
if (remainingSBalance > STATUS.TOTAL_TOKENS_SOLD) {
uint256 burnAmount = remainingSBalance.sub(STATUS.TOTAL_TOKENS_SOLD);
TransferHelper.safeTransfer(address(PRESALE_INFO.S_TOKEN), 0x000000000000000000000000000000000000dEaD, burnAmount);
}
// send remaining base tokens to presale owner
uint256 remainingBaseBalance = PRESALE_INFO.PRESALE_IN_ETH ? address(this).balance : PRESALE_INFO.B_TOKEN.balanceOf(address(this));
TransferHelper.safeTransferBaseToken(address(PRESALE_INFO.B_TOKEN), PRESALE_INFO.PRESALE_OWNER, remainingBaseBalance, !PRESALE_INFO.PRESALE_IN_ETH);
STATUS.LP_GENERATION_COMPLETE = true;
}
function updateMaxSpendLimit(uint256 _maxSpend) external onlyPresaleOwner {
PRESALE_INFO.MAX_SPEND_PER_BUYER = _maxSpend;
}
// postpone or bring a presale forward, this will only work when a presale is inactive.
// i.e. current start block > block.number
function updateBlocks(uint256 _startBlock, uint256 _endBlock) external onlyPresaleOwner {
require(PRESALE_INFO.START_BLOCK > block.number);
require(_endBlock.sub(_startBlock) <= PRESALE_SETTINGS.getMaxPresaleLength());
PRESALE_INFO.START_BLOCK = _startBlock;
PRESALE_INFO.END_BLOCK = _endBlock;
}
// editable at any stage of the presale
function setWhitelistFlag(bool _flag) external onlyPresaleOwner {
STATUS.WHITELIST_ONLY = _flag;
}
// editable at any stage of the presale
function editWhitelist(address[] memory _users, bool _add) external onlyPresaleOwner {
if (_add) {
for (uint i = 0; i < _users.length; i++) {
WHITELIST.add(_users[i]);
}
} else {
for (uint i = 0; i < _users.length; i++) {
WHITELIST.remove(_users[i]);
}
}
}
// whitelist getters
function getWhitelistedUsersLength () external view returns (uint256) {
return WHITELIST.length();
}
function getWhitelistedUserAtIndex (uint256 _index) external view returns (address) {
return WHITELIST.at(_index);
}
function getUserWhitelistStatus (address _user) external view returns (bool) {
return WHITELIST.contains(_user);
}
}