文件 1 的 11:Claimable.sol
pragma solidity >0.8.0;
contract Claimable {
bytes4 private constant SELECTOR_TRANSFER =
bytes4(keccak256(bytes("transfer(address,uint256)")));
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _reentrancyStatus;
function _claimErc20(
address token,
address to,
uint256 amount
) internal {
(bool success, bytes memory data) = token.call(
abi.encodeWithSelector(SELECTOR_TRANSFER, to, amount)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"claimErc20: TRANSFER_FAILED"
);
}
modifier nonReentrant() {
require(_reentrancyStatus != _ENTERED, "claimErc20: reentrant call");
_reentrancyStatus = _ENTERED;
_;
_reentrancyStatus = _NOT_ENTERED;
}
}
文件 2 的 11:Constants.sol
pragma solidity ^0.8.0;
contract Constants {
uint256 internal constant MAX_SUPPLY = 1e27;
uint256 internal constant SCALE = 1e12;
}
文件 3 的 11: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;
}
}
文件 4 的 11:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, 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 sender,
address recipient,
uint256 amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
文件 5 的 11:IMintable.sol
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IMintable is IERC20 {
function mint(address account, uint256 amount) external returns (bool);
}
文件 6 的 11:IVestingPools.sol
pragma solidity ^0.8.0;
import { PoolParams } from "./Types.sol";
interface IVestingPools {
function token() external view returns (address);
function getWallet(uint256 poolId) external view returns (address);
function getPool(uint256 poolId) external view returns (PoolParams memory);
function releasableAmount(uint256 poolId) external view returns (uint256);
function vestedAmount(uint256 poolId) external view returns (uint256);
function release(uint256 poolId, uint256 amount)
external
returns (uint256 released);
function releaseTo(
uint256 poolId,
address account,
uint256 amount
) external returns (uint256 released);
function updatePoolWallet(uint256 poolId, address newWallet) external;
function addVestingPools(
address[] memory wallets,
PoolParams[] memory params
) external;
function updatePoolTime(
uint256 poolId,
uint32 start,
uint16 vestingDays
) external;
event Released(uint256 indexed poolId, address to, uint256 amount);
event WalletUpdated(uint256 indexedpoolId, address indexed newWallet);
event PoolAdded(
uint256 indexed poolId,
address indexed wallet,
uint256 allocation
);
event PoolUpdated(
uint256 indexed poolId,
uint256 start,
uint256 vestingDays
);
}
文件 7 的 11:Linking.sol
pragma solidity ^0.8.0;
library TokenAddress {
function neverCallIt() external pure {
revert("FAKE");
}
}
library VestingPoolsAddress {
function neverCallIt() external pure {
revert("FAKE");
}
}
library DefaultOwnerAddress {
function neverCallIt() external pure {
revert("FAKE");
}
}
文件 8 的 11: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() {
_setOwner(_msgSender());
}
function owner() public view virtual returns (address) {
return _owner;
}
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
function renounceOwnership() public virtual onlyOwner {
_setOwner(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_setOwner(newOwner);
}
function _setOwner(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 9 的 11:SafeUints.sol
pragma solidity >0.8.0;
contract SafeUints {
function _safe96(uint256 n) internal pure returns (uint96) {
require(n < 2**96, "VPools: Unsafe96");
return uint96(n);
}
}
文件 10 的 11:Types.sol
pragma solidity ^0.8.0;
struct PoolParams {
bool isPreMinted;
bool isAdjustable;
uint32 start;
uint16 vestingDays;
uint48 sAllocation;
uint48 sUnlocked;
uint96 vested;
}
文件 11 的 11:VestingPools.sol
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/Constants.sol";
import { PoolParams } from "./interfaces/Types.sol";
import "./interfaces/IMintable.sol";
import "./interfaces/IVestingPools.sol";
import "./utils/Claimable.sol";
import { TokenAddress } from "./utils/Linking.sol";
import "./utils/SafeUints.sol";
contract VestingPools is
Ownable,
Claimable,
SafeUints,
Constants,
IVestingPools
{
uint96 public totalAllocation;
uint96 public totalVested;
address[] internal _wallets;
PoolParams[] internal _pools;
function token() external view override returns (address) {
return _getToken();
}
function getWallet(uint256 poolId)
external
view
override
returns (address)
{
_throwInvalidPoolId(poolId);
return _wallets[poolId];
}
function getPool(uint256 poolId)
external
view
override
returns (PoolParams memory)
{
return _getPool(poolId);
}
function releasableAmount(uint256 poolId)
external
view
override
returns (uint256)
{
PoolParams memory pool = _getPool(poolId);
return _getReleasable(pool, _timeNow());
}
function vestedAmount(uint256 poolId)
external
view
override
returns (uint256)
{
PoolParams memory pool = _getPool(poolId);
return uint256(pool.vested);
}
function release(uint256 poolId, uint256 amount)
external
override
returns (uint256 released)
{
return _releaseTo(poolId, msg.sender, amount);
}
function releaseTo(
uint256 poolId,
address account,
uint256 amount
) external override returns (uint256 released) {
_throwZeroAddress(account);
return _releaseTo(poolId, account, amount);
}
function updatePoolWallet(uint256 poolId, address newWallet)
external
override
{
_throwZeroAddress(newWallet);
_throwUnauthorizedWallet(poolId, msg.sender);
_wallets[poolId] = newWallet;
emit WalletUpdated(poolId, newWallet);
}
function addVestingPools(
address[] memory wallets,
PoolParams[] memory pools
) external override onlyOwner {
require(wallets.length == pools.length, "VPools: length mismatch");
uint256 timeNow = _timeNow();
IMintable theToken = IMintable(_getToken());
uint256 updAllocation = uint256(totalAllocation);
uint256 preMinted = 0;
uint256 poolId = _pools.length;
for (uint256 i = 0; i < wallets.length; i++) {
_throwZeroAddress(wallets[i]);
require(pools[i].start >= timeNow, "VPools: start already passed");
require(pools[i].sAllocation != 0, "VPools: zero sAllocation");
require(
pools[i].sAllocation >= pools[i].sUnlocked,
"VPools: too big sUnlocked"
);
require(pools[i].vested == 0, "VPools: zero vested expected");
uint256 allocation = uint256(pools[i].sAllocation) * SCALE;
updAllocation += allocation;
_wallets.push(wallets[i]);
_pools.push(pools[i]);
emit PoolAdded(poolId++, wallets[i], allocation);
if (pools[i].isPreMinted) {
preMinted += allocation;
}
}
require(updAllocation <= MAX_SUPPLY, "VPools: supply exceeded");
totalAllocation = _safe96(updAllocation);
if (preMinted != 0) {
require(theToken.mint(address(this), preMinted), "VPools:E5");
}
}
function updatePoolTime(
uint256 poolId,
uint32 start,
uint16 vestingDays
) external override onlyOwner {
PoolParams memory pool = _getPool(poolId);
require(pool.isAdjustable, "VPools: non-adjustable");
require(
uint256(pool.sAllocation) * SCALE > uint256(pool.vested),
"VPools: fully vested"
);
uint256 end = uint256(start) + uint256(vestingDays) * 1 days;
require(_timeNow() > end, "VPools: too late updates");
pool.start = start;
pool.vestingDays = vestingDays;
_pools[poolId] = pool;
emit PoolUpdated(poolId, start, vestingDays);
}
function claimErc20(
address claimedToken,
address to,
uint256 amount
) external onlyOwner nonReentrant {
IERC20 vestedToken = IERC20(_getToken());
if (claimedToken == address(vestedToken)) {
uint256 actual = vestedToken.balanceOf(address(this));
uint256 expected = vestedToken.totalSupply() - totalVested;
require(actual >= expected + amount, "VPools: too big amount");
}
_claimErc20(claimedToken, to, amount);
}
function removeContract() external onlyOwner {
require(totalAllocation == totalVested, "VPools:E1");
selfdestruct(payable(msg.sender));
}
function _getToken() internal view virtual returns (address) {
return address(TokenAddress);
}
function _getPool(uint256 poolId)
internal
view
returns (PoolParams memory)
{
_throwInvalidPoolId(poolId);
return _pools[poolId];
}
function _getReleasable(PoolParams memory pool, uint256 timeNow)
internal
pure
returns (uint256)
{
if (timeNow < pool.start) return 0;
uint256 allocation = uint256(pool.sAllocation) * SCALE;
if (pool.vested >= allocation) return 0;
uint256 releasable = allocation - uint256(pool.vested);
uint256 duration = uint256(pool.vestingDays) * 1 days;
uint256 end = uint256(pool.start) + duration;
if (timeNow < end) {
uint256 unlocked = uint256(pool.sUnlocked) * SCALE;
uint256 locked = ((allocation - unlocked) * (end - timeNow)) /
duration;
releasable = locked > releasable ? 0 : releasable - locked;
}
return releasable;
}
function _releaseTo(
uint256 poolId,
address to,
uint256 amount
) internal returns (uint256 released) {
PoolParams memory pool = _getPool(poolId);
_throwUnauthorizedWallet(poolId, msg.sender);
uint256 releasable = _getReleasable(pool, _timeNow());
require(releasable >= amount, "VPools: not enough to release");
released = amount == 0 ? releasable : amount;
_pools[poolId].vested = _safe96(released + uint256(pool.vested));
totalVested = _safe96(released + uint256(totalVested));
if (pool.isPreMinted) {
require(IERC20(_getToken()).transfer(to, released), "VPools:E6");
} else {
require(IMintable(_getToken()).mint(to, released), "VPools:E7");
}
emit Released(poolId, to, released);
}
function _throwZeroAddress(address account) private pure {
require(account != address(0), "VPools: zero address(account|wallet)");
}
function _throwInvalidPoolId(uint256 poolId) private view {
require(poolId < _pools.length, "VPools: invalid pool id");
}
function _throwUnauthorizedWallet(uint256 poolId, address wallet)
private
view
{
_throwZeroAddress(wallet);
require(_wallets[poolId] == wallet, "VPools: unauthorized");
}
function _timeNow() internal view virtual returns (uint256) {
return block.timestamp;
}
}
{
"compilationTarget": {
"contracts/VestingPools.sol": "VestingPools"
},
"evmVersion": "istanbul",
"libraries": {
"contracts/utils/Linking.sol:TokenAddress": "0x909e34d3f6124c324ac83dcca84b74398a6fa173"
},
"metadata": {
"bytecodeHash": "none"
},
"optimizer": {
"enabled": true,
"runs": 800
},
"remappings": []
}
[{"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":"uint256","name":"poolId","type":"uint256"},{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint256","name":"allocation","type":"uint256"}],"name":"PoolAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"poolId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"start","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"vestingDays","type":"uint256"}],"name":"PoolUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"poolId","type":"uint256"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Released","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"indexedpoolId","type":"uint256"},{"indexed":true,"internalType":"address","name":"newWallet","type":"address"}],"name":"WalletUpdated","type":"event"},{"inputs":[{"internalType":"address[]","name":"wallets","type":"address[]"},{"components":[{"internalType":"bool","name":"isPreMinted","type":"bool"},{"internalType":"bool","name":"isAdjustable","type":"bool"},{"internalType":"uint32","name":"start","type":"uint32"},{"internalType":"uint16","name":"vestingDays","type":"uint16"},{"internalType":"uint48","name":"sAllocation","type":"uint48"},{"internalType":"uint48","name":"sUnlocked","type":"uint48"},{"internalType":"uint96","name":"vested","type":"uint96"}],"internalType":"struct PoolParams[]","name":"pools","type":"tuple[]"}],"name":"addVestingPools","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"claimedToken","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimErc20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"getPool","outputs":[{"components":[{"internalType":"bool","name":"isPreMinted","type":"bool"},{"internalType":"bool","name":"isAdjustable","type":"bool"},{"internalType":"uint32","name":"start","type":"uint32"},{"internalType":"uint16","name":"vestingDays","type":"uint16"},{"internalType":"uint48","name":"sAllocation","type":"uint48"},{"internalType":"uint48","name":"sUnlocked","type":"uint48"},{"internalType":"uint96","name":"vested","type":"uint96"}],"internalType":"struct PoolParams","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"getWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"releasableAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"release","outputs":[{"internalType":"uint256","name":"released","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"releaseTo","outputs":[{"internalType":"uint256","name":"released","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"removeContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAllocation","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalVested","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"uint32","name":"start","type":"uint32"},{"internalType":"uint16","name":"vestingDays","type":"uint16"}],"name":"updatePoolTime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"address","name":"newWallet","type":"address"}],"name":"updatePoolWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"vestedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]