Created
August 21, 2020 20:28
-
-
Save rstormsf/7cfb0c6b7a835c0c67b4a394b4fd9383 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Looks good 👍
Worth specifying the
openzeppelin-solidity
version (it's2.0.0
)