@@ -4,7 +4,7 @@ pragma solidity 0.8.10;
4
4
import "@openzeppelin/contracts/access/Ownable.sol " ;
5
5
import "@openzeppelin/contracts/token/ERC20/ERC20.sol " ;
6
6
7
- import "../interfaces/IRevokableTokenLock .sol " ;
7
+ import "../interfaces/ITokenLockVestReader .sol " ;
8
8
9
9
/**
10
10
* @dev Sells a token at a predetermined price to whitelisted buyers. The number of tokens each address can buy can be regulated.
@@ -15,22 +15,25 @@ contract TokenSale is Ownable {
15
15
/// token to give out (ARENA)
16
16
ERC20 public immutable tokenOut;
17
17
/// time when tokens can be first purchased
18
- uint64 public immutable saleStart;
18
+ uint64 public saleStart;
19
19
/// duration of the token sale, cannot purchase afterwards
20
20
uint64 public immutable saleDuration;
21
- /// address receiving the proceeds of the sale
22
- address internal saleRecipient;
21
+ /// address receiving a defined portion proceeds of the sale
22
+ address internal immutable saleRecipient;
23
+ /// amount receivable by sale recipient
24
+ uint256 public remainingSaleRecipientAmount;
23
25
/// vesting contract
24
- IRevokableTokenLock public tokenLock;
26
+ ITokenLockVestReader public immutable tokenLock;
25
27
/// vesting duration
26
- uint256 public vestDuration;
28
+ uint256 public immutable vestDuration;
27
29
28
30
/// how many `tokenOut`s each address may buy
29
31
mapping (address => uint256 ) public whitelistedBuyersAmount;
30
32
/// tokenIn per tokenOut price. precision is in tokenInDecimals - tokenOutDecimals + 18
31
33
/// i.e., it should be provided as tokenInAmount * 1e18 / tokenOutAmount
32
34
uint256 public immutable tokenOutPrice;
33
35
36
+ event BuyerWhitelisted (address indexed buyer , uint256 amount );
34
37
event Sale (address indexed buyer , uint256 amountIn , uint256 amountOut );
35
38
36
39
/**
@@ -40,9 +43,10 @@ contract TokenSale is Ownable {
40
43
* @param _saleStart The time when tokens can be first purchased
41
44
* @param _saleDuration The duration of the token sale
42
45
* @param _tokenOutPrice The tokenIn per tokenOut price. precision should be in tokenInDecimals - tokenOutDecimals + 18
43
- * @param _saleRecipient The address receiving the proceeds of the sale
46
+ * @param _saleRecipient The address receiving a portion proceeds of the sale
44
47
* @param _tokenLock The contract in which _tokenOut will be vested in
45
48
* @param _vestDuration Token vesting duration
49
+ * @param _saleRecipientAmount Amount receivable by sale recipient
46
50
*/
47
51
constructor (
48
52
ERC20 _tokenIn ,
@@ -52,7 +56,8 @@ contract TokenSale is Ownable {
52
56
uint256 _tokenOutPrice ,
53
57
address _saleRecipient ,
54
58
address _tokenLock ,
55
- uint256 _vestDuration
59
+ uint256 _vestDuration ,
60
+ uint256 _saleRecipientAmount
56
61
) {
57
62
require (block .timestamp <= _saleStart, "TokenSale: start date may not be in the past " );
58
63
require (_saleDuration > 0 , "TokenSale: the sale duration must not be zero " );
@@ -70,9 +75,9 @@ contract TokenSale is Ownable {
70
75
saleDuration = _saleDuration;
71
76
tokenOutPrice = _tokenOutPrice;
72
77
saleRecipient = _saleRecipient;
73
-
74
- tokenLock = IRevokableTokenLock (_tokenLock);
78
+ tokenLock = ITokenLockVestReader (_tokenLock);
75
79
vestDuration = _vestDuration;
80
+ remainingSaleRecipientAmount = _saleRecipientAmount;
76
81
}
77
82
78
83
/**
@@ -86,10 +91,31 @@ contract TokenSale is Ownable {
86
91
require (_tokenOutAmount > 0 , "TokenSale: non-whitelisted purchaser or have already bought " );
87
92
whitelistedBuyersAmount[msg .sender ] = 0 ;
88
93
tokenInAmount_ = (_tokenOutAmount * tokenOutPrice) / 1e18 ;
89
- require (
90
- tokenIn.transferFrom (msg .sender , saleRecipient, tokenInAmount_),
91
- "TokenSale: tokenIn transfer failed "
92
- );
94
+
95
+ // saleRecipient will receive proceeds first, until fully allocated
96
+ if (tokenInAmount_ <= remainingSaleRecipientAmount) {
97
+ remainingSaleRecipientAmount -= tokenInAmount_;
98
+ require (
99
+ tokenIn.transferFrom (msg .sender , saleRecipient, tokenInAmount_),
100
+ "TokenSale: tokenIn transfer failed "
101
+ );
102
+ } else {
103
+ // saleRecipient will either be receiving or have received full allocation
104
+ // portion will go to owner
105
+ uint256 ownerAmount = tokenInAmount_ - remainingSaleRecipientAmount;
106
+ require (
107
+ tokenIn.transferFrom (msg .sender , owner (), ownerAmount),
108
+ "TokenSale: tokenIn transfer failed "
109
+ );
110
+ if (remainingSaleRecipientAmount > 0 ) {
111
+ uint256 saleRecipientAmount = remainingSaleRecipientAmount;
112
+ remainingSaleRecipientAmount = 0 ;
113
+ require (
114
+ tokenIn.transferFrom (msg .sender , saleRecipient, saleRecipientAmount),
115
+ "TokenSale: tokenIn transfer failed "
116
+ );
117
+ }
118
+ }
93
119
94
120
uint256 claimableAmount = (_tokenOutAmount * 2_000 ) / 10_000 ;
95
121
uint256 remainingAmount;
@@ -103,7 +129,7 @@ contract TokenSale is Ownable {
103
129
"TokenSale: tokenOut transfer failed "
104
130
);
105
131
106
- // if we use same tokenLock instance as airdrop, we make sure that
132
+ // we use same tokenLock instance as airdrop, we make sure that
107
133
// the claimers and buyers are distinct to not reinitialize vesting
108
134
tokenLock.setupVesting (
109
135
msg .sender ,
@@ -131,18 +157,42 @@ contract TokenSale is Ownable {
131
157
_buyers.length == _newTokenOutAmounts.length ,
132
158
"TokenSale: parameter length mismatch "
133
159
);
134
- require (block .timestamp < saleStart, "TokenSale: sale already started " );
160
+ require (
161
+ block .timestamp < saleStart || block .timestamp > saleStart + saleDuration,
162
+ "TokenSale: ongoing sale "
163
+ );
135
164
136
165
for (uint256 i = 0 ; i < _buyers.length ; i++ ) {
166
+ // Does not cover the case that the buyer has not claimed his airdrop
167
+ // So it will have to be somewhat manually checked
168
+ ITokenLockVestReader.VestingParams memory vestParams = tokenLock.vesting (_buyers[i]);
169
+ require (vestParams.unlockBegin == 0 , "TokenSale: buyer has existing vest schedule " );
137
170
whitelistedBuyersAmount[_buyers[i]] = _newTokenOutAmounts[i];
171
+ emit BuyerWhitelisted (_buyers[i], _newTokenOutAmounts[i]);
138
172
}
139
173
}
140
174
175
+ /**
176
+ * @dev Modifies the start time of the sale. Enables a new sale to be created assuming one is not ongoing
177
+ * @dev A new list of buyers and tokenAmounts can be done by calling changeWhiteList()
178
+ * @param _newSaleStart The new start time of the token sale
179
+ */
180
+ function setNewSaleStart (uint64 _newSaleStart ) external {
181
+ require (msg .sender == owner () || msg .sender == saleRecipient, "TokenSale: not authorized " );
182
+ // can only change if there is no ongoing sale
183
+ require (
184
+ block .timestamp < saleStart || block .timestamp > saleStart + saleDuration,
185
+ "TokenSale: ongoing sale "
186
+ );
187
+ require (block .timestamp < _newSaleStart, "TokenSale: new sale too early " );
188
+ saleStart = _newSaleStart;
189
+ }
190
+
141
191
/**
142
192
* @dev Transfers out any remaining `tokenOut` after the sale to owner
143
193
*/
144
194
function sweepTokenOut () external {
145
- require (saleStart + saleDuration < block .timestamp , "TokenSale: sale did not end yet " );
195
+ require (saleStart + saleDuration < block .timestamp , "TokenSale: ongoing sale " );
146
196
147
197
uint256 tokenOutBalance = tokenOut.balanceOf (address (this ));
148
198
require (tokenOut.transfer (owner (), tokenOutBalance), "TokenSale: transfer failed " );
0 commit comments