Skip to content

Commit 27fa6ce

Browse files
authored
Merge pull request #10 from ApeSwapFinance/feature/linear-vesting
IAO Linear Vesting
2 parents 110b9a4 + 18fc2b8 commit 27fa6ce

20 files changed

+2355
-88
lines changed

.github/workflows/CI.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
test:
1111
strategy:
1212
matrix:
13-
node: ['12.x', '14.x']
13+
node: ['14.x']
1414
os: [ubuntu-latest]
1515

1616
runs-on: ${{ matrix.os }}

README.md

+13-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,21 @@ This set of contracts is used to run Ape Swap's version of initial farm offering
77
## Operation
88
- Each IAO raises a predefined `stakeToken` amount in exchange for a predefined `offeringToken` amount
99
- Once the `startBlock` arrives, users can supply as much of the raising token as they would like
10-
- Once the `endBlock` arrives, users can withdraw their first harvest period and obtain a refund
11-
- `offeringTokens` are sent to the users based on the percentage of their allocation divided by the number of harvest periods
10+
- Once the `endBlock` arrives, users can withdraw their first harvest and obtain a refund
11+
12+
### Linear Vesting
13+
[IAOLinearVesting.sol](./contracts/IAOLinearVesting.sol)
14+
25% of tokens are released at the `endBlock` of this IAO and the other 75% are distributed linearly between the `endBlock` and `vestingEndBlock`.
15+
16+
- `vestingEndBlock` is set to define when all of the offering tokens will be 100% unlocked.
17+
- A refund of `stakeTokens` when there is an oversubscription of the IAO will be given on the first harvest
18+
- `offeringTokens` are sent to users based on the number of vesting blocks that have passed since the end of the IAO. 25% is released on the first harvest along with a refund for over-subscriptions
19+
### Period Based Vesting
20+
[IAO.sol](./contracts/IAO.sol)
1221
- `harvestPeriods` set the block when new vesting tokens may be unlocked. After enough time elapses, a user will be able to withdraw the next harvest until there are no more
1322
- A refund of `stakeTokens` when there is an oversubscription of the IAO will be given on the first harvest of any period
23+
- `offeringTokens` are sent to the users based on the percentage of their allocation divided by the number of harvest periods
24+
1425

1526
# Development
1627

Binary file not shown.

contracts/IAO.sol

+45-21
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,35 @@
22
pragma solidity 0.8.6;
33

44
/*
5-
* ApeSwapFinance
5+
______ ______
6+
/ \ / \
7+
| ▓▓▓▓▓▓\ ______ ______ | ▓▓▓▓▓▓\__ __ __ ______ ______
8+
| ▓▓__| ▓▓/ \ / \| ▓▓___\▓▓ \ | \ | \| \ / \
9+
| ▓▓ ▓▓ ▓▓▓▓▓▓\ ▓▓▓▓▓▓\\▓▓ \| ▓▓ | ▓▓ | ▓▓ \▓▓▓▓▓▓\ ▓▓▓▓▓▓\
10+
| ▓▓▓▓▓▓▓▓ ▓▓ | ▓▓ ▓▓ ▓▓_\▓▓▓▓▓▓\ ▓▓ | ▓▓ | ▓▓/ ▓▓ ▓▓ | ▓▓
11+
| ▓▓ | ▓▓ ▓▓__/ ▓▓ ▓▓▓▓▓▓▓▓ \__| ▓▓ ▓▓_/ ▓▓_/ ▓▓ ▓▓▓▓▓▓▓ ▓▓__/ ▓▓
12+
| ▓▓ | ▓▓ ▓▓ ▓▓\▓▓ \\▓▓ ▓▓\▓▓ ▓▓ ▓▓\▓▓ ▓▓ ▓▓ ▓▓
13+
\▓▓ \▓▓ ▓▓▓▓▓▓▓ \▓▓▓▓▓▓▓ \▓▓▓▓▓▓ \▓▓▓▓▓\▓▓▓▓ \▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓
14+
| ▓▓ | ▓▓
15+
| ▓▓ | ▓▓
16+
\▓▓ \▓▓
17+
618
* App: https://apeswap.finance
719
* Medium: https://ape-swap.medium.com
820
* Twitter: https://twitter.com/ape_swap
21+
* Discord: https://discord.com/invite/apeswap
922
* Telegram: https://t.me/ape_swap
1023
* Announcements: https://t.me/ape_swap_news
1124
* GitHub: https://github.com/ApeSwapFinance
1225
*/
1326

