文件 1 的 5:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 2 的 5:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
文件 3 的 5:IYDF.sol
pragma solidity ^0.8.9;
import '@openzeppelin/contracts/interfaces/IERC20.sol';
interface IYDF is IERC20 {
function addToBuyTracker(address _user, uint256 _amount) external;
function burn(uint256 _amount) external;
function stakeMintToVester(uint256 _amount) external;
}
文件 4 的 5:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(_msgSender());
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 5 的 5:YDFVester.sol
pragma solidity ^0.8.9;
import '@openzeppelin/contracts/access/Ownable.sol';
import './interfaces/IYDF.sol';
contract YDFVester is Ownable {
IYDF private _ydf;
uint256 public fullyVestedPeriod = 90 days;
uint256 public withdrawsPerPeriod = 10;
struct TokenVest {
uint256 start;
uint256 end;
uint256 totalWithdraws;
uint256 withdrawsCompleted;
uint256 amount;
}
mapping(address => TokenVest[]) public vests;
address[] public stakeContracts;
event CreateVest(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 index, uint256 amountWithdrawn);
modifier onlyStake() {
bool isStake;
for (uint256 i = 0; i < stakeContracts.length; i++) {
if (msg.sender == stakeContracts[i]) {
isStake = true;
break;
}
}
require(isStake, 'not a staking contract');
_;
}
constructor(address _token) {
_ydf = IYDF(_token);
}
function createVest(address _user, uint256 _amount) external onlyStake {
vests[_user].push(
TokenVest({
start: block.timestamp,
end: block.timestamp + fullyVestedPeriod,
totalWithdraws: withdrawsPerPeriod,
withdrawsCompleted: 0,
amount: _amount
})
);
emit CreateVest(_user, _amount);
}
function withdraw(uint256 _index) external {
address _user = msg.sender;
TokenVest storage _vest = vests[_user][_index];
require(_vest.amount > 0, 'vest does not exist');
require(
_vest.withdrawsCompleted < _vest.totalWithdraws,
'already withdrew all tokens'
);
uint256 _tokensPerWithdrawPeriod = _vest.amount / _vest.totalWithdraws;
uint256 _withdrawsAllowed = getWithdrawsAllowed(_user, _index);
_withdrawsAllowed = _withdrawsAllowed > _vest.totalWithdraws
? _vest.totalWithdraws
: _withdrawsAllowed;
require(
_vest.withdrawsCompleted < _withdrawsAllowed,
'currently vesting, please wait for next withdrawable time period'
);
uint256 _withdrawsToComplete = _withdrawsAllowed - _vest.withdrawsCompleted;
_vest.withdrawsCompleted = _withdrawsAllowed;
_ydf.transfer(_user, _tokensPerWithdrawPeriod * _withdrawsToComplete);
_ydf.addToBuyTracker(
_user,
_tokensPerWithdrawPeriod * _withdrawsToComplete
);
if (_vest.withdrawsCompleted == _vest.totalWithdraws) {
vests[_user][_index] = vests[_user][vests[_user].length - 1];
vests[_user].pop();
}
emit Withdraw(
_user,
_index,
_tokensPerWithdrawPeriod * _withdrawsToComplete
);
}
function getWithdrawsAllowed(address _user, uint256 _index)
public
view
returns (uint256)
{
TokenVest memory _vest = vests[_user][_index];
uint256 _secondsPerWithdrawPeriod = (_vest.end - _vest.start) /
_vest.totalWithdraws;
return (block.timestamp - _vest.start) / _secondsPerWithdrawPeriod;
}
function getUserVests(address _user)
external
view
returns (TokenVest[] memory)
{
return vests[_user];
}
function getYDF() external view returns (address) {
return address(_ydf);
}
function addStakingContract(address _contract) external onlyOwner {
stakeContracts.push(_contract);
}
}
{
"compilationTarget": {
"contracts/YDFVester.sol": "YDFVester"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "none"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"CreateVest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountWithdrawn","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[{"internalType":"address","name":"_contract","type":"address"}],"name":"addStakingContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"createVest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fullyVestedPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"getUserVests","outputs":[{"components":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"},{"internalType":"uint256","name":"totalWithdraws","type":"uint256"},{"internalType":"uint256","name":"withdrawsCompleted","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct YDFVester.TokenVest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"getWithdrawsAllowed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getYDF","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"stakeContracts","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"vests","outputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"},{"internalType":"uint256","name":"totalWithdraws","type":"uint256"},{"internalType":"uint256","name":"withdrawsCompleted","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawsPerPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]