@@ -4,7 +4,7 @@ pragma solidity 0.8.10;
44import "@openzeppelin/contracts/access/Ownable.sol " ;
55import "@openzeppelin/contracts/token/ERC20/ERC20.sol " ;
66
7- import "../interfaces/IRevokableTokenLock .sol " ;
7+ import "../interfaces/ITokenLockVestReader .sol " ;
88
99/**
1010 * @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 {
1515 /// token to give out (ARENA)
1616 ERC20 public immutable tokenOut;
1717 /// time when tokens can be first purchased
18- uint64 public immutable saleStart;
18+ uint64 public saleStart;
1919 /// duration of the token sale, cannot purchase afterwards
2020 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;
2325 /// vesting contract
24- IRevokableTokenLock public tokenLock;
26+ ITokenLockVestReader public immutable tokenLock;
2527 /// vesting duration
26- uint256 public vestDuration;
28+ uint256 public immutable vestDuration;
2729
2830 /// how many `tokenOut`s each address may buy
2931 mapping (address => uint256 ) public whitelistedBuyersAmount;
3032 /// tokenIn per tokenOut price. precision is in tokenInDecimals - tokenOutDecimals + 18
3133 /// i.e., it should be provided as tokenInAmount * 1e18 / tokenOutAmount
3234 uint256 public immutable tokenOutPrice;
3335
36+ event BuyerWhitelisted (address indexed buyer , uint256 amount );
3437 event Sale (address indexed buyer , uint256 amountIn , uint256 amountOut );
3538
3639 /**
@@ -40,9 +43,10 @@ contract TokenSale is Ownable {
4043 * @param _saleStart The time when tokens can be first purchased
4144 * @param _saleDuration The duration of the token sale
4245 * @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
4447 * @param _tokenLock The contract in which _tokenOut will be vested in
4548 * @param _vestDuration Token vesting duration
49+ * @param _saleRecipientAmount Amount receivable by sale recipient
4650 */
4751 constructor (
4852 ERC20 _tokenIn ,
@@ -52,7 +56,8 @@ contract TokenSale is Ownable {
5256 uint256 _tokenOutPrice ,
5357 address _saleRecipient ,
5458 address _tokenLock ,
55- uint256 _vestDuration
59+ uint256 _vestDuration ,
60+ uint256 _saleRecipientAmount
5661 ) {
5762 require (block .timestamp <= _saleStart, "TokenSale: start date may not be in the past " );
5863 require (_saleDuration > 0 , "TokenSale: the sale duration must not be zero " );
@@ -70,9 +75,9 @@ contract TokenSale is Ownable {
7075 saleDuration = _saleDuration;
7176 tokenOutPrice = _tokenOutPrice;
7277 saleRecipient = _saleRecipient;
73-
74- tokenLock = IRevokableTokenLock (_tokenLock);
78+ tokenLock = ITokenLockVestReader (_tokenLock);
7579 vestDuration = _vestDuration;
80+ remainingSaleRecipientAmount = _saleRecipientAmount;
7681 }
7782
7883 /**
@@ -86,10 +91,31 @@ contract TokenSale is Ownable {
8691 require (_tokenOutAmount > 0 , "TokenSale: non-whitelisted purchaser or have already bought " );
8792 whitelistedBuyersAmount[msg .sender ] = 0 ;
8893 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+ }
93119
94120 uint256 claimableAmount = (_tokenOutAmount * 2_000 ) / 10_000 ;
95121 uint256 remainingAmount;
@@ -103,7 +129,7 @@ contract TokenSale is Ownable {
103129 "TokenSale: tokenOut transfer failed "
104130 );
105131
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
107133 // the claimers and buyers are distinct to not reinitialize vesting
108134 tokenLock.setupVesting (
109135 msg .sender ,
@@ -131,18 +157,42 @@ contract TokenSale is Ownable {
131157 _buyers.length == _newTokenOutAmounts.length ,
132158 "TokenSale: parameter length mismatch "
133159 );
134- require (block .timestamp < saleStart, "TokenSale: sale already started " );
160+ require (
161+ block .timestamp < saleStart || block .timestamp > saleStart + saleDuration,
162+ "TokenSale: ongoing sale "
163+ );
135164
136165 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 " );
137170 whitelistedBuyersAmount[_buyers[i]] = _newTokenOutAmounts[i];
171+ emit BuyerWhitelisted (_buyers[i], _newTokenOutAmounts[i]);
138172 }
139173 }
140174
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+
141191 /**
142192 * @dev Transfers out any remaining `tokenOut` after the sale to owner
143193 */
144194 function sweepTokenOut () external {
145- require (saleStart + saleDuration < block .timestamp , "TokenSale: sale did not end yet " );
195+ require (saleStart + saleDuration < block .timestamp , "TokenSale: ongoing sale " );
146196
147197 uint256 tokenOutBalance = tokenOut.balanceOf (address (this ));
148198 require (tokenOut.transfer (owner (), tokenOutBalance), "TokenSale: transfer failed " );
0 commit comments