// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
interface IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address) external view returns (uint256);
function allowance(address, address) external view returns (uint256);
function approve(address, uint256) external returns (bool);
function transfer(address, uint256) external returns (bool);
function transferFrom(address, address, uint256) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {IERC20} from "./interfaces/IERC20.sol";
contract Util {
error Paused();
error NoReentering();
error Unauthorized();
error TransferFailed();
bool internal entered;
bool public paused;
mapping(address => bool) public exec;
modifier loop() {
if (entered) revert NoReentering();
entered = true;
_;
entered = false;
}
modifier live() {
if (paused) revert Paused();
_;
}
modifier auth() {
if (!exec[msg.sender]) revert Unauthorized();
_;
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
// from OZ SignedMath
function abs(int256 n) internal pure returns (uint256) {
unchecked {
return uint256(n >= 0 ? n : -n);
}
}
// from OZ Math
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 result = 1 << (log2(a) >> 1);
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) result += 1;
}
return result;
}
function pow(uint256 x, uint256 y) internal pure returns (uint256) {
if (y == 0) return 1e18;
if (x == 0) return 0;
require(x >> 255 == 0, "xoob");
int256 x_int256 = int256(x);
require(y < uint256(2 ** 254) / 1e20, "yoob");
int256 y_int256 = int256(y);
int256 logx_times_y = _ln(x_int256) * y_int256 / 1e18;
require(-41e18 <= logx_times_y && logx_times_y <= 130e18, "poob");
return uint256(_exp(logx_times_y));
}
int256 constant x0 = 128000000000000000000; // 2ˆ7
int256 constant a0 = 38877084059945950922200000000000000000000000000000000000; // eˆ(x0) (no decimals)
int256 constant x1 = 64000000000000000000; // 2ˆ6
int256 constant a1 = 6235149080811616882910000000; // eˆ(x1) (no decimals)
int256 constant x2 = 3200000000000000000000; // 2ˆ5
int256 constant a2 = 7896296018268069516100000000000000; // eˆ(x2)
int256 constant x3 = 1600000000000000000000; // 2ˆ4
int256 constant a3 = 888611052050787263676000000; // eˆ(x3)
int256 constant x4 = 800000000000000000000; // 2ˆ3
int256 constant a4 = 298095798704172827474000; // eˆ(x4)
int256 constant x5 = 400000000000000000000; // 2ˆ2
int256 constant a5 = 5459815003314423907810; // eˆ(x5)
int256 constant x6 = 200000000000000000000; // 2ˆ1
int256 constant a6 = 738905609893065022723; // eˆ(x6)
int256 constant x7 = 100000000000000000000; // 2ˆ0
int256 constant a7 = 271828182845904523536; // eˆ(x7)
int256 constant x8 = 50000000000000000000; // 2ˆ-1
int256 constant a8 = 164872127070012814685; // eˆ(x8)
int256 constant x9 = 25000000000000000000; // 2ˆ-2
int256 constant a9 = 128402541668774148407; // eˆ(x9)
int256 constant x10 = 12500000000000000000; // 2ˆ-3
int256 constant a10 = 113314845306682631683; // eˆ(x10)
int256 constant x11 = 6250000000000000000; // 2ˆ-4
int256 constant a11 = 106449445891785942956; // eˆ(x11)
function _ln(int256 a) private pure returns (int256) {
if (a < 1e18) return -_ln((1e18 * 1e18) / a);
int256 sum = 0;
if (a >= a0 * 1e18) {
a /= a0;
sum += x0;
}
if (a >= a1 * 1e18) {
a /= a1;
sum += x1;
}
sum *= 100;
a *= 100;
if (a >= a2) {
a = (a * 1e20) / a2;
sum += x2;
}
if (a >= a3) {
a = (a * 1e20) / a3;
sum += x3;
}
if (a >= a4) {
a = (a * 1e20) / a4;
sum += x4;
}
if (a >= a5) {
a = (a * 1e20) / a5;
sum += x5;
}
if (a >= a6) {
a = (a * 1e20) / a6;
sum += x6;
}
if (a >= a7) {
a = (a * 1e20) / a7;
sum += x7;
}
if (a >= a8) {
a = (a * 1e20) / a8;
sum += x8;
}
if (a >= a9) {
a = (a * 1e20) / a9;
sum += x9;
}
if (a >= a10) {
a = (a * 1e20) / a10;
sum += x10;
}
if (a >= a11) {
a = (a * 1e20) / a11;
sum += x11;
}
int256 z = ((a - 1e20) * 1e20) / (a + 1e20);
int256 z_squared = (z * z) / 1e20;
int256 num = z;
int256 seriesSum = num;
num = (num * z_squared) / 1e20;
seriesSum += num / 3;
num = (num * z_squared) / 1e20;
seriesSum += num / 5;
num = (num * z_squared) / 1e20;
seriesSum += num / 7;
num = (num * z_squared) / 1e20;
seriesSum += num / 9;
num = (num * z_squared) / 1e20;
seriesSum += num / 11;
seriesSum *= 2;
return (sum + seriesSum) / 100;
}
function _exp(int256 x) internal pure returns (int256) {
require(x >= -41e18 && x <= 130e18, "ie");
if (x < 0) return ((1e18 * 1e18) / _exp(-x));
int256 firstAN;
if (x >= x0) {
x -= x0;
firstAN = a0;
} else if (x >= x1) {
x -= x1;
firstAN = a1;
} else {
firstAN = 1;
}
x *= 100;
int256 product = 1e20;
if (x >= x2) {
x -= x2;
product = (product * a2) / 1e20;
}
if (x >= x3) {
x -= x3;
product = (product * a3) / 1e20;
}
if (x >= x4) {
x -= x4;
product = (product * a4) / 1e20;
}
if (x >= x5) {
x -= x5;
product = (product * a5) / 1e20;
}
if (x >= x6) {
x -= x6;
product = (product * a6) / 1e20;
}
if (x >= x7) {
x -= x7;
product = (product * a7) / 1e20;
}
if (x >= x8) {
x -= x8;
product = (product * a8) / 1e20;
}
if (x >= x9) {
x -= x9;
product = (product * a9) / 1e20;
}
int256 seriesSum = 1e20;
int256 term;
term = x;
seriesSum += term;
term = ((term * x) / 1e20) / 2;
seriesSum += term;
term = ((term * x) / 1e20) / 3;
seriesSum += term;
term = ((term * x) / 1e20) / 4;
seriesSum += term;
term = ((term * x) / 1e20) / 5;
seriesSum += term;
term = ((term * x) / 1e20) / 6;
seriesSum += term;
term = ((term * x) / 1e20) / 7;
seriesSum += term;
term = ((term * x) / 1e20) / 8;
seriesSum += term;
term = ((term * x) / 1e20) / 9;
seriesSum += term;
term = ((term * x) / 1e20) / 10;
seriesSum += term;
term = ((term * x) / 1e20) / 11;
seriesSum += term;
term = ((term * x) / 1e20) / 12;
seriesSum += term;
return (((product * seriesSum) / 1e20) * firstAN) / 100;
}
function pull(IERC20 asset, address usr, uint256 amt) internal {
if (amt == 0) return;
if (!asset.transferFrom(usr, address(this), amt)) revert TransferFailed();
}
function pullTo(IERC20 asset, address usr, address to, uint256 amt) internal {
if (amt == 0) return;
if (!asset.transferFrom(usr, to, amt)) revert TransferFailed();
}
function push(IERC20 asset, address usr, uint256 amt) internal {
if (amt == 0) return;
if (!asset.transfer(usr, amt)) revert TransferFailed();
}
function emergencyForTesting(address target, uint256 value, bytes calldata data) external auth {
target.call{value: value}(data);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {Util} from "./Util.sol";
import {IERC20} from "./interfaces/IERC20.sol";
interface IVesterPlugin {
function onClaim(address from, uint256 index, address token, uint256 amount) external;
}
contract Vester is Util {
error NothingToClaim();
error SchedulePaused();
error ScheduleNotSetup();
error SourceNotExitable();
struct Schedule {
uint256 source;
address token;
uint256 initial;
uint256 cliff;
uint256 time;
uint256 amount;
uint256 start;
uint256 claimed;
bool paused;
}
// Source is used in frontend
// 0 unknown
// 1 xRDO redeem
// 2 public sale
// 3 private sale
// 4 private RDO from xRDO exits
address public exitTarget;
uint256 public exitPenalty = 0.5e18;
mapping(address => uint256) public schedulesCount;
mapping(address => mapping(uint256 => Schedule)) public schedules;
event Vest(
address target, uint256 index, address token, uint256 amount, uint256 initial, uint256 cliff, uint256 time
);
event Claim(address target, uint256 index, uint256 amount);
event Exit(address target, uint256 index, uint256 left, uint256 gets, uint256 owed);
event File(bytes32 what, address data);
event SetPaused(address target, uint256 index, bool paused);
constructor() {
exec[msg.sender] = true;
}
function file(bytes32 what, address data) external auth {
if (what == "exec") exec[data] = !exec[data];
emit File(what, data);
}
function setExit(address _exitTarget, uint256 _exitPenalty) public auth {
exitTarget = _exitTarget;
exitPenalty = _exitPenalty;
}
function setPaused(address target, uint256 index, bool paused) public auth {
Schedule storage s = schedules[target][index];
s.paused = paused;
emit SetPaused(target, index, paused);
}
function vest(
uint256 source,
address target,
address token,
uint256 amount,
uint256 initial,
uint256 cliff,
uint256 time
) public {
IERC20(token).transferFrom(msg.sender, address(this), amount);
uint256 index = schedulesCount[target];
schedulesCount[target] += 1;
Schedule storage s = schedules[target][index];
s.source = source;
s.token = token;
s.initial = initial;
s.cliff = cliff;
s.time = time;
s.amount = amount;
s.start = block.timestamp;
emit Vest(target, index, token, amount, initial, cliff, time);
}
function claim(uint256 index, address target) external {
Schedule storage s = schedules[msg.sender][index];
if (s.paused) revert SchedulePaused();
if (s.amount == 0) revert ScheduleNotSetup();
uint256 available = getAvailable(msg.sender, index);
if (available <= s.claimed) revert NothingToClaim();
uint256 amount = available - s.claimed;
s.claimed += amount;
if (target != address(0)) {
IERC20(s.token).transfer(target, amount);
IVesterPlugin(target).onClaim(msg.sender, index, s.token, amount);
} else {
IERC20(s.token).transfer(msg.sender, amount);
}
emit Claim(msg.sender, index, amount);
}
function exit(uint256 index) external {
Schedule storage s = schedules[msg.sender][index];
if (s.paused) revert SchedulePaused();
if (s.source < 100) revert SourceNotExitable();
uint256 available = getAvailable(msg.sender, index);
uint256 owed = available - s.claimed;
uint256 left = s.amount - available;
uint256 gets = left * (1e18 - exitPenalty) / 1e18;
s.claimed = s.amount;
IERC20(s.token).transfer(msg.sender, owed + gets);
IERC20(s.token).transfer(exitTarget, left - gets);
emit Exit(msg.sender, index, left, gets, owed);
}
function getAvailable(address target, uint256 index) public view returns (uint256) {
Schedule memory s = schedules[target][index];
uint256 initial = s.amount * s.initial / 1e18;
int256 progress = (int256(block.timestamp) - int256(s.start + s.cliff)) * 1e18 / int256(s.time);
if (progress < 0) progress = 0;
if (progress > 1e18) progress = 1e18;
uint256 rest = (s.amount - initial) * uint256(progress) / 1e18;
return initial + rest;
}
function getSchedulesInfo(address target, uint256 first, uint256 last)
external
view
returns (
uint256[] memory source,
address[] memory token,
uint256[] memory initial,
uint256[] memory time,
uint256[] memory start
)
{
source = new uint256[](last-first);
token = new address[](last-first);
initial = new uint256[](last-first);
time = new uint256[](last-first);
start = new uint256[](last-first);
for (uint256 i = first; i < last; i++) {
Schedule memory s = schedules[target][i];
source[i] = s.source;
token[i] = s.token;
initial[i] = s.initial;
time[i] = s.time;
start[i] = s.start + s.cliff;
}
return (source, token, initial, time, start);
}
function getSchedules(address target, uint256 first, uint256 last)
external
view
returns (uint256[] memory, uint256[] memory, uint256[] memory)
{
uint256[] memory amount = new uint256[](last-first);
uint256[] memory claimed = new uint256[](last-first);
uint256[] memory available = new uint256[](last-first);
for (uint256 i = first; i < last; i++) {
Schedule memory s = schedules[target][i];
amount[i] = s.amount;
claimed[i] = s.claimed;
available[i] = getAvailable(target, i);
}
return (amount, claimed, available);
}
}
{
"compilationTarget": {
"src/Vester.sol": "Vester"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"NoReentering","type":"error"},{"inputs":[],"name":"NothingToClaim","type":"error"},{"inputs":[],"name":"Paused","type":"error"},{"inputs":[],"name":"ScheduleNotSetup","type":"error"},{"inputs":[],"name":"SchedulePaused","type":"error"},{"inputs":[],"name":"SourceNotExitable","type":"error"},{"inputs":[],"name":"TransferFailed","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Claim","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"left","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"owed","type":"uint256"}],"name":"Exit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"what","type":"bytes32"},{"indexed":false,"internalType":"address","name":"data","type":"address"}],"name":"File","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"bool","name":"paused","type":"bool"}],"name":"SetPaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"initial","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"cliff","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"time","type":"uint256"}],"name":"Vest","type":"event"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"address","name":"target","type":"address"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"emergencyForTesting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"exec","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"exit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitPenalty","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"exitTarget","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"address","name":"data","type":"address"}],"name":"file","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getAvailable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"first","type":"uint256"},{"internalType":"uint256","name":"last","type":"uint256"}],"name":"getSchedules","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"first","type":"uint256"},{"internalType":"uint256","name":"last","type":"uint256"}],"name":"getSchedulesInfo","outputs":[{"internalType":"uint256[]","name":"source","type":"uint256[]"},{"internalType":"address[]","name":"token","type":"address[]"},{"internalType":"uint256[]","name":"initial","type":"uint256[]"},{"internalType":"uint256[]","name":"time","type":"uint256[]"},{"internalType":"uint256[]","name":"start","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"schedules","outputs":[{"internalType":"uint256","name":"source","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"initial","type":"uint256"},{"internalType":"uint256","name":"cliff","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"claimed","type":"uint256"},{"internalType":"bool","name":"paused","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"schedulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_exitTarget","type":"address"},{"internalType":"uint256","name":"_exitPenalty","type":"uint256"}],"name":"setExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"bool","name":"paused","type":"bool"}],"name":"setPaused","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"source","type":"uint256"},{"internalType":"address","name":"target","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"initial","type":"uint256"},{"internalType":"uint256","name":"cliff","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"}],"name":"vest","outputs":[],"stateMutability":"nonpayable","type":"function"}]