Skip to content

Commit

Permalink
Merge pull request #10 from ApeSwapFinance/feature/linear-vesting
Browse files Browse the repository at this point in the history
IAO Linear Vesting
  • Loading branch information
DeFiFoFum authored Jan 31, 2022
2 parents 110b9a4 + 18fc2b8 commit 27fa6ce
Show file tree
Hide file tree
Showing 20 changed files with 2,355 additions and 88 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
test:
strategy:
matrix:
node: ['12.x', '14.x']
node: ['14.x']
os: [ubuntu-latest]

runs-on: ${{ matrix.os }}
Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,21 @@ This set of contracts is used to run Ape Swap's version of initial farm offering
## Operation
- Each IAO raises a predefined `stakeToken` amount in exchange for a predefined `offeringToken` amount
- Once the `startBlock` arrives, users can supply as much of the raising token as they would like
- Once the `endBlock` arrives, users can withdraw their first harvest period and obtain a refund
- `offeringTokens` are sent to the users based on the percentage of their allocation divided by the number of harvest periods
- Once the `endBlock` arrives, users can withdraw their first harvest and obtain a refund

### Linear Vesting
[IAOLinearVesting.sol](./contracts/IAOLinearVesting.sol)
25% of tokens are released at the `endBlock` of this IAO and the other 75% are distributed linearly between the `endBlock` and `vestingEndBlock`.

- `vestingEndBlock` is set to define when all of the offering tokens will be 100% unlocked.
- A refund of `stakeTokens` when there is an oversubscription of the IAO will be given on the first harvest
- `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
### Period Based Vesting
[IAO.sol](./contracts/IAO.sol)
- `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
- A refund of `stakeTokens` when there is an oversubscription of the IAO will be given on the first harvest of any period
- `offeringTokens` are sent to the users based on the percentage of their allocation divided by the number of harvest periods


# Development

Expand Down
Binary file not shown.
66 changes: 45 additions & 21 deletions contracts/IAO.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,35 @@
pragma solidity 0.8.6;

/*
* ApeSwapFinance
______ ______
/ \ / \
| ▓▓▓▓▓▓\ ______ ______ | ▓▓▓▓▓▓\__ __ __ ______ ______
| ▓▓__| ▓▓/ \ / \| ▓▓___\▓▓ \ | \ | \| \ / \
| ▓▓ ▓▓ ▓▓▓▓▓▓\ ▓▓▓▓▓▓\\▓▓ \| ▓▓ | ▓▓ | ▓▓ \▓▓▓▓▓▓\ ▓▓▓▓▓▓\
| ▓▓▓▓▓▓▓▓ ▓▓ | ▓▓ ▓▓ ▓▓_\▓▓▓▓▓▓\ ▓▓ | ▓▓ | ▓▓/ ▓▓ ▓▓ | ▓▓
| ▓▓ | ▓▓ ▓▓__/ ▓▓ ▓▓▓▓▓▓▓▓ \__| ▓▓ ▓▓_/ ▓▓_/ ▓▓ ▓▓▓▓▓▓▓ ▓▓__/ ▓▓
| ▓▓ | ▓▓ ▓▓ ▓▓\▓▓ \\▓▓ ▓▓\▓▓ ▓▓ ▓▓\▓▓ ▓▓ ▓▓ ▓▓
\▓▓ \▓▓ ▓▓▓▓▓▓▓ \▓▓▓▓▓▓▓ \▓▓▓▓▓▓ \▓▓▓▓▓\▓▓▓▓ \▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓
| ▓▓ | ▓▓
| ▓▓ | ▓▓
\▓▓ \▓▓
* App: https://apeswap.finance
* Medium: https://ape-swap.medium.com
* Twitter: https://twitter.com/ape_swap
* Discord: https://discord.com/invite/apeswap
* Telegram: https://t.me/ape_swap
* Announcements: https://t.me/ape_swap_news
* GitHub: https://github.com/ApeSwapFinance
*/

import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import '@openzeppelin/contracts/security/ReentrancyGuard.sol';
import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol';

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