14-
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
1527
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
1628
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
17-
import '@openzeppelin/contracts/security/ReentrancyGuard.sol';
29+
import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol';
1830

19-
contract IAO is ReentrancyGuard, Initializable {
31+
/// @title Harvest Period based Initial Ape Offering
32+
/// @notice safeTransferStakeInternal uses a fixed gas limit for native transfers which should be evaluated when deploying to new networks.
33+
contract IAO is ReentrancyGuardUpgradeable {
2034
using SafeERC20 for IERC20;
2135

2236
uint256 constant public HARVEST_PERIODS = 4;
@@ -61,6 +75,9 @@ contract IAO is ReentrancyGuard, Initializable {
6175
uint256 offeringAmount,
6276
uint256 excessAmount
6377
);
78+
event UpdateOfferingAmount(uint256 previousOfferingAmount, uint256 newOfferingAmount);
79+
event UpdateRaisingAmount(uint256 previousRaisingAmount, uint256 newRaisingAmount);
80+
event AdminFinalWithdraw(uint256 stakeTokenAmount, uint256 offerAmount);
6481
event EmergencySweepWithdraw(address indexed receiver, address indexed token, uint256 balance);
6582

6683

@@ -91,6 +108,7 @@ contract IAO is ReentrancyGuard, Initializable {
91108
raisingAmount = _raisingAmount;
92109
totalAmount = 0;
93110
adminAddress = _adminAddress;
111+
__ReentrancyGuard_init();
94112
}
95113

96114
modifier onlyAdmin() {
@@ -106,26 +124,28 @@ contract IAO is ReentrancyGuard, Initializable {
106124
_;
107125
}
108126

109-
function setOfferingAmount(uint256 _offerAmount) public onlyAdmin {
127+
function setOfferingAmount(uint256 _offerAmount) external onlyAdmin {
110128
require(block.number < startBlock, "cannot update during active iao");
129+
emit UpdateOfferingAmount(offeringAmount, _offerAmount);
111130
offeringAmount = _offerAmount;
112131
}
113132

114-
function setRaisingAmount(uint256 _raisingAmount) public onlyAdmin {
133+
function setRaisingAmount(uint256 _raisingAmount) external onlyAdmin {
115134
require(block.number < startBlock, "cannot update during active iao");
135+
emit UpdateRaisingAmount(raisingAmount, _raisingAmount);
116136
raisingAmount = _raisingAmount;
117137
}
118138

119139
/// @notice Deposits native EVM tokens into the IAO contract as per the value sent
120140
/// in the transaction.
121-
function depositNative() external payable onlyActiveIAO {
141+
function depositNative() external payable onlyActiveIAO nonReentrant {
122142
require(isNativeTokenStaking, 'stake token is not native EVM token');
123143
require(msg.value > 0, 'value not > 0');
124144
depositInternal(msg.value);
125145
}
126146

127147
/// @dev Deposit ERC20 tokens with support for reflect tokens
128-
function deposit(uint256 _amount) external onlyActiveIAO {
148+
function deposit(uint256 _amount) external onlyActiveIAO nonReentrant {
129149
require(!isNativeTokenStaking, "stake token is native token, deposit through 'depositNative'");
130150
require(_amount > 0, "_amount not > 0");
131151
uint256 pre = getTotalStakeTokenBalance();
@@ -135,6 +155,7 @@ contract IAO is ReentrancyGuard, Initializable {
135155
_amount
136156
);
137157
uint256 finalDepositAmount = getTotalStakeTokenBalance() - pre;
158+
require(finalDepositAmount > 0, 'final deposit amount is zero');
138159
depositInternal(finalDepositAmount);
139160
}
140161

@@ -163,14 +184,15 @@ contract IAO is ReentrancyGuard, Initializable {
163184
safeTransferStakeInternal(msg.sender, refundingTokenAmount);
164185
}
165186

166-
uint256 offeringTokenAmountPerPeriod = getOfferingAmountPerPeriod(msg.sender);
167-
offeringToken.safeTransfer(msg.sender, offeringTokenAmountPerPeriod);
168-
169187
userInfo[msg.sender].claimed[harvestPeriod] = true;
170188
// Subtract user debt after refund on initial harvest
171189
if(harvestPeriod == 0) {
172190
totalDebt -= userInfo[msg.sender].amount;
173191
}
192+
193+
uint256 offeringTokenAmountPerPeriod = getOfferingAmountPerPeriod(msg.sender);
194+
offeringToken.safeTransfer(msg.sender, offeringTokenAmountPerPeriod);
195+
174196
emit Harvest(msg.sender, offeringTokenAmountPerPeriod, refundingTokenAmount);
175197
}
176198

@@ -181,16 +203,17 @@ contract IAO is ReentrancyGuard, Initializable {
181203
/// @notice Calculate a users allocation based on the total amount deposited. This is done
182204
/// by first scaling the deposited amount and dividing by the total amount.
183205
/// @param _user Address of the user allocation to look up
184-
function getUserAllocation(address _user) public view returns (uint256) {
206+
/// @notice This function has been deprecated, but leaving in the contract for backwards compatibility.
207+
function getUserAllocation(address _user) external view returns (uint256) {
185208
// avoid division by zero
186209
if(totalAmount == 0) {
187210
return 0;
188211
}
189212

190213
// allocation:
191-
// 1e6 = 100%
192-
// 1e4 = 1%
193-
// 1 = 0.0001%
214+
// 1e12 = 100%
215+
// 1e10 = 1%
216+
// 1e8 = 0.01%
194217
return (userInfo[_user].amount * 1e12 / totalAmount);
195218
}
196219

@@ -205,11 +228,10 @@ contract IAO is ReentrancyGuard, Initializable {
205228

206229
/// @notice Calculate a user's offering amount to be received by multiplying the offering amount by
207230
/// the user allocation percentage.
208-
/// @dev User allocation is scaled up by the ALLOCATION_PRECISION which is scaled down before returning a value.
209231
/// @param _user Address of the user allocation to look up
210232
function getOfferingAmount(address _user) public view returns (uint256) {
211233
if (totalAmount > raisingAmount) {
212-
return (offeringAmount * getUserAllocation(_user)) / 1e12;
234+
return (userInfo[_user].amount * offeringAmount) / totalAmount;
213235
} else {
214236
// Return an offering amount equal to a proportion of the raising amount
215237
return (userInfo[_user].amount * offeringAmount) / raisingAmount;
@@ -223,19 +245,20 @@ contract IAO is ReentrancyGuard, Initializable {
223245

224246
/// @notice Calculate a user's refunding amount to be received by multiplying the raising amount by
225247
/// the user allocation percentage.
226-
/// @dev User allocation is scaled up by the ALLOCATION_PRECISION which is scaled down before returning a value.
227248
/// @param _user Address of the user allocation to look up
228249
function getRefundingAmount(address _user) public view returns (uint256) {
229250
// Users are able to obtain their refund on the first harvest only
230251
if (totalAmount <= raisingAmount || userInfo[_user].refunded == true) {
231252
return 0;
232253
}
233-
uint256 payAmount = (raisingAmount * getUserAllocation(_user)) / 1e12;
234-
return userInfo[_user].amount - payAmount;
254+
uint256 userAmount = userInfo[_user].amount;
255+
uint256 payAmount = (userAmount * raisingAmount) / totalAmount;
256+
return userAmount - payAmount;
235257
}
236258

237259
/// @notice Get the amount of tokens a user is eligible to receive based on current state.
238260
/// @param _user address of user to obtain token status
261+
/// @notice offeringTokensVested should be named offeringTokensVesting. Leaving for backward compatibility
239262
function userTokenStatus(address _user)
240263
public
241264
view
@@ -280,6 +303,7 @@ contract IAO is ReentrancyGuard, Initializable {
280303
);
281304
safeTransferStakeInternal(msg.sender, _stakeTokenAmount);
282305
offeringToken.safeTransfer(msg.sender, _offerAmount);
306+
emit AdminFinalWithdraw(_stakeTokenAmount, _offerAmount);
283307
}
284308

285309
/// @notice Internal function to handle stake token transfers. Depending on the stake
@@ -298,7 +322,7 @@ contract IAO is ReentrancyGuard, Initializable {
298322
require(success, "TransferHelper: NATIVE_TRANSFER_FAILED");
299323
} else {
300324
// Transfer ERC20 to address
301-
IERC20(stakeToken).safeTransfer(_to, _amount);
325+
stakeToken.safeTransfer(_to, _amount);
302326
}
303327
}
304328

0 commit comments

Comments
 (0)