Vesting
Vesting.sol
This contract lock in a certain amount of funds until the terms of the contract are fulfilled
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "hardhat/console.sol";
contract HermesVesting is Ownable {
using SafeERC20 for IERC20;
SafeERC20 is a wrapper around the interface that eliminates the need to handle boolean return values. TokenTimelock can hold tokens for a beneficiary until a specified time.
using Address for address;
Events
event CreateVesting(
uint256 indexed vestingId,
address indexed user,
uint256 amount,
uint256 startEpoch,
uint256 durationInEpoch
);
Emmited when a new vest is created
event Claim(
uint256 indexed vestingId,
address indexed user,
uint256 amount
);
Emmited when a vest is claimed
Structs
struct Vesting {
address user;
uint256 amount; // Hermes token amount in wei
uint256 startEpoch; // start time in epoch
uint256 durationInEpoch; // duration in epoch
uint256 claimedAmount;
}
Struct to keep the vesting informations
Variables
uint256 public startTime;
The timestamp that the contract can start working
uint256 public epochLength;
The size of epoch
IERC20 public hermes;
The HERMES token
Vesting[] public vestings;
A array of vestings structs
bool freeClaimed;
Check if the vesting was already claimed
mapping(address => uint256[]) private _vestingsByAddress;
The list of all vestings address
Setup Functions
constructor(
address _hermes,
uint256 _startTime,
uint256 _epochLength
) Ownable() {
hermes = IERC20(_hermes);
startTime = _startTime;
epochLength = _epochLength;
}
Start the contract setting the hermes token, the start of contract execution and the epoch lenght
Externally Accessible Functions
freeClaim
function freeClaim() external {
require(freeClaimed == false, "already did");
for(uint i = 0; i < vestings.length; i ++) {
Vesting storage vesting = vestings[i];
uint freeClaimAmount = vesting.amount * 166 / 1000;
vesting.claimedAmount = freeClaimAmount;
console.log(freeClaimAmount);
hermes.transfer(vesting.user, freeClaimAmount);
}
freeClaimed = true;
}
currentEpoch
function currentEpoch() public view returns (uint256) {
if (block.timestamp < startTime) {
return 0;
}
return (block.timestamp - startTime) / epochLength + 1;
}
Returns the current epoch
createVesting
function createVesting(
address user,
uint256 amount,
uint256 startEpoch,
uint256 durationInEpoch
) external onlyOwner {
require(
user != address(0) && !user.isContract(),
"Invalid address!"
);
require(amount > 0, "Invalid amount!");
require(
startEpoch > currentEpoch() && durationInEpoch > 0,
"Invalid request!"
);
vestings.push(
Vesting({
user: user,
amount: amount,
startEpoch: startEpoch,
durationInEpoch: durationInEpoch,
claimedAmount: 0
})
);
_vestingsByAddress[user].push(vestings.length - 1);
emit CreateVesting(
vestings.length - 1,
user,
amount,
startEpoch,
durationInEpoch
);
}
Create a new vesting
setPendingAdmin
function vestingsByAddress(address user)
external
view
returns (uint256[] memory)
{
return _vestingsByAddress[user];
}
Set the new admin
claimable
function claimable(uint256 vestingId) external view returns (uint256) {
require(vestingId < vestings.length, "Invalid index!");
return _claimable(vestingId);
}
Check if the vesting is claimable
claim
function claim(uint256 vestingId) external {
require(vestingId < vestings.length, "Invalid index!");
Vesting storage vesting = vestings[vestingId];
require(msg.sender == vesting.user, "unauthorized");
uint256 claimAmount = _claimable(vestingId);
require(claimAmount > 0, "unable to claim");
vesting.claimedAmount += claimAmount;
hermes.safeTransfer(vesting.user, claimAmount);
emit Claim(vestingId, vesting.user, claimAmount);
}
Clain the vest and transfer the tokens
Internal functions
_claimable
function _claimable(uint256 vestingId) internal view returns (uint256) {
Vesting memory vesting = vestings[vestingId];
uint256 current = currentEpoch();
if (current < vesting.startEpoch) {
return 0;
}
uint256 vestedAmount = ((currentEpoch() - vesting.startEpoch + 1) *
vesting.amount) / vesting.durationInEpoch;
if (vestedAmount > vesting.amount) {
vestedAmount = vesting.amount;
}
return vestedAmount - vesting.claimedAmount;
}
Check if a vesting is claimable