Skip to content

Instantly share code, notes, and snippets.

@vsmelov
Created October 18, 2021 08:17
Show Gist options
  • Save vsmelov/6aa249a721e79e534c1a8a0b9eee91fd to your computer and use it in GitHub Desktop.
Save vsmelov/6aa249a721e79e534c1a8a0b9eee91fd to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.1;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract Vesting {
IERC20 public immutable token;
struct Lock {
address receiver;
uint256 amount;
uint256 startTimestamp;
uint256 endTimestamp;
uint256 withdrawn;
}
mapping(address => Lock) public receiverLock;
event OnLock(address indexed sender, address indexed receiver, uint256 amount, uint256 startTimestamp, uint256 endTimestamp);
event OnWithdraw(address indexed receiver, uint256 amount);
constructor(address _token) {
require(_token != address(0), "Vesting: Token must not be 0x0");
token = IERC20(_token);
}
// @notice locks tokens. The only one lock for a receiver can exist.
// @param receiver address what tokens are vested to.
// @param amount of locked tokens.
// @param startTimestamp when vesting starts. Representing the UNIX timestamp and must not be earlier then now.
// @param endTimestamp when vesting ends. Representing the UNIX timestamp and must not be earlier then `startTimestamp`.
function lockTokens(address receiver, uint256 amount, uint256 startTimestamp, uint256 endTimestamp) external {
require(receiver != address(0), "Vesting: must not be 0x0");
require(amount > 0, "Vesting: locked amount must be > 0");
require(startTimestamp >= block.timestamp, "Vesting: startTimestamp must be later then now");
require(startTimestamp < endTimestamp, "Vesting: endTimestamp must be later then startTimestamp");
require(endTimestamp < 4000000000, "Vesting: Invalid unlock time, it must be unix time in seconds");
require(receiverLock[receiver].amount == 0, "Vesting: lock for receiver already exists");
receiverLock[receiver] = Lock(receiver, amount, startTimestamp, endTimestamp, 0);
require(token.transferFrom(msg.sender, address(this), amount), "Vesting: unable to transfer tokens to the contract's address");
emit OnLock(msg.sender, receiver, amount, startTimestamp, endTimestamp);
}
// @notice withdraws tokens which were vested and weren't withdrawn before
function withdraw() external {
Lock storage lock = receiverLock[msg.sender];
require(lock.startTimestamp != 0, "Vesting: Lock doesn't exist");
require(block.timestamp > lock.startTimestamp, "Vesting: vesting hasn't been started yet");
uint256 amountToWithdraw = _allowedToWithdraw(lock);
lock.withdrawn += amountToWithdraw;
require(token.transfer(msg.sender, amountToWithdraw), "Vesting: Transfer was fallen");
emit OnWithdraw(msg.sender, amountToWithdraw);
}
function _allowedToWithdraw(Lock storage lock) private view returns (uint256) {
uint256 timePassed = block.timestamp - lock.startTimestamp;
uint256 timeVesting = lock.endTimestamp - lock.startTimestamp;
if (timePassed >= timeVesting) {
return lock.amount - lock.withdrawn;
}
return ((lock.amount * timePassed) / timeVesting) - lock.withdrawn;
}
// @notice calculates amount of tokens which a user is allowed to withdraw.
// @param receiver user's address.
// @return amount of tokens which `receiver` is allowed to withdraw.
function allowedToWithdraw(address receiver) external view returns (uint256) {
Lock storage lock = receiverLock[receiver];
return _allowedToWithdraw(lock);
}
// @notice returns parameters of a user's lock
// @param receiver user's address.
// @return parameters of the `receiver`'s lock: amount, startTimestamp, end timeStamp
// and withdrawn(how many tokens were already withdrawn).
function lockOf(address receiver) external view returns (uint256, uint256, uint256, uint256) {
Lock storage lock = receiverLock[receiver];
return (lock.amount, lock.startTimestamp, lock.endTimestamp, lock.withdrawn);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment