generated from defi-wonderland/solidity-foundry-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: inheriting cliffed OZ Vesting Wallet and using constants approa…
…ch (#5) According to [Connext Forum](https://forum.connext.network/t/rfc-partnership-token-agreements/938), the agreement that Wonderland, Veil and Bootload (other partners have), unlocks at the same time, for every partner, one year after launch, a date that we all know (sept 5th 2023), and have the same 1/13ths schedule. So, inputting `startTime` at constructor makes no logic, since that's sth that can be shared accross all partners, this PR aims to reduce that unnecessary constructor argument. In the process of crafting this PR, a test failed hinting me that we missed a big issue, we need to use Llamapay V2. Luckily, this means no contract changes, and only to the test suite, but because of this test, we've realized that we almost failed BIG time. Pushing this PoC with some TODOs in the codebase, the constructor args are gonna be only beneficiary and amount (only thing that differs between partners), and in the testing suite, we need to be using LlamaPay v2. Let's do this approach: - import the Deploy script in the Integration tests - generically test `ConnextVestingWallet` using portions of `TOTAL_AMOUNT()` (and not precise numbers) - specify test `LlamaVesting` showing the expected limiting behaviours: - launch date - launch + 1yr - 1 - launch + 1yr (e.g. `assertEq(.., 1_920_000 ether)`) - launch + 1yr + 1mth - launch + 1yr + 1yr ![image](https://github.com/defi-wonderland/connext-vesting/assets/84595958/13d698a9-7c29-459b-9121-dd3f7d93d712) The way to do this test is probably run it, see the failing expectations, and input what's expected to receive in the tests, right? like, too lazy to calc, but that's a good thing!! the fact that the dev KNOWS and inputs the expected amount at each date, means that there are no doubts about what's expected to happen, please declare those expectations as `x ether` numbers (having a max delta of `1 ether`) --------- Co-authored-by: Dristpunk <[email protected]>
- Loading branch information
1 parent
6322983
commit e6ae145
Showing
17 changed files
with
1,826 additions
and
491 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.20; | ||
|
||
// solhint-disable-next-line no-unused-import | ||
import {VestingWallet, VestingWalletWithCliff} from './VestingWalletWithCliff.sol'; | ||
|
||
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; | ||
import {Ownable2Step} from '@openzeppelin/contracts/access/Ownable2Step.sol'; | ||
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; | ||
import {IVestingEscrowSimple} from 'interfaces/IVestingEscrowSimple.sol'; | ||
|
||
/** | ||
* @title ConnextVestingWallet | ||
* NOTE: The NEXT tokens will be subject to a twenty-four (24) months unlock schedule as follows: | ||
* 1/13 (~7.69% of the token grant) unlocks at the 1 year mark from NEXT token launch, | ||
* and 1/13 unlocks every month thereafter for 12 months. All tokens are unlocked after 24 months. | ||
* https://forum.connext.network/t/rfc-partnership-token-agreements/938 | ||
*/ | ||
contract ConnextVestingWallet is VestingWalletWithCliff, Ownable2Step { | ||
/** | ||
* --- Constants --- | ||
*/ | ||
uint64 public constant ONE_YEAR = 365 days; | ||
uint64 public constant ONE_MONTH = ONE_YEAR / 12; | ||
uint64 public constant SEPT_05_2023 = 1_693_872_000; | ||
uint64 public constant NEXT_TOKEN_LAUNCH = SEPT_05_2023; | ||
address public constant NEXT_TOKEN = 0xFE67A4450907459c3e1FFf623aA927dD4e28c67a; // Mainnet NEXT token | ||
|
||
/** | ||
* NOTE: The equivalent vesting schedule has a 13 months duration, with a 1 month cliff, | ||
* offsetted to start from `Sept 5th 2024 - 1 month`: At Sept 5th 2024 the cliff | ||
* is triggered unlocking 1/13 of the tokens, and then 1/13 of the tokens will | ||
* be linearly unlocked every month after that. | ||
*/ | ||
uint64 public constant VESTING_DURATION = ONE_YEAR + ONE_MONTH; // 13 months duration | ||
uint64 public constant VESTING_CLIFF_DURATION = ONE_MONTH; // 1 month cliff | ||
uint64 public constant VESTING_OFFSET = ONE_YEAR - ONE_MONTH; // 11 months offset | ||
uint64 public constant VESTING_START_DATE = NEXT_TOKEN_LAUNCH + VESTING_OFFSET; // Sept 5th 2024 - 1 month | ||
|
||
/** | ||
* --- Settable Storage --- | ||
*/ | ||
uint256 public immutable TOTAL_AMOUNT; | ||
|
||
constructor( | ||
address _beneficiary, | ||
uint256 _totalAmount | ||
) VestingWalletWithCliff(_beneficiary, VESTING_START_DATE, VESTING_DURATION, VESTING_CLIFF_DURATION) { | ||
TOTAL_AMOUNT = _totalAmount; | ||
} | ||
|
||
/** | ||
* --- Errors --- | ||
*/ | ||
error NotAllowed(); | ||
error ZeroAddress(); | ||
|
||
/** | ||
* --- Overrides --- | ||
*/ | ||
|
||
/** | ||
* @inheritdoc VestingWallet | ||
* @notice This contract is only meant to vest NEXT tokens | ||
*/ | ||
function vestedAmount(uint64) public view virtual override returns (uint256 _amount) { | ||
return 0; | ||
} | ||
|
||
/** | ||
* @inheritdoc VestingWallet | ||
* @notice This contract is only meant to vest NEXT tokens | ||
*/ | ||
function vestedAmount(address _token, uint64 _timestamp) public view virtual override returns (uint256 _amount) { | ||
if (_token != NEXT_TOKEN) return 0; | ||
|
||
return _vestingSchedule(TOTAL_AMOUNT, _timestamp); | ||
} | ||
|
||
/** | ||
* @inheritdoc VestingWallet | ||
* @notice This contract is only meant to vest NEXT tokens | ||
*/ | ||
function releasable(address _token) public view virtual override returns (uint256 _amount) { | ||
_amount = vestedAmount(_token, uint64(block.timestamp)) - released(_token); | ||
uint256 _balance = IERC20(_token).balanceOf(address(this)); | ||
_amount = _balance < _amount ? _balance : _amount; | ||
} | ||
|
||
/** | ||
* @inheritdoc Ownable2Step | ||
* @dev Override needed by linearization | ||
*/ | ||
function _transferOwnership(address _newOwner) internal virtual override(Ownable2Step, Ownable) { | ||
super._transferOwnership(_newOwner); | ||
} | ||
|
||
/** | ||
* @inheritdoc Ownable2Step | ||
* @dev Override needed by linearization | ||
*/ | ||
function transferOwnership(address _newOwner) public virtual override(Ownable2Step, Ownable) { | ||
super.transferOwnership(_newOwner); | ||
} | ||
|
||
/** | ||
* --- Dust Collector --- | ||
* @notice Collect dust from the contract | ||
* @dev This contract allows to withdraw any token, with the exception of vested NEXT tokens | ||
*/ | ||
function sendDust(IERC20 _token, uint256 _amount, address _to) external onlyOwner { | ||
if (_to == address(0)) revert ZeroAddress(); | ||
if (_token == IERC20(NEXT_TOKEN) && released(NEXT_TOKEN) != TOTAL_AMOUNT) { | ||
revert NotAllowed(); | ||
} | ||
|
||
if (_token == IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) { | ||
// Sending ETH | ||
payable(_to).transfer(_amount); | ||
} else { | ||
// Sending ERC20s | ||
_token.transfer(_to, _amount); | ||
} | ||
} | ||
|
||
/** | ||
* --- Claim --- | ||
* @notice Claim tokens from Llama Vesting contract | ||
* @dev This func is needed because only the recipients can claim | ||
*/ | ||
function claim(address _llamaVestAddress) external { | ||
IVestingEscrowSimple(_llamaVestAddress).claim(address(this)); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.20; | ||
|
||
import {VestingWallet} from '@openzeppelin/contracts/finance/VestingWallet.sol'; | ||
|
||
contract VestingWalletWithCliff is VestingWallet { | ||
uint64 private immutable _CLIFF; | ||
|
||
constructor( | ||
address _beneficiary, | ||
uint64 _vestingStartTimestamp, | ||
uint64 _durationSeconds, | ||
uint64 _cliffDurationSeconds | ||
) VestingWallet(_beneficiary, _vestingStartTimestamp, _durationSeconds) { | ||
_CLIFF = _vestingStartTimestamp + _cliffDurationSeconds; | ||
} | ||
|
||
/** | ||
* @dev Getter for the cliff timestamp. | ||
*/ | ||
function cliff() public view virtual returns (uint256 _timestamp) { | ||
return _CLIFF; | ||
} | ||
|
||
/** | ||
* @dev Virtual implementation of the vesting formula. This returns the amount vested, as a function of time, for | ||
* an asset given its total historical allocation. | ||
*/ | ||
function _vestingSchedule( | ||
uint256 _totalAllocation, | ||
uint64 _timestamp | ||
) internal view virtual override returns (uint256 _amount) { | ||
if (_timestamp < cliff()) { | ||
return 0; | ||
} else if (_timestamp >= end()) { | ||
return _totalAllocation; | ||
} else { | ||
return (_totalAllocation * (_timestamp - start())) / duration(); | ||
} | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.