Skip to content

Instantly share code, notes, and snippets.

@dumebi
Created October 4, 2022 19:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dumebi/f27d39d856060b8bea9020eb0337e772 to your computer and use it in GitHub Desktop.
Save dumebi/f27d39d856060b8bea9020eb0337e772 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import '@openzeppelin/contracts/utils/Address.sol';
import '@openzeppelin/contracts/utils/Context.sol';
import '@openzeppelin/contracts/utils/math/Math.sol';
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import 'hardhat/console.sol';
/**
* @title VestingWallet
* @dev This contract handles the vesting of Eth and ERC20 tokens for a given beneficiary. Custody of multiple tokens
* can be given to this contract, which will release the token to the beneficiary following a given vesting schedule.
* The vesting schedule is customizable through the {vestedAmount} function.
*
* Any token transferred to this contract will follow the vesting schedule as if they were locked from the beginning.
* Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly)
* be immediately releasable.
*/
contract VestingWallet is Context {
event EtherReleased(uint256 amount);
event ERC20Released(uint256 amount);
struct Vest {
uint64 startTimestamp;
uint64 durationSeconds;
uint64 cliffSeconds;
uint64 periodSeconds;
uint64 lastRelease;
uint256 released;
}
mapping(address => Vest) vesting;
address private _token;
/**
* @dev Set the beneficiary, start timestamp and vesting duration of the vesting wallet.
*/
constructor(address tokenAddress) {
require(tokenAddress != address(0), 'VestingWallet: Token address is zero address');
_token = tokenAddress;
}
function vest(
address _beneficiaryAddress,
uint64 _startTimestamp,
uint64 _durationSeconds,
uint64 _cliffSeconds,
uint64 _periodSeconds
) public {
require(_beneficiaryAddress != address(0), 'VestingWallet: beneficiary is zero address');
Vest storage vest_ = vesting[_beneficiaryAddress];
vest_.startTimestamp = _startTimestamp;
vest_.durationSeconds = _durationSeconds;
vest_.cliffSeconds = _cliffSeconds;
vest_.periodSeconds = _periodSeconds;
vest_.lastRelease = 0;
vest_.released = 0;
}
/**
* @dev The contract should be able to receive Eth.
*/
receive() external payable virtual {}
// /**
// * @dev Getter for the beneficiary address.
// */
// function beneficiary() public view virtual returns (address) {
// return _beneficiary;
// }
/**
* @dev Getter for the start timestamp.
*/
function start(address _beneficiaryAddress) public view virtual returns (uint256) {
return vesting[_beneficiaryAddress].startTimestamp;
}
/**
* @dev Getter for the vesting duration.
*/
function duration(address _beneficiaryAddress) public view virtual returns (uint256) {
return vesting[_beneficiaryAddress].durationSeconds;
}
//TODO: Cliff, Period, Last Release
/**
* @dev Amount of token already released
*/
function released(address _beneficiaryAddress) public view virtual returns (uint256) {
return vesting[_beneficiaryAddress].released;
}
/**
* @dev Release the tokens that have already vested.
*
* Emits a {TokensReleased} event.
*/
function release(address _beneficiaryAddress) public virtual {
require(
block.timestamp >= _nextReleaseTime(_beneficiaryAddress),
'EQUITY DAO: Next vesting period not reached'
);
uint256 releasable = vestedAmount(_beneficiaryAddress, uint64(block.timestamp)) - released(_beneficiaryAddress);
require(releasable > 0, 'EQUITY DAO: No vested token available');
vesting[_beneficiaryAddress].released = vesting[_beneficiaryAddress].released + releasable;
vesting[_beneficiaryAddress].lastRelease = uint64(block.timestamp);
emit ERC20Released(releasable);
SafeERC20.safeTransfer(IERC20(_token), _beneficiaryAddress, releasable);
}
/**
* @dev Calculates the amount of tokens that has already vested. Default implementation is a linear vesting curve.
*/
function vestedAmount(address _beneficiaryAddress, uint64 timestamp) public view virtual returns (uint256) {
return
_vestingSchedule(
_beneficiaryAddress,
IERC20(_token).balanceOf(address(this)) + released(_beneficiaryAddress),
timestamp
);
}
/**
* @dev Virtual implementation of the vesting formula. This returns the amout vested, as a function of time, for
* an asset given its total historical allocation.
*/
function _vestingSchedule(
address _beneficiaryAddress,
uint256 totalAllocation,
uint64 timestamp
) internal view virtual returns (uint256) {
if (timestamp < start(_beneficiaryAddress)) {
return 0;
} else if (timestamp > (start(_beneficiaryAddress) + duration(_beneficiaryAddress))) {
return totalAllocation;
} else {
return (totalAllocation * (timestamp - start(_beneficiaryAddress))) / duration(_beneficiaryAddress);
}
}
/**
* @dev Calculates the next release time depending on whether the token has been released before or not
*/
function _nextReleaseTime(address _beneficiaryAddress) internal view virtual returns (uint256) {
if (vesting[_beneficiaryAddress].lastRelease == 0) {
return vesting[_beneficiaryAddress].startTimestamp + vesting[_beneficiaryAddress].periodSeconds;
} else {
return vesting[_beneficiaryAddress].lastRelease + vesting[_beneficiaryAddress].periodSeconds;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment