Skip to content

Instantly share code, notes, and snippets.

@rstormsf
Created August 21, 2020 20:28
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save rstormsf/7cfb0c6b7a835c0c67b4a394b4fd9383 to your computer and use it in GitHub Desktop.
Save rstormsf/7cfb0c6b7a835c0c67b4a394b4fd9383 to your computer and use it in GitHub Desktop.
pragma solidity 0.4.24;
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
contract VestingVault12 {
using SafeMath for uint256;
using SafeMath for uint16;
modifier onlyV12MultiSig {
require(msg.sender == v12MultiSig, "not owner");
_;
}
modifier onlyValidAddress(address _recipient) {
require(_recipient != address(0) && _recipient != address(this) && _recipient != address(token), "not valid _recipient");
_;
}
uint256 constant internal SECONDS_PER_DAY = 86400;
struct Grant {
uint256 startTime;
uint256 amount;
uint16 vestingDuration;
uint16 vestingCliff;
uint16 daysClaimed;
uint256 totalClaimed;
address recipient;
}
event GrantAdded(address indexed recipient, uint256 vestingId);
event GrantTokensClaimed(address indexed recipient, uint256 amountClaimed);
event GrantRemoved(address recipient, uint256 amountVested, uint256 amountNotVested);
event ChangedMultisig(address multisig);
ERC20 public token;
mapping (uint256 => Grant) public tokenGrants;
mapping (address => uint[]) private activeGrants;
address public v12MultiSig;
uint256 public totalVestingCount;
constructor(ERC20 _token) public {
require(address(_token) != address(0));
v12MultiSig = msg.sender;
token = _token;
}
function addTokenGrant(
address _recipient,
uint256 _startTime,
uint256 _amount,
uint16 _vestingDurationInDays,
uint16 _vestingCliffInDays
)
external
onlyV12MultiSig
{
require(_vestingCliffInDays <= 10*365, "more than 10 years");
require(_vestingDurationInDays <= 25*365, "more than 25 years");
require(_vestingDurationInDays >= _vestingCliffInDays, "Duration < Cliff");
uint256 amountVestedPerDay = _amount.div(_vestingDurationInDays);
require(amountVestedPerDay > 0, "amountVestedPerDay > 0");
// Transfer the grant tokens under the control of the vesting contract
require(token.transferFrom(v12MultiSig, address(this), _amount), "transfer failed");
Grant memory grant = Grant({
startTime: _startTime == 0 ? currentTime() : _startTime,
amount: _amount,
vestingDuration: _vestingDurationInDays,
vestingCliff: _vestingCliffInDays,
daysClaimed: 0,
totalClaimed: 0,
recipient: _recipient
});
tokenGrants[totalVestingCount] = grant;
activeGrants[_recipient].push(totalVestingCount);
emit GrantAdded(_recipient, totalVestingCount);
totalVestingCount++;
}
function getActiveGrants(address _recipient) public view returns(uint256[]){
return activeGrants[_recipient];
}
/// @notice Calculate the vested and unclaimed months and tokens available for `_grantId` to claim
/// Due to rounding errors once grant duration is reached, returns the entire left grant amount
/// Returns (0, 0) if cliff has not been reached
function calculateGrantClaim(uint256 _grantId) public view returns (uint16, uint256) {
Grant storage tokenGrant = tokenGrants[_grantId];
// For grants created with a future start date, that hasn't been reached, return 0, 0
if (currentTime() < tokenGrant.startTime) {
return (0, 0);
}
// Check cliff was reached
uint elapsedTime = currentTime().sub(tokenGrant.startTime);
uint elapsedDays = elapsedTime.div(SECONDS_PER_DAY);
if (elapsedDays < tokenGrant.vestingCliff) {
return (uint16(elapsedDays), 0);
}
// If over vesting duration, all tokens vested
if (elapsedDays >= tokenGrant.vestingDuration) {
uint256 remainingGrant = tokenGrant.amount.sub(tokenGrant.totalClaimed);
return (tokenGrant.vestingDuration, remainingGrant);
} else {
uint16 daysVested = uint16(elapsedDays.sub(tokenGrant.daysClaimed));
uint256 amountVestedPerDay = tokenGrant.amount.div(uint256(tokenGrant.vestingDuration));
uint256 amountVested = uint256(daysVested.mul(amountVestedPerDay));
return (daysVested, amountVested);
}
}
/// @notice Allows a grant recipient to claim their vested tokens. Errors if no tokens have vested
/// It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this
function claimVestedTokens(uint256 _grantId) external {
uint16 daysVested;
uint256 amountVested;
(daysVested, amountVested) = calculateGrantClaim(_grantId);
require(amountVested > 0, "amountVested is 0");
Grant storage tokenGrant = tokenGrants[_grantId];
tokenGrant.daysClaimed = uint16(tokenGrant.daysClaimed.add(daysVested));
tokenGrant.totalClaimed = uint256(tokenGrant.totalClaimed.add(amountVested));
require(token.transfer(tokenGrant.recipient, amountVested), "no tokens");
emit GrantTokensClaimed(tokenGrant.recipient, amountVested);
}
/// @notice Terminate token grant transferring all vested tokens to the `_grantId`
/// and returning all non-vested tokens to the V12 MultiSig
/// Secured to the V12 MultiSig only
/// @param _grantId grantId of the token grant recipient
function removeTokenGrant(uint256 _grantId)
external
onlyV12MultiSig
{
Grant storage tokenGrant = tokenGrants[_grantId];
address recipient = tokenGrant.recipient;
uint16 daysVested;
uint256 amountVested;
(daysVested, amountVested) = calculateGrantClaim(_grantId);
uint256 amountNotVested = (tokenGrant.amount.sub(tokenGrant.totalClaimed)).sub(amountVested);
require(token.transfer(recipient, amountVested));
require(token.transfer(v12MultiSig, amountNotVested));
tokenGrant.startTime = 0;
tokenGrant.amount = 0;
tokenGrant.vestingDuration = 0;
tokenGrant.vestingCliff = 0;
tokenGrant.daysClaimed = 0;
tokenGrant.totalClaimed = 0;
tokenGrant.recipient = address(0);
emit GrantRemoved(recipient, amountVested, amountNotVested);
}
function currentTime() private view returns(uint256) {
return block.timestamp;
}
function tokensVestedPerDay(uint256 _grantId) public view returns(uint256) {
Grant storage tokenGrant = tokenGrants[_grantId];
return tokenGrant.amount.div(uint256(tokenGrant.vestingDuration));
}
function changeMultiSig(address _newMultisig)
external
onlyV12MultiSig
onlyValidAddress(_newMultisig)
{
v12MultiSig = _newMultisig;
emit ChangedMultisig(_newMultisig);
}
}
@leonprou
Copy link

Looks good 👍
Worth specifying the openzeppelin-solidity version (it's 2.0.0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment