diff --git a/contracts/src/TLC.1.sol b/contracts/src/TLC.1.sol deleted file mode 100644 index 1da1fb6b..00000000 --- a/contracts/src/TLC.1.sol +++ /dev/null @@ -1,38 +0,0 @@ -//SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.10; - -import "./components/ERC20VestableVotesUpgradeable.1.sol"; -import "./interfaces/ITLC.1.sol"; - -/// @title TLC (v1) -/// @author Alluvial -/// @notice The TLC token has a max supply of 1,000,000,000 and 18 decimal places. -/// @notice Upon deployment, all minted tokens are send to account provided at construction, in charge of creating the vesting schedules -/// @notice The contract is based on ERC20Votes by OpenZeppelin. Users need to delegate their voting power to someone or themselves to be able to vote. -/// @notice The contract contains vesting logics allowing vested users to still be able to delegate their voting power while their tokens are held in an escrow -contract TLCV1 is ERC20VestableVotesUpgradeableV1, ITLCV1 { - // Token information - string internal constant NAME = "Liquid Collective"; - string internal constant SYMBOL = "TLC"; - - // Initial supply of token minted - uint256 internal constant INITIAL_SUPPLY = 1_000_000_000e18; // 1 billion TLC - - /// @notice Disables implementation initialization - constructor() { - _disableInitializers(); - } - - /// @inheritdoc ITLCV1 - function initTLCV1(address _account) external initializer { - LibSanitize._notZeroAddress(_account); - __ERC20Permit_init(NAME); - __ERC20_init(NAME, SYMBOL); - _mint(_account, INITIAL_SUPPLY); - } - - /// @inheritdoc ITLCV1 - function migrateVestingSchedules() external reinitializer(2) { - ERC20VestableVotesUpgradeableV1.migrateVestingSchedulesFromV1ToV2(); - } -} diff --git a/contracts/src/components/ERC20VestableVotesUpgradeable.1.sol b/contracts/src/components/ERC20VestableVotesUpgradeable.1.sol deleted file mode 100644 index 8fd7cb6c..00000000 --- a/contracts/src/components/ERC20VestableVotesUpgradeable.1.sol +++ /dev/null @@ -1,471 +0,0 @@ -//SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.10; - -import "openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; -import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; - -import "../interfaces/components/IERC20VestableVotesUpgradeable.1.sol"; - -import "../state/tlc/VestingSchedules.2.sol"; -import "../state/tlc/IgnoreGlobalUnlockSchedule.sol"; - -import "../libraries/LibSanitize.sol"; -import "../libraries/LibUint256.sol"; - -/// @title ERC20VestableVotesUpgradeableV1 -/// @author Alluvial -/// @notice This is an ERC20 extension that -/// @notice - can be used as source of vote power (inherited from OpenZeppelin ERC20VotesUpgradeable) -/// @notice - can delegate vote power from an account to another account (inherited from OpenZeppelin ERC20VotesUpgradeable) -/// @notice - can manage token vestings: ownership is progressively transferred to a beneficiary according to a vesting schedule -/// @notice - keeps a history (checkpoints) of each account's vote power -/// @notice -/// @notice Notes from OpenZeppelin [ERC20VotesUpgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol) -/// @notice - vote power can be delegated either by calling the {delegate} function, or by providing a signature to be used with {delegateBySig} -/// @notice - keeps a history (checkpoints) of each account's vote power -/// @notice - power can be queried through the public accessors {getVotes} and {getPastVotes}. -/// @notice - by default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it -/// @notice requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. -/// @notice -/// @notice Notes about token vesting -/// @notice - any token holder can call the method {createVestingSchedule} in order to transfer tokens to a beneficiary according to a vesting schedule. When -/// @notice creating a vesting schedule, tokens are transferred to an escrow that holds the token while the vesting progresses. Voting power of the escrowed token is delegated to the -/// @notice beneficiary or a delegatee account set by the vesting schedule creator -/// @notice - the schedule beneficiary call {releaseVestingSchedule} to get vested tokens transferred from escrow -/// @notice - the schedule creator can revoke a revocable schedule by calling {revokeVestingSchedule} in which case the non-vested tokens are transfered from the escrow back to the creator -/// @notice - the schedule beneficiary can delegate escrow voting power to any account by calling {delegateVestingEscrow} -/// @notice -/// @notice Vesting schedule attributes are -/// @notice - start : start time of the vesting period -/// @notice - cliff duration: duration before which first tokens gets ownable -/// @notice - total duration: duration of the entire vesting (sum of all vesting period durations) -/// @notice - period duration: duration of a single period of vesting -/// @notice - lock duration: duration before tokens gets unlocked. can exceed the duration of the vesting chedule -/// @notice - amount: amount of tokens granted by the vesting schedule -/// @notice - beneficiary: beneficiary of tokens after they are releaseVestingScheduled -/// @notice - revocable: whether the schedule can be revoked -/// @notice - ignoreGlobalUnlockSchedule: whether the schedule should ignore the global unlock schedule -/// @notice -/// @notice Vesting schedule -/// @notice - if currentTime < cliff: vestedToken = 0 -/// @notice - if cliff <= currentTime < end: vestedToken = (vestedPeriodCount(currentTime) * periodDuration * amount) / totalDuration -/// @notice - if end < currentTime: vestedToken = amount -/// @notice -/// @notice Global unlock schedule -/// @notice - the global unlock schedule releases 1/24th of the total scheduled amount every month after the local lock end -/// @notice - the local lock end is the end of the lock period of the vesting schedule -/// @notice - the global unlock schedule is ignored if the vesting schedule has the ignoreGlobalUnlockSchedule flag set to true -/// @notice - the global unlock schedule is only a cap on the vested funds that can be withdrawn, it does not alter the vesting -/// @notice -/// @notice Remark: After cliff new tokens get vested at the end of each period -/// @notice -/// @notice Vested token & lock period -/// @notice - a vested token is a token that will be eventually releasable from the escrow to the beneficiary once the lock period is over -/// @notice - lock period prevents beneficiary from releasing vested tokens before the lock period ends. Vested tokens -/// @notice will eventually be releasable once the lock period is over -/// @notice -/// @notice Example: Joe gets a vesting starting on Jan 1st 2022 with duration of 1 year and a lock period of 2 years. -/// @notice On Jan 1st 2023, Joe will have all tokens vested but can not yet release it due to the lock period. -/// @notice On Jan 1st 2024, lock period is over and Joe can release all tokens. -abstract contract ERC20VestableVotesUpgradeableV1 is - Initializable, - IERC20VestableVotesUpgradeableV1, - ERC20VotesUpgradeable -{ - // internal used to compute the address of the escrow - bytes32 internal constant ESCROW = bytes32(uint256(keccak256("escrow")) - 1); - - function __ERC20VestableVotes_init() internal onlyInitializing {} - - function __ERC20VestableVotes_init_unchained() internal onlyInitializing {} - - /// @notice This method migrates the state of the vesting schedules from V1 to V2 - /// @dev This method should be used if deployment with the old version using V1 state models is upgraded - function migrateVestingSchedulesFromV1ToV2() internal { - if (VestingSchedulesV2.getCount() == 0) { - uint256 existingV1VestingSchedules = VestingSchedulesV1.getCount(); - for (uint256 idx; idx < existingV1VestingSchedules;) { - uint256 scheduleAmount = VestingSchedulesV1.get(idx).amount; - uint256 releasedAmount = - scheduleAmount - LibUint256.min(balanceOf(_deterministicVestingEscrow(idx)), scheduleAmount); - VestingSchedulesV2.migrateVestingScheduleFromV1(idx, releasedAmount); - unchecked { - ++idx; - } - } - } - } - - /// @inheritdoc IERC20VestableVotesUpgradeableV1 - function getVestingSchedule(uint256 _index) external view returns (VestingSchedulesV2.VestingSchedule memory) { - return VestingSchedulesV2.get(_index); - } - - /// @inheritdoc IERC20VestableVotesUpgradeableV1 - function isGlobalUnlockedScheduleIgnored(uint256 _index) external view returns (bool) { - return IgnoreGlobalUnlockSchedule.get(_index); - } - - /// @inheritdoc IERC20VestableVotesUpgradeableV1 - function getVestingScheduleCount() external view returns (uint256) { - return VestingSchedulesV2.getCount(); - } - - /// @inheritdoc IERC20VestableVotesUpgradeableV1 - function vestingEscrow(uint256 _index) external view returns (address) { - return _deterministicVestingEscrow(_index); - } - - /// @inheritdoc IERC20VestableVotesUpgradeableV1 - function computeVestingReleasableAmount(uint256 _index) external view returns (uint256) { - VestingSchedulesV2.VestingSchedule memory vestingSchedule = VestingSchedulesV2.get(_index); - return _computeVestingReleasableAmount(vestingSchedule, false, _index); - } - - /// @inheritdoc IERC20VestableVotesUpgradeableV1 - function computeVestingVestedAmount(uint256 _index) external view returns (uint256) { - VestingSchedulesV2.VestingSchedule memory vestingSchedule = VestingSchedulesV2.get(_index); - return _computeVestedAmount(vestingSchedule, LibUint256.min(_getCurrentTime(), vestingSchedule.end)); - } - - /// @inheritdoc IERC20VestableVotesUpgradeableV1 - function createVestingSchedule( - uint64 _start, - uint32 _cliffDuration, - uint32 _duration, - uint32 _periodDuration, - uint32 _lockDuration, - bool _revocable, - uint256 _amount, - address _beneficiary, - address _delegatee, - bool _ignoreGlobalUnlockSchedule - ) external returns (uint256) { - return _createVestingSchedule( - msg.sender, - _beneficiary, - _delegatee, - _start, - _cliffDuration, - _duration, - _periodDuration, - _lockDuration, - _revocable, - _amount, - _ignoreGlobalUnlockSchedule - ); - } - - /// @inheritdoc IERC20VestableVotesUpgradeableV1 - function revokeVestingSchedule(uint256 _index, uint64 _end) external returns (uint256) { - return _revokeVestingSchedule(_index, _end); - } - - /// @inheritdoc IERC20VestableVotesUpgradeableV1 - function releaseVestingSchedule(uint256 _index) external returns (uint256) { - return _releaseVestingSchedule(_index); - } - - /// @inheritdoc IERC20VestableVotesUpgradeableV1 - function delegateVestingEscrow(uint256 _index, address _delegatee) external returns (bool) { - return _delegateVestingEscrow(_index, _delegatee); - } - - /// @notice Creates a new vesting schedule - /// @param _creator creator of the token vesting - /// @param _beneficiary beneficiary of tokens after they are releaseVestingScheduled - /// @param _delegatee address of the delegate escrowed tokens votes to (if address(0) then it defaults to the beneficiary) - /// @param _start start time of the vesting period - /// @param _cliffDuration duration before which first tokens gets ownable - /// @param _duration duration of the entire vesting (sum of all vesting period durations) - /// @param _periodDuration duration of a single period of vesting - /// @param _lockDuration duration before tokens gets unlocked. can exceed the duration of the vesting chedule - /// @param _revocable whether the schedule can be revoked - /// @param _amount amount of tokens granted by the vesting schedule - /// @param _ignoreGlobalUnlockSchedule whether the schedule should ignore the global unlock schedule - /// @return index of the created vesting schedule - function _createVestingSchedule( - address _creator, - address _beneficiary, - address _delegatee, - uint64 _start, - uint32 _cliffDuration, - uint32 _duration, - uint32 _periodDuration, - uint32 _lockDuration, - bool _revocable, - uint256 _amount, - bool _ignoreGlobalUnlockSchedule - ) internal returns (uint256) { - if (balanceOf(_creator) < _amount) { - revert UnsufficientVestingScheduleCreatorBalance(); - } - - // validate schedule parameters - if (_beneficiary == address(0)) { - revert InvalidVestingScheduleParameter("Vesting schedule beneficiary must be non zero address"); - } - - if (_duration == 0) { - revert InvalidVestingScheduleParameter("Vesting schedule duration must be > 0"); - } - - if (_amount == 0) { - revert InvalidVestingScheduleParameter("Vesting schedule amount must be > 0"); - } - - if (_periodDuration == 0) { - revert InvalidVestingScheduleParameter("Vesting schedule period must be > 0"); - } - - if (_duration % _periodDuration > 0) { - revert InvalidVestingScheduleParameter("Vesting schedule duration must split in exact periods"); - } - - if (_cliffDuration % _periodDuration > 0) { - revert InvalidVestingScheduleParameter("Vesting schedule cliff duration must split in exact periods"); - } - - if (_cliffDuration > _duration) { - revert InvalidVestingScheduleParameter( - "Vesting schedule duration must be greater than or equal to the cliff duration" - ); - } - - if ((_amount * _periodDuration) / _duration == 0) { - revert InvalidVestingScheduleParameter("Vesting schedule amount too low for duration and period"); - } - - // if input start time is 0 then default to the current block time - if (_start == 0) { - _start = uint64(block.timestamp); - } - - // create new vesting schedule - VestingSchedulesV2.VestingSchedule memory vestingSchedule = VestingSchedulesV2.VestingSchedule({ - start: _start, - end: _start + _duration, - lockDuration: _lockDuration, - cliffDuration: _cliffDuration, - duration: _duration, - periodDuration: _periodDuration, - amount: _amount, - creator: _creator, - beneficiary: _beneficiary, - revocable: _revocable, - releasedAmount: 0 - }); - uint256 index = VestingSchedulesV2.push(vestingSchedule) - 1; - - IgnoreGlobalUnlockSchedule.set(index, _ignoreGlobalUnlockSchedule); - - // compute escrow address that will hold the token during the vesting - address escrow = _deterministicVestingEscrow(index); - - // transfer tokens to the escrow - _transfer(_creator, escrow, _amount); - - // delegate escrow tokens - if (_delegatee == address(0)) { - // default delegatee to beneficiary address - _delegate(escrow, _beneficiary); - } else { - _delegate(escrow, _delegatee); - } - - emit CreatedVestingSchedule(index, _creator, _beneficiary, _amount); - - return index; - } - - /// @notice Revoke vesting schedule - /// @param _index Index of the vesting schedule to revoke - /// @param _end End date for the schedule - /// @return returnedAmount amount returned to the vesting schedule creator - function _revokeVestingSchedule(uint256 _index, uint64 _end) internal returns (uint256) { - if (_end == 0) { - // if end time is 0 then default to current block time - _end = uint64(block.timestamp); - } else if (_end < block.timestamp) { - revert VestingScheduleNotRevocableInPast(); - } - - VestingSchedulesV2.VestingSchedule storage vestingSchedule = VestingSchedulesV2.get(_index); - if (!vestingSchedule.revocable) { - revert VestingScheduleNotRevocable(); - } - - // revoked end date MUST be after vesting schedule start and before current end - if ((_end < vestingSchedule.start) || (vestingSchedule.end < _end)) { - revert InvalidRevokedVestingScheduleEnd(); - } - - // only creator can revoke vesting schedule - if (vestingSchedule.creator != msg.sender) { - revert LibErrors.Unauthorized(msg.sender); - } - - // return tokens that will never be vested to creator - uint256 vestedAmountAtOldEnd = _computeVestedAmount(vestingSchedule, vestingSchedule.end); - uint256 vestedAmountAtNewEnd = _computeVestedAmount(vestingSchedule, _end); - uint256 returnedAmount = vestedAmountAtOldEnd - vestedAmountAtNewEnd; - if (returnedAmount > 0) { - address escrow = _deterministicVestingEscrow(_index); - _transfer(escrow, msg.sender, returnedAmount); - } - - // set schedule end - vestingSchedule.end = uint64(_end); - - emit RevokedVestingSchedule(_index, returnedAmount, _end); - - return returnedAmount; - } - - /// @notice Release vesting schedule - /// @param _index Index of the vesting schedule to release - /// @return released amount - function _releaseVestingSchedule(uint256 _index) internal returns (uint256) { - VestingSchedulesV2.VestingSchedule storage vestingSchedule = VestingSchedulesV2.get(_index); - - // only beneficiary can release - if (msg.sender != vestingSchedule.beneficiary) { - revert LibErrors.Unauthorized(msg.sender); - } - - // compute releasable amount (taking into account local lock and global unlock schedule if it applies) - uint256 releasableAmount = _computeVestingReleasableAmount(vestingSchedule, true, _index); - if (releasableAmount == 0) { - revert ZeroReleasableAmount(); - } - - address escrow = _deterministicVestingEscrow(_index); - - // transfer all releasable token to the beneficiary - _transfer(escrow, msg.sender, releasableAmount); - - // increase released amount as per the release - vestingSchedule.releasedAmount += releasableAmount; - - emit ReleasedVestingSchedule(_index, releasableAmount); - - return releasableAmount; - } - - /// @notice Delegate vesting escrowed tokens - /// @param _index index of the vesting schedule - /// @param _delegatee address to delegate the token to - /// @return True on success - function _delegateVestingEscrow(uint256 _index, address _delegatee) internal returns (bool) { - VestingSchedulesV2.VestingSchedule storage vestingSchedule = VestingSchedulesV2.get(_index); - - // only beneficiary can delegate - if (msg.sender != vestingSchedule.beneficiary) { - revert LibErrors.Unauthorized(msg.sender); - } - - // update delegatee - address escrow = _deterministicVestingEscrow(_index); - address oldDelegatee = delegates(escrow); - _delegate(escrow, _delegatee); - - emit DelegatedVestingEscrow(_index, oldDelegatee, _delegatee, msg.sender); - - return true; - } - - /// @notice Internal utility to compute the unique escrow deterministic address - /// @param _index index of the vesting schedule - /// @return escrow The deterministic escrow address for the vesting schedule index - function _deterministicVestingEscrow(uint256 _index) internal view returns (address escrow) { - bytes32 hash = keccak256(abi.encodePacked(address(this), ESCROW, _index)); - return address(uint160(uint256(hash))); - } - - /// @notice Computes the releasable amount of tokens for a vesting schedule. - /// @param _vestingSchedule vesting schedule to compute releasable tokens for - /// @param _revertIfLocked if true will revert if the schedule is locked - /// @param _index index of the vesting schedule - /// @return amount of release tokens - function _computeVestingReleasableAmount( - VestingSchedulesV2.VestingSchedule memory _vestingSchedule, - bool _revertIfLocked, - uint256 _index - ) internal view returns (uint256) { - uint256 time = _getCurrentTime(); - if (time < (_vestingSchedule.start + _vestingSchedule.lockDuration)) { - if (_revertIfLocked) { - revert VestingScheduleIsLocked(); - } else { - return 0; - } - } - uint256 releasedAmount = _vestingSchedule.releasedAmount; - uint256 vestedAmount = - _computeVestedAmount(_vestingSchedule, time > _vestingSchedule.end ? _vestingSchedule.end : time); - if (vestedAmount > releasedAmount) { - if (!IgnoreGlobalUnlockSchedule.get(_index)) { - uint256 globalUnlocked = _computeGlobalUnlocked( - _vestingSchedule.amount, time - (_vestingSchedule.start + _vestingSchedule.lockDuration) - ); - if (releasedAmount > globalUnlocked) { - revert GlobalUnlockUnderlfow(); - } - return LibUint256.min(vestedAmount, globalUnlocked) - releasedAmount; - } - unchecked { - return vestedAmount - releasedAmount; - } - } - - return 0; - } - - /// @notice Computes the vested amount of tokens for a vesting schedule. - /// @param _vestingSchedule vesting schedule to compute vested tokens for - /// @param _time time to compute the vested amount at - /// @return amount of release tokens - function _computeVestedAmount(VestingSchedulesV2.VestingSchedule memory _vestingSchedule, uint256 _time) - internal - pure - returns (uint256) - { - if (_time < _vestingSchedule.start + _vestingSchedule.cliffDuration) { - // pre-cliff no tokens have been vested - return 0; - } else if (_time >= _vestingSchedule.start + _vestingSchedule.duration) { - // post vesting all tokens have been vested - return _vestingSchedule.amount; - } else { - uint256 timeFromStart = _time - _vestingSchedule.start; - - // compute tokens vested for completely elapsed periods - uint256 vestedDuration = timeFromStart - timeFromStart % _vestingSchedule.periodDuration; - - return (vestedDuration * _vestingSchedule.amount) / _vestingSchedule.duration; - } - } - - /// @notice Computes the unlocked amount of tokens for a vesting schedule according to the global unlock schedule - /// @param scheduledAmount amount of tokens scheduled for the vesting schedule - /// @param timeSinceLocalLockEnd time since the local lock end - /// @return amount of unlocked tokens - function _computeGlobalUnlocked(uint256 scheduledAmount, uint256 timeSinceLocalLockEnd) - internal - pure - returns (uint256) - { - // 1/24 th of the amount per month - uint256 unlockedAmount = (scheduledAmount / 24) * (timeSinceLocalLockEnd / (365 days / 12)); - if (unlockedAmount > scheduledAmount) { - return scheduledAmount; - } - return unlockedAmount; - } - - /// @notice Returns current time - /// @return The current time - function _getCurrentTime() internal view virtual returns (uint256) { - return block.timestamp; - } -} diff --git a/contracts/src/interfaces/ITLC.1.sol b/contracts/src/interfaces/ITLC.1.sol deleted file mode 100644 index dd5e7d2c..00000000 --- a/contracts/src/interfaces/ITLC.1.sol +++ /dev/null @@ -1,19 +0,0 @@ -//SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.10; - -import "openzeppelin-contracts-upgradeable/contracts/token/ERC20/IERC20Upgradeable.sol"; -import "openzeppelin-contracts-upgradeable/contracts/governance/utils/IVotesUpgradeable.sol"; - -import "./components/IERC20VestableVotesUpgradeable.1.sol"; - -/// @title TLC Interface (v1) -/// @author Alluvial -/// @notice TLC token interface -interface ITLCV1 is IERC20VestableVotesUpgradeableV1, IVotesUpgradeable, IERC20Upgradeable { - /// @notice Initializes the TLC Token - /// @param _account The initial account to grant all the minted tokens - function initTLCV1(address _account) external; - - /// @notice Migrates the vesting schedule state structures - function migrateVestingSchedules() external; -} diff --git a/contracts/src/interfaces/components/IERC20VestableVotesUpgradeable.1.sol b/contracts/src/interfaces/components/IERC20VestableVotesUpgradeable.1.sol deleted file mode 100644 index 166e10f6..00000000 --- a/contracts/src/interfaces/components/IERC20VestableVotesUpgradeable.1.sol +++ /dev/null @@ -1,140 +0,0 @@ -//SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.10; - -import "../../state/tlc/VestingSchedules.2.sol"; - -/// @title ERC20 Vestable Votes Upgradeable Interface(v1) -/// @author Alluvial -/// @notice This interface exposes methods to manage vestings -interface IERC20VestableVotesUpgradeableV1 { - /// @notice A new vesting schedule has been created - /// @param index Vesting schedule index - /// @param creator Creator of the vesting schedule - /// @param beneficiary Vesting beneficiary address - /// @param amount Vesting schedule amount - event CreatedVestingSchedule(uint256 index, address indexed creator, address indexed beneficiary, uint256 amount); - - /// @notice Vesting schedule has been released - /// @param index Vesting schedule index - /// @param releasedAmount Amount of tokens released to the beneficiary - event ReleasedVestingSchedule(uint256 index, uint256 releasedAmount); - - /// @notice Vesting schedule has been revoked - /// @param index Vesting schedule index - /// @param returnedAmount Amount of tokens returned to the creator - /// @param newEnd New end timestamp after revoke action - event RevokedVestingSchedule(uint256 index, uint256 returnedAmount, uint256 newEnd); - - /// @notice Vesting escrow has been delegated - /// @param index Vesting schedule index - /// @param oldDelegatee old delegatee - /// @param newDelegatee new delegatee - /// @param beneficiary vesting schedule beneficiary - event DelegatedVestingEscrow( - uint256 index, address indexed oldDelegatee, address indexed newDelegatee, address indexed beneficiary - ); - - /// @notice Vesting schedule creator has unsufficient balance to create vesting schedule - error UnsufficientVestingScheduleCreatorBalance(); - - /// @notice Invalid parameter for a vesting schedule - error InvalidVestingScheduleParameter(string msg); - - /// @notice Attempt to revoke a schedule in the past - error VestingScheduleNotRevocableInPast(); - - /// @notice The vesting schedule is not revocable - error VestingScheduleNotRevocable(); - - /// @notice The vesting schedule is locked - error VestingScheduleIsLocked(); - - /// @notice Attempt to revoke a vesting schedule with an invalid end parameter - error InvalidRevokedVestingScheduleEnd(); - - /// @notice No token to release - error ZeroReleasableAmount(); - - /// @notice Underflow in global unlock logic (should never happen) - error GlobalUnlockUnderlfow(); - - /// @notice Get vesting schedule - /// @dev The vesting schedule structure represents a static configuration used to compute the desired - /// @dev vesting details of a beneficiary at all times. The values won't change even after tokens are released. - /// @dev The only dynamic field of the structure is end, and is updated whenever a vesting schedule is revoked - /// @param _index Index of the vesting schedule - function getVestingSchedule(uint256 _index) external view returns (VestingSchedulesV2.VestingSchedule memory); - - /// @notice Get vesting global unlock schedule activation status for a vesting schedule - /// @param _index Index of the vesting schedule - /// @return true if the vesting schedule should ignore the global unlock schedule - function isGlobalUnlockedScheduleIgnored(uint256 _index) external view returns (bool); - - /// @notice Get count of vesting schedules - /// @return count of vesting schedules - function getVestingScheduleCount() external view returns (uint256); - - /// @notice Get the address of the escrow for a vesting schedule - /// @param _index Index of the vesting schedule - /// @return address of the escrow - function vestingEscrow(uint256 _index) external view returns (address); - - /// @notice Computes the releasable amount of tokens for a vesting schedule. - /// @param _index index of the vesting schedule - /// @return amount of releasable tokens - function computeVestingReleasableAmount(uint256 _index) external view returns (uint256); - - /// @notice Computes the vested amount of tokens for a vesting schedule. - /// @param _index index of the vesting schedule - /// @return amount of vested tokens - function computeVestingVestedAmount(uint256 _index) external view returns (uint256); - - /// @notice Creates a new vesting schedule - /// @notice There may delay between the time a user should start vesting tokens and the time the vesting schedule is actually created on the contract. - /// @notice Typically a user joins the Liquid Collective but some weeks pass before the user gets all legal agreements in place and signed for the - /// @notice token grant emission to happen. In this case, the vesting schedule created for the token grant would start on the join date which is in the past. - /// @dev As vesting schedules can be created in the past, this means that you should be careful when creating a vesting schedule and what duration parameters - /// @dev you use as this contract would allow creating a vesting schedule in the past and even a vesting schedule that has already ended. - /// @param _start start time of the vesting - /// @param _cliffDuration duration to vesting cliff (in seconds) - /// @param _duration total vesting schedule duration after which all tokens are vested (in seconds) - /// @param _periodDuration duration of a period after which new tokens unlock (in seconds) - /// @param _lockDuration duration during which tokens are locked (in seconds) - /// @param _revocable whether the vesting schedule is revocable or not - /// @param _amount amount of token attributed by the vesting schedule - /// @param _beneficiary address of the beneficiary of the tokens - /// @param _delegatee address to delegate escrow voting power to - /// @param _ignoreGlobalUnlockSchedule whether the vesting schedule should ignore the global lock - /// @return index of the created vesting schedule - function createVestingSchedule( - uint64 _start, - uint32 _cliffDuration, - uint32 _duration, - uint32 _periodDuration, - uint32 _lockDuration, - bool _revocable, - uint256 _amount, - address _beneficiary, - address _delegatee, - bool _ignoreGlobalUnlockSchedule - ) external returns (uint256); - - /// @notice Revoke vesting schedule - /// @param _index Index of the vesting schedule to revoke - /// @param _end End date for the schedule - /// @return returnedAmount amount returned to the vesting schedule creator - function revokeVestingSchedule(uint256 _index, uint64 _end) external returns (uint256 returnedAmount); - - /// @notice Release vesting schedule - /// @notice When tokens are released from the escrow, the delegated address of the escrow will see its voting power decrease. - /// @notice The beneficiary has to make sure its delegation parameters are set properly to be able to use/delegate the voting power of its balance. - /// @param _index Index of the vesting schedule to release - /// @return released amount - function releaseVestingSchedule(uint256 _index) external returns (uint256); - - /// @notice Delegate vesting escrowed tokens - /// @param _index index of the vesting schedule - /// @param _delegatee address to delegate the token to - /// @return True on success - function delegateVestingEscrow(uint256 _index, address _delegatee) external returns (bool); -} diff --git a/contracts/test/TLC.1.t.sol b/contracts/test/TLC.1.t.sol deleted file mode 100644 index dfab6850..00000000 --- a/contracts/test/TLC.1.t.sol +++ /dev/null @@ -1,51 +0,0 @@ -//SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "../src/TLC.1.sol"; -import "../src/TUPProxy.sol"; -import "forge-std/Test.sol"; - -contract TLCTests is Test { - TLCV1 internal tlcImplem; - TLCV1 internal tlc; - - address internal escrowImplem; - address internal initAccount; - address internal bob; - address internal joe; - address internal admin; - - function setUp() public { - initAccount = makeAddr("init"); - bob = makeAddr("bob"); - joe = makeAddr("joe"); - admin = makeAddr("admin"); - - tlcImplem = new TLCV1(); - tlc = TLCV1( - address( - new TUPProxy(address(tlcImplem), admin, abi.encodeWithSelector(tlcImplem.initTLCV1.selector, initAccount)) - ) - ); - tlc.migrateVestingSchedules(); - } - - function testImplementationInitialization() public { - vm.expectRevert("Initializable: contract is already initialized"); - tlcImplem.initTLCV1(initAccount); - } - - function testName() public view { - assert(keccak256(bytes(tlc.name())) == keccak256("Liquid Collective")); - } - - function testSymbol() public view { - assert(keccak256(bytes(tlc.symbol())) == keccak256("TLC")); - } - - function testInitialSupplyAndBalance() public view { - assert(tlc.totalSupply() == 1_000_000_000e18); - assert(tlc.balanceOf(initAccount) == tlc.totalSupply()); - } -} diff --git a/contracts/test/components/ERC20VestableVotesUpgradeable.1.t.sol b/contracts/test/components/ERC20VestableVotesUpgradeable.1.t.sol deleted file mode 100644 index 7b58c255..00000000 --- a/contracts/test/components/ERC20VestableVotesUpgradeable.1.t.sol +++ /dev/null @@ -1,1497 +0,0 @@ -//SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.10; - -import "../../src/components/ERC20VestableVotesUpgradeable.1.sol"; -import "forge-std/Test.sol"; - -contract TestToken is ERC20VestableVotesUpgradeableV1 { - // Token information - string internal constant NAME = "Test Token"; - string internal constant SYMBOL = "TT"; - - // Initial supply of token minted - uint256 internal constant INITIAL_SUPPLY = 1_000_000_000e18; // 1 billion TLC - - function initTestTokenV1(address _account) external initializer { - LibSanitize._notZeroAddress(_account); - __ERC20Permit_init(NAME); - __ERC20_init(NAME, SYMBOL); - _mint(_account, INITIAL_SUPPLY); - } - - function _maxSupply() internal pure override returns (uint224) { - return type(uint224).max; - } - - function migrateVestingSchedules() external reinitializer(2) { - if (VestingSchedulesV2.getCount() == 0) { - uint256 existingV1VestingSchedules = VestingSchedulesV1.getCount(); - for (uint256 idx; idx < existingV1VestingSchedules;) { - uint256 scheduleAmount = VestingSchedulesV1.get(idx).amount; - uint256 releasedAmount = - scheduleAmount - LibUint256.min(balanceOf(_deterministicVestingEscrow(idx)), scheduleAmount); - VestingSchedulesV2.migrateVestingScheduleFromV1(idx, releasedAmount); - unchecked { - ++idx; - } - } - } - } - - function debugPushV1VestingSchedule( - uint64 start, - uint64 end, - uint32 cliffDuration, - uint32 lockDuration, - uint32 duration, - uint32 periodDuration, - uint128 amount, - address creator, - address beneficiary, - bool revocable - ) external { - VestingSchedulesV1.push( - VestingSchedulesV1.VestingSchedule({ - start: start, - end: end, - cliffDuration: cliffDuration, - lockDuration: lockDuration, - duration: duration, - periodDuration: periodDuration, - amount: amount, - creator: creator, - beneficiary: beneficiary, - revocable: revocable - }) - ); - _mint(_deterministicVestingEscrow(VestingSchedulesV1.getCount() - 1), amount); - } -} - -contract ERC20VestableVotesUpgradeableV1ToV2Migration is Test { - TestToken internal tt; - - address internal escrowImplem; - address internal initAccount; - address internal bob; - address internal joe; - address internal alice; - - function setUp() public { - initAccount = makeAddr("init"); - bob = makeAddr("bob"); - joe = makeAddr("joe"); - alice = makeAddr("alice"); - - tt = new TestToken(); - tt.initTestTokenV1(initAccount); - } - - function test_migrateTwice() external { - tt.migrateVestingSchedules(); - vm.expectRevert("Initializable: contract is already initialized"); - tt.migrateVestingSchedules(); - } - - function test_migrateAndInspectVestingSchedules( - uint64 start, - uint64 end, - uint32 cliffDuration, - uint32 lockDuration, - uint32 duration, - uint32 periodDuration, - uint128 amount, - address creator, - address beneficiary, - bool revocable - ) external { - tt.debugPushV1VestingSchedule( - start, end, cliffDuration, lockDuration, duration, periodDuration, amount, creator, beneficiary, revocable - ); - tt.debugPushV1VestingSchedule( - start, end, cliffDuration, lockDuration, duration, periodDuration, amount, creator, beneficiary, revocable - ); - tt.migrateVestingSchedules(); - { - VestingSchedulesV2.VestingSchedule memory vs0 = tt.getVestingSchedule(0); - assertEq(vs0.start, start); - assertEq(vs0.end, end); - assertEq(vs0.cliffDuration, cliffDuration); - assertEq(vs0.lockDuration, lockDuration); - assertEq(vs0.duration, duration); - assertEq(vs0.periodDuration, periodDuration); - assertEq(vs0.amount, amount); - assertEq(vs0.creator, creator); - assertEq(vs0.beneficiary, beneficiary); - assertEq(vs0.revocable, revocable); - assertEq(vs0.releasedAmount, 0); - } - { - VestingSchedulesV2.VestingSchedule memory vs1 = tt.getVestingSchedule(1); - assertEq(vs1.start, start); - assertEq(vs1.end, end); - assertEq(vs1.cliffDuration, cliffDuration); - assertEq(vs1.lockDuration, lockDuration); - assertEq(vs1.duration, duration); - assertEq(vs1.periodDuration, periodDuration); - assertEq(vs1.amount, amount); - assertEq(vs1.creator, creator); - assertEq(vs1.beneficiary, beneficiary); - assertEq(vs1.revocable, revocable); - assertEq(vs1.releasedAmount, 0); - } - } -} - -contract ERC20VestableVotesUpgradeableV1Tests is Test { - TestToken internal tt; - - address internal escrowImplem; - address internal initAccount; - address internal bob; - address internal joe; - address internal alice; - - function setUp() public { - initAccount = makeAddr("init"); - bob = makeAddr("bob"); - joe = makeAddr("joe"); - alice = makeAddr("alice"); - - tt = new TestToken(); - tt.initTestTokenV1(initAccount); - tt.migrateVestingSchedules(); - } - - function testTransfer() public { - vm.startPrank(initAccount); - tt.transfer(joe, 5_000e18); - vm.stopPrank(); - - assert(tt.balanceOf(joe) == 5_000e18); - assert(tt.balanceOf(initAccount) == 999_995_000e18); - } - - function testDelegate() public { - vm.startPrank(initAccount); - tt.transfer(joe, 5_000e18); - vm.stopPrank(); - assert(tt.balanceOf(joe) == 5_000e18); - - // before self delegating voting power is zero (this is an implementation choice from - // open zeppelin to optimize gas) - assert(tt.getVotes(joe) == 0); - - vm.startPrank(joe); - tt.delegate(joe); - vm.stopPrank(); - - assert(tt.balanceOf(joe) == 5_000e18); - assert(tt.getVotes(joe) == 5_000e18); - } - - function testCheckpoints() public { - vm.roll(1000); - - vm.startPrank(initAccount); - tt.transfer(joe, 5_000e18); - vm.stopPrank(); - - vm.startPrank(joe); - tt.delegate(joe); - vm.stopPrank(); - - assert(tt.balanceOf(joe) == 5_000e18); - assert(tt.getVotes(joe) == 5_000e18); - - vm.roll(1010); - assert(tt.getPastVotes(joe, 999) == 0); - assert(tt.getPastVotes(joe, 1005) == 5_000e18); - - vm.startPrank(joe); - tt.transfer(bob, 2_500e18); - vm.stopPrank(); - - vm.startPrank(bob); - tt.delegate(bob); - vm.stopPrank(); - - vm.roll(1020); - assert(tt.getPastVotes(joe, 999) == 0); - assert(tt.getPastVotes(joe, 1005) == 5_000e18); - assert(tt.getPastVotes(joe, 1010) == 2_500e18); - assert(tt.getPastVotes(bob, 1005) == 0); - assert(tt.getPastVotes(bob, 1010) == 2_500e18); - } - - function testDelegateAndTransfer() public { - vm.startPrank(initAccount); - tt.transfer(joe, 5_000e18); - vm.stopPrank(); - assert(tt.balanceOf(joe) == 5_000e18); - - // before self delegating voting power is zero - // (this is an implementation choice from open zeppelin to optimize gas) - assert(tt.getVotes(joe) == 0); - - vm.startPrank(joe); - tt.delegate(joe); - vm.stopPrank(); - - assert(tt.balanceOf(joe) == 5_000e18); - assert(tt.getVotes(joe) == 5_000e18); - - vm.startPrank(joe); - tt.transfer(bob, 2_500e18); - vm.stopPrank(); - - assert(tt.balanceOf(joe) == 2_500e18); - assert(tt.balanceOf(bob) == 2_500e18); - assert(tt.getVotes(joe) == 2_500e18); - - // before self delegating voting power is zero - assert(tt.getVotes(bob) == 0); - - vm.startPrank(bob); - tt.delegate(bob); - vm.stopPrank(); - - assert(tt.balanceOf(joe) == 2_500e18); - assert(tt.balanceOf(bob) == 2_500e18); - assert(tt.getVotes(joe) == 2_500e18); - assert(tt.getVotes(bob) == 2_500e18); - - vm.startPrank(joe); - tt.transfer(bob, 1_000e18); - vm.stopPrank(); - - assert(tt.balanceOf(joe) == 1_500e18); - assert(tt.balanceOf(bob) == 3_500e18); - assert(tt.getVotes(joe) == 1_500e18); - assert(tt.getVotes(bob) == 3_500e18); - } - - event CreatedVestingSchedule(uint256 index, address indexed creator, address indexed beneficiary, uint256 amount); - - function createVestingSchedule( - address beneficiary, - uint256 start, - uint256 cliffDuration, - uint256 duration, - uint256 period, - uint256 lockDuration, - bool revocable, - uint256 amount - ) internal returns (uint256) { - return createVestingSchedulesV2tackOptimized( - uint64(start), - uint32(cliffDuration), - uint32(duration), - uint32(period), - uint32(lockDuration), - amount, - beneficiary, - revocable, - true - ); - } - - function createVestingScheduleWithGlobalUnlock( - address beneficiary, - uint256 start, - uint256 cliffDuration, - uint256 duration, - uint256 period, - uint256 lockDuration, - bool revocable, - uint256 amount - ) internal returns (uint256) { - return createVestingSchedulesV2tackOptimized( - uint64(start), - uint32(cliffDuration), - uint32(duration), - uint32(period), - uint32(lockDuration), - amount, - beneficiary, - revocable, - false - ); - } - - function createVestingSchedulesV2tackOptimized( - uint64 start, - uint32 cliffDuration, - uint32 duration, - uint32 period, - uint32 lockDuration, - uint256 amount, - address beneficiary, - bool revocable, - bool ignoreGlobalUnlockSchedule - ) internal returns (uint256) { - return tt.createVestingSchedule( - start, - cliffDuration, - duration, - period, - lockDuration, - revocable, - amount, - beneficiary, - address(0), - ignoreGlobalUnlockSchedule - ); - } - - function testCreateVesting() public { - vm.startPrank(initAccount); - vm.expectEmit(true, true, true, true); - emit CreatedVestingSchedule(0, initAccount, joe, 10_000e18); - assert( - createVestingSchedule( - joe, - block.timestamp, - 365 * 24 * 3600, - 4 * 365 * 24 * 3600, - 365 * 2 * 3600, - 365 * 24 * 3600, - true, - 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - assert(tt.getVestingScheduleCount() == 1); - - // Verify balances - assert(tt.balanceOf(initAccount) == 999_990_000e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 10_000e18); - assert(tt.balanceOf(joe) == 0); - - // Verify vesting schedule object has been properly created - VestingSchedulesV2.VestingSchedule memory vestingSchedule = tt.getVestingSchedule(0); - - assert(vestingSchedule.start == block.timestamp); - assert(vestingSchedule.cliffDuration == 365 * 24 * 3600); - assert(vestingSchedule.lockDuration == 365 * 24 * 3600); - assert(vestingSchedule.duration == 4 * 365 * 24 * 3600); - assert(vestingSchedule.periodDuration == 365 * 2 * 3600); - assert(vestingSchedule.amount == 10_000e18); - assert(vestingSchedule.creator == initAccount); - assert(vestingSchedule.beneficiary == joe); - assert(vestingSchedule.revocable == true); - assert(vestingSchedule.end == block.timestamp + 4 * 365 * 24 * 3600); - - assert(tt.isGlobalUnlockedScheduleIgnored(0) == true); - - // Verify escrow delegated to beneficiary - assert(tt.delegates(tt.vestingEscrow(0)) == joe); - } - - function testCreateVestingWithGlobalUnlock() public { - vm.startPrank(initAccount); - vm.expectEmit(true, true, true, true); - emit CreatedVestingSchedule(0, initAccount, joe, 10_000e18); - assert( - createVestingScheduleWithGlobalUnlock( - joe, - block.timestamp, - 365 * 24 * 3600, - 4 * 365 * 24 * 3600, - 365 * 2 * 3600, - 365 * 24 * 3600, - true, - 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - assert(tt.getVestingScheduleCount() == 1); - - // Verify balances - assert(tt.balanceOf(initAccount) == 999_990_000e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 10_000e18); - assert(tt.balanceOf(joe) == 0); - - // Verify vesting schedule object has been properly created - VestingSchedulesV2.VestingSchedule memory vestingSchedule = tt.getVestingSchedule(0); - - assert(vestingSchedule.start == block.timestamp); - assert(vestingSchedule.cliffDuration == 365 * 24 * 3600); - assert(vestingSchedule.lockDuration == 365 * 24 * 3600); - assert(vestingSchedule.duration == 4 * 365 * 24 * 3600); - assert(vestingSchedule.periodDuration == 365 * 2 * 3600); - assert(vestingSchedule.amount == 10_000e18); - assert(vestingSchedule.creator == initAccount); - assert(vestingSchedule.beneficiary == joe); - assert(vestingSchedule.revocable == true); - assert(vestingSchedule.end == block.timestamp + 4 * 365 * 24 * 3600); - - assert(tt.isGlobalUnlockedScheduleIgnored(0) == false); - - // Verify escrow delegated to beneficiary - assert(tt.delegates(tt.vestingEscrow(0)) == joe); - } - - function testCreateVestingWithDelegatee() public { - vm.startPrank(initAccount); - vm.expectEmit(true, true, true, true); - emit CreatedVestingSchedule(0, initAccount, joe, 10_000e18); - assert( - tt.createVestingSchedule( - 0, - 365 * 24 * 3600, - 4 * 365 * 24 * 3600, - 365 * 2 * 3600, - 365 * 24 * 3600, - true, - 10_000e18, - joe, - bob, - true - ) == 0 - ); - vm.stopPrank(); - - assert(tt.getVestingScheduleCount() == 1); - - // Verify balances - assert(tt.balanceOf(initAccount) == 999_990_000e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 10_000e18); - assert(tt.balanceOf(joe) == 0); - - // Verify vesting schedule object has been properly created - VestingSchedulesV2.VestingSchedule memory vestingSchedule = tt.getVestingSchedule(0); - - assert(vestingSchedule.start == block.timestamp); - assert(vestingSchedule.lockDuration == 365 * 24 * 3600); - assert(vestingSchedule.duration == 4 * 365 * 24 * 3600); - assert(vestingSchedule.periodDuration == 365 * 2 * 3600); - assert(vestingSchedule.amount == 10_000e18); - assert(vestingSchedule.creator == initAccount); - assert(vestingSchedule.beneficiary == joe); - assert(vestingSchedule.revocable == true); - assert(vestingSchedule.end == block.timestamp + 4 * 365 * 24 * 3600); - - // Verify escrow delegated to beneficiary - assert(tt.delegates(tt.vestingEscrow(0)) == bob); - } - - function testCreateInvalidVestingZeroBeneficiary() public { - vm.startPrank(initAccount); - vm.expectRevert( - abi.encodeWithSignature( - "InvalidVestingScheduleParameter(string)", "Vesting schedule beneficiary must be non zero address" - ) - ); - createVestingSchedule( - address(0), - block.timestamp, - 365 * 24 * 3600, - 4 * 365 * 24 * 3600, - 365 * 2 * 3600, - 365 * 24 * 3600, - true, - 10_000e18 - ); - vm.stopPrank(); - } - - function testCreateInvalidVestingAmountTooLowForPeriodAndDuration() public { - vm.startPrank(initAccount); - vm.expectRevert( - abi.encodeWithSignature( - "InvalidVestingScheduleParameter(string)", "Vesting schedule amount too low for duration and period" - ) - ); - createVestingSchedule(joe, block.timestamp, 0, 365, 1, 0, true, 364); - vm.stopPrank(); - } - - function testCreateInvalidVestingZeroDuration() public { - vm.startPrank(initAccount); - vm.expectRevert( - abi.encodeWithSignature("InvalidVestingScheduleParameter(string)", "Vesting schedule duration must be > 0") - ); - createVestingSchedule( - joe, block.timestamp, 365 * 24 * 3600, 0, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ); - vm.stopPrank(); - } - - function testCreateInvalidVestingZeroAmount() public { - vm.startPrank(initAccount); - vm.expectRevert( - abi.encodeWithSignature("InvalidVestingScheduleParameter(string)", "Vesting schedule amount must be > 0") - ); - createVestingSchedule( - joe, block.timestamp, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 0 - ); - vm.stopPrank(); - } - - function testCreateInvalidVestingZeroPeriod() public { - vm.startPrank(initAccount); - vm.expectRevert( - abi.encodeWithSignature("InvalidVestingScheduleParameter(string)", "Vesting schedule period must be > 0") - ); - createVestingSchedule( - joe, block.timestamp, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 0, 365 * 24 * 3600, true, 10_000e18 - ); - vm.stopPrank(); - } - - function testCreateInvalidVestingPeriodDoesNotDivideDuration() public { - vm.startPrank(initAccount); - vm.expectRevert( - abi.encodeWithSignature( - "InvalidVestingScheduleParameter(string)", "Vesting schedule duration must split in exact periods" - ) - ); - createVestingSchedule( - joe, - block.timestamp, - 365 * 24 * 3600, - 4 * 365 * 24 * 3600 + 1, - 365 * 2 * 3600, - 365 * 24 * 3600, - true, - 10_000e18 - ); - vm.stopPrank(); - } - - function testCreateInvalidVestingPeriodDoesNotDivideCliffDuration() public { - vm.startPrank(initAccount); - vm.expectRevert( - abi.encodeWithSignature( - "InvalidVestingScheduleParameter(string)", "Vesting schedule cliff duration must split in exact periods" - ) - ); - createVestingSchedule( - joe, - block.timestamp, - 365 * 24 * 3600 + 1, - 4 * 365 * 24 * 3600, - 365 * 2 * 3600, - 365 * 24 * 3600, - true, - 10_000e18 - ); - vm.stopPrank(); - } - - function testCreateMultipleVestings() public { - vm.startPrank(initAccount); - vm.expectEmit(true, true, true, true); - emit CreatedVestingSchedule(0, initAccount, joe, 10_000e18); - assert( - createVestingSchedule( - joe, - block.timestamp, - 365 * 24 * 3600, - 4 * 365 * 24 * 3600, - 365 * 2 * 3600, - 365 * 24 * 3600, - true, - 10_000e18 - ) == 0 - ); - - vm.expectEmit(true, true, true, true); - emit CreatedVestingSchedule(1, initAccount, bob, 10_000e18); - assert( - createVestingSchedule( - bob, - block.timestamp, - 365 * 24 * 3600, - 4 * 365 * 24 * 3600, - 365 * 2 * 3600, - 365 * 24 * 3600, - true, - 10_000e18 - ) == 1 - ); - vm.stopPrank(); - - assert(tt.getVestingScheduleCount() == 2); - assert(tt.balanceOf(initAccount) == 999_980_000e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 10_000e18); - assert(tt.balanceOf(tt.vestingEscrow(1)) == 10_000e18); - } - - function testCreateVestingDefaultStart(uint40 start) public { - vm.warp(start); - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - // Verify vesting schedule object has been properly created - VestingSchedulesV2.VestingSchedule memory vestingSchedule = tt.getVestingSchedule(0); - - assert(vestingSchedule.start == start); - } - - function testReleaseVestingScheduleBeforeCliff() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule(joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 0, true, 10_000e18) == 0 - ); - vm.stopPrank(); - - // Move time right before cliff - vm.warp(365 * 24 * 3600 - 1); - - vm.startPrank(joe); - vm.expectRevert(abi.encodeWithSignature("ZeroReleasableAmount()")); - // Attempts to releaseVestingSchedule - tt.releaseVestingSchedule(0); - vm.stopPrank(); - } - - function testReleaseVestingScheduleAfterCliffButBeforeLock() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600 + 1, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - // Move time right before cliff - vm.warp(365 * 24 * 3600); - - vm.startPrank(joe); - vm.expectRevert(abi.encodeWithSignature("VestingScheduleIsLocked()")); - // Attempts to releaseVestingSchedule - tt.releaseVestingSchedule(0); - vm.stopPrank(); - } - - event ReleasedVestingSchedule(uint256 index, uint256 releasedAmount); - - function testReleaseVestingScheduleAtLockDuration() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - // Move to cliff - vm.warp(365 * 24 * 3600); - - vm.startPrank(joe); - vm.expectEmit(true, true, true, true); - emit ReleasedVestingSchedule(0, 2_500e18); - // Attempts to releaseVestingSchedule all vested tokens - tt.releaseVestingSchedule(0); - vm.stopPrank(); - - // Verify balances - assert(tt.balanceOf(tt.vestingEscrow(0)) == 7_500e18); - assert(tt.balanceOf(joe) == 2_500e18); - } - - function testReleaseVestingScheduleAfterLockDuration() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - // Move to end half way vesting - vm.warp(2 * 365 * 24 * 3600); - - vm.startPrank(joe); - vm.expectEmit(true, true, true, true); - emit ReleasedVestingSchedule(0, 5_000e18); - // Attempts to releaseVestingSchedule all vested tokens - tt.releaseVestingSchedule(0); - vm.stopPrank(); - - // Verify balances - assert(tt.balanceOf(tt.vestingEscrow(0)) == 5_000e18); - assert(tt.balanceOf(joe) == 5_000e18); - } - - function testcomputeVestingAmounts() public { - vm.warp(0); - - // Create a schedule such as - // - cliff 1 year - // - total duration 4 years - // - lock duration 2 years - // - not subject to global unlock schedule - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 2 * 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - // At beginning of schedule - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 0); - - // Move right after beginning of schedule - vm.warp(1); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 0); - - // Move to half way cliff - vm.warp(365 * 12 * 3600); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 0); - - // Move right before cliff - vm.warp(365 * 24 * 3600 - 1); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 0); - - // Move at cliff - vm.warp(365 * 24 * 3600); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 2_500e18); - - // Move right after cliff - vm.warp(365 * 24 * 3600 + 1); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 2_500e18); - - // Move right before slice period - vm.warp(365 * 24 * 3600 + 365 * 2 * 3600 - 1); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 2_500e18); - - // Move at slice period - vm.warp(365 * 24 * 3600 + 365 * 2 * 3600); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 2708333333333333333333); - - // Move right after slice period - vm.warp(365 * 24 * 3600 + 365 * 2 * 3600 + 1); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 2708333333333333333333); - - // Move right before lock - vm.warp(2 * 365 * 24 * 3600 - 1); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 4791666666666666666666); - - // Move at lock - vm.warp(2 * 365 * 24 * 3600); - assert(tt.computeVestingReleasableAmount(0) == 5_000e18); - assert(tt.computeVestingVestedAmount(0) == 5_000e18); - - // Move right after lock - vm.warp(2 * 365 * 24 * 3600); - assert(tt.computeVestingReleasableAmount(0) == 5_000e18); - assert(tt.computeVestingVestedAmount(0) == 5_000e18); - - // Move right before vesting end - vm.warp(4 * 365 * 24 * 3600 - 1); - assert(tt.computeVestingReleasableAmount(0) == 9791666666666666666666); - assert(tt.computeVestingVestedAmount(0) == 9791666666666666666666); - - // Move at vesting end - vm.warp(4 * 365 * 24 * 3600); - assert(tt.computeVestingReleasableAmount(0) == 10_000e18); - assert(tt.computeVestingVestedAmount(0) == 10_000e18); - - // Move right after vesting end - vm.warp(4 * 365 * 24 * 3600 + 1); - assert(tt.computeVestingReleasableAmount(0) == 10_000e18); - assert(tt.computeVestingVestedAmount(0) == 10_000e18); - - // Move 1 year after vesting end - vm.warp(5 * 365 * 24 * 3600 + 1); - assert(tt.computeVestingReleasableAmount(0) == 10_000e18); - assert(tt.computeVestingVestedAmount(0) == 10_000e18); - } - - function testcomputeVestingAmountsWithGlobalUnlockSchedule() public { - vm.warp(0); - - // Create a schedule such as - // - cliff 1 year - // - total duration 1 years - // - lock duration 1 years - // - subject to global unlock schedule - vm.startPrank(initAccount); - assert( - createVestingScheduleWithGlobalUnlock( - joe, 0, 365 * 24 * 3600, 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 24_000e18 - ) == 0 - ); - vm.stopPrank(); - - // At beginning of schedule - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 0); - - // Move right after beginning of schedule - vm.warp(1); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 0); - - // Move to half way cliff - vm.warp(365 * 12 * 3600); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 0); - - // Move right before cliff / local lock - vm.warp(365 * 24 * 3600 - 1); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 0); - - // Move at cliff / local lock - vm.warp(365 * 24 * 3600); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 24_000e18); - - // Move right after cliff - vm.warp(365 * 24 * 3600 + 1); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 24_000e18); - - // Move at local lock + 1 month - 1 - vm.warp(365 * 24 * 3600 + (365 / 12) * 24 * 3600 - 1); - assert(tt.computeVestingReleasableAmount(0) == 0); - assert(tt.computeVestingVestedAmount(0) == 24_000e18); - - // Move at local lock + 1 month - vm.warp(365 * 24 * 3600 + (365 / 12) * 24 * 3600); - assertEq(tt.computeVestingReleasableAmount(0), 1_000e18); // 1/24 th after a month - assertEq(tt.computeVestingVestedAmount(0), 24_000e18); - - // Move at local lock + 1 month + 1 - vm.warp(365 * 24 * 3600 + (365 / 12) * 24 * 3600 + 1); - assertEq(tt.computeVestingReleasableAmount(0), 1_000e18); // 1/24 th after a month - assertEq(tt.computeVestingVestedAmount(0), 24_000e18); - - // Move at local lock + 2 month - 1 - vm.warp(365 * 24 * 3600 + (365 / 6) * 24 * 3600 - 1); - assert(tt.computeVestingReleasableAmount(0) == 1_000e18); - assert(tt.computeVestingVestedAmount(0) == 24_000e18); - - // Move at local lock + 2 month - vm.warp(365 * 24 * 3600 + (365 / 6) * 24 * 3600); - assertEq(tt.computeVestingReleasableAmount(0), 2_000e18); - assertEq(tt.computeVestingVestedAmount(0), 24_000e18); - - // Move at local lock + 2 month + 1 - vm.warp(365 * 24 * 3600 + (365 / 6) * 24 * 3600 + 1); - assertEq(tt.computeVestingReleasableAmount(0), 2_000e18); - assertEq(tt.computeVestingVestedAmount(0), 24_000e18); - - // Move at the end of global unlock schedule - 1 - vm.warp(365 * 24 * 3600 + 2 * 365 * 24 * 3600 - 1); - assertEq(tt.computeVestingReleasableAmount(0), 23_000e18); - assertEq(tt.computeVestingVestedAmount(0), 24_000e18); - - // Move at the end of global unlock schedule (2 years) - vm.warp(365 * 24 * 3600 + 2 * 365 * 24 * 3600); - assertEq(tt.computeVestingReleasableAmount(0), 24_000e18); - assertEq(tt.computeVestingVestedAmount(0), 24_000e18); - - // Move at the end of global unlock schedule + 1 - vm.warp(365 * 24 * 3600 + 2 * 365 * 24 * 3600 + 1); - assertEq(tt.computeVestingReleasableAmount(0), 24_000e18); - assertEq(tt.computeVestingVestedAmount(0), 24_000e18); - - // Move 1 year after global unlock schedule - vm.warp(365 * 24 * 3600 + 3 * 365 * 24 * 3600 + 1); - assertEq(tt.computeVestingReleasableAmount(0), 24_000e18); - assertEq(tt.computeVestingVestedAmount(0), 24_000e18); - } - - function testReleaseVestingScheduleAfterVestingBeforeGlobalUnlock() public { - vm.warp(0); - - // Create a schedule such as - // - cliff 1 year - // - total duration 1 years - // - lock duration 1 years - // - subject to global unlock schedule - vm.startPrank(initAccount); - assert( - createVestingScheduleWithGlobalUnlock( - joe, 0, 365 * 24 * 3600, 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 24_000e18 - ) == 0 - ); - vm.stopPrank(); - - // Move to end of cliff, lock & vesting - vm.warp(1 * 365 * 24 * 3600); - - vm.startPrank(joe); - vm.expectRevert(abi.encodeWithSignature("ZeroReleasableAmount()")); - // Attempts to releaseVestingSchedule - tt.releaseVestingSchedule(0); - vm.stopPrank(); - - // Verify balances - assert(tt.balanceOf(tt.vestingEscrow(0)) == 24_000e18); - assert(tt.balanceOf(joe) == 0); - - // Move to half way through the global unlock schedule - vm.warp(2 * 365 * 24 * 3600); - - vm.startPrank(joe); - vm.expectEmit(true, true, true, true); - emit ReleasedVestingSchedule(0, 12_000e18); - // Attempts to releaseVestingSchedule - tt.releaseVestingSchedule(0); - vm.stopPrank(); - - // Verify balances - assert(tt.balanceOf(tt.vestingEscrow(0)) == 12_000e18); - assert(tt.balanceOf(joe) == 12_000e18); - - // Move to end of global unlock schedule - vm.warp(3 * 365 * 24 * 3600); - - vm.startPrank(joe); - vm.expectEmit(true, true, true, true); - emit ReleasedVestingSchedule(0, 12_000e18); - - // Attempts to releaseVestingSchedule - tt.releaseVestingSchedule(0); - vm.stopPrank(); - - // Verify balances - assert(tt.balanceOf(tt.vestingEscrow(0)) == 0); - assert(tt.balanceOf(joe) == 24_000e18); - } - - function testReleaseVestingScheduleFromInvalidAccount() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - // Move to end half way vesting - vm.warp(2 * 365 * 24 * 3600); - - vm.startPrank(bob); - vm.expectRevert(abi.encodeWithSignature("Unauthorized(address)", bob)); - tt.releaseVestingSchedule(0); - vm.stopPrank(); - } - - event RevokedVestingSchedule(uint256 index, uint256 returnedAmount, uint256 newEnd); - - function testRevokeBeforeCliff() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - vm.startPrank(initAccount); - vm.expectEmit(true, true, true, true); - emit RevokedVestingSchedule(0, 10_000e18, 365 * 24 * 3600 - 1); - tt.revokeVestingSchedule(0, 365 * 24 * 3600 - 1); - vm.stopPrank(); - - assert(tt.balanceOf(initAccount) == 1_000_000_000e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 0); - assert(tt.balanceOf(joe) == 0); - - // Verify vesting schedule object has been properly updated - VestingSchedulesV2.VestingSchedule memory vestingSchedule = tt.getVestingSchedule(0); - assert(vestingSchedule.end == 365 * 24 * 3600 - 1); - } - - function testRevokeAtCliff() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - vm.startPrank(initAccount); - vm.expectEmit(true, true, true, true); - emit RevokedVestingSchedule(0, 7_500e18, 365 * 24 * 3600); - tt.revokeVestingSchedule(0, 365 * 24 * 3600); - vm.stopPrank(); - - assert(tt.balanceOf(initAccount) == 999_997_500e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 2_500e18); - assert(tt.balanceOf(joe) == 0); - - // Verify vesting schedule object has been properly updated - VestingSchedulesV2.VestingSchedule memory vestingSchedule = tt.getVestingSchedule(0); - assert(vestingSchedule.end == 365 * 24 * 3600); - } - - function testRevokeAtDuration() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - vm.startPrank(initAccount); - vm.expectEmit(true, true, true, true); - emit RevokedVestingSchedule(0, 0, 4 * 365 * 24 * 3600); - tt.revokeVestingSchedule(0, 4 * 365 * 24 * 3600); - vm.stopPrank(); - - assert(tt.balanceOf(initAccount) == 999_990_000e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 10_000e18); - - // Verify vesting schedule object has been properly updated - VestingSchedulesV2.VestingSchedule memory vestingSchedule = tt.getVestingSchedule(0); - assert(vestingSchedule.end == 4 * 365 * 24 * 3600); - } - - function testRevokeDefault() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - vm.warp(2 * 365 * 24 * 3600); - - vm.startPrank(initAccount); - vm.expectEmit(true, true, true, true); - emit RevokedVestingSchedule(0, 5_000e18, block.timestamp); - tt.revokeVestingSchedule(0, 0); - vm.stopPrank(); - - assert(tt.balanceOf(initAccount) == 999_995_000e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 5_000e18); - - // Verify vesting schedule object has been properly updated - VestingSchedulesV2.VestingSchedule memory vestingSchedule = tt.getVestingSchedule(0); - assert(vestingSchedule.end == 2 * 365 * 24 * 3600); - } - - function testRevokeNotRevokable() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, false, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - vm.startPrank(initAccount); - vm.expectRevert(abi.encodeWithSignature("VestingScheduleNotRevocable()")); - tt.revokeVestingSchedule(0, 4 * 365 * 24 * 3600); - vm.stopPrank(); - } - - function testRevokeFromInvalidAccount() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - vm.startPrank(joe); - vm.expectRevert(abi.encodeWithSignature("Unauthorized(address)", joe)); - tt.revokeVestingSchedule(0, 4 * 365 * 24 * 3600); - vm.stopPrank(); - } - - function testRevokeTwice() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - vm.startPrank(initAccount); - tt.revokeVestingSchedule(0, 2 * 365 * 24 * 3600); - vm.stopPrank(); - - assert(tt.balanceOf(initAccount) == 999_995_000e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 5_000e18); - - vm.startPrank(initAccount); - tt.revokeVestingSchedule(0, 1 * 365 * 24 * 3600); - vm.stopPrank(); - - assert(tt.balanceOf(initAccount) == 999_997_500e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 2_500e18); - } - - function testRevokeTwiceAfterEnd() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - vm.startPrank(initAccount); - tt.revokeVestingSchedule(0, 1 * 365 * 24 * 3600); - vm.stopPrank(); - - vm.startPrank(initAccount); - vm.expectRevert(abi.encodeWithSignature("InvalidRevokedVestingScheduleEnd()")); - tt.revokeVestingSchedule(0, 2 * 365 * 24 * 3600); - vm.stopPrank(); - } - - function testReleaseVestingScheduleAfterRevoke() public { - vm.warp(0); - - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, 0, 365 * 24 * 3600, 4 * 365 * 24 * 3600, 365 * 2 * 3600, 365 * 24 * 3600, true, 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - vm.startPrank(initAccount); - // revoke at mid vesting duration - tt.revokeVestingSchedule(0, 2 * 365 * 24 * 3600); - vm.stopPrank(); - - assert(tt.balanceOf(initAccount) == 999_995_000e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 5_000e18); - - // move to cliff - vm.warp(365 * 24 * 3600); - - vm.startPrank(joe); - tt.releaseVestingSchedule(0); - vm.stopPrank(); - - assert(tt.balanceOf(initAccount) == 999_995_000e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 2_500e18); - assert(tt.balanceOf(joe) == 2_500e18); - - // move to vesting schedule end - vm.warp(2 * 365 * 24 * 3600); - - vm.startPrank(joe); - tt.releaseVestingSchedule(0); - vm.stopPrank(); - - assert(tt.balanceOf(initAccount) == 999_995_000e18); - assert(tt.balanceOf(tt.vestingEscrow(0)) == 0); - assert(tt.balanceOf(joe) == 5_000e18); - } - - event DelegatedVestingEscrow( - uint256 index, address indexed oldDelegatee, address indexed newDelegatee, address indexed beneficiary - ); - - function testDelegateVestingEscrow() public { - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, - block.timestamp, - 365 * 24 * 3600, - 4 * 365 * 24 * 3600, - 365 * 2 * 3600, - 365 * 24 * 3600, - true, - 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - // Verify escrow delegated to beneficiary - assert(tt.delegates(tt.vestingEscrow(0)) == joe); - - vm.startPrank(joe); - vm.expectEmit(true, true, true, true); - emit DelegatedVestingEscrow(0, joe, bob, joe); - tt.delegateVestingEscrow(0, bob); - vm.stopPrank(); - - // Verify escrow delegation has been updated - assert(tt.delegates(tt.vestingEscrow(0)) == bob); - } - - function testDelegateVestingEscrowFromInvalidAccount() public { - vm.startPrank(initAccount); - assert( - createVestingSchedule( - joe, - block.timestamp, - 365 * 24 * 3600, - 4 * 365 * 24 * 3600, - 365 * 2 * 3600, - 365 * 24 * 3600, - true, - 10_000e18 - ) == 0 - ); - vm.stopPrank(); - - // Verify escrow delegated to beneficiary - assert(tt.delegates(tt.vestingEscrow(0)) == joe); - - vm.startPrank(bob); - vm.expectRevert(abi.encodeWithSignature("Unauthorized(address)", bob)); - tt.delegateVestingEscrow(0, bob); - vm.stopPrank(); - } - - function testVestingScheduleFuzzing( - uint24 periodDuration, - uint32 lockDuration, - uint8 cliffPeriodCount, - uint8 vestingPeriodCount, - uint256 amount, - uint256 releaseAt, - uint256 revokeAt - ) public { - vm.warp(0); - if (periodDuration == 0) { - // period duration should be a list one - periodDuration = 1; - } - - if (vestingPeriodCount == 0) { - // we should have at list one period for the vesting - vestingPeriodCount = 1; - } - - // make sure that at least one token can be released for each period - if (amount < vestingPeriodCount) { - amount = uint256(vestingPeriodCount); - } - - // make sure that initAccount has enough funds to create the schedule - amount = amount % tt.balanceOf(initAccount); - - uint32 totalDuration = uint32(vestingPeriodCount) * uint32(periodDuration); - - vm.assume(amount >= vestingPeriodCount); - - uint32 cliffDuration = (cliffPeriodCount % vestingPeriodCount) * uint32(periodDuration); - lockDuration = lockDuration % (totalDuration + periodDuration); - - vm.startPrank(initAccount); - assert( - createVestingSchedule(joe, 0, cliffDuration, totalDuration, periodDuration, lockDuration, true, amount) == 0 - ); - vm.stopPrank(); - - assert(tt.balanceOf(initAccount) == 1_000_000_000e18 - amount); - assert(tt.balanceOf(tt.vestingEscrow(0)) == amount); - - revokeAt = revokeAt % totalDuration; - if (revokeAt > 0) { - vm.startPrank(initAccount); - assert(tt.revokeVestingSchedule(0, uint64(revokeAt)) > 0); - vm.stopPrank(); - - if (revokeAt < cliffDuration) { - assert(tt.balanceOf(tt.vestingEscrow(0)) == 0); - } else if (revokeAt >= periodDuration) { - assert(tt.balanceOf(tt.vestingEscrow(0)) > 0); - } - } - - releaseAt = releaseAt % periodDuration; - while (true) { - vm.warp(releaseAt); - if (releaseAt < lockDuration) { - vm.startPrank(joe); - vm.expectRevert(abi.encodeWithSignature("VestingScheduleIsLocked()")); - tt.releaseVestingSchedule(0); - vm.stopPrank(); - } else if ( - (releaseAt < periodDuration) || (releaseAt < cliffDuration) - || (revokeAt > 0) && ((revokeAt < cliffDuration) || (revokeAt < periodDuration)) - ) { - // we are in either of this situation - // - before end of lock duration - // - lock duration is 0 and we are before end of first period - // - vesting has been revoked and end is before end of lock duration - // - vesting has been revoked and end is before end of first period - vm.startPrank(joe); - vm.expectRevert(abi.encodeWithSignature("ZeroReleasableAmount()")); - tt.releaseVestingSchedule(0); - vm.stopPrank(); - } else { - uint256 oldJoeBalance = tt.balanceOf(joe); - uint256 oldEscrowBalance = tt.balanceOf(tt.vestingEscrow(0)); - - vm.startPrank(joe); - uint256 releasedAmount = tt.releaseVestingSchedule(0); - vm.stopPrank(); - - // make sure funds are properly transferred from escrow to beneficiary - assert(releasedAmount > 0); - assert(tt.balanceOf(joe) == oldJoeBalance + releasedAmount); - assert(tt.balanceOf(tt.vestingEscrow(0)) == oldEscrowBalance - releasedAmount); - - // make sure invariant is respected - assert( - tt.balanceOf(initAccount) + tt.balanceOf(joe) + tt.balanceOf(tt.vestingEscrow(0)) - == 1_000_000_000e18 - ); - } - - if ( - releaseAt >= ((tt.getVestingSchedule(0).end / periodDuration) * periodDuration) - && (releaseAt >= lockDuration) - ) { - // we got into the end period and passed the lock duration so all tokens should have been released - assert(tt.balanceOf(tt.vestingEscrow(0)) == 0); - break; - } - - // Release at next period - releaseAt += periodDuration; - } - } - - struct VestingSchedule { - uint256 start; - uint256 cliffDuration; - uint256 lockDuration; - uint256 duration; - uint256 period; - uint256 amount; - address beneficiary; - address delegatee; - bool revocable; - } - - // test DOS following spearbit audit - function testDOSReleaseVestingSchedule() public { - // send Bob 1 vote token - vm.prank(initAccount); - tt.transfer(bob, 1); - - // create a vesting schedule for Alice - vm.prank(initAccount); - createVestingSchedule(alice, block.timestamp, 1 days, 10 days, 1 days, 0, true, 10); - - address aliceEscrow = tt.vestingEscrow(0); - - // Bob send one token directly to the Escrow contract of alice - vm.prank(bob); - tt.transfer(aliceEscrow, 1); - - // Cliff period has passed and Alice try to get the first batch of the vested token - vm.warp(block.timestamp + 1 days); - vm.prank(alice); - - // The transaction should not revert for UNDERFLOW because now the balance of the escrow has been increased externally - tt.releaseVestingSchedule(0); - - // Warp at the vesting schedule period end - vm.warp(block.timestamp + 9 days); - - // Alice is able to get the whole vesting schedule amount - // but not the token sent by the attacker to the escrow contract - vm.prank(alice); - tt.releaseVestingSchedule(0); - - assertEq(tt.balanceOf(alice), 10); - } - - // test DOS following spearbit audit - function testDOSRevokeVestingSchedule() public { - // send Bob 1 vote token - vm.prank(initAccount); - tt.transfer(bob, 1); - - // create a vesting schedule for Alice - vm.prank(initAccount); - - createVestingSchedule(alice, block.timestamp, 1 days, 10 days, 1 days, 0, true, 10); - - address aliceEscrow = tt.vestingEscrow(0); - - // Bob send one token directly to the Escrow contract of alice - vm.prank(bob); - tt.transfer(aliceEscrow, 1); - - // The creator decide to revoke the vesting schedule before the end timestamp - // It should not throw an underflow error - vm.prank(initAccount); - tt.revokeVestingSchedule(0, uint64(block.timestamp + 1)); - } - - // test DOS following spearbit audit - function testDOSComputeVestingReleasableAmount() public { - // send Bob 1 vote token - vm.prank(initAccount); - tt.transfer(bob, 1); - - // create a vesting schedule for Alice - vm.prank(initAccount); - createVestingSchedule(alice, block.timestamp, 1 days, 10 days, 1 days, 0, true, 10); - - address aliceEscrow = tt.vestingEscrow(0); - - // Bob send one token directly to the Escrow contract of alice - vm.prank(bob); - tt.transfer(aliceEscrow, 1); - - // Should not UNDERFLOW - uint256 releasableAmount = tt.computeVestingReleasableAmount(0); - - // Warp to the end of the vesting schedule - vm.warp(block.timestamp + 10 days); - releasableAmount = tt.computeVestingReleasableAmount(0); - assertEq(releasableAmount, 10); - } -} diff --git a/contracts/test/fork/mainnet/1.vestingSchedulesMigrationV1toV2.t.sol b/contracts/test/fork/mainnet/1.vestingSchedulesMigrationV1toV2.t.sol index 357477a1..ee4f1f2b 100644 --- a/contracts/test/fork/mainnet/1.vestingSchedulesMigrationV1toV2.t.sol +++ b/contracts/test/fork/mainnet/1.vestingSchedulesMigrationV1toV2.t.sol @@ -1,5 +1,8 @@ //SPDX-License-Identifier: MIT +/* +Left here for legacy reasons, you can now execute this test from the TLC repository + pragma solidity 0.8.10; import "forge-std/Test.sol"; @@ -235,3 +238,4 @@ contract VestingSchedulesMigrationV1ToV2 is Test { vs[13].releasedAmount = 0; } } +*/ \ No newline at end of file diff --git a/contracts/test/migration/TLC_globalUnlockScheduleMigration.t.sol b/contracts/test/migration/TLC_globalUnlockScheduleMigration.t.sol deleted file mode 100644 index 3c24a97a..00000000 --- a/contracts/test/migration/TLC_globalUnlockScheduleMigration.t.sol +++ /dev/null @@ -1,118 +0,0 @@ -//SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.10; - -import "forge-std/Test.sol"; - -import "../../src/migration/TLC_globalUnlockScheduleMigration.sol"; -import "../../src/TLC.1.sol"; - -contract TlcMigrationTest is Test { - TlcMigration migrationsContract; - TLCV1 tlc; - - function testCreate() public { - migrationsContract = new TlcMigration(); - } - - function testGas() public { - vm.createSelectFork("http://127.0.0.1:8545", 18063740); - - migrationsContract = new TlcMigration(); - proxy tlcProxy = proxy(0xb5Fe6946836D687848B5aBd42dAbF531d5819632); - vm.prank(0x0D1dE267015a75F5069fD1c9ed382210B3002cEb); - tlcProxy.upgradeToAndCall(address(migrationsContract), abi.encodeWithSignature("migrate()")); - } - - function testMigrate() public { - // Significantly faster when cached locally, run a local fork for best perf (anvil recommended) - vm.createSelectFork("http://127.0.0.1:8545", 18063740); - - proxy tlcProxy = proxy(0xb5Fe6946836D687848B5aBd42dAbF531d5819632); - assertEq(tlcProxy.getVestingScheduleCount(), 67); - - VestingSchedulesV2.VestingSchedule[] memory schedulesBefore = new VestingSchedulesV2.VestingSchedule[](67); - for (uint256 i = 0; i < 67; i++) { - schedulesBefore[i] = TLCV1(address(tlcProxy)).getVestingSchedule(i); - //console.log("%s,%s,%s", i, schedulesBefore[i].start, schedulesBefore[i].end); - } - - migrationsContract = new TlcMigration(); - vm.prank(0x0D1dE267015a75F5069fD1c9ed382210B3002cEb); - tlcProxy.upgradeToAndCall(address(migrationsContract), abi.encodeWithSignature("migrate()")); - - tlc = new TLCV1(); - vm.prank(0x0D1dE267015a75F5069fD1c9ed382210B3002cEb); - tlcProxy.upgradeTo(address(tlc)); - - assertEq(tlcProxy.getVestingScheduleCount(), 67); - - // Check that the all the values that shouldn't change didn't - for (uint256 i = 0; i < 67; i++) { - VestingSchedulesV2.VestingSchedule memory schedule = TLCV1(address(tlcProxy)).getVestingSchedule(i); - assertEq(schedule.amount, schedulesBefore[i].amount); - assertEq(schedule.creator, schedulesBefore[i].creator); - assertEq(schedule.beneficiary, schedulesBefore[i].beneficiary); - assertEq(schedule.revocable, schedulesBefore[i].revocable); - assertEq(schedule.releasedAmount, schedulesBefore[i].releasedAmount); - } - // Check that the value we should have changed did change - // Schedule 0 - VestingSchedulesV2.VestingSchedule memory schedule = TLCV1(address(tlcProxy)).getVestingSchedule(0); - assertEq(schedule.start, schedulesBefore[0].start); - assertEq(schedule.end, schedulesBefore[0].end); - assertEq(schedule.lockDuration, 75772800); - assertEq(schedule.cliffDuration, schedulesBefore[0].cliffDuration); - assertEq(schedule.duration, schedulesBefore[0].duration); - assertEq(schedule.periodDuration, schedulesBefore[0].periodDuration); - assertFalse(TLCV1(address(tlcProxy)).isGlobalUnlockedScheduleIgnored(0)); - - // Schedule 17 - schedule = TLCV1(address(tlcProxy)).getVestingSchedule(17); - assertEq(schedule.start, schedulesBefore[17].start); - assertEq(schedule.end, schedulesBefore[17].end); - assertEq(schedule.lockDuration, 53740800); - assertEq(schedule.cliffDuration, schedulesBefore[17].cliffDuration); - assertEq(schedule.duration, schedulesBefore[17].duration); - assertEq(schedule.periodDuration, schedulesBefore[17].periodDuration); - assertTrue(TLCV1(address(tlcProxy)).isGlobalUnlockedScheduleIgnored(17)); - - // Schedule 36 - schedule = TLCV1(address(tlcProxy)).getVestingSchedule(36); - assertEq(schedule.start, 1686175200); - assertEq(schedule.end, 1686261600); - assertEq(schedule.lockDuration, 42861600); - assertEq(schedule.cliffDuration, schedulesBefore[36].cliffDuration); - assertEq(schedule.duration, 86400); - assertEq(schedule.periodDuration, 86400); - assertFalse(TLCV1(address(tlcProxy)).isGlobalUnlockedScheduleIgnored(36)); - - // Schedule 60 - schedule = TLCV1(address(tlcProxy)).getVestingSchedule(60); - assertEq(schedule.start, 1686175200); - assertEq(schedule.end, 1686261600); - assertEq(schedule.lockDuration, 42861600); - assertEq(schedule.cliffDuration, schedulesBefore[60].cliffDuration); - assertEq(schedule.duration, 86400); - assertEq(schedule.periodDuration, 86400); - assertFalse(TLCV1(address(tlcProxy)).isGlobalUnlockedScheduleIgnored(60)); - - // Schedule 66 - schedule = TLCV1(address(tlcProxy)).getVestingSchedule(66); - assertEq(schedule.start, schedulesBefore[66].start); - assertEq(schedule.end, schedulesBefore[66].end); - assertEq(schedule.lockDuration, 38188800); - assertEq(schedule.cliffDuration, schedulesBefore[66].cliffDuration); - assertEq(schedule.duration, schedulesBefore[66].duration); - assertEq(schedule.periodDuration, schedulesBefore[66].periodDuration); - assertTrue(TLCV1(address(tlcProxy)).isGlobalUnlockedScheduleIgnored(66)); - } -} - -interface proxy { - function admin() external view returns (address); - function upgradeTo(address newImplementation) external; - function upgradeToAndCall(address newImplementation, bytes calldata cdata) external payable; - function migrate() external; - function getVestingScheduleCount() external view returns (uint256); - function getMigrationCount() external view returns (uint256); -} diff --git a/contracts/test/state/tlc/VestingSchedules.2.t.sol b/contracts/test/state/tlc/VestingSchedules.2.t.sol deleted file mode 100644 index 610b37c4..00000000 --- a/contracts/test/state/tlc/VestingSchedules.2.t.sol +++ /dev/null @@ -1,155 +0,0 @@ -//SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.10; - -import "../../../src/state/tlc/VestingSchedules.1.sol"; -import "../../../src/state/tlc/VestingSchedules.2.sol"; -import "forge-std/Test.sol"; - -contract VestingSchedulesMigrationTest is Test { - address internal alice; - address internal bob; - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - } - - function _createV1VestingSchedule(VestingSchedulesV1.VestingSchedule memory vestingSchedule) - internal - returns (uint256) - { - // Create a V1 schedule - return VestingSchedulesV1.push(vestingSchedule) - 1; - } - - function _updateV2VestingSchedule(uint256 index, VestingSchedulesV2.VestingSchedule memory newVestingSchedule) - internal - returns (bool) - { - VestingSchedulesV2.VestingSchedule storage vestingScheduleV2 = VestingSchedulesV2.get(index); - - vestingScheduleV2.start = newVestingSchedule.start; - vestingScheduleV2.end = newVestingSchedule.end; - vestingScheduleV2.lockDuration = newVestingSchedule.lockDuration; - vestingScheduleV2.cliffDuration = newVestingSchedule.cliffDuration; - vestingScheduleV2.duration = newVestingSchedule.duration; - vestingScheduleV2.periodDuration = newVestingSchedule.periodDuration; - vestingScheduleV2.amount = newVestingSchedule.amount; - vestingScheduleV2.creator = newVestingSchedule.creator; - vestingScheduleV2.beneficiary = newVestingSchedule.beneficiary; - vestingScheduleV2.revocable = newVestingSchedule.revocable; - vestingScheduleV2.releasedAmount = newVestingSchedule.releasedAmount; - - return true; - } - - function _migrate() internal returns (uint256) { - if (VestingSchedulesV2.getCount() == 0) { - uint256 existingV1VestingSchedules = VestingSchedulesV1.getCount(); - for (uint256 idx; idx < existingV1VestingSchedules;) { - VestingSchedulesV2.migrateVestingScheduleFromV1(idx, 0); - unchecked { - ++idx; - } - } - } - return VestingSchedulesV2.getCount(); - } - - function testVestingScheduleV1ToV2Compatibility( - uint64 _start, - uint64 _end, - uint32 _duration, - uint32 _periodDuration, - uint32 _cliffDuration, - uint32 _lockDuration, - bool _revocable, - uint256 _amount, - uint256 _releasedAmount - ) public { - // #1. Create two V1 schedule - VestingSchedulesV1.VestingSchedule memory vestingScheduleV1 = VestingSchedulesV1.VestingSchedule({ - start: _start, - end: _end, - lockDuration: _lockDuration, - cliffDuration: _cliffDuration, - duration: _duration, - periodDuration: _periodDuration, - amount: _amount, - creator: bob, - beneficiary: alice, - revocable: _revocable - }); - - vm.startPrank(bob); - _createV1VestingSchedule(vestingScheduleV1); - _createV1VestingSchedule(vestingScheduleV1); - vm.stopPrank(); - - // #2. Migrate from v1 to v2 - vm.startPrank(bob); - uint256 count = _migrate(); - vm.stopPrank(); - - assert(count == 2); - - // #3. Get v2 schedules and check validity of inputs - for (uint256 idx = 0; idx < count;) { - VestingSchedulesV2.VestingSchedule memory vestingScheduleV2 = VestingSchedulesV2.get(idx); - assert(vestingScheduleV2.start == vestingScheduleV1.start); - assert(vestingScheduleV2.end == vestingScheduleV1.end); - assert(vestingScheduleV2.cliffDuration == vestingScheduleV1.cliffDuration); - assert(vestingScheduleV2.lockDuration == vestingScheduleV1.lockDuration); - assert(vestingScheduleV2.periodDuration == vestingScheduleV1.periodDuration); - assert(vestingScheduleV2.amount == vestingScheduleV1.amount); - assert(vestingScheduleV2.creator == vestingScheduleV1.creator); - assert(vestingScheduleV2.beneficiary == vestingScheduleV1.beneficiary); - assert(vestingScheduleV2.revocable == vestingScheduleV1.revocable); - assert(vestingScheduleV2.releasedAmount == 0); - unchecked { - ++idx; - } - } - - // Arguments are mixed on purpose to increase fuzzing variability - VestingSchedulesV2.VestingSchedule memory newVestingScheduleV2 = VestingSchedulesV2.VestingSchedule({ - start: _end, - end: _start, - lockDuration: _cliffDuration, - cliffDuration: _lockDuration, - duration: _periodDuration, - periodDuration: _duration, - amount: _releasedAmount, - creator: bob, - beneficiary: alice, - revocable: _start % 2 == 0, - releasedAmount: _amount - }); - - // #3. Update V2 schedule - for (uint256 idx = 0; idx < count;) { - assert(_updateV2VestingSchedule(idx, newVestingScheduleV2)); - unchecked { - ++idx; - } - } - - // #4. Verify V2 schedule have been updated properly - for (uint256 idx = 0; idx < count;) { - VestingSchedulesV2.VestingSchedule memory vestingScheduleV2 = VestingSchedulesV2.get(idx); - assert(vestingScheduleV2.start == newVestingScheduleV2.start); - assert(vestingScheduleV2.end == newVestingScheduleV2.end); - assert(vestingScheduleV2.cliffDuration == newVestingScheduleV2.cliffDuration); - assert(vestingScheduleV2.lockDuration == newVestingScheduleV2.lockDuration); - assert(vestingScheduleV2.periodDuration == newVestingScheduleV2.periodDuration); - assert(vestingScheduleV2.amount == newVestingScheduleV2.amount); - assert(vestingScheduleV2.creator == newVestingScheduleV2.creator); - assert(vestingScheduleV2.beneficiary == newVestingScheduleV2.beneficiary); - assert(vestingScheduleV2.revocable == newVestingScheduleV2.revocable); - assert(vestingScheduleV2.releasedAmount == newVestingScheduleV2.releasedAmount); - unchecked { - ++idx; - } - } - } -}