uint256 constant public HARVEST_PERIODS = 4;
Expand Down Expand Up @@ -61,6 +75,9 @@ contract IAO is ReentrancyGuard, Initializable {
uint256 offeringAmount,
uint256 excessAmount
);
event UpdateOfferingAmount(uint256 previousOfferingAmount, uint256 newOfferingAmount);
event UpdateRaisingAmount(uint256 previousRaisingAmount, uint256 newRaisingAmount);
event AdminFinalWithdraw(uint256 stakeTokenAmount, uint256 offerAmount);
event EmergencySweepWithdraw(address indexed receiver, address indexed token, uint256 balance);


Expand Down Expand Up @@ -91,6 +108,7 @@ contract IAO is ReentrancyGuard, Initializable {
raisingAmount = _raisingAmount;
totalAmount = 0;
adminAddress = _adminAddress;
__ReentrancyGuard_init();
}

modifier onlyAdmin() {
Expand All @@ -106,26 +124,28 @@ contract IAO is ReentrancyGuard, Initializable {
_;
}

function setOfferingAmount(uint256 _offerAmount) public onlyAdmin {
function setOfferingAmount(uint256 _offerAmount) external onlyAdmin {
require(block.number < startBlock, "cannot update during active iao");
emit UpdateOfferingAmount(offeringAmount, _offerAmount);
offeringAmount = _offerAmount;
}

function setRaisingAmount(uint256 _raisingAmount) public onlyAdmin {
function setRaisingAmount(uint256 _raisingAmount) external onlyAdmin {
require(block.number < startBlock, "cannot update during active iao");
emit UpdateRaisingAmount(raisingAmount, _raisingAmount);
raisingAmount = _raisingAmount;
}

/// @notice Deposits native EVM tokens into the IAO contract as per the value sent
/// in the transaction.
function depositNative() external payable onlyActiveIAO {
function depositNative() external payable onlyActiveIAO nonReentrant {
require(isNativeTokenStaking, 'stake token is not native EVM token');
require(msg.value > 0, 'value not > 0');
depositInternal(msg.value);
}

/// @dev Deposit ERC20 tokens with support for reflect tokens
function deposit(uint256 _amount) external onlyActiveIAO {
function deposit(uint256 _amount) external onlyActiveIAO nonReentrant {
require(!isNativeTokenStaking, "stake token is native token, deposit through 'depositNative'");
require(_amount > 0, "_amount not > 0");
uint256 pre = getTotalStakeTokenBalance();
Expand All @@ -135,6 +155,7 @@ contract IAO is ReentrancyGuard, Initializable {
_amount
);
uint256 finalDepositAmount = getTotalStakeTokenBalance() - pre;
require(finalDepositAmount > 0, 'final deposit amount is zero');
depositInternal(finalDepositAmount);
}

Expand Down Expand Up @@ -163,14 +184,15 @@ contract IAO is ReentrancyGuard, Initializable {
safeTransferStakeInternal(msg.sender, refundingTokenAmount);
}

uint256 offeringTokenAmountPerPeriod = getOfferingAmountPerPeriod(msg.sender);
offeringToken.safeTransfer(msg.sender, offeringTokenAmountPerPeriod);

userInfo[msg.sender].claimed[harvestPeriod] = true;
// Subtract user debt after refund on initial harvest
if(harvestPeriod == 0) {
totalDebt -= userInfo[msg.sender].amount;
}

uint256 offeringTokenAmountPerPeriod = getOfferingAmountPerPeriod(msg.sender);
offeringToken.safeTransfer(msg.sender, offeringTokenAmountPerPeriod);

emit Harvest(msg.sender, offeringTokenAmountPerPeriod, refundingTokenAmount);
}

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

// allocation:
// 1e6 = 100%
// 1e4 = 1%
// 1 = 0.0001%
// 1e12 = 100%
// 1e10 = 1%
// 1e8 = 0.01%
return (userInfo[_user].amount * 1e12 / totalAmount);
}

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

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

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

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

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

Expand Down
Loading

0 comments on commit 27fa6ce

Please sign in to comment.